Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
136 commits
Select commit Hold shift + click to select a range
eb2a9a8
feat(installer): add standalone archive installation
yiliang114 Apr 30, 2026
d2e5b1b
fix(installer): harden standalone archive installs
yiliang114 May 2, 2026
d7277a6
fix(installer): address standalone review findings
yiliang114 May 2, 2026
5719595
chore(installer): clarify review followups
yiliang114 May 2, 2026
472e51e
fix(installer): stabilize standalone script checks
yiliang114 May 2, 2026
47fdfbc
chore(installer): remove internal planning docs
yiliang114 May 2, 2026
2b40bad
chore(installer): simplify standalone release review fixes
yiliang114 May 2, 2026
a4a15e1
test(installer): add Windows batch install smoke
yiliang114 May 2, 2026
fee13e4
test(installer): fix Windows batch smoke quoting
yiliang114 May 2, 2026
69e22f9
test(installer): preserve Windows cmd quotes
yiliang114 May 2, 2026
faa9087
fix(installer): use robust Windows checksum hashing
yiliang114 May 2, 2026
66dec5f
ci: narrow installer debug matrix
yiliang114 May 2, 2026
1ca86fe
fix(installer): address standalone review hardening
yiliang114 May 4, 2026
0eb58a8
fix(installer): avoid Windows validation parse errors
yiliang114 May 4, 2026
e7e3f90
fix(installer): simplify Windows option validation
yiliang114 May 4, 2026
fee51d1
fix(installer): harden standalone review fixes
yiliang114 May 4, 2026
ec654dd
feat(installer): publish release installer assets
yiliang114 May 4, 2026
ccf83e9
fix(installer): address release asset review feedback
yiliang114 May 5, 2026
b078ade
fix(installer): avoid prerelease installer asset links
yiliang114 May 5, 2026
668c006
test(installer): isolate standalone dist fixture
yiliang114 May 5, 2026
a205e6c
feat(installer): add hosted install release alias
yiliang114 May 5, 2026
bbaa8ff
chore: no changes - code review requested
Copilot May 5, 2026
6534e6f
fix(installer): pin versioned installer assets
yiliang114 May 5, 2026
5983d11
fix: parallelize Node.js binary downloads in standalone release build
wenshao May 5, 2026
6c80ef8
fix(installer): address release asset review followups
yiliang114 May 5, 2026
bca4a7f
refactor(installer): share release CLI parsing
yiliang114 May 5, 2026
3570f93
fix(installer): address release asset review followups
yiliang114 May 6, 2026
51c14f4
fix(installer): unblock Windows CI for standalone install path
yiliang114 May 7, 2026
44ac229
fix(installer): address release asset review findings
yiliang114 May 7, 2026
f9d9a3b
fix(installer): keep installer entrypoint hosted
yiliang114 May 7, 2026
587dc1f
feat(installer): stage hosted installation assets
yiliang114 May 7, 2026
27a1bbe
fix(installer): reject stale hosted assets
yiliang114 May 7, 2026
51778f9
fix(installer): refine hosted asset staging
yiliang114 May 7, 2026
2e4086a
fix(installer): tighten hosted default-version check, flag legacy URL
yiliang114 May 7, 2026
4100b8e
feat(installer): verify installation release assets
yiliang114 May 7, 2026
362bf58
fix(installer): tighten verifier base-url + clarify test helper
yiliang114 May 7, 2026
4eb3108
chore(installer): merge main after standalone installer landed
yiliang114 May 11, 2026
1917316
fix(installer): address standalone review follow-ups
yiliang114 May 11, 2026
1502f72
fix(installer): repair Windows installer tests
yiliang114 May 11, 2026
fddbfdc
fix(release): tighten standalone asset checks
yiliang114 May 11, 2026
1f1f5cd
fix(installer): stabilize Windows managed install checks
yiliang114 May 11, 2026
7d4fc13
test(installer): relax Windows installer timeout
yiliang114 May 11, 2026
f6696e8
feat(installer): fold hosted release checks into installer flow
yiliang114 May 11, 2026
a244a2e
fix(test): escape release asset regex
yiliang114 May 11, 2026
9bde745
test(cli): avoid POSIX node path in relaunch test
yiliang114 May 11, 2026
728b35f
fix(installer): align npm fallback node gate with engines
yiliang114 May 11, 2026
bb8b810
test(installer): allow Windows archive validation more time
yiliang114 May 11, 2026
55f28b6
Merge remote-tracking branch 'origin/main' into codex/installer-relea…
yiliang114 May 11, 2026
d2acf7f
fix(installer): remove stale node 20 installer references
yiliang114 May 11, 2026
725a06e
docs(installer): clarify hosted endpoint sync requirement
yiliang114 May 11, 2026
a10bd1c
refactor(installer): reuse standaloneArchiveName in release verifier
yiliang114 May 11, 2026
d0bd8c6
fix(scripts): address release verifier review feedback
yiliang114 May 11, 2026
926c10a
feat(installer): add standalone archive installer with multi-platform…
yiliang114 May 12, 2026
2ae9d0c
feat(installer): support QWEN_INSTALL_GITHUB_REPO env var for custom …
yiliang114 May 12, 2026
02e5706
chore(installer): exclude local-only staging tools from PR
yiliang114 May 12, 2026
34a86bd
fix(installer): enforce CRLF line endings for .bat files via gitattri…
yiliang114 May 12, 2026
2ca9dcd
fix(installer): store .bat files with CRLF in git blob for raw GitHub…
yiliang114 May 12, 2026
3549a34
fix(installer): follow HTTP redirects in UrlExists and RaceMirrorHead…
yiliang114 May 12, 2026
bd7602b
fix(installer): surface download errors and add MaximumRedirection 10
yiliang114 May 12, 2026
68f05db
feat(installer): add hosted install-qwen.ps1 shim for irm|iex one-liner
yiliang114 May 13, 2026
439092e
Merge branch 'main' of https://github.com/QwenLM/qwen-code into codex…
yiliang114 May 13, 2026
eeabd1b
fix(installer): stage direct hosted install scripts
yiliang114 May 13, 2026
7b9817a
Merge branch 'main' of https://github.com/QwenLM/qwen-code into codex…
yiliang114 May 13, 2026
b8e5490
chore(installer): trim hosted release diff scope
yiliang114 May 13, 2026
fedcbae
chore(installer): narrow hosted release diff
yiliang114 May 13, 2026
af64da8
feat(installer): restore hosted PowerShell entrypoint
yiliang114 May 13, 2026
5f649c9
chore(installer): stage standalone hosted entrypoints
yiliang114 May 13, 2026
984b1a9
fix(installer): address hosted installer review followups
yiliang114 May 13, 2026
9093577
fix(installer): stabilize Windows installer tests
yiliang114 May 13, 2026
72a5efe
fix(installer): make Windows option validation readable
yiliang114 May 13, 2026
7d328be
feat(installer): wire Aliyun OSS sync, address review followups
yiliang114 May 13, 2026
b0ae7f8
ci(installer): add temporary OSS smoke test
yiliang114 May 13, 2026
48977ae
fix(installer): make OSS release assets public-readable
yiliang114 May 13, 2026
645edb8
chore(installer): remove temporary OSS smoke workflow
yiliang114 May 13, 2026
0c8022d
fix(installer): address hosted installer review gaps
yiliang114 May 13, 2026
c6005a7
feat(installer): refactor argument parsing and utility functions for …
yiliang114 May 13, 2026
540bcb8
fix(installer): harden hosted release script checks
yiliang114 May 14, 2026
4095171
fix(installer): suppress PowerShell progress bar in hosted entrypoint…
yiliang114 May 14, 2026
4b950ab
fix(installer): suppress PowerShell progress bar in bat installer dow…
yiliang114 May 14, 2026
01e9767
fix(installer): use curl.exe -# progress bar in Windows downloads
yiliang114 May 14, 2026
046d200
fix(installer): suppress progress bars for small downloads and Expand…
yiliang114 May 14, 2026
ccbdd34
fix(installer): auto-backup non-qwen directories and simplify output
yiliang114 May 14, 2026
24cb1c7
fix(installer): revert Expand-Archive progress suppression in bat
yiliang114 May 14, 2026
31e3c43
fix(installer): fix cmd.exe parsing error in backup fallback code
yiliang114 May 14, 2026
b7f190a
fix(installer): always persist install bin to user PATH
yiliang114 May 14, 2026
c3b488f
fix(installer): persist PATH to current terminal session on Windows
yiliang114 May 14, 2026
bdf101b
docs(installer): document cmd.exe one-liner for immediate PATH availa…
yiliang114 May 14, 2026
38c4706
feat(installer): make qwen usable immediately from PowerShell after i…
yiliang114 May 14, 2026
564f899
fix(installer): remove non-functional doskey approach for cmd parent
yiliang114 May 14, 2026
74130fc
fix(installer): make Windows standalone shim available in cmd
yiliang114 May 14, 2026
75e5b58
feat(installer): add standalone uninstall scripts
yiliang114 May 14, 2026
d2ebed9
fix(uninstall): match shell-quoted paths when removing the wrapper
yiliang114 May 14, 2026
db0a44d
fix(installer): update download commands to use progress indicators f…
yiliang114 May 14, 2026
8fb2bc1
fix(installer): resolve Aliyun latest via version pointer
yiliang114 May 14, 2026
db8677f
fix(installer): cleanup mirror probe temp dirs
yiliang114 May 14, 2026
40ac124
fix(installer): harden standalone release fallback
yiliang114 May 14, 2026
bae9333
fix(installer): address standalone review feedback
yiliang114 May 15, 2026
a79745b
style(installer): align standalone install output
yiliang114 May 15, 2026
7fd314a
fix(installer): print standalone uninstall commands
yiliang114 May 15, 2026
7d665f3
fix(installer): address release review follow-ups
yiliang114 May 15, 2026
c959913
fix(installer): harden Windows target detection
yiliang114 May 15, 2026
b2b43b5
test(installer): stabilize Windows fake tool path
yiliang114 May 15, 2026
308fe4d
fix(installer): allow explicit Windows curl path
yiliang114 May 15, 2026
1d272f6
test(installer): use cmd fake curl on Windows
yiliang114 May 15, 2026
778919f
test(installer): cover Windows fake curl helper
yiliang114 May 15, 2026
ecfad99
test(installer): inject Windows arch overrides in cmd
yiliang114 May 15, 2026
ce45abd
test(cli): wait for prompt suggestion render
yiliang114 May 15, 2026
78d0c86
test(cli): revert prompt suggestion wait tweak
yiliang114 May 15, 2026
b3e21a5
fix(installer): harden hosted release publishing
yiliang114 May 15, 2026
0994a80
fix(installer): harden Windows latest pointer parsing
yiliang114 May 16, 2026
63ec525
fix(installer): bound Windows download timeouts
yiliang114 May 16, 2026
0155a4f
fix(installer): bound hosted installer probes
yiliang114 May 16, 2026
308358d
fix(release): make ossutil download configurable
yiliang114 May 16, 2026
c2a3e5c
Merge remote-tracking branch 'origin/main' into codex/pr-3828-install…
yiliang114 May 17, 2026
3eb6d6c
fix(installer): address hosted release review feedback
yiliang114 May 17, 2026
625dfbf
test(installer): keep dist backup on same filesystem
yiliang114 May 17, 2026
3f60c2c
fix(installer): address remaining review feedback on PR #3828
yiliang114 May 17, 2026
273a012
fix(release): correct OSS credentials lifetime and mirror probe fallback
yiliang114 May 17, 2026
a1ef869
fix(installer): address review feedback round 2
yiliang114 May 17, 2026
0a5d308
fix(installer): address review feedback round 3
yiliang114 May 17, 2026
89b272d
test(installer): add parseUploadArgs unit tests and align verify deri…
yiliang114 May 17, 2026
547713a
test(installer): add uploadAssets integration tests with fake ossutil
yiliang114 May 17, 2026
bab96dc
revert(installer): drop over-engineered ossutil/upload changes
yiliang114 May 17, 2026
3caf3e2
test(installer): cover Windows release script regressions
yiliang114 May 18, 2026
4a8fea5
test(release): avoid Windows shim lookup in oss upload tests
yiliang114 May 18, 2026
98315b2
test(installer): use stable fake Aliyun version on Windows
yiliang114 May 18, 2026
584bb59
fix(installer): parse Aliyun latest version in batch
yiliang114 May 18, 2026
62ab5f4
fix(installer): validate Aliyun latest version without findstr
yiliang114 May 18, 2026
b69af67
fix(installer): normalize Aliyun latest version via PowerShell
yiliang114 May 18, 2026
f832d33
fix(installer): avoid captured PowerShell output in batch latest parsing
yiliang114 May 18, 2026
994f2f4
fix(installer): normalize Aliyun latest pointer from file
yiliang114 May 18, 2026
ba695a8
test(installer): fix fake Windows curl output parsing
yiliang114 May 18, 2026
09b04d6
fix(installer): print checksum path on miss, gate hardcoded version p…
yiliang114 May 20, 2026
89d58aa
fix: stamp release version in hosted installers and add Zip Slip prot…
yiliang114 May 20, 2026
cb3f356
fix(installer): add SOURCE to PowerShell unsafe-character validation …
yiliang114 May 20, 2026
18525b3
fix: correct copyright year 2025 -> 2026 in new files [skip ci]
yiliang114 May 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
*.bash eol=lf
Makefile eol=lf

# Windows cmd.exe expects batch installers to be checked out with CRLF.
scripts/installation/install-qwen-standalone.bat text eol=crlf

# Explicitly declare binary file types to prevent Git from attempting to
# normalize their line endings.
*.png binary
Expand Down
199 changes: 187 additions & 12 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ jobs:

steps:
- name: 'Checkout'
uses: 'actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd' # v6.0.2
uses: 'actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd' # v6.0.2
with:
ref: '${{ github.event.inputs.ref || github.sha }}'
fetch-depth: 0
Expand Down Expand Up @@ -89,7 +89,7 @@ jobs:
echo "is_dry_run=${is_dry_run}" >> "${GITHUB_OUTPUT}"

- name: 'Setup Node.js'
uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
with:
node-version-file: '.nvmrc'
cache: 'npm'
Expand Down Expand Up @@ -153,13 +153,13 @@ jobs:

steps:
- name: 'Checkout'
uses: 'actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd' # v6.0.2
uses: 'actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd' # v6.0.2
with:
ref: '${{ github.event.inputs.ref || github.sha }}'
fetch-depth: 0

- name: 'Setup Node.js'
uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
with:
node-version-file: '.nvmrc'
cache: 'npm'
Expand Down Expand Up @@ -206,13 +206,13 @@ jobs:

