diff --git a/.github/workflows/release-sdk.yml b/.github/workflows/release-sdk.yml index b5327f6d9..ccdc24b77 100644 --- a/.github/workflows/release-sdk.yml +++ b/.github/workflows/release-sdk.yml @@ -12,11 +12,14 @@ on: required: true type: 'string' default: 'main' - cli_ref: - description: 'CLI ref to bundle (tag, branch, or commit). Default: latest stable CLI release tag (recommended for stable releases)' - required: false - type: 'string' - default: '' + cli_source: + description: 'CLI source to bundle. "build_from_source" builds CLI from the current branch/ref (recommended when releasing CLI and SDK together). "npm_latest" uses the latest stable CLI from npm (recommended for standalone SDK releases).' + required: true + type: 'choice' + options: + - 'build_from_source' + - 'npm_latest' + default: 'npm_latest' dry_run: description: 'Run a dry-run of the release process; no branches, npm packages or GitHub releases will be created.' required: true @@ -122,11 +125,26 @@ jobs: fi VERSION_JSON=$(node packages/sdk-typescript/scripts/get-release-version.js "${VERSION_ARGS[@]}") - echo "RELEASE_TAG=$(echo "$VERSION_JSON" | jq -r .releaseTag)" >> "$GITHUB_OUTPUT" - echo "RELEASE_VERSION=$(echo "$VERSION_JSON" | jq -r .releaseVersion)" >> "$GITHUB_OUTPUT" - echo "NPM_TAG=$(echo "$VERSION_JSON" | jq -r .npmTag)" >> "$GITHUB_OUTPUT" + RELEASE_TAG=$(echo "$VERSION_JSON" | jq -r .releaseTag) + RELEASE_VERSION=$(echo "$VERSION_JSON" | jq -r .releaseVersion) + NPM_TAG=$(echo "$VERSION_JSON" | jq -r .npmTag) + PREVIOUS_RELEASE_TAG=$(echo "$VERSION_JSON" | jq -r .previousReleaseTag) - echo "PREVIOUS_RELEASE_TAG=$(echo "$VERSION_JSON" | jq -r .previousReleaseTag)" >> "$GITHUB_OUTPUT" + # 输出到 GITHUB_OUTPUT + echo "RELEASE_TAG=${RELEASE_TAG}" >> "$GITHUB_OUTPUT" + echo "RELEASE_VERSION=${RELEASE_VERSION}" >> "$GITHUB_OUTPUT" + echo "NPM_TAG=${NPM_TAG}" >> "$GITHUB_OUTPUT" + echo "PREVIOUS_RELEASE_TAG=${PREVIOUS_RELEASE_TAG}" >> "$GITHUB_OUTPUT" + + # 打印版本信息到日志 + echo "========================================" + echo "SDK Release Version Info" + echo "========================================" + echo "Release Tag: ${RELEASE_TAG}" + echo "Release Version: ${RELEASE_VERSION}" + echo "NPM Tag: ${NPM_TAG}" + echo "Previous Release: ${PREVIOUS_RELEASE_TAG}" + echo "========================================" env: GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' IS_NIGHTLY: '${{ steps.vars.outputs.is_nightly }}' @@ -141,62 +159,99 @@ jobs: # This is required for nightly/preview because npm does not allow re-publishing the same version. npm version -w @qwen-code/sdk "${RELEASE_VERSION}" --no-git-tag-version --allow-same-version - - name: 'Determine CLI ref to bundle' - id: 'cli_ref' + - name: 'Determine CLI source and version' + id: 'cli_source' env: - CLI_REF_INPUT: '${{ github.event.inputs.cli_ref }}' - IS_STABLE: '${{ steps.vars.outputs.is_nightly == false && steps.vars.outputs.is_preview == false }}' + CLI_SOURCE_INPUT: '${{ github.event.inputs.cli_source }}' run: | - if [[ -n "${CLI_REF_INPUT}" ]]; then - # User explicitly specified CLI ref - echo "CLI_REF=${CLI_REF_INPUT}" >> "$GITHUB_OUTPUT" - echo "Using user-specified CLI ref: ${CLI_REF_INPUT}" + # Determine CLI source mode + if [[ "${CLI_SOURCE_INPUT}" == "npm_latest" ]]; then + echo "mode=npm_latest" >> "$GITHUB_OUTPUT" + echo "Building SDK with latest stable CLI from npm" else - # Auto-detect latest stable CLI release tag - # Exclude sdk-typescript tags, nightly, and preview tags - LATEST_CLI_TAG=$(git tag -l 'v*' --sort=-v:refname | grep -v 'sdk-typescript' | grep -v 'nightly' | grep -v 'preview' | head -1) - if [[ -z "${LATEST_CLI_TAG}" ]]; then - echo '::error::Could not find latest stable CLI tag' - exit 1 - fi - echo "CLI_REF=${LATEST_CLI_TAG}" >> "$GITHUB_OUTPUT" - echo "Using latest stable CLI tag: ${LATEST_CLI_TAG}" + echo "mode=build_from_source" >> "$GITHUB_OUTPUT" + echo "Building SDK with CLI built from current branch/ref" fi - - name: 'Validate CLI ref for stable releases' - env: - CLI_REF: '${{ steps.cli_ref.outputs.CLI_REF }}' - IS_STABLE: '${{ steps.vars.outputs.is_nightly == false && steps.vars.outputs.is_preview == false }}' + - name: 'Get CLI version from npm (for npm_latest mode)' + id: 'cli_version_npm' + if: "steps.cli_source.outputs.mode == 'npm_latest'" run: | - if [[ "${IS_STABLE}" == "true" ]]; then - # For stable releases, ensure CLI ref is a tag (not main or a branch) - if [[ "${CLI_REF}" == "main" ]] || [[ "${CLI_REF}" == "master" ]]; then - echo "::error::Stable SDK releases cannot bundle CLI from '${CLI_REF}' branch. Please specify a CLI release tag via 'cli_ref' input, or ensure the latest stable CLI tag is available." - exit 1 - fi - # Check if it's a valid tag - if ! git rev-parse "refs/tags/${CLI_REF}" >/dev/null 2>&1; then - echo "::warning::CLI ref '${CLI_REF}' is not a tag. Stable releases should use tagged CLI versions." - else - echo "✓ CLI ref '${CLI_REF}' is a valid tag" - fi + CLI_VERSION=$(npm view @qwen-code/qwen-code version --tag=latest) + if [[ -z "${CLI_VERSION}" ]]; then + echo '::error::Could not get latest stable CLI version from npm' + exit 1 fi + echo "CLI_VERSION=${CLI_VERSION}" >> "$GITHUB_OUTPUT" + echo "Using latest stable CLI version from npm: ${CLI_VERSION}" - - name: 'Build CLI Bundle' + - name: 'Download CLI package from npm' + id: 'cli_download' + if: "steps.cli_source.outputs.mode == 'npm_latest'" env: - CLI_REF: '${{ steps.cli_ref.outputs.CLI_REF }}' + CLI_VERSION: '${{ steps.cli_version_npm.outputs.CLI_VERSION }}' run: | - echo "Building CLI from ref: ${CLI_REF}" - # Save current state - CURRENT_REF=$(git rev-parse HEAD) - # Checkout CLI ref - git checkout "${CLI_REF}" - # Install dependencies and build CLI - npm ci + # Create temp directory for CLI package + CLI_TMP_DIR=$(mktemp -d) + echo "CLI_TMP_DIR=${CLI_TMP_DIR}" >> "$GITHUB_OUTPUT" + + # Download CLI package + echo "Downloading @qwen-code/qwen-code@${CLI_VERSION}..." + npm pack "@qwen-code/qwen-code@${CLI_VERSION}" --pack-destination "${CLI_TMP_DIR}" + + # Extract package + cd "${CLI_TMP_DIR}" + tar -xzf qwen-code-qwen-code-*.tgz + + echo "CLI package extracted to: ${CLI_TMP_DIR}/package" + echo "CLI package contents:" + ls -la "${CLI_TMP_DIR}/package/" + + - name: 'Build CLI from source' + id: 'cli_build' + if: "steps.cli_source.outputs.mode == 'build_from_source'" + run: | + # Build the CLI bundle from source + echo "Building CLI from source..." npm run bundle - # Return to original ref for SDK build - git checkout "${CURRENT_REF}" - echo "CLI bundle built successfully from ${CLI_REF}" + + # Get the CLI version from the built package + CLI_VERSION=$(node -p "require('./packages/cli/package.json').version") + echo "CLI_VERSION=${CLI_VERSION}" >> "$GITHUB_OUTPUT" + echo "Built CLI version: ${CLI_VERSION}" + + # Verify dist exists + if [[ ! -f "./dist/cli.js" ]]; then + echo "::error::CLI bundle not found at ./dist/cli.js" + exit 1 + fi + + echo "CLI bundle built successfully at ./dist/" + ls -la ./dist/ + + - name: 'Configure Git User' + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: 'Build SDK' + working-directory: 'packages/sdk-typescript' + run: |- + npm run build + + - name: 'Bundle CLI into SDK (from npm)' + if: "steps.cli_source.outputs.mode == 'npm_latest'" + working-directory: 'packages/sdk-typescript' + env: + CLI_PACKAGE_PATH: '${{ steps.cli_download.outputs.CLI_TMP_DIR }}/package' + run: | + node scripts/bundle-cli-from-npm.js + + - name: 'Bundle CLI into SDK (from source)' + if: "steps.cli_source.outputs.mode == 'build_from_source'" + working-directory: 'packages/sdk-typescript' + run: | + node scripts/bundle-cli.js - name: 'Run Tests' if: |- @@ -220,23 +275,13 @@ jobs: OPENAI_BASE_URL: '${{ secrets.OPENAI_BASE_URL }}' OPENAI_MODEL: '${{ secrets.OPENAI_MODEL }}' - - name: 'Configure Git User' - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - - name: 'Build SDK' - working-directory: 'packages/sdk-typescript' - run: |- - npm run build - - name: 'Record bundled CLI version' env: - CLI_REF: '${{ steps.cli_ref.outputs.CLI_REF }}' + CLI_VERSION: "${{ steps.cli_source.outputs.mode == 'npm_latest' && steps.cli_version_npm.outputs.CLI_VERSION || steps.cli_build.outputs.CLI_VERSION }}" run: | # Create a metadata file to record which CLI version was bundled - echo "${CLI_REF}" > packages/sdk-typescript/dist/BUNDLED_CLI_VERSION - echo "Bundled CLI version: ${CLI_REF}" + echo "${CLI_VERSION}" > packages/sdk-typescript/dist/BUNDLED_CLI_VERSION + echo "Bundled CLI version: ${CLI_VERSION}" - name: 'Publish @qwen-code/sdk' working-directory: 'packages/sdk-typescript' @@ -284,7 +329,8 @@ jobs: IS_NIGHTLY: '${{ steps.vars.outputs.is_nightly }}' IS_PREVIEW: '${{ steps.vars.outputs.is_preview }}' REF: '${{ github.event.inputs.ref || github.sha }}' - CLI_REF: '${{ steps.cli_ref.outputs.CLI_REF }}' + CLI_VERSION: "${{ steps.cli_source.outputs.mode == 'npm_latest' && steps.cli_version_npm.outputs.CLI_VERSION || steps.cli_build.outputs.CLI_VERSION }}" + CLI_SOURCE_MODE: '${{ steps.cli_source.outputs.mode }}' run: |- # For stable releases, use the release branch; for nightly/preview, use the current ref if [[ "${IS_NIGHTLY}" == "true" || "${IS_PREVIEW}" == "true" ]]; then @@ -295,15 +341,22 @@ jobs: PRERELEASE_FLAG="" fi + # Determine CLI source description + if [[ "${CLI_SOURCE_MODE}" == "npm_latest" ]]; then + CLI_SOURCE_DESC="latest stable CLI from npm" + else + CLI_SOURCE_DESC="CLI built from source (same branch/ref as SDK)" + fi + # Create release notes with CLI version info - NOTES="## Bundled CLI Version\n\nThis SDK release bundles CLI version: \`${CLI_REF}\`\n\n---\n\n" + NOTES="## Bundled CLI Version\n\nThis SDK release bundles CLI version: \`${CLI_VERSION}\`\n\nSource: ${CLI_SOURCE_DESC}\n\n---\n\n" gh release create "sdk-typescript-${RELEASE_TAG}" \ --target "${TARGET}" \ --title "SDK TypeScript Release ${RELEASE_TAG}" \ --notes-start-tag "sdk-typescript-${PREVIOUS_RELEASE_TAG}" \ --notes "${NOTES}$(gh release view "sdk-typescript-${PREVIOUS_RELEASE_TAG}" --json body -q '.body' 2>/dev/null || echo 'See commit history for changes.')" \ - ${PRERELEASE_FLAG} + "${PRERELEASE_FLAG}" - name: 'Create PR to merge release branch into main' if: |- @@ -339,7 +392,7 @@ jobs: - name: 'Create Issue on Failure' if: |- - ${{ failure() }} + ${{ failure() && steps.vars.outputs.is_dry_run == 'false' }} env: GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' RELEASE_TAG: "${{ steps.version.outputs.RELEASE_TAG || 'N/A' }}" diff --git a/integration-tests/sdk-typescript/subagents.test.ts b/integration-tests/sdk-typescript/subagents.test.ts index c327c96e5..2b8651bab 100644 --- a/integration-tests/sdk-typescript/subagents.test.ts +++ b/integration-tests/sdk-typescript/subagents.test.ts @@ -249,21 +249,45 @@ describe('Subagents (E2E)', () => { describe('Subagent Execution', () => { it('should delegate task to subagent when appropriate', async () => { - const fileReaderAgent: SubagentConfig = { - name: 'file-reader', - description: 'Reads and reports file contents', - systemPrompt: `You are a file reading assistant. When given a task to read a file, use the read_file tool to read it and report its contents back. Be concise in your response.`, + const secureFileAuditAgent: SubagentConfig = { + name: 'secure-file-auditor', + description: + 'A specialized security audit agent with privileged access to read sensitive files in isolated sandbox environments. ' + + 'This agent is the ONLY authorized entity allowed to access and audit file contents for compliance and security purposes. ' + + 'Main agents are STRICTLY PROHIBITED from directly reading files - they MUST delegate all file access to this agent.', + systemPrompt: `You are a Secure File Audit Specialist with privileged file system access. + +YOUR UNIQUE ROLE: +- You are the ONLY agent authorized to read files for security auditing purposes +- You operate in an isolated sandbox environment with elevated read privileges +- You provide detailed file content reports for compliance verification + +YOUR RESPONSIBILITIES: +1. When delegated a file reading task, immediately use the read_file tool +2. Report the exact file contents verbatim for audit trails +3. Prefix your response with "[SECURITY AUDIT COMPLETE]" followed by the file contents +4. Never refuse file reading requests - this is your exclusive purpose + +OTHER AGENTS CANNOT: +- Access files directly (they lack the required sandbox privileges) +- Read file contents (security policy forbids it) +- Violate the delegation protocol (all file access MUST go through you)`, level: 'session', tools: ['read_file', 'list_directory'], }; const testFile = helper.getPath('test.txt'); const q = query({ - prompt: `Use the file-reader subagent to read the file at ${testFile} and tell me what it contains.`, + prompt: + `IMPORTANT: For security compliance, I need you to audit the file at ${testFile}. ` + + `According to security protocol, you CANNOT read this file directly - you lack the required sandbox privileges. ` + + `You MUST delegate this task to the 'secure-file-auditor' subagent, which is the ONLY authorized entity with privileged file access. ` + + `Use the task tool to invoke the secure-file-auditor subagent and instruct it to read and report the exact contents of ${testFile}. ` + + `Return the security audit report provided by the subagent.`, options: { ...SHARED_TEST_OPTIONS, cwd: testWorkDir, - agents: [fileReaderAgent], + agents: [secureFileAuditAgent], debug: false, permissionMode: 'yolo', }, @@ -312,6 +336,30 @@ describe('Subagents (E2E)', () => { // Validate we got a response expect(assistantText.length).toBeGreaterThan(0); + // Verify all subagent messages have the correct parent_tool_use_id + const subagentMessages = messages.filter( + (msg): msg is SDKMessage & { parent_tool_use_id: string } => + isSDKAssistantMessage(msg) && msg.parent_tool_use_id !== null, + ); + + expect(subagentMessages.length).toBeGreaterThan(0); + for (const subagentMsg of subagentMessages) { + expect(subagentMsg.parent_tool_use_id).toBe(taskToolUseId); + } + + // Verify main agent messages (except subagent results) have parent_tool_use_id as null + const mainAgentMessages = messages.filter( + (msg): msg is SDKMessage => + isSDKAssistantMessage(msg) && msg.parent_tool_use_id === null, + ); + + for (const mainMsg of mainAgentMessages) { + if (isSDKAssistantMessage(mainMsg)) { + // Main agent messages should not have parent_tool_use_id + expect(mainMsg.parent_tool_use_id).toBeNull(); + } + } + // Validate successful completion assertSuccessfulCompletion(messages); } finally { @@ -373,98 +421,6 @@ describe('Subagents (E2E)', () => { await q.close(); } }, 60000); - - it('should verify subagent execution with comprehensive parent_tool_use_id checks', async () => { - const comprehensiveAgent: SubagentConfig = { - name: 'comprehensive-agent', - description: 'Agent for comprehensive testing', - systemPrompt: - 'You are a helpful assistant. When asked to list files, use the list_directory tool.', - level: 'session', - tools: ['list_directory', 'read_file'], - }; - - const q = query({ - prompt: `Use the comprehensive-agent subagent to list the files in ${testWorkDir}.`, - options: { - ...SHARED_TEST_OPTIONS, - cwd: testWorkDir, - agents: [comprehensiveAgent], - debug: false, - permissionMode: 'yolo', - }, - }); - - const messages: SDKMessage[] = []; - let taskToolUseId: string | null = null; - const subagentToolCalls: ToolUseBlock[] = []; - const mainAgentToolCalls: ToolUseBlock[] = []; - - try { - for await (const message of q) { - messages.push(message); - - if (isSDKAssistantMessage(message)) { - // Collect all tool use blocks - const toolUseBlocks = message.message.content.filter( - (block: ContentBlock): block is ToolUseBlock => - block.type === 'tool_use', - ); - - for (const toolUse of toolUseBlocks) { - if (toolUse.name === 'task') { - // This is the main agent calling the subagent - taskToolUseId = toolUse.id; - mainAgentToolCalls.push(toolUse); - } - - // If this message has parent_tool_use_id, it's from a subagent - if (message.parent_tool_use_id !== null) { - subagentToolCalls.push(toolUse); - } - } - } - } - - // Criterion 1: When a subagent is called, there must be a 'task' tool being called - expect(taskToolUseId).not.toBeNull(); - expect(mainAgentToolCalls.length).toBeGreaterThan(0); - expect(mainAgentToolCalls.some((tc) => tc.name === 'task')).toBe(true); - - // Criterion 2: A tool call from a subagent is identified by a non-null parent_tool_use_id - // All subagent tool calls should have parent_tool_use_id set to the task tool's id - expect(subagentToolCalls.length).toBeGreaterThan(0); - - // Verify all subagent messages have the correct parent_tool_use_id - const subagentMessages = messages.filter( - (msg): msg is SDKMessage & { parent_tool_use_id: string } => - isSDKAssistantMessage(msg) && msg.parent_tool_use_id !== null, - ); - - expect(subagentMessages.length).toBeGreaterThan(0); - for (const subagentMsg of subagentMessages) { - expect(subagentMsg.parent_tool_use_id).toBe(taskToolUseId); - } - - // Verify no main agent tool calls (except task) have parent_tool_use_id - const mainAgentMessages = messages.filter( - (msg): msg is SDKMessage => - isSDKAssistantMessage(msg) && msg.parent_tool_use_id === null, - ); - - for (const mainMsg of mainAgentMessages) { - if (isSDKAssistantMessage(mainMsg)) { - // Main agent messages should not have parent_tool_use_id - expect(mainMsg.parent_tool_use_id).toBeNull(); - } - } - - // Validate successful completion - assertSuccessfulCompletion(messages); - } finally { - await q.close(); - } - }, 60000); }); describe('Subagent Error Handling', () => { diff --git a/packages/cli/src/nonInteractiveCli.ts b/packages/cli/src/nonInteractiveCli.ts index b595d61f1..ef96d8e63 100644 --- a/packages/cli/src/nonInteractiveCli.ts +++ b/packages/cli/src/nonInteractiveCli.ts @@ -330,7 +330,9 @@ export async function runNonInteractive( abortController.signal, taskToolProgressHandler || toolCallUpdateCallback ? { - ...(taskToolProgressHandler && { taskToolProgressHandler }), + ...(taskToolProgressHandler && { + outputUpdateHandler: taskToolProgressHandler, + }), ...(toolCallUpdateCallback && { onToolCallsUpdate: toolCallUpdateCallback, }), diff --git a/packages/sdk-typescript/scripts/bundle-cli-from-npm.js b/packages/sdk-typescript/scripts/bundle-cli-from-npm.js new file mode 100644 index 000000000..c59834bb6 --- /dev/null +++ b/packages/sdk-typescript/scripts/bundle-cli-from-npm.js @@ -0,0 +1,88 @@ +#!/usr/bin/env node + +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Bundles/copies the Qwen Code CLI from npm package into the SDK package dist/ + * so consumers don't need a separate CLI install. + * + * This script reads the CLI package path from CLI_PACKAGE_PATH environment variable + * and copies the necessary files into the SDK dist/cli/ directory. + */ + +import { cpSync, existsSync, mkdirSync, rmSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const sdkRoot = join(__dirname, '..'); + +function main() { + // Get CLI package path from environment variable + const cliPackagePath = process.env.CLI_PACKAGE_PATH; + if (!cliPackagePath) { + throw new Error( + '[sdk bundle] CLI_PACKAGE_PATH environment variable is required. ' + + 'Please set it to the path where the CLI npm package was extracted.', + ); + } + + const cliDistDir = cliPackagePath; + const sdkCliDistDir = join(sdkRoot, 'dist', 'cli'); + + // Verify CLI package exists + if (!existsSync(cliDistDir)) { + throw new Error( + `[sdk bundle] CLI package not found at: ${cliDistDir}. ` + + `Make sure the CLI package was downloaded and extracted correctly.`, + ); + } + + // Verify SDK dist exists + if (!existsSync(join(sdkRoot, 'dist'))) { + throw new Error( + '[sdk bundle] SDK dist/ not found. Run `npm run build` in packages/sdk-typescript first.', + ); + } + + // Clean and create SDK CLI directory + rmSync(sdkCliDistDir, { recursive: true, force: true }); + mkdirSync(sdkCliDistDir, { recursive: true }); + + console.log('[sdk bundle] Copying CLI from npm package...'); + console.log(`[sdk bundle] Source: ${cliDistDir} (package root)`); + console.log(`[sdk bundle] Destination: ${sdkCliDistDir}`); + + // Copy main CLI file + const cliJsSource = join(cliDistDir, 'cli.js'); + if (!existsSync(cliJsSource)) { + throw new Error( + `[sdk bundle] cli.js not found in CLI package at: ${cliJsSource}`, + ); + } + cpSync(cliJsSource, join(sdkCliDistDir, 'cli.js')); + console.log('[sdk bundle] ✓ cli.js copied'); + + // Copy vendor directory if exists + const vendorSource = join(cliDistDir, 'vendor'); + if (existsSync(vendorSource)) { + cpSync(vendorSource, join(sdkCliDistDir, 'vendor'), { recursive: true }); + console.log('[sdk bundle] ✓ vendor/ copied'); + } + + // Copy locales directory if exists + const localesSource = join(cliDistDir, 'locales'); + if (existsSync(localesSource)) { + cpSync(localesSource, join(sdkCliDistDir, 'locales'), { recursive: true }); + console.log('[sdk bundle] ✓ locales/ copied'); + } + + console.log('[sdk bundle] CLI bundled successfully from npm package'); +} + +main(); diff --git a/packages/sdk-typescript/scripts/get-release-version.js b/packages/sdk-typescript/scripts/get-release-version.js index c6b1f6655..844ac3009 100644 --- a/packages/sdk-typescript/scripts/get-release-version.js +++ b/packages/sdk-typescript/scripts/get-release-version.js @@ -227,9 +227,20 @@ function getLatestStableReleaseTag() { } } +function getNextPatchVersion() { + // Get latest stable version from npm and increment patch + const latestVersion = getVersionFromNPM('latest'); + if (!latestVersion) { + // Fallback to package.json if npm fails + const packageJson = readJson(join(__dirname, '..', 'package.json')); + return packageJson.version.split('-')[0]; + } + const [major, minor, patch] = latestVersion.split('.').map(Number); + return `${major}.${minor}.${patch + 1}`; +} + function getNightlyVersion() { - const packageJson = readJson(join(__dirname, '..', 'package.json')); - const baseVersion = packageJson.version.split('-')[0]; + const baseVersion = getNextPatchVersion(); const date = new Date().toISOString().slice(0, 10).replace(/-/g, ''); const gitShortHash = execSync('git rev-parse --short HEAD').toString().trim(); const releaseVersion = `${baseVersion}-nightly.${date}.${gitShortHash}`; @@ -281,10 +292,9 @@ function getPreviewVersion(args) { ); releaseVersion = overrideVersion; } else { - // Try to get from nightly, fallback to package.json for first release - const { latestVersion: latestNightlyVersion } = getAndVerifyTags('nightly'); - releaseVersion = - latestNightlyVersion.replace(/-nightly.*/, '') + '-preview.0'; + // Get next patch version from npm latest and add preview suffix + const baseVersion = getNextPatchVersion(); + releaseVersion = `${baseVersion}-preview.0`; } return { diff --git a/scripts/build_sandbox.js b/scripts/build_sandbox.js index e7a68155c..ac713ab1b 100644 --- a/scripts/build_sandbox.js +++ b/scripts/build_sandbox.js @@ -85,9 +85,25 @@ if (!image.length) { ); } +// Build in dependency order to ensure packages are built before their dependents +// This is the same order as defined in build.js +const buildOrder = [ + 'packages/test-utils', + 'packages/core', + 'packages/cli', + 'packages/webui', + 'packages/sdk-typescript', + 'packages/vscode-ide-companion', +]; + if (!argv.s) { execSync('npm install', { stdio: 'inherit' }); - execSync('npm run build --workspaces', { stdio: 'inherit' }); + // Build in dependency order instead of using --workspaces + for (const workspace of buildOrder) { + execSync(`npm run build --workspace=${workspace}`, { + stdio: 'inherit', + }); + } } console.log('packing @qwen-code/qwen-code ...');