diff --git a/.github/scripts/run-release-step.sh b/.github/scripts/run-release-step.sh index 8be120125dd..79d6beb903a 100755 --- a/.github/scripts/run-release-step.sh +++ b/.github/scripts/run-release-step.sh @@ -197,6 +197,12 @@ case "${step}" in publish_package "packages/channels/${channel}" "${publish_marker}" "${channel}" echo "::endgroup::" done + + # Last on purpose: this is the only name in the sequence CI has never + # published, so a failure here leaves every long-shipped package already + # out instead of stranding them under `set -eo pipefail`. + publish_package 'packages/web-shell' + if [[ "${IS_DRY_RUN}" != "true" ]] && [[ ! -s "${publish_marker}" ]]; then echo "::warning::Every channel package was already published; nothing shipped" fi diff --git a/packages/web-shell/package.json b/packages/web-shell/package.json index 071e29d8c78..7f195845536 100644 --- a/packages/web-shell/package.json +++ b/packages/web-shell/package.json @@ -1,6 +1,11 @@ { "name": "@qwen-code/web-shell", "version": "0.24.0", + "repository": { + "type": "git", + "url": "git+https://github.com/QwenLM/qwen-code.git", + "directory": "packages/web-shell" + }, "type": "module", "main": "./dist/index.js", "module": "./dist/index.js", diff --git a/scripts/assert-release-version.mjs b/scripts/assert-release-version.mjs index c6280484aa7..8b3ab47cd50 100644 --- a/scripts/assert-release-version.mjs +++ b/scripts/assert-release-version.mjs @@ -22,6 +22,7 @@ export const PUBLISHED_PACKAGES = [ '@qwen-code/channel-telegram', '@qwen-code/channel-wecom', '@qwen-code/channel-weixin', + '@qwen-code/web-shell', ]; // Deliberately a copy of `scripts/lib/release-helpers.js`, not an import: diff --git a/scripts/tests/get-release-version.test.js b/scripts/tests/get-release-version.test.js index 995e99a0549..97ca4cbe56b 100644 --- a/scripts/tests/get-release-version.test.js +++ b/scripts/tests/get-release-version.test.js @@ -738,12 +738,13 @@ describe('assertVersionUnreleased', () => { `Version 1.2.3 has already shipped; refusing to force-push the release branch over it. Found on: ${foundOn}. If a previous attempt published only part of the release, complete the remaining artifacts manually — re-running this job will keep failing here while the version stays published.`; it('pins the full published-package set', () => { - // The push-time guard derives from this list; the publish allowlist in - // `.github/scripts/run-release-step.sh` enumerates the same channels - // separately, so adding or removing a package must update both. The two - // are pinned against each other by "keeps the publish allowlist and the - // guard package set in step" in release-workflow.test.js — release.yml - // itself no longer contains any publish step. + // The push-time guard and the release-version picker derive from this + // list; the publish allowlist in `.github/scripts/run-release-step.sh` + // enumerates the same packages separately, so adding or removing one must + // update both. They are pinned against each other by "keeps the publish + // allowlist and the guard package set in step" in release-workflow.test.js + // — release.yml itself no longer contains any publish step. Append rather + // than insert: the tests below read this array by index. expect(PUBLISHED_PACKAGES).toEqual([ '@qwen-code/qwen-code', '@qwen-code/external-context-mem0', @@ -757,6 +758,7 @@ describe('assertVersionUnreleased', () => { '@qwen-code/channel-telegram', '@qwen-code/channel-wecom', '@qwen-code/channel-weixin', + '@qwen-code/web-shell', ]); }); diff --git a/scripts/tests/package-scripts.test.js b/scripts/tests/package-scripts.test.js index 709be2839ed..a936a28fb13 100644 --- a/scripts/tests/package-scripts.test.js +++ b/scripts/tests/package-scripts.test.js @@ -1772,6 +1772,7 @@ describe('package scripts', () => { 'packages/mobile-mcp', 'packages/node-repl', 'packages/sdk-typescript', + 'packages/web-shell', ]) { const packageJson = JSON.parse( readFileSync(path.join(root, packageDirectory, 'package.json'), 'utf8'), diff --git a/scripts/tests/release-workflow.test.js b/scripts/tests/release-workflow.test.js index d2c4e93fe77..b9f50052a67 100644 --- a/scripts/tests/release-workflow.test.js +++ b/scripts/tests/release-workflow.test.js @@ -524,6 +524,28 @@ describe('release workflow', () => { .filter((name) => name !== 'base') .sort(); expect(published).toEqual(guarded); + + // Non-channel packages are literal calls, so verify those against the + // guard too. Resolve their names from package.json to avoid a second map. + const publishStep = releaseStepScript.slice( + releaseStepScript.indexOf('\n publish-packages)'), + releaseStepScript.indexOf('\n verify-archives)'), + ); + const literalTargets = [ + ...publishStep.matchAll(/publish_package '([^']+)'/g), + ].map(([, directory]) => directory); + expect(literalTargets.length).toBeGreaterThan(1); + const guardedNames = new Set(PUBLISHED_PACKAGES); + for (const directory of literalTargets) { + // `dist` is the root package's bundle output rather than a workspace + // directory, and does not exist in a source checkout. + const manifest = + directory === 'dist' ? 'package.json' : `${directory}/package.json`; + const { name } = JSON.parse(readFileSync(manifest, 'utf8')); + expect(guardedNames.has(name), `${directory} publishes ${name}`).toBe( + true, + ); + } }); it('keeps the workflow focused on orchestration', () => { @@ -598,6 +620,7 @@ describe('release workflow', () => { mkdirSync(bin); for (const path of [ 'dist', + 'packages/web-shell', 'packages/channels/base', ...channels.map((channel) => `packages/channels/${channel}`), ]) { @@ -643,12 +666,13 @@ describe('release workflow', () => { expect(publishCalls.map(([cwd]) => cwd)).toEqual([ join(canonicalDirectory, 'dist'), join(canonicalDirectory, 'packages/channels/base'), + join(canonicalDirectory, 'packages/web-shell'), ]); // The dist-tag is the reason NPM_TAG is set in this child env at all. // Without it `npm publish` defaults to `latest`, so the 21:00 UTC // nightly would take over the tag every end-user install and the ECS // fleet updater resolve through. `--access public` is here for the - // same reason: one array feeds all twelve published packages. + // same reason: one array feeds all thirteen published packages. for (const [cwd, args] of publishCalls) { expect(args, cwd).toContain('--access public'); expect(args, cwd).toContain('--tag=latest');