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()
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())