steps:
- name: 'Checkout'
uses: 'actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd' # v6.0.2
uses: 'actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd' # v6.0.2
with:
ref: '${{ github.event.inputs.ref || github.sha }}'
fetch-depth: 0

- name: 'Setup Node.js'
uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
with:
node-version-file: '.nvmrc'
cache: 'npm'
Expand Down Expand Up @@ -247,13 +247,13 @@ jobs:

steps:
- name: 'Checkout'
uses: 'actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd' # v6.0.2
uses: 'actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd' # v6.0.2
with:
ref: '${{ github.event.inputs.ref || github.sha }}'
fetch-depth: 0

- name: 'Setup Node.js'
uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
with:
node-version-file: '.nvmrc'
cache: 'npm'
Expand Down Expand Up @@ -317,13 +317,13 @@ jobs:

steps:
- name: 'Checkout'
uses: 'actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd' # v6.0.2
uses: 'actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd' # v6.0.2
with:
ref: '${{ github.event.inputs.ref || github.sha }}'
fetch-depth: 0

- name: 'Setup Node.js'
uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0
with:
node-version-file: '.nvmrc'
cache: 'npm'
Expand Down Expand Up @@ -386,6 +386,63 @@ jobs:
RELEASE_VERSION: '${{ needs.prepare.outputs.release_version }}'
run: 'npm run package:standalone:release -- --version "${RELEASE_VERSION}" --out-dir dist/standalone'

