XuqmGroup-RNSDK/packages/update/scripts/xuqm-config.mjs

166 行
6.4 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
}
if (config.modules.length > 1 && !config.metroConfig) {
errors.push('metroConfig is required for multi-module builds')
}
if (config.metroConfig && !existsSync(path.resolve(root, config.metroConfig))) {
errors.push(`Metro config not found: ${config.metroConfig}`)
}
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 }
}