XuqmGroup-RNSDK/packages/update/scripts/xuqm_release.mjs

121 行
4.7 KiB
JavaScript

#!/usr/bin/env node
import { existsSync, readFileSync } from 'node:fs'
import { createInterface } from 'node:readline'
import process from 'node:process'
import {
SUPPORTED_PLATFORMS,
bundleFile,
parseModuleOptions,
readConfig,
resolveVersionedModules,
selectModules,
} from './xuqm-config.mjs'
import { computeNativeBaseline } from './native-baseline.mjs'
const root = process.cwd()
const rawArgs = process.argv.slice(2)
function takeOption(name) {
const index = rawArgs.indexOf(name)
if (index < 0) return undefined
const value = rawArgs[index + 1]
if (!value || value.startsWith('--')) throw new Error(`${name} requires a value`)
rawArgs.splice(index, 2)
return value
}
const platform = takeOption('--platform') ?? 'android'
const noteOption = takeOption('--note')
const minNativeApiLevelOption = takeOption('--min-native-api-level')
const publishOptionIndex = rawArgs.indexOf('--publish')
const publishOption = publishOptionIndex >= 0
if (publishOption) rawArgs.splice(publishOptionIndex, 1)
const { moduleIds, remaining } = parseModuleOptions(rawArgs)
if (remaining.length) throw new Error(`unknown option(s): ${remaining.join(' ')}`)
if (!SUPPORTED_PLATFORMS.has(platform)) throw new Error('platform must be android or ios')
const config = readConfig(root)
const versioned = resolveVersionedModules(config, root, process.env.XUQM_APP_VERSION)
const modules = selectModules({ ...config, modules: versioned.modules }, moduleIds)
const nativeBaselineId = computeNativeBaseline(root)
const serverUrl = process.env.XUQM_SERVER_URL ?? config.release?.serverUrl
const appKey = process.env.XUQM_APP_KEY ?? config.release?.appKey
const apiToken = process.env.XUQM_API_TOKEN ?? config.release?.apiToken
if (!serverUrl || !appKey || !apiToken) {
throw new Error(
'publish requires XUQM_SERVER_URL, XUQM_APP_KEY and XUQM_API_TOKEN (or release.* config)',
)
}
const rl = createInterface({ input: process.stdin, output: process.stdout })
const ask = question => new Promise(resolve => rl.question(`\x1b[36m${question}\x1b[0m`, resolve))
const confirm = async question => /^y/i.test(((await ask(`${question} [y/N]: `)) ?? '').trim())
function apiHeaders() {
return { Authorization: `Bearer ${apiToken}` }
}
async function uploadBundle(module, note, minNativeApiLevel) {
const file = bundleFile(root, config, platform, module.id)
if (!existsSync(file)) throw new Error(`bundle is missing: ${file}`)
const form = new FormData()
2026-05-07 19:39:41 +08:00
form.append('appKey', appKey)
form.append('moduleId', module.id)
form.append('platform', platform.toUpperCase())
form.append('version', module.moduleVersion)
form.append('appVersionRange', module.appVersionRange)
form.append('builtAgainstNativeBaselineId', nativeBaselineId)
form.append('commonVersionRange', module.commonVersionRange ?? '')
form.append('minNativeApiLevel', String(module.minNativeApiLevel ?? minNativeApiLevel))
form.append('note', note)
if (config.packageName) form.append('packageName', config.packageName)
form.append('bundle', new Blob([readFileSync(file)]), `${module.id}.${platform}.bundle`)
const response = await fetch(`${serverUrl}/api/v1/rn/upload`, {
method: 'POST',
headers: apiHeaders(),
body: form,
})
if (!response.ok) throw new Error(`upload failed for ${module.id}: ${await response.text()}`)
return response.json()
}
async function main() {
const note = noteOption ?? (await ask('Release notes: '))
const minNativeApiLevel = Number(
minNativeApiLevelOption ?? config.release?.minNativeApiLevel ?? 1,
)
if (!Number.isInteger(minNativeApiLevel) || minNativeApiLevel < 1) {
throw new Error('minNativeApiLevel must be a positive integer')
}
const publishImmediately = publishOption || config.release?.publishImmediately === true
console.log('\n\x1b[36m--- Xuqm RN plugin publish ---\x1b[0m')
console.log(` Platform: ${platform}`)
console.log(
` Modules: ${modules.map(module => `${module.id}@${module.moduleVersion}`).join(', ')}`,
)
console.log(` Publish immediately: ${publishImmediately}`)
if (!(await confirm('Proceed?'))) return
for (const module of modules) {
const result = await uploadBundle(module, note, minNativeApiLevel)
const bundleId = result.data?.id
console.log(`\x1b[32m✓ ${module.id}/${platform} uploaded, ID: ${bundleId}\x1b[0m`)
if (publishImmediately) {
const response = await fetch(`${serverUrl}/api/v1/rn/${bundleId}/publish`, {
method: 'POST',
headers: apiHeaders(),
})
if (!response.ok) throw new Error(`publish failed for ${module.id}: ${await response.text()}`)
console.log(` ${module.id}/${platform} published`)
}
}
}
main()
.catch(error => {
console.error(`\n\x1b[31m${error.message}\x1b[0m`)
process.exitCode = 1
})
.finally(() => rl.close())