Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
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
65 changes: 56 additions & 9 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@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
with:
ref: '${{ github.event.inputs.ref || github.sha }}'
fetch-depth: 0
Expand Down Expand Up @@ -154,7 +154,7 @@ jobs:

steps:
- name: 'Checkout'
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
with:
ref: '${{ github.event.inputs.ref || github.sha }}'
fetch-depth: 0
Expand Down Expand Up @@ -212,7 +212,7 @@ jobs:

steps:
- name: 'Checkout'
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
with:
ref: '${{ github.event.inputs.ref || github.sha }}'
fetch-depth: 0
Expand Down Expand Up @@ -259,7 +259,7 @@ jobs:

steps:
- name: 'Checkout'
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
with:
ref: '${{ github.event.inputs.ref || github.sha }}'
fetch-depth: 0
Expand Down Expand Up @@ -347,7 +347,7 @@ jobs:

steps:
- name: 'Checkout'
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
with:
# Persist the bot PAT for release-branch pushes so downstream CI
# workflows are triggered.
Expand Down Expand Up @@ -438,35 +438,82 @@ jobs:
${{ github.repository == 'QwenLM/qwen-code' }}
working-directory: 'packages/audio-capture'
run: |-
npm publish --access public --tag=${{ needs.prepare.outputs.npm_tag }} ${{ needs.prepare.outputs.is_dry_run == 'true' && '--dry-run' || '' }}
PACKAGE_NAME="$(node -p "require('./package.json').name")"
PUBLISH_ARGS=(--access public "--tag=${NPM_TAG}")
if [[ "${IS_DRY_RUN}" == "true" ]]; then
PUBLISH_ARGS+=(--dry-run)
elif npm view "${PACKAGE_NAME}@${RELEASE_VERSION}" version >/dev/null 2>&1; then
echo "::notice::${PACKAGE_NAME}@${RELEASE_VERSION} already published; skipping"
exit 0
fi
npm publish "${PUBLISH_ARGS[@]}"
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
env:
NODE_AUTH_TOKEN: '${{ secrets.NPM_TOKEN }}'
RELEASE_VERSION: '${{ needs.prepare.outputs.release_version }}'
NPM_TAG: '${{ needs.prepare.outputs.npm_tag }}'
IS_DRY_RUN: '${{ needs.prepare.outputs.is_dry_run }}'

- name: 'Publish @qwen-code/qwen-code'
working-directory: 'dist'
run: |-
npm publish --access public --tag=${{ needs.prepare.outputs.npm_tag }} ${{ needs.prepare.outputs.is_dry_run == 'true' && '--dry-run' || '' }}
PACKAGE_NAME="$(node -p "require('./package.json').name")"
PUBLISH_ARGS=(--access public "--tag=${NPM_TAG}")
if [[ "${IS_DRY_RUN}" == "true" ]]; then
PUBLISH_ARGS+=(--dry-run)
elif npm view "${PACKAGE_NAME}@${RELEASE_VERSION}" version >/dev/null 2>&1; then
echo "::notice::${PACKAGE_NAME}@${RELEASE_VERSION} already published; skipping"
exit 0
fi
npm publish "${PUBLISH_ARGS[@]}"
env:
NODE_AUTH_TOKEN: '${{ secrets.NPM_TOKEN }}'
RELEASE_VERSION: '${{ needs.prepare.outputs.release_version }}'
NPM_TAG: '${{ needs.prepare.outputs.npm_tag }}'
IS_DRY_RUN: '${{ needs.prepare.outputs.is_dry_run }}'

- name: 'Publish @qwen-code/channel-base'
working-directory: 'packages/channels/base'
run: |-
npm publish --access public --tag=${{ needs.prepare.outputs.npm_tag }} ${{ needs.prepare.outputs.is_dry_run == 'true' && '--dry-run' || '' }}
PACKAGE_NAME="$(node -p "require('./package.json').name")"
PUBLISH_ARGS=(--access public "--tag=${NPM_TAG}")
if [[ "${IS_DRY_RUN}" == "true" ]]; then
PUBLISH_ARGS+=(--dry-run)
elif npm view "${PACKAGE_NAME}@${RELEASE_VERSION}" version >/dev/null 2>&1; then
echo "::notice::${PACKAGE_NAME}@${RELEASE_VERSION} already published; skipping"
exit 0
fi
npm publish "${PUBLISH_ARGS[@]}"
env:
NODE_AUTH_TOKEN: '${{ secrets.NPM_TOKEN }}'
RELEASE_VERSION: '${{ needs.prepare.outputs.release_version }}'
NPM_TAG: '${{ needs.prepare.outputs.npm_tag }}'
IS_DRY_RUN: '${{ needs.prepare.outputs.is_dry_run }}'

- name: 'Publish remaining channel packages'
working-directory: 'packages/channels'
run: |-
# Explicit allowlist: new channel workspaces require release approval.
for channel in dingtalk feishu github qqbot telegram wecom weixin; do
echo "::group::Publishing @qwen-code/channel-${channel}"
(cd "${channel}" && npm publish --access public --tag=${{ needs.prepare.outputs.npm_tag }} ${{ needs.prepare.outputs.is_dry_run == 'true' && '--dry-run' || '' }})
(
cd "${channel}"
PACKAGE_NAME="$(node -p "require('./package.json').name")"
PUBLISH_ARGS=(--access public "--tag=${NPM_TAG}")
if [[ "${IS_DRY_RUN}" == "true" ]]; then
PUBLISH_ARGS+=(--dry-run)
elif npm view "${PACKAGE_NAME}@${RELEASE_VERSION}" version >/dev/null 2>&1; then
echo "::notice::${PACKAGE_NAME}@${RELEASE_VERSION} already published; skipping"
exit 0
fi
npm publish "${PUBLISH_ARGS[@]}"
)
echo "::endgroup::"
done
env:
NODE_AUTH_TOKEN: '${{ secrets.NPM_TOKEN }}'
RELEASE_VERSION: '${{ needs.prepare.outputs.release_version }}'
NPM_TAG: '${{ needs.prepare.outputs.npm_tag }}'
IS_DRY_RUN: '${{ needs.prepare.outputs.is_dry_run }}'

- name: 'Verify Standalone Archives'
run: |-
Expand Down
26 changes: 24 additions & 2 deletions scripts/get-release-version.js
Original file line number Diff line number Diff line change
Expand Up @@ -335,8 +335,30 @@ function getPreviewVersion(args) {
);
releaseVersion = overrideVersion;
} else if (tagResult) {
releaseVersion =
tagResult.latestVersion.replace(/-nightly.*/, '') + '-preview.0';
let baseVersion = tagResult.latestVersion.replace(/-nightly.*/, '');
// When the nightly base is already published as stable, the preview must
// target the next patch — otherwise the scheduled Tuesday release derives
// a version whose channel packages already exist on npm (E403).
const latestStable = getVersionFromNPM('latest');
if (
latestStable &&
semver.valid(latestStable) &&
semver.valid(baseVersion)
) {
if (semver.gt(latestStable, baseVersion)) {
throw new Error(
`Nightly base ${baseVersion} is lower than published latest ${latestStable}. Refusing divergent preview baseline.`,
);
}
if (semver.eq(latestStable, baseVersion)) {
const bumped = semver.inc(latestStable, 'patch');
console.error(
`Nightly base ${baseVersion} already released as stable (${latestStable}); bumping preview base to ${bumped}.`,
);
baseVersion = bumped;
}
}
releaseVersion = baseVersion + '-preview.0';
validateVersion(
releaseVersion,
'X.Y.Z-preview.N',
Expand Down
23 changes: 23 additions & 0 deletions scripts/tests/get-release-version.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,29 @@ describe('getVersion', () => {
expect(result.previousReleaseTag).toBe('v0.6.1');
});

it('should bump the preview base when the latest stable matches it', () => {
vi.mocked(execSync).mockImplementation((command) => {
if (command.includes('npm view') && command.includes('--tag=latest'))
return '0.8.0';
return mockExecSync(command);
});

const result = getVersion({ type: 'preview' });
expect(result.releaseVersion).toBe('0.8.1-preview.0');
});

it('should reject the preview when the latest stable is ahead', () => {
vi.mocked(execSync).mockImplementation((command) => {
if (command.includes('npm view') && command.includes('--tag=latest'))
return '0.9.0';
return mockExecSync(command);
});

expect(() => getVersion({ type: 'preview' })).toThrow(
'Nightly base 0.8.0 is lower than published latest 0.9.0. Refusing divergent preview baseline.',
);
});

it('should calculate the next nightly version from package.json', () => {
vi.mocked(execSync).mockImplementation(mockExecSync);
const result = getVersion({ type: 'nightly' });
Expand Down
26 changes: 26 additions & 0 deletions scripts/tests/package-scripts.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,32 @@ describe('package scripts', () => {
expect(buildStep).toContain('npm run build\n npm run bundle');
});

it('skips npm packages whose release version is already published', () => {
const workflow = readWorkflow('.github/workflows/release.yml');
const publishJob = getWorkflowJob(workflow, 'publish');

for (const stepName of [
'Publish @qwen-code/audio-capture',
'Publish @qwen-code/qwen-code',
'Publish @qwen-code/channel-base',
'Publish remaining channel packages',
]) {
const publishStep = getWorkflowStep(publishJob, stepName);
expect(publishStep).toContain(
"RELEASE_VERSION: '${{ needs.prepare.outputs.release_version }}'",
);
expect(publishStep).toContain(
'PACKAGE_NAME="$(node -p "require(\'./package.json\').name")"',
);
expect(publishStep).toContain('PUBLISH_ARGS+=(--dry-run)');
expect(publishStep).toContain(
'npm view "${PACKAGE_NAME}@${RELEASE_VERSION}" version',
);
expect(publishStep).toContain('already published; skipping');
expect(publishStep).toContain('npm publish "${PUBLISH_ARGS[@]}"');
}
});

it('fast-tracks trusted autofix issue triggers before LLM assessment', () => {
const workflow = readWorkflow('.github/workflows/qwen-autofix.yml');
const issueJob = getWorkflowJob(workflow, 'issue-autofix');
Expand Down
Loading