qwen-code/scripts/assert-release-version.mjs
易良 baf0ff41f7
refactor(build,release): Use pnpm workspace builds and publishing (#12400)
* refactor(release): Use pnpm for workspace publishing and versioning

* fix(ci): revert desktop-release install to npm ci for Windows compatibility

The pnpm migration added a bash conditional to check for pnpm-lock.yaml,
but the step did not specify shell: 'bash'. Windows runners default to
PowerShell, which cannot parse bash syntax, causing the build to fail
with a ParserError.

Since desktop-release always checks out the same commit and the root
lockfile is now pnpm-lock.yaml, the conditional is unnecessary. Revert
to the simple npm ci command that works on all platforms.

Fixes the Windows build failure in Desktop release v0.24.2.

* refactor(build): Schedule workspace builds with pnpm

* fix(ci): Preserve Windows release installation and version filters

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-09-21 15:38:36 +00:00

128 lines
4.2 KiB
JavaScript

#!/usr/bin/env node
/**
* @license
* Copyright 2026 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
import { execSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { RELEASE_WORKSPACES } from './release-packages.mjs';
export const PUBLISHED_PACKAGES = [
'@qwen-code/qwen-code',
...RELEASE_WORKSPACES,
];
// Deliberately a copy of `scripts/lib/release-helpers.js`, not an import:
// the workflow's sparse checkout excludes `scripts/lib/`, so importing it
// resolves in every test lane and throws ERR_MODULE_NOT_FOUND only in the
// publish job. `scripts/tests/release-workflow.test.js` pins the two bodies
// identical — widen or harden them together, and never widen this copy alone:
// treating a rate-limited probe as "release absent" would let the force push
// proceed over a shipped version.
function isExpectedMissingGitHubRelease(error) {
const stderr = error.stderr?.toString() ?? '';
const stdout = error.stdout?.toString() ?? '';
const message = `${error.message}\n${stderr}\n${stdout}`;
return message.includes('release not found') || message.includes('Not Found');
}
export function assertVersionUnreleased(version) {
if (
typeof version !== 'string' ||
!/^\d+\.\d+\.\d+(?:-preview\.\d+|-nightly\.\d{8}\.[0-9a-f]+)?$/.test(
version,
)
) {
const error = new Error(
'assert-unreleased requires a version in release format, e.g. --assert-unreleased=1.2.3',
);
// Not a probe failure: a malformed version is refused identically on
// every attempt, so it must not land in the exit-2 bucket that
// run-release-step.sh retries three times while logging it as one
// transient probe error.
error.code = 'VERSION_FORMAT';
throw error;
}
const shippedTo = [];
for (const pkg of PUBLISHED_PACKAGES) {
try {
const output = execSync(`npm view ${pkg}@${version} version`)
.toString()
.trim();
if (output === version) shippedTo.push(pkg);
} catch (error) {
if (shippedTo.length === 0 && !error.message?.includes('E404')) {
throw new Error(
`Failed to verify ${pkg}@${version} on npm: ${error.message}`,
);
}
}
}
if (shippedTo.length === 0) {
try {
execSync(`git ls-remote --exit-code origin "refs/tags/v${version}"`);
shippedTo.push(`origin tag v${version}`);
} catch (error) {
if (error.status !== 2) {
throw new Error(
`Failed to verify tag v${version} on origin: ${error.message}`,
);
}
}
}
if (shippedTo.length === 0) {
try {
const output = execSync(
`gh release view "v${version}" --json tagName --jq .tagName`,
)
.toString()
.trim();
if (output === `v${version}`)
shippedTo.push(`GitHub release v${version}`);
} catch (error) {
if (!isExpectedMissingGitHubRelease(error)) {
throw new Error(
`Failed to verify release v${version} on GitHub: ${error.message}`,
);
}
}
}
if (shippedTo.length > 0) {
const error = new Error(
`Version ${version} has already shipped; refusing to force-push the release branch over it. Found on: ${shippedTo.join(', ')}. 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.`,
);
error.code = 'VERSION_SHIPPED';
throw error;
}
}
export function runAssertVersionCli(version) {
try {
assertVersionUnreleased(version);
} catch (error) {
console.log(`::error::${error.message}`);
// 3 = already shipped (decisive, marked so the workflow skips the
// release-failed notification); 4 = malformed version (decisive, not
// retryable, and not a benign refusal); 2 = probe failure, retried.
if (error.code === 'VERSION_SHIPPED') return 3;
if (error.code === 'VERSION_FORMAT') return 4;
return 2;
}
return 0;
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
const prefix = '--assert-unreleased=';
const arg = process.argv[2] ?? '';
const exitCode = runAssertVersionCli(
arg.startsWith(prefix) ? arg.slice(prefix.length) : '',
);
if (exitCode !== 0) process.exit(exitCode);
}