mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 06:19:43 -10:00
* fix(build): stop bundling the OpenTUI library for the other libc The OpenTUI asset copier ships the native render library from every installed `@opentui/core-*` platform package. On Linux that is two packages, not one: the glibc and musl builds differ only by the `libc` field, so a glibc host installs both and the bundle carries both ELF libraries. That breaks AppImage packaging. linuxdeploy walks every ELF file in the AppDir to deploy its dependencies, and the musl library needs `libc.so`, which no glibc system resolves; `ldd` exits non-zero and linuxdeploy aborts with nothing but `failed to run linuxdeploy` reaching the log. Desktop release 0.2.3-preview.0 failed this way on Linux, because the desktop app copies this dist tree into its bundled runtime verbatim. Skip the Linux package built for the libc this host does not use. The remaining platforms are untouched: their libraries are not ELF, so the Linux bundler ignores them and the completeness gate keeps working. It also drops roughly 27 MB from every Linux bundle. * fix(desktop): sign every Mach-O binary in the bundled runtime The macOS signing step walks a list: the ripgrep vendor binaries and the Node.js runtime. That list covered everything the bundled runtime carried when it was written, but the runtime is a copy of the CLI's dist tree, so its native payload changes whenever a CLI dependency does — with nothing in this package changing. A renderer library arrived that way and shipped unsigned. Release 0.2.3-preview.0 built for twenty minutes on both macOS targets and then lost the archive to the notary service: "The binary is not signed with a valid Developer ID certificate." Enumerate the Mach-O files under the staged runtime and sign all of them, keeping the JIT entitlement on the Node.js binary alone. Verify the signatures in the same step so a gap fails the build in seconds instead of surfacing from the notary service after the build.
190 lines
6.5 KiB
JavaScript
190 lines
6.5 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright 2026 Qwen Team
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* Verifies copy_bundle_assets.js relocates the OpenTUI renderer runtime
|
|
* assets (parser worker, tree-sitter grammars, web-tree-sitter wasm and the
|
|
* native render library) into dist/opentui-assets under exactly the keys the
|
|
* runtime gate (packages/cli/src/ui/opentui/opentui-assets.ts) checks before
|
|
* pointing OTUI_ASSET_ROOT at them.
|
|
*/
|
|
|
|
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
existsSync,
|
|
mkdirSync,
|
|
mkdtempSync,
|
|
rmSync,
|
|
writeFileSync,
|
|
} from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { dirname, join } from 'node:path';
|
|
import { copyOpenTuiAssets } from '../copy_bundle_assets.js';
|
|
|
|
function seedOpentuiPackages(root) {
|
|
const coreDir = join(root, 'node_modules', '@opentui', 'core');
|
|
mkdirSync(coreDir, { recursive: true });
|
|
writeFileSync(
|
|
join(coreDir, 'package.json'),
|
|
JSON.stringify({ name: '@opentui/core', main: 'index.node.js' }),
|
|
);
|
|
writeFileSync(join(coreDir, 'index.node.js'), 'module.exports = {};');
|
|
writeFileSync(join(coreDir, 'parser.worker.js'), '// worker');
|
|
for (const [lang, files] of [
|
|
['javascript', ['highlights.scm', 'tree-sitter-javascript.wasm']],
|
|
[
|
|
'markdown',
|
|
['highlights.scm', 'injections.scm', 'tree-sitter-markdown.wasm'],
|
|
],
|
|
]) {
|
|
const langDir = join(coreDir, 'assets', lang);
|
|
mkdirSync(langDir, { recursive: true });
|
|
for (const file of files) {
|
|
writeFileSync(join(langDir, file), `${lang}/${file}`);
|
|
}
|
|
}
|
|
|
|
const platformDir = join(
|
|
root,
|
|
'node_modules',
|
|
'@opentui',
|
|
'core-darwin-arm64',
|
|
);
|
|
mkdirSync(platformDir, { recursive: true });
|
|
writeFileSync(
|
|
join(platformDir, 'package.json'),
|
|
JSON.stringify({ name: '@opentui/core-darwin-arm64' }),
|
|
);
|
|
writeFileSync(join(platformDir, 'libopentui.dylib'), 'native');
|
|
|
|
const treeSitterDir = join(root, 'node_modules', 'web-tree-sitter');
|
|
mkdirSync(treeSitterDir, { recursive: true });
|
|
writeFileSync(
|
|
join(treeSitterDir, 'package.json'),
|
|
JSON.stringify({ name: 'web-tree-sitter', main: 'tree-sitter.js' }),
|
|
);
|
|
writeFileSync(join(treeSitterDir, 'tree-sitter.js'), '');
|
|
writeFileSync(join(treeSitterDir, 'tree-sitter.wasm'), 'wasm');
|
|
}
|
|
|
|
function seedLinuxPlatformPackages(root) {
|
|
for (const name of ['core-linux-x64', 'core-linux-x64-musl']) {
|
|
const platformDir = join(root, 'node_modules', '@opentui', name);
|
|
mkdirSync(platformDir, { recursive: true });
|
|
writeFileSync(
|
|
join(platformDir, 'package.json'),
|
|
JSON.stringify({ name: `@opentui/${name}` }),
|
|
);
|
|
writeFileSync(join(platformDir, 'libopentui.so'), 'native');
|
|
}
|
|
}
|
|
|
|
describe('copyOpenTuiAssets', () => {
|
|
it('copies the runtime assets under the exact OTUI_ASSET_ROOT keys', () => {
|
|
const root = mkdtempSync(join(tmpdir(), 'opentui-bundle-assets-'));
|
|
try {
|
|
writeFileSync(join(root, 'package.json'), JSON.stringify({}));
|
|
seedOpentuiPackages(root);
|
|
|
|
const copied = copyOpenTuiAssets({ root });
|
|
|
|
const dest = join(root, 'dist', 'opentui-assets');
|
|
expect(copied).toContain('@opentui/core/parser.worker.js');
|
|
expect(copied).toContain(
|
|
'@opentui/core/assets/javascript/tree-sitter-javascript.wasm',
|
|
);
|
|
expect(copied).toContain('@opentui/core/assets/markdown/injections.scm');
|
|
expect(copied).toContain('web-tree-sitter/tree-sitter.wasm');
|
|
expect(copied).toContain('@opentui/core-darwin-arm64/libopentui.dylib');
|
|
for (const key of copied) {
|
|
expect(existsSync(join(dest, key)), key).toBe(true);
|
|
}
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it('warns and skips (never fails the bundle) without @opentui/core', () => {
|
|
const root = mkdtempSync(join(tmpdir(), 'opentui-bundle-assets-'));
|
|
try {
|
|
writeFileSync(join(root, 'package.json'), JSON.stringify({}));
|
|
const copied = copyOpenTuiAssets({ root });
|
|
expect(copied).toEqual([]);
|
|
expect(existsSync(join(root, 'dist', 'opentui-assets'))).toBe(false);
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it('drops a stale tree from an earlier bundle', () => {
|
|
const root = mkdtempSync(join(tmpdir(), 'opentui-bundle-assets-'));
|
|
try {
|
|
writeFileSync(join(root, 'package.json'), JSON.stringify({}));
|
|
seedOpentuiPackages(root);
|
|
// Simulate a leftover from an earlier bundle whose source set still
|
|
// included a platform this install no longer carries: the runtime
|
|
// gate checks key existence only, so it must not survive a re-copy.
|
|
const staleKey = '@opentui/core-linux-x64-musl/libopentui.so';
|
|
const stalePath = join(
|
|
root,
|
|
'dist',
|
|
'opentui-assets',
|
|
...staleKey.split('/'),
|
|
);
|
|
mkdirSync(dirname(stalePath), { recursive: true });
|
|
writeFileSync(stalePath, 'stale');
|
|
|
|
const copied = copyOpenTuiAssets({ root });
|
|
|
|
expect(copied).not.toContain(staleKey);
|
|
expect(existsSync(stalePath)).toBe(false);
|
|
expect(
|
|
existsSync(
|
|
join(
|
|
root,
|
|
'dist',
|
|
'opentui-assets',
|
|
'@opentui',
|
|
'core-darwin-arm64',
|
|
'libopentui.dylib',
|
|
),
|
|
),
|
|
).toBe(true);
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
// npm installs both Linux flavors on a glibc host — only `libc` separates
|
|
// them — and the musl library then aborts AppImage packaging, because
|
|
// linuxdeploy runs `ldd` over every ELF in the tree and no glibc system
|
|
// resolves its `libc.so`.
|
|
it.each([
|
|
{ musl: false, kept: 'core-linux-x64', dropped: 'core-linux-x64-musl' },
|
|
{ musl: true, kept: 'core-linux-x64-musl', dropped: 'core-linux-x64' },
|
|
])(
|
|
'copies only the Linux library for this host libc (musl: $musl)',
|
|
({ musl, kept, dropped }) => {
|
|
const root = mkdtempSync(join(tmpdir(), 'opentui-bundle-assets-'));
|
|
try {
|
|
writeFileSync(join(root, 'package.json'), JSON.stringify({}));
|
|
seedOpentuiPackages(root);
|
|
seedLinuxPlatformPackages(root);
|
|
|
|
const copied = copyOpenTuiAssets({ root, musl });
|
|
|
|
expect(copied).toContain(`@opentui/${kept}/libopentui.so`);
|
|
expect(copied).not.toContain(`@opentui/${dropped}/libopentui.so`);
|
|
const dest = join(root, 'dist', 'opentui-assets', '@opentui');
|
|
expect(existsSync(join(dest, kept, 'libopentui.so'))).toBe(true);
|
|
expect(existsSync(join(dest, dropped))).toBe(false);
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
},
|
|
);
|
|
});
|