Skip to content

feat(installer): keep hosted entrypoint and verify release assets#3828

Open
yiliang114 wants to merge 52 commits into
mainfrom
codex/installer-release-assets
Open

feat(installer): keep hosted entrypoint and verify release assets#3828
yiliang114 wants to merge 52 commits into
mainfrom
codex/installer-release-assets

Conversation

@yiliang114
Copy link
Copy Markdown
Collaborator

@yiliang114 yiliang114 commented May 4, 2026

Summary

  • What changed: keeps the public installer on stable hosted entrypoints, removes per-release installer asset publishing, packages standalone GitHub Release archives, stages hosted installer assets for OSS/CDN sync, and verifies release assets before publish.
  • Why it changed: installer users should get a lightweight entrypoint while releases remain versioned through standalone archives. Operators also need a local staging and verification path so the release surface can be checked as one closed loop.
  • Reviewer focus: release workflow asset scope, standalone archive checksum behavior, hosted installer staging, release asset verification, and installer script test coverage.

Scope

This PR intentionally does not publish install-qwen.sh or install-qwen.bat as GitHub Release assets. Those installer scripts are staged for the hosted installation endpoint, while each GitHub Release publishes the platform-specific standalone archives and SHA256SUMS.

The hosted install flow remains:

curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh | bash
curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh | bash -s -- --version vX.Y.Z

Validation

Commands run after folding the hosted staging and release verification PRs into this branch:

npm run test:scripts
npx eslint scripts/build-hosted-installation-assets.js scripts/verify-installation-release.js scripts/tests/install-script.test.js scripts/release-script-utils.js scripts/build-standalone-release.js scripts/create-standalone-package.js
npx prettier --check .github/workflows/release.yml package.json scripts/build-hosted-installation-assets.js scripts/verify-installation-release.js scripts/installation/INSTALLATION_GUIDE.md scripts/tests/install-script.test.js
bash -n scripts/installation/install-qwen-with-source.sh
git diff --check
node scripts/build-hosted-installation-assets.js --out-dir <tmpdir>
node scripts/verify-installation-release.js --help

Observed locally: npm run test:scripts reports 120 passed and 7 Windows-only tests skipped on macOS. eslint, prettier, shell syntax, whitespace checks, hosted asset staging smoke, and verifier help smoke are clean.

Scope / Risk

  • No CLI runtime behavior change.
  • No public quick-install URL switch in this PR.
  • GitHub Release assets remain standalone archives plus SHA256SUMS.
  • Hosted installer assets are staged separately for manual OSS/CDN sync; upload automation remains a follow-up release operation.
  • Release workflow now verifies standalone archive assets before publishing.
  • Installer scripts continue to support --version / QWEN_INSTALL_VERSION for pinned installs from the hosted entrypoint.
  • Standalone installs still differ from npm installs for target-specific optional native modules such as shell pty and clipboard integrations; that limitation remains documented.

Testing Matrix

🍏 🪟 🐧
npm run ⚠️ ⚠️
npx ⚠️ ⚠️
Docker N/A N/A N/A
Podman N/A N/A N/A
Seatbelt N/A N/A N/A

Testing matrix notes:

  • macOS local focused script checks passed.
  • Windows and Linux are covered by repository CI for this PR.

Linked Issues / Bugs

Related #3728

Comment thread scripts/create-standalone-package.js Outdated
Comment thread scripts/build-installation-assets.js Outdated
Comment thread scripts/create-standalone-package.js Outdated
Comment thread scripts/build-installation-assets.js Outdated
Copy link
Copy Markdown
Collaborator

@wenshao wenshao left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes due to a deterministic script test failure.

— gpt-5.5 via Qwen Code /review

Comment thread scripts/tests/install-script.test.js
@yiliang114 yiliang114 marked this pull request as ready for review May 5, 2026 12:52
@yiliang114 yiliang114 requested a review from wenshao May 5, 2026 12:53
@yiliang114
Copy link
Copy Markdown
Collaborator Author

@copilot pls review for me

@yiliang114 yiliang114 changed the base branch from codex/installer-standalone to main May 5, 2026 13:21
wenshao
wenshao previously approved these changes May 8, 2026
Comment thread scripts/create-standalone-package.js Outdated
Comment thread scripts/create-standalone-package.js
Comment thread scripts/release-asset-config.js
# Conflicts:
#	scripts/build-standalone-release.js
#	scripts/create-standalone-package.js
#	scripts/installation/INSTALLATION_GUIDE.md
#	scripts/installation/install-qwen-with-source.bat
#	scripts/installation/install-qwen-with-source.sh
#	scripts/tests/install-script.test.js
@yiliang114 yiliang114 changed the title [Depends on #3776] chore(installer): keep installer entrypoint hosted chore(installer): keep installer entrypoint hosted May 11, 2026
@yiliang114 yiliang114 changed the base branch from codex/installer-standalone to main May 11, 2026 05:38
@yiliang114 yiliang114 dismissed wenshao’s stale review May 11, 2026 05:38

The base branch was changed.

@yiliang114
Copy link
Copy Markdown
Collaborator Author

Updated after #3776 was merged into main:

  • Merged origin/main into codex/installer-release-assets with a normal merge commit, no force push.
  • Changed this PR base from codex/installer-standalone to main.
  • Removed the stale dependency prefix from the title/body.
  • Confirmed the PR diff is still scoped to the phase-2 installer release-assets changes only: 9 files, 740 insertions, 250 deletions.

Local validation after the base change:

npm run test:scripts
npx eslint scripts/build-standalone-release.js scripts/create-standalone-package.js scripts/release-script-utils.js scripts/release-asset-config.js scripts/tests/install-script.test.js
npx prettier --check scripts/build-standalone-release.js scripts/create-standalone-package.js scripts/release-script-utils.js scripts/release-asset-config.js scripts/tests/install-script.test.js scripts/installation/INSTALLATION_GUIDE.md .github/workflows/release.yml package.json
bash -n scripts/installation/install-qwen-with-source.sh
git diff --check origin/main...HEAD

Result: npm run test:scripts passed with 104 tests passed and 4 Windows-only tests skipped on macOS. ESLint, Prettier, shell syntax, and whitespace checks are clean.

@yiliang114
Copy link
Copy Markdown
Collaborator Author

Pushed an additional follow-up commit (1917316b1) to cover the #3776 review items that fit this PR's scope:

  • Keep the standalone archive root package.json aligned with the production dist/package.json when it exists.
  • Accept --node-version v20.x.y by normalizing the leading v before building Node.js download URLs.
  • Harden Unix and Windows managed-install detection so an arbitrary directory with a manifest.json is not considered safe to overwrite unless the manifest and expected standalone layout match Qwen Code.
  • Add Windows directory-creation error handling instead of silently continuing after failed mkdir calls.
  • Tighten Windows version validation so multiple leading v characters are rejected.
  • Add/extend tests for the package metadata, Node version normalization, unrelated manifest protection, Windows missing checksum, and Windows missing required archive entries.

I intentionally did not fold Aliyun OSS/CDN archive upload automation into this PR. That belongs to the later release-publishing step because it requires the actual upload path/credentials and would expand this PR beyond the hosted-entrypoint/release-asset cleanup scope.

Local validation after this commit:

npm run test:scripts
npx eslint scripts/build-standalone-release.js scripts/create-standalone-package.js scripts/release-script-utils.js scripts/release-asset-config.js scripts/tests/install-script.test.js
npx prettier --check scripts/build-standalone-release.js scripts/create-standalone-package.js scripts/release-script-utils.js scripts/release-asset-config.js scripts/tests/install-script.test.js scripts/installation/INSTALLATION_GUIDE.md .github/workflows/release.yml package.json
bash -n scripts/installation/install-qwen-with-source.sh
git diff --check

Result: npm run test:scripts passed with 106 tests passed and 7 Windows-only tests skipped on macOS. ESLint, Prettier, shell syntax, and whitespace checks are clean.

@yiliang114 yiliang114 changed the title chore(installer): keep installer entrypoint hosted feat(installer): keep hosted entrypoint and verify release assets May 11, 2026
Comment thread scripts/tests/install-script.test.js Fixed
@yiliang114
Copy link
Copy Markdown
Collaborator Author

@copilot pls review pr

Copy link
Copy Markdown
Contributor

Copilot AI commented May 11, 2026

@copilot pls review pr

Reviewed. Here's a summary:

All critical issues from prior rounds are resolved:

  • install-qwen-with-source.bat now emits echo ERROR: Unknown option. (constant message, no raw %~1 interpolation)
  • build-standalone-release.js uses Promise.allSettled so all targets settle before runtimeDir is cleaned up
  • create-standalone-package.js DIST_IGNORED_ENTRIES now includes esbuild.json, so a DEV=true bundle environment no longer fails packaging
  • SOURCE is included in :ValidateOptions's PowerShell unsafe-char scan (line 224–225 of the bat file)
  • writeSha256Sums is documented as a full-overwrite/rebuild; hashes are computed in parallel with Promise.all
  • TARGETS is exported and consumed by assertStandaloneOutput to derive extension names

Structural quality:

  • release-script-utils.js cleanly centralizes sha256File, parseSha256Sums, parseCliArgs, isMainModule, and fail — the three release/package scripts share them uniformly
  • parseCliArgs handles --flag=value inline syntax, boolean flags, missing values, and unknown options; test coverage for all branches is solid
  • build-hosted-installation-assets.js validates source content (required fragments + default-version regex) before copying, and verifies checksums post-write
  • verify-installation-release.js correctly handles both local directory and remote --base-url modes, with a ranged-GET fallback for object-storage HEAD restrictions

One minor observation: verify-installation-release.js line 27–29 inlines qwenTarget === 'win-x64' ? 'zip' : 'tar.gz' to build EXPECTED_STANDALONE_ARCHIVE_NAMES, whereas assertStandaloneOutput in build-standalone-release.js derives extensions from the exported TARGETS map. Functionally equivalent today (all non-Windows platforms use .tar.gz), but adding a new platform would require updating both files. Not blocking — the test at line 510 ('exposes only standalone archive classification') would catch a mismatch before release.

Tests: 54 tests, 47 pass on macOS (7 Windows-only skipped). ESLint, Prettier, shell syntax, and git diff --check all clean on the installer/release scripts. The check-i18n.test.ts failure is pre-existing (requires the core package to be built) and unrelated to this PR.

The verify-installation-release script was duplicating the archive name
derivation logic with a hardcoded ternary instead of reusing the
standaloneArchiveName helper from build-standalone-release. Export the
helper and import it so the extension mapping lives in one place.
Comment thread scripts/verify-installation-release.js
Comment thread scripts/installation/install-qwen-with-source.bat Outdated
Comment thread scripts/release-script-utils.js
Comment thread scripts/build-standalone-release.js Outdated
Comment thread scripts/verify-installation-release.js
Comment thread scripts/verify-installation-release.js
Comment thread scripts/build-standalone-release.js
Comment thread scripts/create-standalone-package.js
Comment thread scripts/installation/install-qwen-with-source.sh
Comment thread scripts/verify-installation-release.js

const match = /^([0-9a-fA-F]{64})\s+\*?(.+)$/.exec(trimmed);
if (!match) {
fail(`Malformed SHA256SUMS line ${index + 1}: ${trimmed}`);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] parseSha256Sums throws on malformed/unrecognized lines (fail()), replacing the old tolerant parsing that silently skipped unparseable lines.

This function is used to parse upstream SHASUMS256.txt from nodejs.org (via parseChecksums in build-standalone-release.js:106). If nodejs.org adds comments or tweaks the format, the release pipeline breaks entirely — while the old code would just use the subset of entries it could parse.

Suggested change
fail(`Malformed SHA256SUMS line ${index + 1}: ${trimmed}`);
function parseSha256Sums(content, { tolerant = false } = {}) {
const checksums = new Map();
for (const [index, line] of content.split(/\r?\n/).entries()) {
const trimmed = line.trim();
if (!trimmed) continue;
const match = /^([0-9a-fA-F]{64})\s+\*?(.+)$/.exec(trimmed);
if (!match) {
if (tolerant) continue;
fail(`Malformed SHA256SUMS line ${index + 1}: ${trimmed}`);
}
checksums.set(match[2], match[1].toLowerCase());
}
return checksums;
}

Then use parseSha256Sums(content, { tolerant: true }) when parsing upstream Node.js SHASUMS, and keep strict mode for self-generated checksums.

— DeepSeek/deepseek-v4-pro via Qwen Code /review

const { fetchImpl = fetch } = options;
const normalizedBaseUrl = normalizeHttpsBaseUrl(baseUrl);
const checksumUrl = new URL('SHA256SUMS', normalizedBaseUrl).toString();
const checksums = parseSha256Sums(await fetchText(checksumUrl, fetchImpl));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] verifyReleaseBaseUrl makes fetch calls without a timeout. If a remote server (GitHub Releases, OSS) accepts the TCP connection but never sends data, the new npm run verify:installation-release -- --base-url ... CI step hangs indefinitely until the job timeout (potentially hours).

