XuqmGroup-RNSDK/packages/update/scripts/xuqm-rn.test.mjs

289 行
10 KiB
JavaScript

import assert from 'node:assert/strict'
import { execFileSync } from 'node:child_process'
import {
chmodSync,
copyFileSync,
mkdirSync,
mkdtempSync,
readFileSync,
rmSync,
writeFileSync,
} from 'node:fs'
import { createRequire } from 'node:module'
import { tmpdir } from 'node:os'
import path from 'node:path'
import test from 'node:test'
import { fileURLToPath } from 'node:url'
const cli = fileURLToPath(new URL('./xuqm-rn.mjs', import.meta.url))
const workspaceRequire = createRequire(import.meta.url)
test('embed creates a complete versioned manifest from a host fixture', () => {
const root = mkdtempSync(path.join(tmpdir(), 'xuqm-cli-'))
const output = path.join(root, 'generated', 'rn-bundles')
try {
writeFileSync(
path.join(root, 'package.json'),
JSON.stringify({
name: 'fixture',
version: '7.2.14',
dependencies: { '@xuqm/rn-update': '0.5.0-alpha.5' },
}),
)
writeFileSync(path.join(root, 'common.ts'), '')
writeFileSync(path.join(root, 'app.ts'), '')
writeFileSync(
path.join(root, 'xuqm.config.json'),
JSON.stringify({
schemaVersion: 3,
appId: 'fixture.app',
mainModuleName: 'Fixture',
pluginVersion: '1.0.0',
appVersionRange: '>=7.2.14 <8.0.0',
outputDir: './bundle',
embeddedOutput: { android: './unused' },
modules: [
{ id: 'common', type: 'common', entry: './common.ts' },
{ id: 'app', type: 'app', entry: './app.ts' },
],
}),
)
for (const moduleId of ['common', 'app']) {
const directory = path.join(root, 'bundle', 'android', moduleId)
mkdirSync(directory, { recursive: true })
writeFileSync(path.join(directory, `${moduleId}.android.bundle`), moduleId)
}
execFileSync(process.execPath, [cli, 'embed', 'android', '--skip-build', '--output', output], {
cwd: root,
stdio: 'pipe',
})
const manifest = JSON.parse(readFileSync(path.join(output, 'manifest.json'), 'utf8'))
assert.equal(manifest.appVersion, '7.2.14')
assert.equal(manifest.bundleFormat, 'javascript')
assert.equal(manifest.modules.common.moduleVersion, '1.0.0')
assert.equal(manifest.modules.app.moduleVersion, '1.0.0')
assert.equal(manifest.modules.app.commonVersionRange, '>=1.0.0 <2.0.0')
assert.equal(manifest.modules.app.minNativeApiLevel, 1)
assert.equal(manifest.modules.app.appVersionRange, '>=7.2.14 <8.0.0')
assert.equal(readFileSync(path.join(output, 'app.android.bundle'), 'utf8'), 'app')
} finally {
rmSync(root, { recursive: true, force: true })
}
})
test('Android Hermes hosts compile plugin bundles to bytecode', () => {
const root = mkdtempSync(path.join(tmpdir(), 'xuqm-cli-hermes-'))
try {
writeFileSync(
path.join(root, 'package.json'),
JSON.stringify({
name: 'fixture',
version: '1.0.0',
dependencies: { '@xuqm/rn-update': '0.5.0-alpha.10' },
}),
)
writeFileSync(path.join(root, 'common.ts'), '')
mkdirSync(path.join(root, 'android'), { recursive: true })
writeFileSync(path.join(root, 'android', 'gradle.properties'), 'hermesEnabled=true\n')
writeFileSync(
path.join(root, 'xuqm.config.json'),
JSON.stringify({
schemaVersion: 3,
appId: 'fixture.app',
mainModuleName: 'Fixture',
pluginVersion: '1.0.0',
appVersionRange: '>=1.0.0 <2.0.0',
outputDir: './bundle',
embeddedOutput: { android: './generated' },
modules: [{ id: 'common', type: 'common', entry: './common.ts' }],
}),
)
const reactNativeDirectory = path.join(root, 'node_modules', 'react-native')
const compilerDirectory = path.join(root, 'node_modules', 'hermes-compiler')
mkdirSync(path.join(reactNativeDirectory, 'scripts'), { recursive: true })
mkdirSync(
path.join(
compilerDirectory,
'hermesc',
process.platform === 'win32'
? 'win64-bin'
: process.platform === 'darwin'
? 'osx-bin'
: 'linux64-bin',
),
{ recursive: true },
)
writeFileSync(
path.join(reactNativeDirectory, 'package.json'),
JSON.stringify({
name: 'react-native',
version: '0.86.0',
dependencies: { 'hermes-compiler': '1.0.0' },
}),
)
writeFileSync(
path.join(reactNativeDirectory, 'cli.js'),
`
const fs = require('node:fs');
const path = require('node:path');
const args = process.argv.slice(2);
const output = args[args.indexOf('--bundle-output') + 1];
fs.mkdirSync(path.dirname(output), { recursive: true });
fs.writeFileSync(output, 'javascript');
`,
)
writeFileSync(
path.join(compilerDirectory, 'package.json'),
JSON.stringify({ name: 'hermes-compiler', version: '1.0.0' }),
)
const compiler = path.join(
compilerDirectory,
'hermesc',
process.platform === 'win32'
? 'win64-bin'
: process.platform === 'darwin'
? 'osx-bin'
: 'linux64-bin',
process.platform === 'win32' ? 'hermesc.exe' : 'hermesc',
)
// 使用当前 React Native 实际依赖的 Hermes 编译器。文本脚本即使命名为
// hermesc.exe 也不是 Windows 可执行文件,会让 CI 只验证到测试替身而非真实链路。
const workspaceReactNativeManifest = workspaceRequire.resolve('react-native/package.json')
const workspaceReactNativeRequire = createRequire(workspaceReactNativeManifest)
const workspaceCompilerManifest = workspaceReactNativeRequire.resolve(
'hermes-compiler/package.json',
)
const workspaceCompiler = path.join(
path.dirname(workspaceCompilerManifest),
'hermesc',
process.platform === 'win32'
? 'win64-bin'
: process.platform === 'darwin'
? 'osx-bin'
: 'linux64-bin',
process.platform === 'win32' ? 'hermesc.exe' : 'hermesc',
)
copyFileSync(workspaceCompiler, compiler)
if (process.platform !== 'win32') chmodSync(compiler, 0o755)
execFileSync(process.execPath, [cli, 'embed', 'android'], {
cwd: root,
stdio: 'pipe',
})
const bytecode = readFileSync(path.join(root, 'generated', 'common.android.bundle'))
assert.ok(bytecode.length > Buffer.byteLength('javascript'))
assert.notEqual(bytecode.toString('utf8'), 'javascript')
const manifest = JSON.parse(readFileSync(path.join(root, 'generated', 'manifest.json'), 'utf8'))
assert.equal(manifest.bundleFormat, 'hermes-bytecode')
} finally {
rmSync(root, { recursive: true, force: true })
}
})
test('build invokes the host-local React Native CLI with a consistent color environment', () => {
const root = mkdtempSync(path.join(tmpdir(), 'xuqm-cli-local-rn-'))
try {
writeFileSync(
path.join(root, 'package.json'),
JSON.stringify({
name: 'fixture',
version: '1.0.0',
dependencies: { '@xuqm/rn-update': '0.5.0-alpha.8' },
}),
)
writeFileSync(path.join(root, 'common.ts'), '')
writeFileSync(
path.join(root, 'xuqm.config.json'),
JSON.stringify({
schemaVersion: 3,
appId: 'fixture.app',
mainModuleName: 'Fixture',
pluginVersion: '1.0.0',
appVersionRange: '>=1.0.0 <2.0.0',
outputDir: './bundle',
embeddedOutput: { android: './generated' },
modules: [{ id: 'common', type: 'common', entry: './common.ts' }],
}),
)
const reactNativeDirectory = path.join(root, 'node_modules', 'react-native')
mkdirSync(reactNativeDirectory, { recursive: true })
writeFileSync(
path.join(reactNativeDirectory, 'cli.js'),
`
const fs = require('node:fs');
const path = require('node:path');
const args = process.argv.slice(2);
const output = args[args.indexOf('--bundle-output') + 1];
fs.mkdirSync(path.dirname(output), { recursive: true });
fs.writeFileSync(output, 'bundle');
fs.writeFileSync(path.join(process.cwd(), 'child.json'), JSON.stringify({
args,
forceColor: process.env.FORCE_COLOR ?? null,
noColor: process.env.NO_COLOR ?? null,
}));
`,
)
execFileSync(process.execPath, [cli, 'build', 'android'], {
cwd: root,
env: { ...process.env, FORCE_COLOR: '1', NO_COLOR: '1' },
stdio: 'pipe',
})
const child = JSON.parse(readFileSync(path.join(root, 'child.json'), 'utf8'))
assert.equal(child.args[0], 'bundle')
assert.equal(child.forceColor, '0')
assert.equal(child.noColor, null)
} finally {
rmSync(root, { recursive: true, force: true })
}
})
test('start and run forward arguments through the host-local React Native CLI', () => {
const root = mkdtempSync(path.join(tmpdir(), 'xuqm-cli-run-rn-'))
try {
const reactNativeDirectory = path.join(root, 'node_modules', 'react-native')
mkdirSync(reactNativeDirectory, { recursive: true })
writeFileSync(
path.join(reactNativeDirectory, 'cli.js'),
`
const fs = require('node:fs');
const callsFile = require('node:path').join(process.cwd(), 'calls.json');
const calls = fs.existsSync(callsFile) ? JSON.parse(fs.readFileSync(callsFile, 'utf8')) : [];
calls.push({
args: process.argv.slice(2),
forceColor: process.env.FORCE_COLOR ?? null,
noColor: process.env.NO_COLOR ?? null,
});
fs.writeFileSync(callsFile, JSON.stringify(calls));
`,
)
const environment = { ...process.env, FORCE_COLOR: '1', NO_COLOR: '1' }
execFileSync(process.execPath, [cli, 'start', '--reset-cache'], {
cwd: root,
env: environment,
stdio: 'pipe',
})
execFileSync(process.execPath, [cli, 'run', 'android', '--device', 'emulator-5554'], {
cwd: root,
env: environment,
stdio: 'pipe',
})
const calls = JSON.parse(readFileSync(path.join(root, 'calls.json'), 'utf8'))
assert.deepEqual(calls[0].args, ['start', '--reset-cache'])
assert.deepEqual(calls[1].args, ['run-android', '--device', 'emulator-5554'])
assert.equal(calls[0].forceColor, '0')
assert.equal(calls[1].forceColor, '0')
assert.equal(calls[0].noColor, null)
assert.equal(calls[1].noColor, null)
} finally {
rmSync(root, { recursive: true, force: true })
}
})