mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-10-21 14:21:48 +00:00
fix(standalone): pin @lydell/node-pty-linux-arm64 and fail release on missing prebuilds (#12649)
* fix(standalone): pin @lydell/node-pty-linux-arm64 and make missing prebuilds fatal at release The wrapper publishes a linux-arm64 prebuild at the same 1.2.0-beta.10 pin, but the root and core manifests never declared it, so the linux-arm64 standalone archive and the linux-arm64 Desktop runtime both degraded to "PTY not available" while every other target shipped a working web terminal. With the pin in place every shipped target resolves a prebuild package, so the release archive build can stop degrading silently: release.yml now exports QWEN_STANDALONE_REQUIRE_NODE_PTY_PREBUILD=1 next to the existing audio-capture gate, turning a missing prebuild into a hard failure instead of another #11872. Fixes #11872 * fix(standalone): update pnpm lockfile for the linux-arm64 node-pty pin The two package.json manifests gained @lydell/node-pty-linux-arm64 but pnpm-lock.yaml was not regenerated, so every CI job failed at the frozen-lockfile install with ERR_PNPM_OUTDATED_LOCKFILE. * docs(standalone): align the linux-arm64 PTY guide and tripwire with the pin Pinning @lydell/node-pty-linux-arm64 makes linux-arm64 a supported PTY target, but INSTALLATION_GUIDE.md still told users that archive ships without PTY and reports "PTY not available". CI could not catch the drift because install-script.test.js pinned that exact sentence as its tripwire, so the guide and the assertion have to move together. List all five shipped targets in the guide, retarget the tripwire at the new sentence, and refresh the matching stale comment in desktop-shell's test-release.js degrade fixture - that arm now only fires for a target the map does not know. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(standalone): complete the node-pty linux-arm64 pin in esbuild externals `create-standalone-package.js` documents that "esbuild.config.js keeps every node-pty specifier external" so the bundled CLI resolves the platform prebuild from `lib/node_modules` at runtime. The external list carried five of the six pinned platform packages and omitted `@lydell/node-pty-linux-arm64` — the exact platform this PR pins. Nothing imports that specifier statically today, so the bundle is unaffected; the gap is that the invariant the archive's PTY support rests on was one entry short of complete. Probe (esbuild, `--platform=node --format=esm`, entry importing both specifiers): - without the entry: `Could not resolve "@lydell/node-pty-linux-arm64"` - with the entry: builds clean and emits the import left external, matching the other five platforms Also corrects two comments this PR rewrote in the previous round. Both claimed the desktop degrade arm "fires only for a target the map does not know (a future platform)". That trigger is unreachable: `desktopTarget()` throws `Unsupported desktop target` for anything outside its five-entry allowlist and `NODE_PTY_PREBUILD_PACKAGE` maps exactly those five, so `prebuildPackage` is always a real name. Verified by running the script: $ QWEN_DESKTOP_SKIP_BUILD=1 QWEN_DESKTOP_TARGET=freebsd-x64 \ node packages/desktop-shell/scripts/prepare-runtime.js Error: Unsupported desktop target: freebsd-x64 at desktopTarget (prepare-runtime.js:351:11) The reachable trigger is the one the warn string already names and the one `test-release.js` reproduces four lines below its own comment: a known target whose prebuild package is unpinned in the source root manifest. Comment-only apart from the single external entry. prettier --check and eslint --max-warnings 0 clean on all three files; node --check passes. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(standalone): pin the node-pty release gate and its target map Closes out three review findings on the node-pty pin. Test- and comment-only apart from one new export; no production behavior changes. R1-1 prepare-runtime.js: the nodePtyPackageSpecs() docstring still claimed its null return "is how an unsupported target degrades". desktopTarget() throws for anything outside its five-entry allowlist, and test-release.js:testRuntimeNodePtyTargetMapping() asserts NODE_PTY_PREBUILD_PACKAGE's keys equal exactly that allowlist, so an unknown target can never reach it. A null here is a dropped pin. Verified by re-running that tripwire's extraction against the edited file: both literals still match and keys == allowlist == the same five targets. R1-2 create-standalone-package.js: export TARGET_PREBUILD_DIR and assert every release target maps to a package the manifests actually pin. The guide sentence this PR adds states that invariant while release.yml:707 makes violating it abort the whole archive step, yet the map had zero test references. The expectation is derived from readNodePtyPackageSpecs() so the version list stays single-sourced, and is one-directional because @lydell/node-pty-win32-arm64 is pinned while no target maps to it. R1-3 install-script.test.js: the release.yml assertion matched only the variable name, which also appears in the packager's usage text, so nothing connected producer to consumer and no test executed the fail() branch at create-standalone-package.js:578. Tightened to the full wiring line and added the behavioral twin of the audio-capture pair: gate on with the addon removed must throw, gate off must still build a PTY-less archive. packageFakeStandalone() gained an env option for this; spreading process.env leaves its 15 existing callers unchanged. Mutation-checked, each mutant applied alone then restored and md5 verified: win-x64 mapped to an unpinned name, gate reader renamed to '2', gate polarity flipped to !==, and release.yml no longer evaluating to '1' are each caught by a new or tightened assertion. The polarity flip was previously undetectable on the PR lane, where the linux-x64 prebuild is present so the gate is never reached. Focused: install-script.test.js 130 passed / 11 skipped (141). prettier --check and eslint --max-warnings 0 clean on all three files. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmufv3et42h * docs(standalone): state the real node-pty degrade conditions in comments prepare-runtime.js said the degrade arm fires only "where the repo pins nothing", but nodePtyPackageSpecs returns null at the first unpinned name of the pair, so it also fires when the wrapper pin is the one that went missing while the warning names the prebuild package. Say "either name in the pair" and note which package the warning names. create-standalone-package.js justified the non-fatal default with "a developer archive stays usable on an unmapped future target", which cannot happen: main() calls copyNativeAddon first and it reads the same TARGET_PREBUILD_DIR in an unconditional path.join(), so an unmapped target dies with a TypeError in the audio step before copyNodePtyAddon runs. Replace the rationale with the case that does reach the warn arm (a target whose prebuild the host install does not have). Comment-only; no behaviour change. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmug1iw422v * test(standalone): make the node-pty gate assertions discriminate Pin the `Required ` prefix on the fatal-gate regex. copyNodePtyAddon builds one message and emits it on both paths, so the prefix-less regex was also satisfied by the degrade warning folded into the child's stderr — the suite would have reported the gate as covered after the prefix was dropped from fail(). The sibling clipboard gate assertion already spells the prefix. Assert QWEN_STANDALONE_REQUIRE_NODE_PTY_PREBUILD structurally on the `Build Standalone Archives` step env instead of as a whole-file substring, so commenting the line out or moving it into a neighbouring step's env goes red. build-standalone-release.js spawns the packager with no env override, so that step's env is the only route to the gate. The comment defending the substring pin named a collision with create-standalone-package.js's usage text, which cannot occur: the searched string is release.yml alone. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmug1iw422v * test(standalone): cover the node-pty target map and the win-x64 dir name Two coverage gaps left by the previous round of gate tests: - The map test looped over TARGET_PREBUILD_DIR itself, so a release target with no map entry was never visited, deleting a whole entry merely removed an iteration, and an emptied map passed vacuously. Drive the loop from TARGETS and assert the lookup before the pin. Still one-directional: @lydell/node-pty-win32-arm64 is pinned while no target maps to it. - Both gate tests reached the packager through packageFakeStandalone, which hardcoded --target linux-x64, the one dialect where the prebuild dir name equals the target name. Add a win-x64 arm so the 'win-x64' -> 'win32-x64' translation in copyNodePtyAddon is covered on every lane rather than only on test_windows. Parameterise the staged-modules fixture and the packaging helper to make that reachable without a Windows host. Test-only change; no production code is modified. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmug5t7mp33 --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
parent
2686cad25f
commit
010941d202
11 changed files with 276 additions and 45 deletions
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
|
|
@ -704,6 +704,7 @@ jobs:
|
|||
env:
|
||||
RELEASE_VERSION: '${{ needs.prepare.outputs.release_version }}'
|
||||
QWEN_STANDALONE_REQUIRE_AUDIO_CAPTURE_PREBUILD: "${{ github.repository == 'QwenLM/qwen-code' && '1' || '' }}"
|
||||
QWEN_STANDALONE_REQUIRE_NODE_PTY_PREBUILD: "${{ github.repository == 'QwenLM/qwen-code' && '1' || '' }}"
|
||||
OPENTUI_PREVIEW_RELEASE_ENABLED: "${{ vars.OPENTUI_PREVIEW_RELEASE_ENABLED == 'true' }}"
|
||||
run: '.release-workflow/.github/scripts/run-release-step.sh build-archives'
|
||||
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ const external = [
|
|||
'node-pty',
|
||||
'@lydell/node-pty-darwin-arm64',
|
||||
'@lydell/node-pty-darwin-x64',
|
||||
'@lydell/node-pty-linux-arm64',
|
||||
'@lydell/node-pty-linux-x64',
|
||||
'@lydell/node-pty-win32-arm64',
|
||||
'@lydell/node-pty-win32-x64',
|
||||
|
|
|
|||
|
|
@ -195,6 +195,7 @@
|
|||
"@lydell/node-pty": "1.2.0-beta.10",
|
||||
"@lydell/node-pty-darwin-arm64": "1.2.0-beta.10",
|
||||
"@lydell/node-pty-darwin-x64": "1.2.0-beta.10",
|
||||
"@lydell/node-pty-linux-arm64": "1.2.0-beta.10",
|
||||
"@lydell/node-pty-linux-x64": "1.2.0-beta.10",
|
||||
"@lydell/node-pty-win32-arm64": "1.2.0-beta.10",
|
||||
"@lydell/node-pty-win32-x64": "1.2.0-beta.10"
|
||||
|
|
|
|||
|
|
@ -176,6 +176,7 @@
|
|||
"@lydell/node-pty": "1.2.0-beta.10",
|
||||
"@lydell/node-pty-darwin-arm64": "1.2.0-beta.10",
|
||||
"@lydell/node-pty-darwin-x64": "1.2.0-beta.10",
|
||||
"@lydell/node-pty-linux-arm64": "1.2.0-beta.10",
|
||||
"@lydell/node-pty-linux-x64": "1.2.0-beta.10",
|
||||
"@lydell/node-pty-win32-arm64": "1.2.0-beta.10",
|
||||
"@lydell/node-pty-win32-x64": "1.2.0-beta.10"
|
||||
|
|
|
|||
|
|
@ -61,12 +61,12 @@ describe('conpty-host', () => {
|
|||
// human re-check the comment above describes. The WindowsPtyAgent field
|
||||
// shape and conpty.cc baton-erase semantics live in the win32 prebuilds, so
|
||||
// those keys must trip the same guard as the loader.
|
||||
expect(Object.keys(corePins)).toHaveLength(6);
|
||||
expect(Object.keys(corePins)).toHaveLength(7);
|
||||
expect(
|
||||
Object.values(corePins).every((version) => version === VERIFIED_NODE_PTY),
|
||||
).toBe(true);
|
||||
// packages/cli declares no node-pty and resolves the root-hoisted copy, so
|
||||
// the root manifest's six must stay in lockstep with core's six — the
|
||||
// the root manifest's seven must stay in lockstep with core's seven — the
|
||||
// declaration agent-view actually loads in a dev tree.
|
||||
expect(corePins).toEqual(rootPins);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -231,10 +231,15 @@ function stageNodePty(desktopTarget) {
|
|||
const packageNames = ['@lydell/node-pty', prebuildPackage];
|
||||
const specs = nodePtyPackageSpecs(packageNames);
|
||||
if (!specs) {
|
||||
// Degrade only where the repo pins nothing: upstream publishes
|
||||
// @lydell/node-pty-linux-arm64, but the root package.json does not pin it,
|
||||
// and failing there would trade a missing Web Terminal for no app at all.
|
||||
// The release job still refuses to publish such a runtime —
|
||||
// Degrade when the source root leaves either name in the pair unpinned —
|
||||
// the wrapper or this target's prebuild. desktopTarget() throws for any
|
||||
// target outside the five it allows and NODE_PTY_PREBUILD_PACKAGE maps
|
||||
// exactly those five, so prebuildPackage is always a real name: reaching
|
||||
// this arm means the source root's optionalDependencies dropped a pin, not
|
||||
// that the target is unknown. The warning below names the prebuild package
|
||||
// whichever of the two is missing. Degrading still beats failing — a
|
||||
// checkout missing one pin would otherwise trade a missing Web Terminal for
|
||||
// no app at all. The release job still refuses to publish such a runtime —
|
||||
// smoke-runtime.js's PTY round-trip hard-fails.
|
||||
console.warn(
|
||||
`[desktop] ${prebuildPackage} is not pinned in ` +
|
||||
|
|
@ -302,8 +307,10 @@ function stageNodePty(desktopTarget) {
|
|||
|
||||
// The exact versions declared by the checkout the release job installed
|
||||
// (QWEN_CODE_ROOT). The source's frozen install already verifies these pins
|
||||
// against its lockfile. Returns null when the repo pins none of them, which is
|
||||
// how an unsupported target degrades instead of inventing a version.
|
||||
// against its lockfile. Returns null when the source root's
|
||||
// optionalDependencies is missing one of them; desktopTarget() has already
|
||||
// rejected every target the map does not cover, so a null here is a dropped
|
||||
// pin, never an unknown target.
|
||||
function nodePtyPackageSpecs(packageNames) {
|
||||
const rootPackage = JSON.parse(
|
||||
fs.readFileSync(path.join(sourceRoot, 'package.json'), 'utf8'),
|
||||
|
|
|
|||
|
|
@ -767,11 +767,11 @@ globalThis.fetch = async (url) => {
|
|||
);
|
||||
assert.equal(fs.existsSync(path.join(cacheDir, 'SHASUMS256.txt')), false);
|
||||
|
||||
// A target the repo pins nothing for must still produce a runtime: the root
|
||||
// package.json does not pin @lydell/node-pty-linux-arm64 (upstream publishes
|
||||
// it), and failing the build there would trade a missing Web Terminal for no
|
||||
// app at all (#11872). Dropping the pins reproduces that for this fixture's
|
||||
// target.
|
||||
// A target the repo pins nothing for must still produce a runtime: the
|
||||
// degrade arm fires on a dropped pin, not on an unknown target, and failing
|
||||
// there would trade a missing Web Terminal for no app at all (#11872).
|
||||
// Emptying optionalDependencies reproduces that for this fixture's target,
|
||||
// which desktopTarget() accepts.
|
||||
fs.writeFileSync(
|
||||
path.join(sourceRoot, 'package.json'),
|
||||
JSON.stringify({ version: '0.0.0-test' }),
|
||||
|
|
|
|||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
|
|
@ -218,6 +218,9 @@ importers:
|
|||
'@lydell/node-pty-darwin-x64':
|
||||
specifier: 1.2.0-beta.10
|
||||
version: 1.2.0-beta.10
|
||||
'@lydell/node-pty-linux-arm64':
|
||||
specifier: 1.2.0-beta.10
|
||||
version: 1.2.0-beta.10
|
||||
'@lydell/node-pty-linux-x64':
|
||||
specifier: 1.2.0-beta.10
|
||||
version: 1.2.0-beta.10
|
||||
|
|
@ -1077,6 +1080,9 @@ importers:
|
|||
'@lydell/node-pty-darwin-x64':
|
||||
specifier: 1.2.0-beta.10
|
||||
version: 1.2.0-beta.10
|
||||
'@lydell/node-pty-linux-arm64':
|
||||
specifier: 1.2.0-beta.10
|
||||
version: 1.2.0-beta.10
|
||||
'@lydell/node-pty-linux-x64':
|
||||
specifier: 1.2.0-beta.10
|
||||
version: 1.2.0-beta.10
|
||||
|
|
|
|||
|
|
@ -550,12 +550,17 @@ function copyClipboardAddon(packageRoot, target, nativeModulesDir) {
|
|||
// the archive declares the packages in optionalDependencies but ships none of
|
||||
// them, so every web terminal creation fails with "PTY not available"
|
||||
// (#11872). Missing packages warn-and-degrade locally (like the audio-capture
|
||||
// step) rather than failing the build: unlike clipboard this set has a known
|
||||
// gap (no pinned linux-arm64 package), so --native-modules-dir cannot be the
|
||||
// fatal gate. QWEN_STANDALONE_REQUIRE_NODE_PTY_PREBUILD=1 opts in to the fatal
|
||||
// gate; nothing sets it today — release.yml exports only
|
||||
// QWEN_STANDALONE_REQUIRE_AUDIO_CAPTURE_PREBUILD for the archive build — and
|
||||
// it cannot be wired unconditionally while linux-arm64 has no pinned package.
|
||||
// step) rather than failing the build, so a developer archive still builds for
|
||||
// a target whose prebuild the host install does not have: npm skips
|
||||
// optionalDependencies whose os/cpu do not match the installing machine, and a
|
||||
// staged --native-modules-dir can carry the clipboard packages without this
|
||||
// target's node-pty prebuild. An unmapped target is not that case —
|
||||
// copyNativeAddon reads the same TARGET_PREBUILD_DIR and its unconditional
|
||||
// path.join() throws on the undefined dir name before this step is reached.
|
||||
// Release builds cannot degrade silently: release.yml exports
|
||||
// QWEN_STANDALONE_REQUIRE_NODE_PTY_PREBUILD=1 for the archive build, turning a
|
||||
// missing prebuild into a hard failure now that every shipped target has a
|
||||
// pinned package.
|
||||
function copyNodePtyAddon(packageRoot, target, nativeModulesDir) {
|
||||
const prebuildDirName = TARGET_PREBUILD_DIR.get(target);
|
||||
const nativePackage = `@lydell/node-pty-${prebuildDirName}`;
|
||||
|
|
@ -1141,6 +1146,11 @@ function fail(message) {
|
|||
|
||||
export {
|
||||
TARGET_CLIPBOARD_PACKAGE,
|
||||
// Exported so a test can hold the target map and the manifest's pins
|
||||
// together: the release build now fails on a missing prebuild, so a target
|
||||
// mapped to an unpinned package name would abort the whole archive step
|
||||
// instead of degrading one archive.
|
||||
TARGET_PREBUILD_DIR,
|
||||
TARGETS,
|
||||
standaloneArchiveName,
|
||||
writeSha256Sums,
|
||||
|
|
|
|||
|
|
@ -146,11 +146,10 @@ install-qwen-standalone.bat --repair-path --path-scope machine
|
|||
The standalone archives bundle Qwen Code, a private Node.js runtime, and the
|
||||
matching `@teddyzhu/clipboard` native package for clipboard image paste. They
|
||||
also ship `@lydell/node-pty` plus the matching platform prebuild for
|
||||
`darwin-arm64`, `darwin-x64`, `linux-x64` and `win-x64`, which is what the web
|
||||
terminal and the PTY-backed interactive shell resolve at runtime. `linux-arm64`
|
||||
is the exception: `@lydell/node-pty-linux-arm64` is published, but this repo
|
||||
does not pin it, so that archive ships without PTY support and reports
|
||||
`PTY not available`.
|
||||
`darwin-arm64`, `darwin-x64`, `linux-arm64`, `linux-x64` and `win-x64`, which
|
||||
is what the web terminal and the PTY-backed interactive shell resolve at
|
||||
runtime. Every target the release builds pins its prebuild package, and the
|
||||
release archive build fails rather than publish one that is missing it.
|
||||
|
||||
Use `--method npm` if you specifically need npm to resolve optional native
|
||||
modules for the current machine.
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { parse } from 'yaml';
|
||||
|
||||
const {
|
||||
appendFileSync,
|
||||
|
|
@ -1186,11 +1187,12 @@ describe('standalone release packaging', () => {
|
|||
);
|
||||
|
||||
// The list tracks the root package.json optionalDependencies, so a newly
|
||||
// pinned platform package (e.g. linux-arm64) is staged automatically.
|
||||
// pinned platform package is staged automatically.
|
||||
expect(readNodePtyPackageSpecs()).toEqual([
|
||||
'@lydell/node-pty@1.2.0-beta.10',
|
||||
'@lydell/node-pty-darwin-arm64@1.2.0-beta.10',
|
||||
'@lydell/node-pty-darwin-x64@1.2.0-beta.10',
|
||||
'@lydell/node-pty-linux-arm64@1.2.0-beta.10',
|
||||
'@lydell/node-pty-linux-x64@1.2.0-beta.10',
|
||||
'@lydell/node-pty-win32-arm64@1.2.0-beta.10',
|
||||
'@lydell/node-pty-win32-x64@1.2.0-beta.10',
|
||||
|
|
@ -1211,6 +1213,37 @@ describe('standalone release packaging', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('maps every release target to a pinned node-pty prebuild package', async () => {
|
||||
const { readNodePtyPackageSpecs } = await import(
|
||||
standaloneReleaseScriptUrl
|
||||
);
|
||||
const { TARGETS, TARGET_PREBUILD_DIR } = await import(
|
||||
standalonePackageScriptUrl
|
||||
);
|
||||
|
||||
// Derive the expectation from the pins so the version list stays
|
||||
// single-sourced. One-directional on purpose: @lydell/node-pty-win32-arm64
|
||||
// is pinned while no target maps to it. The prebuild dir name is not the
|
||||
// target name on Windows ('win-x64' -> 'win32-x64'), so the lookup has to
|
||||
// go through the map instead of comparing targets to package suffixes.
|
||||
// Drive the loop from TARGETS rather than from the map being validated: a
|
||||
// target with no map entry would otherwise never be visited, deleting a
|
||||
// whole entry would merely remove an iteration, and an emptied map would
|
||||
// pass vacuously.
|
||||
const specs = readNodePtyPackageSpecs();
|
||||
for (const target of TARGETS.keys()) {
|
||||
const dir = TARGET_PREBUILD_DIR.get(target);
|
||||
expect(
|
||||
dir,
|
||||
`${target} has no node-pty prebuild dir mapping`,
|
||||
).toBeDefined();
|
||||
expect(
|
||||
specs.some((spec) => spec.startsWith(`@lydell/node-pty-${dir}@`)),
|
||||
`${target} maps to @lydell/node-pty-${dir}, which no manifest pins`,
|
||||
).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('validates standalone release checksum output', async () => {
|
||||
const { assertStandaloneOutput, RELEASE_TARGETS } = await import(
|
||||
standaloneReleaseScriptUrl
|
||||
|
|
@ -2544,6 +2577,132 @@ describe('standalone release packaging', () => {
|
|||
}
|
||||
});
|
||||
|
||||
itOnUnix(
|
||||
'fails the release build when a node-pty prebuild is missing',
|
||||
() => {
|
||||
const createdDist = ensureMinimalDist();
|
||||
const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-package-test-'));
|
||||
|
||||
try {
|
||||
const nativeModulesDir = createFakeNodePtyModulesWithoutAddon(tmpDir);
|
||||
|
||||
// `Required ` is what separates the fatal gate from the degrade arm:
|
||||
// copyNodePtyAddon emits the same message on both, so a prefix-less
|
||||
// match is also satisfied by the warning a build prints before dying
|
||||
// for an unrelated reason.
|
||||
expect(() =>
|
||||
packageFakeStandalone(
|
||||
tmpDir,
|
||||
{},
|
||||
{
|
||||
nativeModulesDir,
|
||||
env: { QWEN_STANDALONE_REQUIRE_NODE_PTY_PREBUILD: '1' },
|
||||
},
|
||||
),
|
||||
).toThrow(/Required node-pty packages for linux-x64/);
|
||||
} finally {
|
||||
restoreMinimalDist(createdDist);
|
||||
rmSync(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
itOnUnix('still builds a PTY-less archive when the gate is off', () => {
|
||||
const createdDist = ensureMinimalDist();
|
||||
const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-package-test-'));
|
||||
|
||||
try {
|
||||
const nativeModulesDir = createFakeNodePtyModulesWithoutAddon(tmpDir);
|
||||
|
||||
// The converse arm: a polarity flip on the gate would throw here instead
|
||||
// of building, and degrade still has to ship an archive — just without
|
||||
// the addon it could not find.
|
||||
const archive = packageFakeStandalone(tmpDir, {}, { nativeModulesDir });
|
||||
const extractDir = path.join(tmpDir, 'extract');
|
||||
mkdirSync(extractDir, { recursive: true });
|
||||
execFileSync('tar', ['-xzf', archive, '-C', extractDir], {
|
||||
stdio: 'ignore',
|
||||
});
|
||||
|
||||
expect(
|
||||
existsSync(
|
||||
path.join(
|
||||
extractDir,
|
||||
'qwen-code',
|
||||
'lib',
|
||||
'node_modules',
|
||||
'@lydell',
|
||||
'node-pty-linux-x64',
|
||||
),
|
||||
),
|
||||
).toBe(false);
|
||||
} finally {
|
||||
restoreMinimalDist(createdDist);
|
||||
rmSync(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
// The two gate arms above stage linux-x64, the one target whose prebuild dir
|
||||
// name equals its target name, so neither can tell `prebuildDirName` apart
|
||||
// from `target` in copyNodePtyAddon. win-x64 is the dialect where the two
|
||||
// diverge ('win-x64' -> 'win32-x64'), and packaging it with the gate on
|
||||
// reddens a swap of either name. Nothing here is POSIX-specific — the
|
||||
// packager is host-independent — so this arm is live on every lane instead
|
||||
// of only on test_windows.
|
||||
itWithZip(
|
||||
'bundles the win-x64 node-pty prebuild under its win32-x64 dir name',
|
||||
() => {
|
||||
const createdDist = ensureMinimalDist();
|
||||
const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-package-test-'));
|
||||
|
||||
try {
|
||||
const nativeModulesDir = createFakeNodePtyModules(tmpDir, {
|
||||
prebuildDirName: 'win32-x64',
|
||||
clipboard: '@teddyzhu/clipboard-win32-x64-msvc',
|
||||
});
|
||||
const archive = packageFakeStandalone(
|
||||
tmpDir,
|
||||
{},
|
||||
{
|
||||
nativeModulesDir,
|
||||
target: 'win-x64',
|
||||
nodeArchive: createFakeWindowsNodeArchive(tmpDir),
|
||||
env: { QWEN_STANDALONE_REQUIRE_NODE_PTY_PREBUILD: '1' },
|
||||
},
|
||||
);
|
||||
const extractDir = path.join(tmpDir, 'extract');
|
||||
mkdirSync(extractDir, { recursive: true });
|
||||
extractZipForTest(archive, extractDir);
|
||||
|
||||
const scopedDir = path.join(
|
||||
extractDir,
|
||||
'qwen-code',
|
||||
'lib',
|
||||
'node_modules',
|
||||
'@lydell',
|
||||
);
|
||||
expect(
|
||||
existsSync(
|
||||
path.join(
|
||||
scopedDir,
|
||||
'node-pty-win32-x64',
|
||||
'prebuilds',
|
||||
'win32-x64',
|
||||
'pty.node',
|
||||
),
|
||||
),
|
||||
).toBe(true);
|
||||
// The staged package name comes from the prebuild dir, never from the
|
||||
// target name, so the target-spelled directory must not exist.
|
||||
const targetSpelled = path.join(scopedDir, 'node-pty-win-x64');
|
||||
expect(existsSync(targetSpelled)).toBe(false);
|
||||
} finally {
|
||||
restoreMinimalDist(createdDist);
|
||||
rmSync(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
itOnUnix(
|
||||
'packages the bun flavor as an opentui-preview archive with a renderer-default shim',
|
||||
() => {
|
||||
|
|
@ -2854,6 +3013,19 @@ describe('standalone release packaging', () => {
|
|||
expect(releaseWorkflow).toContain(
|
||||
'QWEN_STANDALONE_REQUIRE_AUDIO_CAPTURE_PREBUILD',
|
||||
);
|
||||
// Pin the gate structurally on the step that actually runs the packager:
|
||||
// build-standalone-release.js spawns it with no env override, so the value
|
||||
// reaches create-standalone-package.js only from this step's env. A
|
||||
// whole-file substring stays green when the line is commented out or moved
|
||||
// into a neighbouring step's env, and the gate then silently reverts to
|
||||
// warn-and-degrade. The value stays the conditional expression because the
|
||||
// publish job also runs on forks, where the gate is deliberately off.
|
||||
const archiveStep = parse(releaseWorkflow).jobs.publish.steps.find(
|
||||
(step) => step.name === 'Build Standalone Archives',
|
||||
);
|
||||
expect(archiveStep?.env?.QWEN_STANDALONE_REQUIRE_NODE_PTY_PREBUILD).toBe(
|
||||
"${{ github.repository == 'QwenLM/qwen-code' && '1' || '' }}",
|
||||
);
|
||||
expect(releaseStepScript).toContain(
|
||||
'npm run verify:installation-release -- --dir dist/standalone',
|
||||
);
|
||||
|
|
@ -3065,14 +3237,13 @@ describe('standalone release packaging', () => {
|
|||
expect(guide).toContain('node-pty');
|
||||
expect(guide).toContain('clipboard');
|
||||
// The archives ship the node-pty wrapper plus the target prebuild, and the
|
||||
// guide has to say so instead of sending PTY users to an npm install; the
|
||||
// linux-arm64 gap it does not cover must stay named. Bare 'linux-arm64'
|
||||
// also occurs in the release-artifact list, so pin the sentence itself:
|
||||
// the prebuild package is published and the gap is a missing pin (#11898).
|
||||
// guide has to say so instead of sending PTY users to an npm install. Bare
|
||||
// 'linux-arm64' also occurs in the release-artifact list, so pin the
|
||||
// sentence that names it as a covered PTY target (#11872).
|
||||
expect(guide).toContain('@lydell/node-pty');
|
||||
const flattenedGuide = guide.replace(/\s+/g, ' ');
|
||||
expect(flattenedGuide).toContain(
|
||||
'`linux-arm64` is the exception: `@lydell/node-pty-linux-arm64` is published, but this repo does not pin it',
|
||||
'`darwin-arm64`, `darwin-x64`, `linux-arm64`, `linux-x64` and `win-x64`',
|
||||
);
|
||||
expect(flattenedGuide).not.toContain('package is published yet');
|
||||
expect(guide).not.toContain(
|
||||
|
|
@ -5361,16 +5532,16 @@ function extractZipForTest(archive, destination) {
|
|||
function packageFakeStandalone(
|
||||
tmpDir,
|
||||
nodeArchiveOptions = {},
|
||||
{ nativeModulesDir } = {},
|
||||
{ nativeModulesDir, env, target = 'linux-x64', nodeArchive } = {},
|
||||
) {
|
||||
const outDir = path.join(tmpDir, 'out');
|
||||
mkdirSync(outDir, { recursive: true });
|
||||
const args = [
|
||||
'scripts/create-standalone-package.js',
|
||||
'--target',
|
||||
'linux-x64',
|
||||
target,
|
||||
'--node-archive',
|
||||
createFakeNodeArchive(tmpDir, nodeArchiveOptions),
|
||||
nodeArchive || createFakeNodeArchive(tmpDir, nodeArchiveOptions),
|
||||
'--out-dir',
|
||||
outDir,
|
||||
'--version',
|
||||
|
|
@ -5379,8 +5550,12 @@ function packageFakeStandalone(
|
|||
if (nativeModulesDir) {
|
||||
args.push('--native-modules-dir', nativeModulesDir);
|
||||
}
|
||||
execFileSync('node', args, { stdio: 'pipe' });
|
||||
return path.join(outDir, 'qwen-code-linux-x64.tar.gz');
|
||||
execFileSync('node', args, {
|
||||
stdio: 'pipe',
|
||||
env: { ...process.env, ...env },
|
||||
});
|
||||
const extension = target === 'win-x64' ? 'zip' : 'tar.gz';
|
||||
return path.join(outDir, `qwen-code-${target}.${extension}`);
|
||||
}
|
||||
|
||||
function createFakeClipboardModules(tmpDir, nativePackages) {
|
||||
|
|
@ -5412,18 +5587,27 @@ function createFakeClipboardModules(tmpDir, nativePackages) {
|
|||
|
||||
// The clipboard packages are mandatory for --native-modules-dir, and the
|
||||
// node-pty prebuild comes from the same directory, so both live here. The
|
||||
// .pdb files model the win-x64 prebuild package's payload.
|
||||
function createFakeNodePtyModules(tmpDir) {
|
||||
const modulesDir = createFakeClipboardModules(tmpDir, [
|
||||
'@teddyzhu/clipboard-linux-x64-gnu',
|
||||
]);
|
||||
// .pdb files model the win-x64 prebuild package's payload. `prebuildDirName`
|
||||
// is the prebuildify platform-arch name, which differs from the target name on
|
||||
// Windows: --target win-x64 stages node-pty-win32-x64/prebuilds/win32-x64.
|
||||
function createFakeNodePtyModules(
|
||||
tmpDir,
|
||||
{
|
||||
prebuildDirName = 'linux-x64',
|
||||
clipboard = '@teddyzhu/clipboard-linux-x64-gnu',
|
||||
} = {},
|
||||
) {
|
||||
const modulesDir = createFakeClipboardModules(tmpDir, [clipboard]);
|
||||
const wrapperDir = path.join(modulesDir, '@lydell', 'node-pty');
|
||||
const prebuildDir = path.join(
|
||||
const prebuildPackageDir = path.join(
|
||||
modulesDir,
|
||||
'@lydell',
|
||||
'node-pty-linux-x64',
|
||||
`node-pty-${prebuildDirName}`,
|
||||
);
|
||||
const prebuildDir = path.join(
|
||||
prebuildPackageDir,
|
||||
'prebuilds',
|
||||
'linux-x64',
|
||||
prebuildDirName,
|
||||
);
|
||||
|
||||
mkdirSync(path.join(wrapperDir, 'lib'), { recursive: true });
|
||||
|
|
@ -5438,9 +5622,9 @@ function createFakeNodePtyModules(tmpDir) {
|
|||
|
||||
mkdirSync(prebuildDir, { recursive: true });
|
||||
writeFileSync(
|
||||
path.join(modulesDir, '@lydell', 'node-pty-linux-x64', 'package.json'),
|
||||
path.join(prebuildPackageDir, 'package.json'),
|
||||
JSON.stringify({
|
||||
name: '@lydell/node-pty-linux-x64',
|
||||
name: `@lydell/node-pty-${prebuildDirName}`,
|
||||
version: '1.2.0-beta.10',
|
||||
}),
|
||||
);
|
||||
|
|
@ -5451,6 +5635,27 @@ function createFakeNodePtyModules(tmpDir) {
|
|||
return modulesDir;
|
||||
}
|
||||
|
||||
// createFakeNodePtyModules minus the addon, so the packager sees a staged dir
|
||||
// with valid clipboard packages but no prebuild to copy. Dropping only pty.node
|
||||
// matters: copyClipboardAddon runs first and hard-fails on a staged dir that
|
||||
// lacks them, while hasNativePrebuild() keys off a .node entry, so the leftover
|
||||
// .pdb files do not satisfy it.
|
||||
function createFakeNodePtyModulesWithoutAddon(tmpDir, options = {}) {
|
||||
const { prebuildDirName = 'linux-x64' } = options;
|
||||
const modulesDir = createFakeNodePtyModules(tmpDir, options);
|
||||
rmSync(
|
||||
path.join(
|
||||
modulesDir,
|
||||
'@lydell',
|
||||
`node-pty-${prebuildDirName}`,
|
||||
'prebuilds',
|
||||
prebuildDirName,
|
||||
'pty.node',
|
||||
),
|
||||
);
|
||||
return modulesDir;
|
||||
}
|
||||
|
||||
function runUnixInstaller(
|
||||
archive,
|
||||
installRoot,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue