diff --git a/.github/workflows/release-sdk.yml b/.github/workflows/release-sdk.yml index 5ff38ec9a3..112a9cd837 100644 --- a/.github/workflows/release-sdk.yml +++ b/.github/workflows/release-sdk.yml @@ -1,12 +1,29 @@ -# Manual SDK release workflow. -# Use this workflow to run SDK releases directly from GitHub Actions. -name: "\U0001F4E6 Release SDK (manual fallback)" +# Auto-releases SDK on push to main (@next channel). +# Stable releases are local-only (pnpm run release:local). +# Also supports manual dispatch as a fallback for one-off releases. +name: "\U0001F4E6 Release SDK" on: + push: + branches: + - main + paths: + # Keep in sync with packages/sdk/.releaserc.cjs includePaths (patch-commit-filter). + - 'packages/sdk/**' + - 'apps/cli/**' + - 'packages/document-api/**' + - 'packages/superdoc/**' + - 'packages/super-editor/**' + - 'packages/layout-engine/**' + - 'packages/ai/**' + - 'packages/word-layout/**' + - 'packages/preset-geometry/**' + - 'scripts/semantic-release/**' + - '!**/*.md' workflow_dispatch: inputs: version: - description: "Version to release (e.g. 1.0.0-alpha.7). Leave empty to release the current version." + description: "Version to release (e.g. 1.0.0-next.7). Leave empty to publish the current repo version." required: false type: string dry-run: @@ -15,21 +32,151 @@ on: type: boolean default: false npm-tag: - description: "npm dist-tag for Node SDK publish (e.g. latest, next)" + description: "npm dist-tag for Node SDK publish (e.g. latest, next). Only used for manual version override." required: false type: string default: latest permissions: - contents: read + contents: write + packages: write id-token: write # PyPI trusted publishing (OIDC) concurrency: - group: release-sdk + group: release-sdk-${{ github.ref }} cancel-in-progress: false jobs: - release: + # ------------------------------------------------------------------- + # Auto-release via semantic-release (push trigger) + # ------------------------------------------------------------------- + auto-release: + if: github.event_name == 'push' + runs-on: ubuntu-24.04 + environment: pypi + outputs: + released: ${{ steps.detect.outputs.released }} + version: ${{ steps.detect.outputs.version }} + steps: + - name: Generate token + id: generate_token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + token: ${{ steps.generate_token.outputs.token }} + + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v6 + with: + node-version-file: .nvmrc + cache: pnpm + registry-url: "https://registry.npmjs.org" + + - uses: oven-sh/setup-bun@v2 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install canvas system dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + build-essential libcairo2-dev libpango1.0-dev \ + libjpeg-dev libgif-dev librsvg2-dev libpixman-1-dev + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Install Python build tools + run: pip install build + + - name: Build packages + run: pnpm run build + + - name: Snapshot tags before release + id: tags_before + run: echo "tags=$(git tag --list 'sdk-v*' | sort | tr '\n' ',')" >> "$GITHUB_OUTPUT" + + - name: Run semantic-release + env: + GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + LINEAR_TOKEN: ${{ secrets.LINEAR_TOKEN }} + working-directory: packages/sdk + run: pnpx semantic-release + + - name: Detect whether a release was created + id: detect + run: | + BEFORE="${{ steps.tags_before.outputs.tags }}" + AFTER=$(git tag --list 'sdk-v*' | sort | tr '\n' ',') + RELEASE_TAG=$(git tag --points-at HEAD --list 'sdk-v*' --sort=-version:refname | head -n 1) + if [ -z "$RELEASE_TAG" ]; then + echo "release_present=false" >> "$GITHUB_OUTPUT" + echo "released=false" >> "$GITHUB_OUTPUT" + echo "version=" >> "$GITHUB_OUTPUT" + echo "dist_tag=" >> "$GITHUB_OUTPUT" + echo "No SDK release tag at HEAD." + else + echo "release_present=true" >> "$GITHUB_OUTPUT" + if [ "$BEFORE" = "$AFTER" ]; then + echo "released=false" >> "$GITHUB_OUTPUT" + else + echo "released=true" >> "$GITHUB_OUTPUT" + fi + VERSION="${RELEASE_TAG#sdk-v}" + if [[ "$VERSION" == *-next.* ]]; then + DIST_TAG="next" + else + DIST_TAG="latest" + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "dist_tag=$DIST_TAG" >> "$GITHUB_OUTPUT" + if [ "$BEFORE" = "$AFTER" ]; then + echo "SDK release tag already present at HEAD: $RELEASE_TAG" + else + echo "Released SDK v$VERSION" + fi + fi + + - name: Resume Node SDK publish for existing release tag + if: steps.detect.outputs.release_present == 'true' && steps.detect.outputs.released != 'true' + run: node packages/sdk/scripts/sdk-release-publish.mjs --tag "${{ steps.detect.outputs.dist_tag }}" --npm-only + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + # Build + publish Python whenever this commit corresponds to an SDK release + - name: Build and verify Python SDK + if: steps.detect.outputs.release_present == 'true' + run: node packages/sdk/scripts/build-python-sdk.mjs + + - name: Publish companion Python packages to PyPI + if: steps.detect.outputs.release_present == 'true' + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: packages/sdk/langs/python/companion-dist/ + skip-existing: true + + - name: Publish main Python SDK to PyPI + if: steps.detect.outputs.release_present == 'true' + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: packages/sdk/langs/python/dist/ + skip-existing: true + + # ------------------------------------------------------------------- + # Manual fallback (workflow_dispatch) + # ------------------------------------------------------------------- + manual-release: + if: github.event_name == 'workflow_dispatch' runs-on: ubuntu-24.04 environment: ${{ inputs.dry-run && '' || 'pypi' }} steps: @@ -55,13 +202,10 @@ jobs: - name: Install Python build tools run: pip install build - # --------------------------------------------------------------- - # Show current version (visible in the Actions run summary) - # --------------------------------------------------------------- - name: Show current version run: | CURRENT=$(node -p "require('./packages/sdk/package.json').version") - echo "### SDK Release" >> $GITHUB_STEP_SUMMARY + echo "### SDK Release (manual)" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| | Version |" >> $GITHUB_STEP_SUMMARY echo "|---|---|" >> $GITHUB_STEP_SUMMARY @@ -74,22 +218,13 @@ jobs: echo "| **Dry run** | \`${{ inputs.dry-run }}\` |" >> $GITHUB_STEP_SUMMARY echo "| **npm tag** | \`${{ inputs.npm-tag }}\` |" >> $GITHUB_STEP_SUMMARY - # --------------------------------------------------------------- - # Set version (if provided) - # --------------------------------------------------------------- - name: Set version if: inputs.version != '' run: node packages/sdk/scripts/sync-sdk-version.mjs --set "${{ inputs.version }}" - # --------------------------------------------------------------- - # Generate + validate - # --------------------------------------------------------------- - name: Generate all artifacts run: pnpm run generate:all - # --------------------------------------------------------------- - # Node SDK - # --------------------------------------------------------------- - name: Build Node SDK run: pnpm --prefix packages/sdk/langs/node run build @@ -114,13 +249,9 @@ jobs: if: ${{ inputs.dry-run }} run: node packages/sdk/scripts/sdk-release-publish.mjs --tag "${{ inputs.npm-tag }}" --npm-only --dry-run - # --------------------------------------------------------------- - # Python SDK (main + 5 companion packages) - # --------------------------------------------------------------- - name: Build and verify Python SDK run: node packages/sdk/scripts/build-python-sdk.mjs - # Publish companions first — root depends on them being on PyPI. - name: Publish companion Python packages to PyPI if: ${{ !inputs.dry-run }} uses: pypa/gh-action-pypi-publish@release/v1 @@ -128,7 +259,6 @@ jobs: packages-dir: packages/sdk/langs/python/companion-dist/ skip-existing: true - # Publish root last. - name: Publish main Python SDK to PyPI if: ${{ !inputs.dry-run }} uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/cicd.md b/cicd.md index 696920fed7..0171f724f9 100644 --- a/cicd.md +++ b/cicd.md @@ -177,13 +177,16 @@ The workflow is `.github/workflows/release-cli.yml`. It analyzes commits across | Command | What it does | |---------|-------------| -| `pnpm run release:local` | Releases **superdoc then CLI** in sequence on `stable` | +| `pnpm run release:local` | Releases **superdoc → CLI → SDK** in sequence on `stable` | | `pnpm run release:local:superdoc` | Releases superdoc only | | `pnpm run release:local:cli` | Releases CLI only | +| `pnpm run release:local:sdk` | Releases SDK only | All accept `-- --dry-run` to preview without publishing. The combined orchestrator (`release:local`) enforces a `stable` branch guard (override with `--branch=`). -`@semantic-release/git` automatically pushes version commits and tags when releasing on the `stable` branch. This is existing behavior for both superdoc and CLI. +`@semantic-release/git` automatically pushes version commits and tags when releasing on the `stable` branch. This is existing behavior for superdoc, CLI, and SDK. + +SDK stable releases publish both npm (via `sdk-release-publish.mjs`) and PyPI (via `twine`). Prerequisites: `pip install twine` and `PYPI_PUBLISH_TOKEN` in your shell env. ### Raw Platform Publish (bypass semantic-release) diff --git a/package.json b/package.json index c91988ee93..5dfb37f40a 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "release:local": "pnpm run build:superdoc && pnpm run type-check && node scripts/release-local-stable.mjs", "release:local:superdoc": "pnpm run build:superdoc && pnpm run type-check && node scripts/release-local-superdoc.mjs", "release:local:cli": "pnpm run build:superdoc && pnpm run type-check && node scripts/release-local-cli.mjs", + "release:local:sdk": "pnpm run build:superdoc && pnpm run type-check && node scripts/release-local-sdk.mjs", "prepare": "if [ -z \"$CI\" ]; then lefthook install; fi", "test:layout": "bun scripts/test-layout.mjs", "test:visual": "bun scripts/test-visual.mjs", diff --git a/packages/sdk/.releaserc.cjs b/packages/sdk/.releaserc.cjs index ad610e1a30..420dc14ad4 100644 --- a/packages/sdk/.releaserc.cjs +++ b/packages/sdk/.releaserc.cjs @@ -22,11 +22,8 @@ const branch = process.env.GITHUB_REF_NAME || process.env.CI_COMMIT_BRANCH; const config = { branches: [ - // Semantic-release requires at least one non-prerelease release branch. { name: 'stable', channel: 'latest' }, - // SDK auto-release runs from main and should publish alpha prereleases - // on the latest dist-tag (no next channel). - { name: 'main', prerelease: 'alpha', channel: 'latest' }, + { name: 'main', prerelease: 'next', channel: 'next' }, ], tagFormat: 'sdk-v${version}', plugins: [ @@ -46,9 +43,8 @@ const config = { 'pnpm --prefix langs/node run build', 'node scripts/sdk-validate.mjs', ].join(' && '), - // Publish: build artifacts + publish npm packages (PyPI handled by workflow) - publishCmd: - 'node scripts/sdk-release-publish.mjs --tag ${nextRelease.channel || "latest"} --npm-only', + // publishCmd is set dynamically below based on branch (prerelease vs stable) + publishCmd: null, }, ], ], @@ -58,6 +54,19 @@ const isPrerelease = config.branches.some( (b) => typeof b === 'object' && b.name === branch && b.prerelease, ); +// On prerelease (main), PyPI is handled by GHA OIDC — keep --npm-only. +// On stable (local release), sdk-release-publish.mjs uploads to PyPI via twine. +const execPlugin = config.plugins.find( + (p) => Array.isArray(p) && p[0] === '@semantic-release/exec', +); +if (isPrerelease) { + execPlugin[1].publishCmd = + 'node scripts/sdk-release-publish.mjs --tag ${nextRelease.channel || "latest"} --npm-only'; +} else { + execPlugin[1].publishCmd = + 'node scripts/sdk-release-publish.mjs --tag ${nextRelease.channel || "latest"}'; +} + if (!isPrerelease) { config.plugins.push([ '@semantic-release/git', diff --git a/packages/sdk/scripts/__tests__/release-order.test.mjs b/packages/sdk/scripts/__tests__/release-order.test.mjs index 91022c95d5..22691d93a6 100644 --- a/packages/sdk/scripts/__tests__/release-order.test.mjs +++ b/packages/sdk/scripts/__tests__/release-order.test.mjs @@ -61,6 +61,39 @@ test('release-sdk fallback workflow publishes Node SDK via sdk-release-publish', ); }); +test('release-sdk manual version input description matches manual fallback behavior', async () => { + const content = await readRepoFile('.github/workflows/release-sdk.yml'); + assert.ok( + content.includes('Leave empty to publish the current repo version.'), + '.github/workflows/release-sdk.yml: manual version input must describe current-version publish behavior', + ); + assert.equal( + content.includes('Leave empty to let semantic-release decide.'), + false, + '.github/workflows/release-sdk.yml: manual fallback must not claim semantic-release chooses the version', + ); +}); + +test('release-sdk auto workflow resumes releases from sdk-v tags at HEAD', async () => { + const content = await readRepoFile('.github/workflows/release-sdk.yml'); + assert.ok( + content.includes("git tag --points-at HEAD --list 'sdk-v*' --sort=-version:refname | head -n 1"), + '.github/workflows/release-sdk.yml: auto-release must detect sdk release tags at HEAD', + ); + assert.ok( + content.includes("if: steps.detect.outputs.release_present == 'true'"), + '.github/workflows/release-sdk.yml: Python publish must key off release tag presence, not per-run tag creation', + ); + assert.ok( + content.includes('Resume Node SDK publish for existing release tag'), + '.github/workflows/release-sdk.yml: auto-release must have an npm publish recovery step for reruns', + ); + assert.ok( + content.includes('node packages/sdk/scripts/sdk-release-publish.mjs --tag "${{ steps.detect.outputs.dist_tag }}" --npm-only'), + '.github/workflows/release-sdk.yml: npm publish recovery must reuse sdk-release-publish.mjs', + ); +}); + test('sdk semantic-release prepareCmd builds Node SDK before validate', async () => { const content = await readRepoFile('packages/sdk/.releaserc.cjs'); assertOrder( @@ -77,19 +110,24 @@ test('sdk semantic-release prepareCmd builds Node SDK before validate', async () ); }); -test('sdk semantic-release main branch uses alpha prerelease on latest channel', async () => { +test('sdk semantic-release matches CLI channel model (next/next on main, latest on stable)', async () => { const content = await readRepoFile('packages/sdk/.releaserc.cjs'); assert.ok( content.includes("{ name: 'stable', channel: 'latest' }"), "packages/sdk/.releaserc.cjs: stable release branch must remain configured", ); assert.ok( - content.includes("{ name: 'main', prerelease: 'alpha', channel: 'latest' }"), - "packages/sdk/.releaserc.cjs: main branch must release alpha versions on latest", + content.includes("{ name: 'main', prerelease: 'next', channel: 'next' }"), + "packages/sdk/.releaserc.cjs: main branch must release next versions on next channel", ); - assert.equal( - content.includes("prerelease: 'next'"), - false, - "packages/sdk/.releaserc.cjs: SDK release config must not use 'next' prerelease channel", +}); + +test('sdk-release-publish validates local PyPI prerequisites before Node publish', async () => { + const content = await readRepoFile('packages/sdk/scripts/sdk-release-publish.mjs'); + assertOrder( + content, + 'const localPypiPublishConfig = resolveLocalPypiPublishConfig({ npmOnly, dryRun });', + "const nodePublishArgs = [path.join(__dirname, 'publish-node-sdk.mjs'), '--tag', tag];", + 'packages/sdk/scripts/sdk-release-publish.mjs', ); }); diff --git a/packages/sdk/scripts/sdk-release-publish.mjs b/packages/sdk/scripts/sdk-release-publish.mjs index ed001e6036..25e4d1485d 100644 --- a/packages/sdk/scripts/sdk-release-publish.mjs +++ b/packages/sdk/scripts/sdk-release-publish.mjs @@ -19,6 +19,7 @@ */ import { execFileSync } from 'node:child_process'; +import { readdirSync } from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -26,12 +27,12 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const REPO_ROOT = path.resolve(__dirname, '../../../'); -function run(command, args, { cwd = REPO_ROOT, label } = {}) { +function run(command, args, { cwd = REPO_ROOT, label, env = process.env } = {}) { console.log(`\n${'='.repeat(60)}`); console.log(` ${label || `${command} ${args.join(' ')}`}`); console.log(`${'='.repeat(60)}\n`); - execFileSync(command, args, { cwd, stdio: 'inherit', env: process.env }); + execFileSync(command, args, { cwd, stdio: 'inherit', env }); } function parseArgs(argv) { @@ -44,6 +45,35 @@ function parseArgs(argv) { return { tag, npmOnly, dryRun }; } +function resolveLocalPypiPublishConfig({ npmOnly, dryRun }) { + if (npmOnly || dryRun) return null; + + try { + execFileSync('python3', ['-m', 'twine', '--version'], { stdio: 'pipe' }); + } catch { + throw new Error( + 'twine is not installed. Install it with:\n pip install twine', + ); + } + + const pypiToken = process.env.PYPI_PUBLISH_TOKEN; + if (!pypiToken) { + throw new Error( + 'PYPI_PUBLISH_TOKEN env var is required for local PyPI publishing.\n' + + 'Set it in your shell profile or pass it inline:\n' + + ' PYPI_PUBLISH_TOKEN=pypi-... node sdk-release-publish.mjs --tag latest', + ); + } + + return { + twineEnv: { + ...process.env, + TWINE_USERNAME: '__token__', + TWINE_PASSWORD: pypiToken, + }, + }; +} + function main() { const { tag, npmOnly, dryRun } = parseArgs(process.argv.slice(2)); const dryRunSuffix = dryRun ? ' [dry-run]' : ''; @@ -52,36 +82,41 @@ function main() { console.log(` tag: ${tag}`); console.log(` npm-only: ${npmOnly}`); + const totalSteps = npmOnly ? 6 : 8; + // Stable local releases must fail before any npm publish if PyPI upload + // cannot run; otherwise the release can become partially published. + const localPypiPublishConfig = resolveLocalPypiPublishConfig({ npmOnly, dryRun }); + // 1. Build superdoc (required for CLI native bundling) run('pnpm', ['--prefix', path.join(REPO_ROOT, 'packages/superdoc'), 'run', 'build:es'], { - label: 'Step 1/7: Build superdoc package', + label: `Step 1/${totalSteps}: Build superdoc package`, }); // 2. Build CLI native artifacts for all platforms run('pnpm', ['--prefix', path.join(REPO_ROOT, 'apps/cli'), 'run', 'build:native:all'], { - label: 'Step 2/7: Build CLI native binaries (all platforms)', + label: `Step 2/${totalSteps}: Build CLI native binaries (all platforms)`, }); // 3. Stage CLI artifacts into CLI platform packages run('pnpm', ['--prefix', path.join(REPO_ROOT, 'apps/cli'), 'run', 'build:stage'], { - label: 'Step 3/7: Stage CLI artifacts', + label: `Step 3/${totalSteps}: Stage CLI artifacts`, }); // 4. Stage binaries into Node SDK platform packages run('node', [path.join(__dirname, 'stage-node-sdk-platform-cli.mjs')], { - label: 'Step 4/7: Stage Node SDK platform binaries', + label: `Step 4/${totalSteps}: Stage Node SDK platform binaries`, }); // 5. Stage binaries into Python companion packages run('node', [path.join(__dirname, 'stage-python-companion-cli.mjs')], { - label: 'Step 5/7: Stage Python companion binaries', + label: `Step 5/${totalSteps}: Stage Python companion binaries`, }); // 6. Publish Node SDK (platforms first, then root) const nodePublishArgs = [path.join(__dirname, 'publish-node-sdk.mjs'), '--tag', tag]; if (dryRun) nodePublishArgs.push('--dry-run'); run('node', nodePublishArgs, { - label: `Step 6/7: Publish Node SDK packages (tag: ${tag})${dryRunSuffix}`, + label: `Step 6/${totalSteps}: Publish Node SDK packages (tag: ${tag})${dryRunSuffix}`, }); // 7. Python publish (unless --npm-only, which defers to workflow-level PyPI action) @@ -89,11 +124,35 @@ function main() { console.log('\n Skipping Python build (--npm-only). Python build + PyPI publish handled by workflow.\n'); } else { run('node', [path.join(__dirname, 'build-python-sdk.mjs')], { - label: 'Step 7/7: Build and verify Python SDK', + label: `Step 7/${totalSteps}: Build and verify Python SDK`, }); if (!dryRun) { - console.log('\n Note: PyPI publish requires OIDC token. Use pypa/gh-action-pypi-publish in workflow.\n'); + const companionDist = path.join(REPO_ROOT, 'packages/sdk/langs/python/companion-dist'); + const rootDist = path.join(REPO_ROOT, 'packages/sdk/langs/python/dist'); + + const companionFiles = readdirSync(companionDist) + .filter((f) => f.endsWith('.whl') || f.endsWith('.tar.gz')) + .map((f) => path.join(companionDist, f)); + + const rootFiles = readdirSync(rootDist) + .filter((f) => f.endsWith('.whl') || f.endsWith('.tar.gz')) + .map((f) => path.join(rootDist, f)); + + if (companionFiles.length === 0) throw new Error('No companion wheels found in companion-dist/'); + if (rootFiles.length === 0) throw new Error('No root wheel found in dist/'); + + run('python3', ['-m', 'twine', 'upload', '--skip-existing', ...companionFiles], { + label: `Step 8/${totalSteps}: Publish companion Python packages to PyPI`, + env: localPypiPublishConfig.twineEnv, + }); + + run('python3', ['-m', 'twine', 'upload', '--skip-existing', ...rootFiles], { + label: `Step 8/${totalSteps}: Publish main Python SDK to PyPI`, + env: localPypiPublishConfig.twineEnv, + }); + } else { + console.log('\n Dry run — skipping PyPI upload.\n'); } } diff --git a/scripts/__tests__/release-local.test.mjs b/scripts/__tests__/release-local.test.mjs index c847cc01cc..70df5517de 100644 --- a/scripts/__tests__/release-local.test.mjs +++ b/scripts/__tests__/release-local.test.mjs @@ -57,3 +57,19 @@ test('stable orchestrator prunes before snapshot and reports would-release previ 'scripts/release-local-stable.mjs: dry-run previews must be reported as would-release', ); }); + +test('stable orchestrator releases superdoc, cli, then sdk in order', async () => { + const content = await readRepoFile('scripts/release-local-stable.mjs'); + assertOrder( + content, + "name: 'superdoc'", + "name: 'cli'", + 'scripts/release-local-stable.mjs (superdoc before cli)', + ); + assertOrder( + content, + "name: 'cli'", + "name: 'sdk'", + 'scripts/release-local-stable.mjs (cli before sdk)', + ); +}); diff --git a/scripts/release-local-sdk.mjs b/scripts/release-local-sdk.mjs new file mode 100644 index 0000000000..4fcb1ee709 --- /dev/null +++ b/scripts/release-local-sdk.mjs @@ -0,0 +1,16 @@ +#!/usr/bin/env node + +/** + * Thin wrapper — releases the SDK package locally via semantic-release. + * See release-local.mjs for the reusable runner logic. + */ + +import { releasePackage } from './release-local.mjs'; + +try { + releasePackage({ packageCwd: 'packages/sdk', extraArgs: process.argv.slice(2) }); +} catch (error) { + const message = error && typeof error.message === 'string' ? error.message : String(error); + console.error(message); + process.exitCode = 1; +} diff --git a/scripts/release-local-stable.mjs b/scripts/release-local-stable.mjs index 291393851c..f5471f2b54 100644 --- a/scripts/release-local-stable.mjs +++ b/scripts/release-local-stable.mjs @@ -1,7 +1,7 @@ #!/usr/bin/env node /** - * Combined stable orchestrator — releases superdoc then CLI in sequence. + * Combined stable orchestrator — releases superdoc, CLI, then SDK in sequence. * * Usage: * pnpm run release:local [-- --dry-run] @@ -62,6 +62,7 @@ const isDryRun = forwardedArgs.includes('--dry-run') || forwardedArgs.includes(' const packages = [ { name: 'superdoc', packageCwd: 'packages/superdoc', tagPrefix: 'v' }, { name: 'cli', packageCwd: 'apps/cli', tagPrefix: 'cli-v' }, + { name: 'sdk', packageCwd: 'packages/sdk', tagPrefix: 'sdk-v' }, ]; /**