Skip to content

Commit 2e4086a

Browse files
committed
fix(installer): tighten hosted default-version check, flag legacy URL
- Replace the loose `latest` fragment check with per-format regex patterns in HOSTED_INSTALLER_DEFAULT_VERSION_PATTERNS so an unrelated occurrence of `latest` (comment, help text) cannot satisfy the staging guard. The patterns still tolerate whitespace variation, only the default-version assignment itself must be intact. - Add a "Hosted endpoint status" callout in INSTALLATION_GUIDE.md before the curl examples. The documented `--version` flow does not work against the OSS URL today because it currently serves the legacy NVM-based installer; the callout points users at a local checkout until the next release sync. - Tests: drop `latest` from the fragments equality assertion, add positive and negative regex coverage, add a failure-path case for sources whose default version is not `latest`, and pin the new guide markers so the callout cannot silently disappear.
1 parent 51778f9 commit 2e4086a

3 files changed

Lines changed: 87 additions & 2 deletions

File tree

scripts/build-hosted-installation-assets.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,15 @@ const HOSTED_INSTALLATION_ASSET_NAMES = HOSTED_INSTALLATION_ASSETS.map(
3838
const HOSTED_INSTALLER_REQUIRED_FRAGMENTS = [
3939
'--version',
4040
'QWEN_INSTALL_VERSION',
41-
'latest',
4241
];
42+
// Narrow regexes that pin the default-version assignment to `latest`.
43+
// Substring matching alone would let the word "latest" leak in via comments
44+
// or help text even when the actual default has been changed. The patterns
45+
// allow whitespace flexibility but require the literal default value.
46+
const HOSTED_INSTALLER_DEFAULT_VERSION_PATTERNS = {
47+
'install-qwen.sh': /VERSION\s*=\s*"\$\{QWEN_INSTALL_VERSION:-latest\}"/,
48+
'install-qwen.bat': /set\s+"VERSION=latest"/,
49+
};
4350
// SHA256SUMS is allowed in an existing output directory because every staging
4451
// run rewrites it from scratch after copying the hosted installer assets.
4552
const HOSTED_INSTALLATION_OUTPUT_NAMES = new Set([
@@ -133,6 +140,13 @@ function assertHostedInstallerSource(source, output) {
133140
`${output} is missing hosted installer behavior: ${missing.join(', ')}`,
134141
);
135142
}
143+
144+
const defaultPattern = HOSTED_INSTALLER_DEFAULT_VERSION_PATTERNS[output];
145+
if (defaultPattern && !defaultPattern.test(contents)) {
146+
fail(
147+
`${output} default install version must be 'latest' for the hosted entrypoint`,
148+
);
149+
}
136150
}
137151

138152
async function writeHostedSha256Sums(outDir) {
@@ -167,6 +181,7 @@ async function assertHostedInstallationAssetChecksums(outDir) {
167181
export {
168182
HOSTED_INSTALLATION_ASSETS,
169183
HOSTED_INSTALLATION_ASSET_NAMES,
184+
HOSTED_INSTALLER_DEFAULT_VERSION_PATTERNS,
170185
HOSTED_INSTALLER_REQUIRED_FRAGMENTS,
171186
assertHostedInstallationAssetChecksums,
172187
buildHostedInstallationAssets,

scripts/installation/INSTALLATION_GUIDE.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ specific standalone release. This keeps the public install command on a stable
4343
hosted entrypoint while still allowing version pinning, rather than using
4444
per-release installer URLs.
4545

46+
> **Hosted endpoint status**: Until the hosted endpoint is re-synced after the
47+
> next release, the URL below still serves the legacy NVM-based installer,
48+
> which does not honor `--version` or `QWEN_INSTALL_VERSION` in the way
49+
> documented here. To get the standalone-archive-first behavior immediately,
50+
> run `install-qwen-with-source.sh` from a local checkout of this repository.
51+
> The `--version` examples below describe the post-sync behavior.
52+
4653
Latest hosted entrypoints used today:
4754

4855
```bash

scripts/tests/install-script.test.js

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,7 @@ describe('standalone release packaging', () => {
487487
const {
488488
HOSTED_INSTALLATION_ASSET_NAMES,
489489
HOSTED_INSTALLATION_ASSETS,
490+
HOSTED_INSTALLER_DEFAULT_VERSION_PATTERNS,
490491
HOSTED_INSTALLER_REQUIRED_FRAGMENTS,
491492
assertHostedInstallationAssetChecksums,
492493
buildHostedInstallationAssets,
@@ -511,8 +512,29 @@ describe('standalone release packaging', () => {
511512
expect(HOSTED_INSTALLER_REQUIRED_FRAGMENTS).toEqual([
512513
'--version',
513514
'QWEN_INSTALL_VERSION',
514-
'latest',
515515
]);
516+
// The default-version regex pins `latest` semantically rather than as a
517+
// loose substring, so a stray `latest` in a comment cannot satisfy it.
518+
expect(
519+
HOSTED_INSTALLER_DEFAULT_VERSION_PATTERNS['install-qwen.sh'].test(
520+
'VERSION="${QWEN_INSTALL_VERSION:-latest}"',
521+
),
522+
).toBe(true);
523+
expect(
524+
HOSTED_INSTALLER_DEFAULT_VERSION_PATTERNS['install-qwen.sh'].test(
525+
'# defaults to latest',
526+
),
527+
).toBe(false);
528+
expect(
529+
HOSTED_INSTALLER_DEFAULT_VERSION_PATTERNS['install-qwen.bat'].test(
530+
'set "VERSION=latest"',
531+
),
532+
).toBe(true);
533+
expect(
534+
HOSTED_INSTALLER_DEFAULT_VERSION_PATTERNS['install-qwen.bat'].test(
535+
'rem defaults to latest',
536+
),
537+
).toBe(false);
516538
expect(readScript(installSh)).toBe(
517539
readScript('scripts/installation/install-qwen-with-source.sh'),
518540
);
@@ -568,6 +590,42 @@ describe('standalone release packaging', () => {
568590
}
569591
});
570592

593+
it('rejects hosted installer sources whose default version is not latest', async () => {
594+
const { buildHostedInstallationAssets } = await import(
595+
hostedInstallationScriptUrl
596+
);
597+
const tmpRoot = mkdtempSync(path.join(tmpdir(), 'qwen-hosted-root-'));
598+
const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-hosted-install-'));
599+
const sourceDir = path.join(tmpRoot, 'scripts', 'installation');
600+
601+
try {
602+
mkdirSync(sourceDir, { recursive: true });
603+
// Both fragments are present, but the default version was changed to
604+
// something other than `latest`. The default-version pattern guard
605+
// catches this, even though loose substring matching would not.
606+
writeFileSync(
607+
path.join(sourceDir, 'install-qwen-with-source.sh'),
608+
'#!/usr/bin/env bash\n' +
609+
'# Defaults to latest unless --version is passed.\n' +
610+
'VERSION="${QWEN_INSTALL_VERSION:-stable}"\n' +
611+
'case "$1" in --version) shift; VERSION="$1" ;; esac\n',
612+
);
613+
writeFileSync(
614+
path.join(sourceDir, 'install-qwen-with-source.bat'),
615+
'@echo off\r\nset "VERSION=stable"\r\n',
616+
);
617+
618+
await expect(
619+
buildHostedInstallationAssets(tmpDir, { root: tmpRoot }),
620+
).rejects.toThrow(
621+
/install-qwen\.sh default install version must be 'latest'/,
622+
);
623+
} finally {
624+
rmSync(tmpRoot, { recursive: true, force: true });
625+
rmSync(tmpDir, { recursive: true, force: true });
626+
}
627+
});
628+
571629
it('rejects stale hosted installation assets in the output directory', async () => {
572630
const { buildHostedInstallationAssets } = await import(
573631
hostedInstallationScriptUrl
@@ -848,6 +906,11 @@ describe('standalone release packaging', () => {
848906
expect(guide).toContain('installation/install-qwen.sh');
849907
expect(guide).toContain('installation/install-qwen.bat');
850908
expect(guide).toContain('release operators must sync these staged files');
909+
// The hosted-endpoint status callout must keep flagging the transition
910+
// window so users do not assume the documented --version flow works
911+
// before the next OSS sync.
912+
expect(guide).toContain('Hosted endpoint status');
913+
expect(guide).toContain('legacy NVM-based installer');
851914
expect(guide).toContain('node-pty');
852915
expect(guide).toContain('clipboard');
853916
});

0 commit comments

Comments
 (0)