160 行
6.2 KiB
JavaScript
160 行
6.2 KiB
JavaScript
import { existsSync, readFileSync } from 'node:fs'
|
|
import path from 'node:path'
|
|
import semver from 'semver'
|
|
|
|
export const CONFIG_FILE_NAME = 'xuqm.config.json'
|
|
export const SUPPORTED_PLATFORMS = new Set(['android', 'ios'])
|
|
export const SUPPORTED_MODULE_TYPES = new Set(['startup', 'common', 'app', 'buz'])
|
|
|
|
export function readConfig(root = process.cwd()) {
|
|
const configFile = path.join(root, CONFIG_FILE_NAME)
|
|
if (!existsSync(configFile)) {
|
|
throw new Error(`missing ${CONFIG_FILE_NAME}; run \`xuqm-rn init\` first`)
|
|
}
|
|
let config
|
|
try {
|
|
config = JSON.parse(readFileSync(configFile, 'utf8'))
|
|
} catch (error) {
|
|
throw new Error(`invalid ${CONFIG_FILE_NAME}: ${error.message}`)
|
|
}
|
|
if (config.schemaVersion !== 3) {
|
|
throw new Error(`${CONFIG_FILE_NAME} schemaVersion must be 3`)
|
|
}
|
|
return config
|
|
}
|
|
|
|
export function validateConfig(config, root = process.cwd()) {
|
|
const errors = []
|
|
if (!config.appId) errors.push('appId is required')
|
|
if (!config.mainModuleName) errors.push('mainModuleName is required')
|
|
if (typeof config.pluginVersion !== 'string' || !semver.valid(config.pluginVersion)) {
|
|
errors.push('pluginVersion must be SemVer')
|
|
}
|
|
if (typeof config.appVersionRange !== 'string' || !semver.validRange(config.appVersionRange)) {
|
|
errors.push('appVersionRange must be a valid SemVer range')
|
|
}
|
|
if (!Array.isArray(config.modules) || config.modules.length === 0) {
|
|
errors.push('modules must contain at least one module')
|
|
return errors
|
|
}
|
|
|
|
const ids = new Set()
|
|
for (const module of config.modules) {
|
|
if (!/^[a-z][a-z0-9-]*$/.test(module.id ?? '')) {
|
|
errors.push(`invalid module id: ${module.id ?? '<missing>'}`)
|
|
}
|
|
if (ids.has(module.id)) errors.push(`duplicate module id: ${module.id}`)
|
|
ids.add(module.id)
|
|
if (!SUPPORTED_MODULE_TYPES.has(module.type)) {
|
|
errors.push(`invalid module type for ${module.id}: ${module.type ?? '<missing>'}`)
|
|
}
|
|
if (!module.entry || !existsSync(path.resolve(root, module.entry))) {
|
|
errors.push(`module entry not found for ${module.id}: ${module.entry ?? '<missing>'}`)
|
|
}
|
|
if (module.moduleVersion !== undefined && typeof module.moduleVersion !== 'string') {
|
|
errors.push(`moduleVersion must be a string for ${module.id}`)
|
|
} else if (module.moduleVersion !== undefined && !semver.valid(module.moduleVersion)) {
|
|
errors.push(`moduleVersion must be SemVer for ${module.id}`)
|
|
}
|
|
if (['app', 'buz'].includes(module.type) && module.commonVersionRange !== undefined) {
|
|
if (
|
|
typeof module.commonVersionRange !== 'string' ||
|
|
!semver.validRange(module.commonVersionRange)
|
|
) {
|
|
errors.push(`commonVersionRange must be a valid SemVer range for ${module.id}`)
|
|
}
|
|
}
|
|
if (
|
|
module.minNativeApiLevel !== undefined &&
|
|
(!Number.isInteger(module.minNativeApiLevel) || module.minNativeApiLevel < 1)
|
|
) {
|
|
errors.push(`minNativeApiLevel must be a positive integer for ${module.id}`)
|
|
}
|
|
if (module.metroConfig && !existsSync(path.resolve(root, module.metroConfig))) {
|
|
errors.push(`Metro config not found for ${module.id}: ${module.metroConfig}`)
|
|
}
|
|
}
|
|
return errors
|
|
}
|
|
|
|
export function readPackageVersion(root = process.cwd()) {
|
|
const packageFile = path.join(root, 'package.json')
|
|
if (!existsSync(packageFile)) throw new Error('package.json is missing')
|
|
const pkg = JSON.parse(readFileSync(packageFile, 'utf8'))
|
|
if (typeof pkg.version !== 'string' || pkg.version.trim() === '') {
|
|
throw new Error('package.json version must be a non-empty string')
|
|
}
|
|
return pkg.version.trim()
|
|
}
|
|
|
|
export function compatibleAppRange(appVersion) {
|
|
const parsed = semver.parse(appVersion)
|
|
if (!parsed) throw new Error(`app version must be SemVer: ${appVersion}`)
|
|
return `>=${parsed.version} <${parsed.major + 1}.0.0`
|
|
}
|
|
|
|
export function resolveVersionedModules(config, root = process.cwd(), appVersionOverride) {
|
|
const appVersion = appVersionOverride?.trim() || readPackageVersion(root)
|
|
const pluginVersion = semver.valid(config.pluginVersion)
|
|
if (!pluginVersion) throw new Error('pluginVersion must be SemVer')
|
|
const common = config.modules.find(module => module.type === 'common')
|
|
const commonVersion = common?.moduleVersion ?? pluginVersion
|
|
const commonSemVer = semver.parse(commonVersion)
|
|
if (!commonSemVer) throw new Error(`common module version must be SemVer: ${commonVersion}`)
|
|
const defaultCommonRange = `>=${commonSemVer.version} <${commonSemVer.major + 1}.0.0`
|
|
return {
|
|
appVersion,
|
|
modules: config.modules.map(module => ({
|
|
...module,
|
|
moduleVersion: module.moduleVersion ?? pluginVersion,
|
|
appVersionRange: module.appVersionRange ?? config.appVersionRange,
|
|
...(module.commonVersionRange === undefined && ['app', 'buz'].includes(module.type)
|
|
? { commonVersionRange: defaultCommonRange }
|
|
: {}),
|
|
minNativeApiLevel: module.minNativeApiLevel ?? 1,
|
|
})),
|
|
}
|
|
}
|
|
|
|
export function selectModules(config, moduleIds = []) {
|
|
if (moduleIds.length === 0) return config.modules
|
|
const requested = new Set(moduleIds)
|
|
const selected = config.modules.filter(module => requested.has(module.id))
|
|
const found = new Set(selected.map(module => module.id))
|
|
const missing = [...requested].filter(id => !found.has(id))
|
|
if (missing.length) throw new Error(`unknown module(s): ${missing.join(', ')}`)
|
|
return selected
|
|
}
|
|
|
|
export function bundleOutputDirectory(root, config, platform, moduleId) {
|
|
return path.resolve(root, config.outputDir ?? './bundle', platform, moduleId)
|
|
}
|
|
|
|
export function bundleFile(root, config, platform, moduleId) {
|
|
return path.join(
|
|
bundleOutputDirectory(root, config, platform, moduleId),
|
|
`${moduleId}.${platform}.bundle`,
|
|
)
|
|
}
|
|
|
|
export function parseModuleOptions(args) {
|
|
const moduleIds = []
|
|
const remaining = []
|
|
for (let index = 0; index < args.length; index += 1) {
|
|
if (args[index] === '--module') {
|
|
const value = args[index + 1]
|
|
if (!value) throw new Error('--module requires a module id')
|
|
moduleIds.push(
|
|
...value
|
|
.split(',')
|
|
.map(id => id.trim())
|
|
.filter(Boolean),
|
|
)
|
|
index += 1
|
|
} else {
|
|
remaining.push(args[index])
|
|
}
|
|
}
|
|
return { moduleIds: [...new Set(moduleIds)], remaining }
|
|
}
|