2026-04-29 00:37:10 +08:00
|
|
|
#!/usr/bin/env node
|
|
|
|
|
|
2026-07-17 13:50:30 +08:00
|
|
|
import { existsSync, readFileSync } from 'node:fs'
|
2026-04-29 00:37:10 +08:00
|
|
|
import { createInterface } from 'node:readline'
|
2026-07-17 13:50:30 +08:00
|
|
|
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
|
2026-04-29 00:37:10 +08:00
|
|
|
}
|
2026-07-17 13:50:30 +08:00
|
|
|
|
|
|
|
|
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
|
2026-04-29 15:46:40 +08:00
|
|
|
if (!serverUrl || !appKey || !apiToken) {
|
2026-07-17 13:50:30 +08:00
|
|
|
throw new Error(
|
|
|
|
|
'publish requires XUQM_SERVER_URL, XUQM_APP_KEY and XUQM_API_TOKEN (or release.* config)',
|
|
|
|
|
)
|
2026-04-29 00:37:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const rl = createInterface({ input: process.stdin, output: process.stdout })
|
2026-07-17 13:50:30 +08:00
|
|
|
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())
|
2026-04-29 00:37:10 +08:00
|
|
|
|
2026-07-17 13:50:30 +08:00
|
|
|
function apiHeaders() {
|
|
|
|
|
return { Authorization: `Bearer ${apiToken}` }
|
2026-04-29 00:37:10 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-17 13:50:30 +08:00
|
|
|
async function uploadBundle(module, note, minNativeApiLevel) {
|
|
|
|
|
const file = bundleFile(root, config, platform, module.id)
|
|
|
|
|
if (!existsSync(file)) throw new Error(`bundle is missing: ${file}`)
|
2026-04-29 00:37:10 +08:00
|
|
|
const form = new FormData()
|
2026-05-07 19:39:41 +08:00
|
|
|
form.append('appKey', appKey)
|
2026-07-17 13:50:30 +08:00
|
|
|
form.append('moduleId', module.id)
|
2026-04-29 00:37:10 +08:00
|
|
|
form.append('platform', platform.toUpperCase())
|
2026-07-17 13:50:30 +08:00
|
|
|
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))
|
2026-04-29 00:37:10 +08:00
|
|
|
form.append('note', note)
|
2026-07-17 13:50:30 +08:00
|
|
|
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`, {
|
2026-04-29 00:37:10 +08:00
|
|
|
method: 'POST',
|
|
|
|
|
headers: apiHeaders(),
|
|
|
|
|
body: form,
|
|
|
|
|
})
|
2026-07-17 13:50:30 +08:00
|
|
|
if (!response.ok) throw new Error(`upload failed for ${module.id}: ${await response.text()}`)
|
|
|
|
|
return response.json()
|
2026-04-29 00:37:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function main() {
|
2026-07-17 13:50:30 +08:00
|
|
|
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')
|
2026-04-29 00:37:10 +08:00
|
|
|
}
|
2026-07-17 13:50:30 +08:00
|
|
|
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`)
|
2026-04-29 00:37:10 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-17 13:50:30 +08:00
|
|
|
main()
|
|
|
|
|
.catch(error => {
|
|
|
|
|
console.error(`\n\x1b[31m${error.message}\x1b[0m`)
|
|
|
|
|
process.exitCode = 1
|
|
|
|
|
})
|
|
|
|
|
.finally(() => rl.close())
|