- name: 'Verify Installation Release Assets'
run: 'npm run verify:installation-release -- --dir dist/standalone'

- name: 'Package Hosted Installation Assets'
env:
RELEASE_VERSION: '${{ needs.prepare.outputs.release_version }}'
run: 'npm run package:hosted-installation -- --out-dir dist/installation --version "${RELEASE_VERSION}"'

- name: 'Install ossutil'
if: |-
${{ needs.prepare.outputs.is_dry_run == 'false' }}
env:
OSSUTIL_URL: "${{ vars.OSSUTIL_URL || 'https://gosspublic.alicdn.com/ossutil/1.7.19/ossutil-v1.7.19-linux-amd64.zip' }}"
OSSUTIL_SHA256: "${{ vars.OSSUTIL_SHA256 || 'dcc512e4a893e16bbee63bc769339d8e56b21744fd83c8212a9d8baf28767343' }}"
run: |-
set -euo pipefail

tmp_dir="$(mktemp -d)"
curl -fsSL --connect-timeout 15 --max-time 300 "${OSSUTIL_URL}" -o "${tmp_dir}/ossutil.zip"
echo "${OSSUTIL_SHA256} ${tmp_dir}/ossutil.zip" | sha256sum -c -
unzip -q "${tmp_dir}/ossutil.zip" -d "${tmp_dir}"

ossutil_path="$(find "${tmp_dir}" -type f \( -name 'ossutil' -o -name 'ossutil64' \) -print -quit)"
if [[ -z "${ossutil_path}" ]]; then
echo "::error::ossutil binary not found in downloaded archive"
exit 1
fi

chmod +x "${ossutil_path}"
mkdir -p "${HOME}/.local/bin"
install -m 0755 "${ossutil_path}" "${HOME}/.local/bin/ossutil"
echo "${HOME}/.local/bin" >> "${GITHUB_PATH}"
rm -rf "${tmp_dir}"
"${HOME}/.local/bin/ossutil" >/dev/null