Suggested change
const checksums = parseSha256Sums(await fetchText(checksumUrl, fetchImpl));
const response = await fetchImpl(checksumUrl, {
signal: AbortSignal.timeout(30_000),
});

Apply to all fetch calls: fetchText (line 239), assertRemoteAssetAvailable (lines 210, 220), and here.

— DeepSeek/deepseek-v4-pro via Qwen Code /review

if %ERRORLEVEL% EQU 0 exit /b 0
echo(!VERSION!| findstr /R /C:"^[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*[A-Za-z0-9.-]*$" >nul
if !ERRORLEVEL! EQU 0 exit /b 0
echo(!VERSION!| findstr /R /C:"^v[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*[A-Za-z0-9.-]*$" >nul
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The .bat version validation regex ([A-Za-z0-9.-]*$) is looser than the .sh counterpart (([.-][A-Za-z0-9]+)*$). It allows multiple consecutive separators, trailing dots/dashes, and empty pre-release segments — e.g. v1.2.3., v1.2.3--rc1, v1.2.3..beta pass validation but then fail later with confusing 404/"archive not found" errors. The .sh installer correctly rejects these with a clear "must be a semver string" message.

Tighten the suffix portion of the regex to match the .sh semantics, or use a PowerShell-based semver check for consistency across platforms.

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants