2026-07-26 23:47:30 +08:00
|
|
|
import assert from 'node:assert/strict'
|
|
|
|
|
import { createHash } from 'node:crypto'
|
|
|
|
|
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
|
|
|
import { tmpdir } from 'node:os'
|
|
|
|
|
import path from 'node:path'
|
|
|
|
|
import test from 'node:test'
|
|
|
|
|
import { strToU8, zipSync } from 'fflate'
|
|
|
|
|
import { readReleasePackageIdentity } from './release-package.mjs'
|
|
|
|
|
|
|
|
|
|
function createPackage(root, overrides = {}, bundle = 'bundle') {
|
|
|
|
|
const bundleBytes = strToU8(bundle)
|
|
|
|
|
const manifest = {
|
|
|
|
|
schemaVersion: 1,
|
2026-07-27 00:39:26 +08:00
|
|
|
packageName: 'com.example.host',
|
2026-07-26 23:47:30 +08:00
|
|
|
moduleId: 'app',
|
|
|
|
|
platform: 'android',
|
|
|
|
|
version: '1.0.1',
|
|
|
|
|
appVersion: '8.0.0',
|
|
|
|
|
buildId: 'build-20260726',
|
|
|
|
|
bundleFile: 'app.android.bundle',
|
|
|
|
|
bundleSha256: createHash('sha256').update(bundleBytes).digest('hex'),
|
|
|
|
|
bundleFormat: 'hermes-bytecode',
|
|
|
|
|
builtAgainstNativeBaselineId: 'native-1',
|
|
|
|
|
appVersionRange: '>=8.0.0 <9.0.0',
|
|
|
|
|
commonVersionRange: '>=1.0.0 <2.0.0',
|
|
|
|
|
minNativeApiLevel: 2,
|
|
|
|
|
...overrides,
|
|
|
|
|
}
|
|
|
|
|
const file = path.join(root, 'app.android.xuqm.zip')
|
|
|
|
|
writeFileSync(
|
|
|
|
|
file,
|
|
|
|
|
zipSync({
|
|
|
|
|
'rn-manifest.json': strToU8(JSON.stringify(manifest)),
|
|
|
|
|
'app.android.bundle': bundleBytes,
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
return file
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const expected = {
|
2026-07-27 00:39:26 +08:00
|
|
|
packageName: 'com.example.host',
|
2026-07-26 23:47:30 +08:00
|
|
|
moduleId: 'app',
|
|
|
|
|
platform: 'android',
|
|
|
|
|
moduleVersion: '1.0.1',
|
|
|
|
|
appVersion: '8.0.0',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test('release identity is read from and verified against the packaged Bundle', () => {
|
|
|
|
|
const root = mkdtempSync(path.join(tmpdir(), 'xuqm-release-package-'))
|
|
|
|
|
try {
|
|
|
|
|
const identity = readReleasePackageIdentity(createPackage(root), expected)
|
2026-07-27 00:39:26 +08:00
|
|
|
assert.equal(identity.packageName, 'com.example.host')
|
2026-07-26 23:47:30 +08:00
|
|
|
assert.equal(identity.buildId, 'build-20260726')
|
|
|
|
|
assert.equal(identity.bundleFormat, 'hermes-bytecode')
|
|
|
|
|
assert.equal(identity.nativeBaselineId, 'native-1')
|
|
|
|
|
assert.equal(identity.appVersionRange, '>=8.0.0 <9.0.0')
|
|
|
|
|
assert.equal(identity.commonVersionRange, '>=1.0.0 <2.0.0')
|
|
|
|
|
assert.equal(identity.minNativeApiLevel, 2)
|
|
|
|
|
} finally {
|
|
|
|
|
rmSync(root, { recursive: true, force: true })
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2026-07-27 00:39:26 +08:00
|
|
|
test('publishing requires and exactly matches the packaged packageName', () => {
|
|
|
|
|
const root = mkdtempSync(path.join(tmpdir(), 'xuqm-release-package-name-'))
|
|
|
|
|
try {
|
|
|
|
|
assert.throws(
|
|
|
|
|
() => readReleasePackageIdentity(createPackage(root, { packageName: undefined }), expected),
|
|
|
|
|
/manifest packageName is missing/,
|
|
|
|
|
)
|
|
|
|
|
assert.throws(
|
|
|
|
|
() =>
|
|
|
|
|
readReleasePackageIdentity(
|
|
|
|
|
createPackage(root, { packageName: 'com.example.other' }),
|
|
|
|
|
expected,
|
|
|
|
|
),
|
|
|
|
|
/packageName mismatch: expected com\.example\.host, got com\.example\.other/,
|
|
|
|
|
)
|
|
|
|
|
} finally {
|
|
|
|
|
rmSync(root, { recursive: true, force: true })
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2026-07-26 23:47:30 +08:00
|
|
|
test('publishing rejects development identity and mismatched packaged Bundle hash', () => {
|
|
|
|
|
const root = mkdtempSync(path.join(tmpdir(), 'xuqm-release-package-invalid-'))
|
|
|
|
|
try {
|
|
|
|
|
assert.throws(
|
|
|
|
|
() => readReleasePackageIdentity(createPackage(root, { buildId: 'development' }), expected),
|
|
|
|
|
/without a release buildId/,
|
|
|
|
|
)
|
|
|
|
|
assert.throws(
|
|
|
|
|
() =>
|
|
|
|
|
readReleasePackageIdentity(createPackage(root, { bundleSha256: 'a'.repeat(64) }), expected),
|
|
|
|
|
/does not match bundle content/,
|
|
|
|
|
)
|
|
|
|
|
} finally {
|
|
|
|
|
rmSync(root, { recursive: true, force: true })
|
|
|
|
|
}
|
|
|
|
|
})
|