- name: 'Configure Aliyun OSS Credentials'
if: |-
${{ needs.prepare.outputs.is_dry_run == 'false' }}
env:
ALIYUN_OSS_ACCESS_KEY_ID: '${{ secrets.ALIYUN_OSS_ACCESS_KEY_ID }}'
ALIYUN_OSS_ACCESS_KEY_SECRET: '${{ secrets.ALIYUN_OSS_ACCESS_KEY_SECRET }}'
ALIYUN_OSS_ENDPOINT: "${{ vars.ALIYUN_OSS_ENDPOINT || 'https://oss-cn-hangzhou.aliyuncs.com' }}"
run: |-
set -euo pipefail

if [[ -z "${ALIYUN_OSS_ACCESS_KEY_ID}" || -z "${ALIYUN_OSS_ACCESS_KEY_SECRET}" ]]; then
echo "::error::Missing Aliyun OSS credentials. Set ALIYUN_OSS_ACCESS_KEY_ID and ALIYUN_OSS_ACCESS_KEY_SECRET in the production-release environment secrets."
exit 1
fi

ossutil config \

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] ossutil config writes Aliyun credentials in plaintext to ${RUNNER_TEMP}/.ossutilconfig via -i and -k. The cleanup rm -f runs on always(), but if the runner is killed (OOM, timeout), credentials remain on disk.

Suggested change
ossutil config \
# Use OSS_ACCESS_KEY_ID / OSS_ACCESS_KEY_SECRET env vars instead of
# ossutil config (ossutil supports these natively).

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

-e "${ALIYUN_OSS_ENDPOINT}" \
-i "${ALIYUN_OSS_ACCESS_KEY_ID}" \
-k "${ALIYUN_OSS_ACCESS_KEY_SECRET}" \
-L EN \
-c "${RUNNER_TEMP}/.ossutilconfig"

- name: 'Publish @qwen-code/qwen-code'

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] npm publish(第 444 行)在 OSS 同步(第 533 行)之前执行。如果 OSS 上传失败,npm 用户已获得新版本,而 OSS latest 仍指向旧版本——跨渠道版本偏移,无自动恢复机制。

建议:将 OSS 同步移到 npm publish 之前,或至少添加一个验证步骤:在 npm publish 后确认 OSS 资产已可用,否则回滚 npm 发布。

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

working-directory: 'dist'
run: |-
Expand All @@ -411,21 +468,139 @@ jobs:
IS_NIGHTLY: '${{ needs.prepare.outputs.is_nightly }}'
IS_PREVIEW: '${{ needs.prepare.outputs.is_preview }}'
run: |-
set -euo pipefail

PRERELEASE_FLAG=""
if [[ "${IS_NIGHTLY}" == "true" || "${IS_PREVIEW}" == "true" ]]; then
PRERELEASE_FLAG="--prerelease"
fi

mapfile -t release_assets < <(node scripts/verify-installation-release.js --dir dist/standalone --list-release-asset-paths)

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] mapfile -t release_assets < <(node ...) 使用进程替换,set -euo pipefail 不会捕获进程替换中命令的失败退出码。若 verify-installation-release.js 异常退出,mapfile 会读取空/部分输出,导致后续 gh release create 在缺少资产的情况下继续执行。

Suggested change
mapfile -t release_assets < <(node scripts/verify-installation-release.js --dir dist/standalone --list-release-asset-paths)
release_assets_file="$(mktemp)"
node scripts/verify-installation-release.js --dir dist/standalone --list-release-asset-paths > "${release_assets_file}"
mapfile -t release_assets < "${release_assets_file}"
rm -f "${release_assets_file}"

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

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] Bash mapfile silently swallows failures from the process substitution <(...), even under set -euo pipefail. If verify-installation-release.js exits non-zero, release_assets is empty and gh release create proceeds with only dist/cli.js — publishing a release with no standalone archives.

Add a guard:

if [[ ${#release_assets[@]} -eq 0 ]]; then
  echo "::error::Release asset verification produced no assets"
  exit 1
fi

— qwen-latest-series-invite-beta-v34 via Qwen Code /review

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] This ossutil cp call bypasses the retry wrapper (upload-aliyun-oss-assets.js), unlike every other OSS upload in the workflow. If it fails transiently after gh release create has already succeeded, re-running the workflow hits a duplicate-tag error — the workflow cannot make progress without manual intervention (delete the GitHub release or manually upload VERSION).

Route through upload-aliyun-oss-assets.js or replicate the retry loop inline.

— qwen-latest-series-invite-beta-v34 via Qwen Code /review


gh release create "${RELEASE_TAG}" \
dist/cli.js \
dist/standalone/qwen-code-* \
dist/standalone/SHA256SUMS \
"${release_assets[@]}" \
--target "${RELEASE_BRANCH}" \
--title "Release ${RELEASE_TAG}" \
--notes-start-tag "${PREVIOUS_RELEASE_TAG}" \
--generate-notes \
${PRERELEASE_FLAG}

- name: 'Sync Release Assets to Aliyun OSS'
if: |-
${{ needs.prepare.outputs.is_dry_run == 'false' }}
env:
ALIYUN_OSS_BUCKET: "${{ vars.ALIYUN_OSS_BUCKET || 'qwen-code-assets' }}"
RELEASE_TAG: '${{ needs.prepare.outputs.release_tag }}'
run: |-
set -euo pipefail

mapfile -t release_assets < <(node scripts/verify-installation-release.js --dir dist/standalone --list-release-asset-paths)
node scripts/upload-aliyun-oss-assets.js \
--bucket "${ALIYUN_OSS_BUCKET}" \
--config "${RUNNER_TEMP}/.ossutilconfig" \
--prefix "releases/qwen-code/${RELEASE_TAG}" \
"${release_assets[@]}"

- name: 'Verify Aliyun OSS Release Assets'
Comment thread
yiliang114 marked this conversation as resolved.
if: |-
${{ needs.prepare.outputs.is_dry_run == 'false' }}
env:
ALIYUN_OSS_PUBLIC_BASE_URL: "${{ vars.ALIYUN_OSS_PUBLIC_BASE_URL || 'https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com' }}"
RELEASE_TAG: '${{ needs.prepare.outputs.release_tag }}'
run: |-
set -euo pipefail

npm run verify:installation-release -- --base-url "${ALIYUN_OSS_PUBLIC_BASE_URL}/releases/qwen-code/${RELEASE_TAG}"

- name: 'Sync Hosted Installation Assets to Aliyun OSS'

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] 每个稳定版本无条件覆盖全局非版本化 installation/ 前缀(与版本化 installation/${RELEASE_TAG}/ 前缀并列上传)。没有灰度发布机制——如果损坏的安装脚本被推送,所有 curl \| bash 用户立即受影响,且无法自动回滚。

建议:不要在 release workflow 中自动上传到全局 installation/ 前缀。改为单独的手动触发步骤,由 release manager 在验证后显式执行。或维护一个独立的 installation/stable/ 前缀,仅通过显式提升步骤更新。

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

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] "Sync Hosted Installation Assets" uploads to both installation/${RELEASE_TAG} (versioned) and installation (global) prefixes in sequence. If ossutil fails between these two operations, installation/SHA256SUMS (what users download) points to the previous release while installation/${RELEASE_TAG}/SHA256SUMS points to the new release — causing checksum mismatches for curl ... | bash users.

Suggested change
- name: 'Sync Hosted Installation Assets to Aliyun OSS'
# Upload the versioned prefix LAST, or stage to a temp prefix and atomically
# promote. At minimum, verify global SHA256SUMS matches versioned after both.

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

if: |-
${{ needs.prepare.outputs.is_dry_run == 'false' && needs.prepare.outputs.is_nightly == 'false' && needs.prepare.outputs.is_preview == 'false' }}
env:
ALIYUN_OSS_BUCKET: "${{ vars.ALIYUN_OSS_BUCKET || 'qwen-code-assets' }}"
RELEASE_TAG: '${{ needs.prepare.outputs.release_tag }}'
run: |-
set -euo pipefail

hosted_assets=(

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] This hardcoded hosted_assets=() array must stay in sync with two other locations: (1) HOSTED_INSTALLATION_ASSETS in build-hosted-installation-assets.js and (2) the for asset in ... loop in the "Verify Aliyun OSS Hosted Installation Assets" step. Adding a new hosted installer to the build script without updating this array means the new file silently never reaches the CDN.

Consider deriving the upload list from the build output (e.g., node scripts/build-hosted-installation-assets.js --list-output-names) instead of hardcoding.

— qwen-latest-series-invite-beta-v34 via Qwen Code /review

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] If this second upload (global installation/ prefix) fails partway through, users get a mix of old archives and new SHA256SUMS (or vice versa), causing checksum verification failures for anyone running irm .../installation/install-qwen-standalone.ps1 | iex. The versioned prefix is safe, but the global alias is not atomic.

