import assert from 'node:assert/strict' import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import path from 'node:path' import test from 'node:test' import { resolveHostPackageId, resolveVersionedModules, validateConfig } from './xuqm-config.mjs' function fixture() { const root = mkdtempSync(path.join(tmpdir(), 'xuqm-config-')) writeFileSync(path.join(root, 'package.json'), JSON.stringify({ version: '7.2.14' })) writeFileSync(path.join(root, 'common.ts'), '') writeFileSync(path.join(root, 'app.ts'), '') writeFileSync(path.join(root, 'metro.config.js'), 'module.exports = {}\n') return root } test('plugins start at the configured plugin version independently from the app version', () => { const root = fixture() try { const config = { appId: 'example', packageName: 'com.example.host', mainModuleName: 'Example', pluginVersion: '1.0.0', appVersionRange: '>=7.2.14 <8.0.0', metroConfig: './metro.config.js', modules: [ { id: 'common', type: 'common', entry: './common.ts' }, { id: 'app', type: 'app', entry: './app.ts' }, ], } assert.deepEqual(validateConfig(config, root), []) const resolved = resolveVersionedModules(config, root) assert.equal(resolved.appVersion, '7.2.14') assert.equal(resolved.modules[0].moduleVersion, '1.0.0') assert.equal(resolved.modules[1].moduleVersion, '1.0.0') assert.equal(resolved.modules[1].commonVersionRange, '>=1.0.0 <2.0.0') assert.equal(resolved.modules[1].minNativeApiLevel, 2) } finally { rmSync(root, { recursive: true, force: true }) } }) test('an explicitly independently released module keeps its own version', () => { const root = fixture() try { const resolved = resolveVersionedModules( { pluginVersion: '1.0.0', appVersionRange: '>=8.0.0 <9.0.0', modules: [ { id: 'common', type: 'common', moduleVersion: '3.0.0' }, { id: 'feature', type: 'buz', moduleVersion: '4.1.0' }, ], }, root, '8.0.0', ) assert.equal(resolved.appVersion, '8.0.0') assert.equal(resolved.modules[0].moduleVersion, '3.0.0') assert.equal(resolved.modules[1].moduleVersion, '4.1.0') assert.equal(resolved.modules[1].commonVersionRange, '>=3.0.0 <4.0.0') } finally { rmSync(root, { recursive: true, force: true }) } }) test('only common can own existing shared source roots', () => { const root = fixture() try { mkdirSync(path.join(root, 'src', 'common'), { recursive: true }) const base = { appId: 'example', packageName: 'com.example.host', mainModuleName: 'Example', pluginVersion: '1.0.0', appVersionRange: '>=1.0.0 <2.0.0', metroConfig: './metro.config.js', } assert.deepEqual( validateConfig( { ...base, modules: [ { id: 'common', type: 'common', entry: './common.ts', ownershipRoots: ['./src/common'], }, ], }, root, ), [], ) assert.match( validateConfig( { ...base, modules: [ { id: 'app', type: 'app', entry: './app.ts', ownershipRoots: ['./src/common'], }, ], }, root, ).join('\n'), /only supported by the common module/, ) } finally { rmSync(root, { recursive: true, force: true }) } }) test('release token cannot be stored in xuqm.modules.json', () => { const root = fixture() try { const errors = validateConfig( { appId: 'example', packageName: 'com.example.host', mainModuleName: 'Example', pluginVersion: '1.0.0', appVersionRange: '>=1.0.0 <2.0.0', metroConfig: './metro.config.js', release: { apiToken: 'must-not-enter-source' }, modules: [{ id: 'common', type: 'common', entry: './common.ts' }], }, root, ) assert.match(errors.join('\n'), /release\.apiToken is forbidden.*XUQM_API_TOKEN/) } finally { rmSync(root, { recursive: true, force: true }) } }) test('host package identity resolves exactly once for each target platform', () => { const config = { packageName: 'com.example.android', iosBundleId: 'com.example.ios', } assert.equal(resolveHostPackageId(config, 'android'), 'com.example.android') assert.equal(resolveHostPackageId(config, 'ios'), 'com.example.ios') assert.throws( () => resolveHostPackageId({ packageName: 'com.example.android' }, 'ios'), /iosBundleId is required for ios build and release/, ) assert.throws( () => resolveHostPackageId({ iosBundleId: 'com.example.ios' }, 'android'), /packageName is required for android build and release/, ) })