Fix: reorder the hosted_assets array so SHA256SUMS is first (not last), ensuring checksums are uploaded before archives. A partial failure then leaves old-but-consistent state (new SHA256SUMS referencing new archives that weren't uploaded yet — old archives still match old SHA256SUMS).

— qwen-latest-series-invite-beta-v34 via Qwen Code /review

dist/installation/install-qwen-standalone.sh
dist/installation/install-qwen-standalone.ps1
dist/installation/install-qwen-standalone.bat
dist/installation/uninstall-qwen-standalone.sh
dist/installation/uninstall-qwen-standalone.ps1
dist/installation/SHA256SUMS
)
node scripts/upload-aliyun-oss-assets.js \
--bucket "${ALIYUN_OSS_BUCKET}" \
--config "${RUNNER_TEMP}/.ossutilconfig" \
--prefix "installation/${RELEASE_TAG}" \

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] 「Sync Hosted Installation Assets」步骤先将资产上传到全局 installation/ 前缀(覆盖对外入口),之后才执行「Verify Aliyun OSS Hosted Installation Assets」验证步骤。若验证失败,全局入口已被覆盖为未验证内容。建议调整顺序:先上传到版本化 installation/${RELEASE_TAG}/ 路径,验证通过后再复制到 installation/ 全局前缀。

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

"${hosted_assets[@]}"
node scripts/upload-aliyun-oss-assets.js \

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] This ossutil cp call bypasses the retry wrapper (upload-aliyun-oss-assets.js), unlike every other OSS upload in the workflow. If it fails transiently after gh release create has already succeeded, re-running the workflow hits a duplicate-tag error — the workflow cannot make progress without manual intervention (delete the GitHub release or manually upload VERSION).

Route through upload-aliyun-oss-assets.js or replicate the retry loop inline.

— qwen-latest-series-invite-beta-v34 via Qwen Code /review

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] If this second upload (global installation/ prefix) fails partway through, users get a mix of old archives and new SHA256SUMS (or vice versa), causing checksum verification failures for anyone running irm .../installation/install-qwen-standalone.ps1 | iex. The versioned prefix is safe, but the global alias is not atomic.

Fix: reorder the hosted_assets array so SHA256SUMS is first (not last), ensuring checksums are uploaded before archives. A partial failure then leaves old-but-consistent state (new SHA256SUMS referencing new archives that weren't uploaded yet — old archives still match old SHA256SUMS).

— qwen-latest-series-invite-beta-v34 via Qwen Code /review

--bucket "${ALIYUN_OSS_BUCKET}" \
--config "${RUNNER_TEMP}/.ossutilconfig" \
--prefix "installation" \
"${hosted_assets[@]}"

- name: 'Verify Aliyun OSS Hosted Installation Assets'
if: |-
${{ needs.prepare.outputs.is_dry_run == 'false' && needs.prepare.outputs.is_nightly == 'false' && needs.prepare.outputs.is_preview == 'false' }}
env:
ALIYUN_OSS_PUBLIC_BASE_URL: "${{ vars.ALIYUN_OSS_PUBLIC_BASE_URL || 'https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com' }}"
RELEASE_TAG: '${{ needs.prepare.outputs.release_tag }}'
run: |-
set -euo pipefail

hosted_tmp_dir="$(mktemp -d)"
trap 'rm -rf "${hosted_tmp_dir}"' EXIT
mkdir -p "${hosted_tmp_dir}/versioned" "${hosted_tmp_dir}/global"
for asset in install-qwen-standalone.sh install-qwen-standalone.ps1 install-qwen-standalone.bat uninstall-qwen-standalone.sh uninstall-qwen-standalone.ps1 SHA256SUMS; do
url="${ALIYUN_OSS_PUBLIC_BASE_URL}/installation/${RELEASE_TAG}/${asset}"
global_url="${ALIYUN_OSS_PUBLIC_BASE_URL}/installation/${asset}"
curl -fsSL --connect-timeout 15 --max-time 300 "${url}" -o "${hosted_tmp_dir}/versioned/${asset}"
curl -fsSL --connect-timeout 15 --max-time 300 "${global_url}" -o "${hosted_tmp_dir}/global/${asset}"
done
cmp -s "dist/installation/SHA256SUMS" "${hosted_tmp_dir}/versioned/SHA256SUMS" || {
echo "::error::Hosted installation SHA256SUMS does not match local dist/installation/SHA256SUMS"
diff -u "dist/installation/SHA256SUMS" "${hosted_tmp_dir}/versioned/SHA256SUMS" || true
exit 1
}
cmp -s "dist/installation/SHA256SUMS" "${hosted_tmp_dir}/global/SHA256SUMS" || {
echo "::error::Global hosted installation SHA256SUMS does not match local dist/installation/SHA256SUMS"
diff -u "dist/installation/SHA256SUMS" "${hosted_tmp_dir}/global/SHA256SUMS" || true
exit 1
}
(cd "${hosted_tmp_dir}/versioned" && sha256sum -c SHA256SUMS)
(cd "${hosted_tmp_dir}/global" && sha256sum -c SHA256SUMS)

- name: 'Publish Aliyun OSS Latest VERSION'
# Run last so the `latest/VERSION` pointer only flips after every
# release asset and hosted installer object has been uploaded and
# verified. If any earlier step fails, the pointer keeps referring
# to the previously-good release.
if: |-
${{ needs.prepare.outputs.is_dry_run == 'false' && needs.prepare.outputs.is_nightly == 'false' && needs.prepare.outputs.is_preview == 'false' }}
env:
ALIYUN_OSS_BUCKET: "${{ vars.ALIYUN_OSS_BUCKET || 'qwen-code-assets' }}"
ALIYUN_OSS_PUBLIC_BASE_URL: "${{ vars.ALIYUN_OSS_PUBLIC_BASE_URL || 'https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com' }}"
RELEASE_TAG: '${{ needs.prepare.outputs.release_tag }}'
run: |-
set -euo pipefail

printf '%s\n' "${RELEASE_TAG}" > "${RUNNER_TEMP}/qwen-code-latest-version"
ossutil cp "${RUNNER_TEMP}/qwen-code-latest-version" "oss://${ALIYUN_OSS_BUCKET}/releases/qwen-code/latest/VERSION" -c "${RUNNER_TEMP}/.ossutilconfig" -f --acl public-read

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] If this second upload (global installation/ prefix) fails partway through, users get a mix of old archives and new SHA256SUMS (or vice versa), causing checksum verification failures for anyone running irm .../installation/install-qwen-standalone.ps1 | iex. The versioned prefix is safe, but the global alias is not atomic.

Fix: reorder the hosted_assets array so SHA256SUMS is first (not last), ensuring checksums are uploaded before archives. A partial failure then leaves old-but-consistent state (new SHA256SUMS referencing new archives that weren't uploaded yet — old archives still match old SHA256SUMS).

— qwen-latest-series-invite-beta-v34 via Qwen Code /review

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] This ossutil cp call bypasses the retry wrapper (upload-aliyun-oss-assets.js), unlike every other OSS upload in the workflow. If it fails transiently after gh release create has already succeeded, re-running the workflow fails with a duplicate-tag error — the workflow cannot make progress without manual intervention (delete the GitHub release or manually upload VERSION).

Route through upload-aliyun-oss-assets.js or replicate the retry loop inline.

— qwen-latest-series-invite-beta-v34 via Qwen Code /review


latest_version="$(curl -fsSL --connect-timeout 15 --max-time 300 "${ALIYUN_OSS_PUBLIC_BASE_URL}/releases/qwen-code/latest/VERSION" | tr -d '[:space:]')"
if [[ "${latest_version}" != "${RELEASE_TAG}" ]]; then
echo "::error::Aliyun latest VERSION points to ${latest_version}, expected ${RELEASE_TAG}"
exit 1
fi

- name: 'Cleanup Aliyun OSS Credentials'
if: |-
${{ always() && needs.prepare.outputs.is_dry_run == 'false' }}
run: |-
rm -f "${RUNNER_TEMP}/.ossutilconfig"

- name: 'Create PR to merge release branch into main'
if: |-
${{ needs.prepare.outputs.is_dry_run == 'false' && needs.prepare.outputs.is_nightly == 'false' && needs.prepare.outputs.is_preview == 'false' }}
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,10 @@
"preflight": "npm run clean && npm ci && npm run format && npm run lint:ci && npm run build && npm run typecheck && npm run test:ci",
"prepare": "husky && npm run build && npm run bundle",
"prepare:package": "node scripts/prepare-package.js",
"package:hosted-installation": "node scripts/build-hosted-installation-assets.js",
"package:standalone": "node scripts/create-standalone-package.js",
"package:standalone:release": "node scripts/build-standalone-release.js",
"verify:installation-release": "node scripts/verify-installation-release.js",
"release:version": "node scripts/version.js",
"telemetry": "node scripts/telemetry.js",
"check:lockfile": "node scripts/check-lockfile.js",
Expand Down
Loading