Skip to content

Commit a093891

Browse files
committed
chore: add sdk and cli cicd
1 parent 108c14d commit a093891

24 files changed

Lines changed: 1189 additions & 16 deletions

File tree

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
# Automatic release orchestrator for CLI + SDK.
2+
#
3+
# Runs CLI release first, then SDK release sequentially to prevent race
4+
# conditions on tags/commits. Both jobs rely on semantic-release to determine
5+
# whether a release is needed. On a no-op (no releasable commits),
6+
# semantic-release exits early — the initial TS/Vite build still runs, but
7+
# the expensive native cross-compilation and publish steps are skipped
8+
# (they live inside semantic-release's exec prepare/publish lifecycle).
9+
#
10+
# push.paths is a workflow-level filter: if ANY path matches, ALL jobs run.
11+
# Per-job conditioning relies on semantic-release itself no-oping.
12+
name: "\U0001F680 Release CLI + SDK"
13+
14+
on:
15+
push:
16+
branches:
17+
- main
18+
- stable
19+
paths:
20+
- 'apps/cli/**'
21+
- 'packages/sdk/**'
22+
- 'packages/document-api/**'
23+
- 'packages/superdoc/**'
24+
- 'packages/super-editor/**'
25+
- 'packages/layout-engine/**'
26+
- 'packages/ai/**'
27+
- 'packages/word-layout/**'
28+
- 'packages/preset-geometry/**'
29+
- '!**/*.md'
30+
workflow_dispatch:
31+
32+
permissions:
33+
contents: write
34+
packages: write
35+
id-token: write # PyPI trusted publishing (OIDC)
36+
37+
concurrency:
38+
group: release-${{ github.ref }}
39+
cancel-in-progress: false
40+
41+
jobs:
42+
# ===================================================================
43+
# Job 1: CLI Release
44+
# ===================================================================
45+
release-cli:
46+
name: Release CLI
47+
runs-on: ubuntu-24.04
48+
steps:
49+
- name: Generate token
50+
id: generate_token
51+
uses: actions/create-github-app-token@v2
52+
with:
53+
app-id: ${{ secrets.APP_ID }}
54+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
55+
56+
- uses: actions/checkout@v6
57+
with:
58+
fetch-depth: 0
59+
token: ${{ steps.generate_token.outputs.token }}
60+
61+
- uses: pnpm/action-setup@v4
62+
63+
- uses: actions/setup-node@v6
64+
with:
65+
node-version-file: .nvmrc
66+
cache: pnpm
67+
registry-url: 'https://registry.npmjs.org'
68+
69+
- uses: oven-sh/setup-bun@v2
70+
71+
- name: Cache apt packages
72+
uses: actions/cache@v5
73+
with:
74+
path: ~/apt-cache
75+
key: apt-canvas-${{ runner.os }}-v1
76+
77+
- name: Install canvas system dependencies
78+
run: |
79+
mkdir -p ~/apt-cache
80+
sudo apt-get update
81+
sudo apt-get install -y -o Dir::Cache::Archives="$HOME/apt-cache" \
82+
build-essential \
83+
libcairo2-dev \
84+
libpango1.0-dev \
85+
libjpeg-dev \
86+
libgif-dev \
87+
librsvg2-dev \
88+
libpixman-1-dev
89+
90+
- name: Install dependencies
91+
run: pnpm install --frozen-lockfile
92+
93+
- name: Build packages
94+
run: pnpm run build
95+
96+
- name: Release CLI
97+
env:
98+
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
99+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
100+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
101+
LINEAR_TOKEN: ${{ secrets.LINEAR_TOKEN }}
102+
working-directory: apps/cli
103+
run: pnpx semantic-release
104+
105+
# ===================================================================
106+
# Job 2: SDK Release (runs after CLI, even if CLI was a no-op)
107+
# ===================================================================
108+
release-sdk:
109+
name: Release SDK
110+
needs: release-cli
111+
if: ${{ always() && !failure() }}
112+
runs-on: ubuntu-24.04
113+
environment: pypi
114+
steps:
115+
- name: Generate token
116+
id: generate_token
117+
uses: actions/create-github-app-token@v2
118+
with:
119+
app-id: ${{ secrets.APP_ID }}
120+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
121+
122+
- uses: actions/checkout@v6
123+
with:
124+
fetch-depth: 0
125+
token: ${{ steps.generate_token.outputs.token }}
126+
127+
# On stable, CLI job may have pushed a version-bump commit.
128+
# Pull latest to avoid building stale code.
129+
- name: Pull latest (stable branch)
130+
if: github.ref_name == 'stable'
131+
run: git pull origin stable
132+
133+
- uses: pnpm/action-setup@v4
134+
135+
- uses: actions/setup-node@v6
136+
with:
137+
node-version-file: .nvmrc
138+
cache: pnpm
139+
registry-url: 'https://registry.npmjs.org'
140+
141+
- uses: oven-sh/setup-bun@v2
142+
143+
- uses: actions/setup-python@v5
144+
with:
145+
python-version: '3.12'
146+
147+
- name: Cache apt packages
148+
uses: actions/cache@v5
149+
with:
150+
path: ~/apt-cache
151+
key: apt-canvas-${{ runner.os }}-v1
152+
153+
- name: Install canvas system dependencies
154+
run: |
155+
mkdir -p ~/apt-cache
156+
sudo apt-get update
157+
sudo apt-get install -y -o Dir::Cache::Archives="$HOME/apt-cache" \
158+
build-essential \
159+
libcairo2-dev \
160+
libpango1.0-dev \
161+
libjpeg-dev \
162+
libgif-dev \
163+
librsvg2-dev \
164+
libpixman-1-dev
165+
166+
- name: Install dependencies
167+
run: pnpm install --frozen-lockfile
168+
169+
- name: Install Python build tools
170+
run: pip install build
171+
172+
- name: Build packages
173+
run: pnpm run build
174+
175+
# ---------------------------------------------------------------
176+
# Semantic-release determines version + runs npm publish via exec
177+
# ---------------------------------------------------------------
178+
- name: Release SDK (semantic-release)
179+
id: sdk_release
180+
env:
181+
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
182+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
183+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
184+
LINEAR_TOKEN: ${{ secrets.LINEAR_TOKEN }}
185+
working-directory: packages/sdk
186+
run: |
187+
# Snapshot existing sdk-v* tags before semantic-release runs
188+
TAGS_BEFORE=$(git tag -l 'sdk-v*' | sort)
189+
pnpx semantic-release
190+
# Compare tags after — a new tag means a release happened
191+
TAGS_AFTER=$(git tag -l 'sdk-v*' | sort)
192+
if [ "$TAGS_BEFORE" != "$TAGS_AFTER" ]; then
193+
echo "released=true" >> "$GITHUB_OUTPUT"
194+
else
195+
echo "released=false" >> "$GITHUB_OUTPUT"
196+
fi
197+
198+
# ---------------------------------------------------------------
199+
# Python SDK publish (only if semantic-release created a release)
200+
# PyPI uses OIDC trusted publishing via gh-action-pypi-publish.
201+
# ---------------------------------------------------------------
202+
- name: Prepare Python SDK tools
203+
if: steps.sdk_release.outputs.released == 'true'
204+
run: |
205+
rm -rf packages/sdk/langs/python/superdoc/tools
206+
cp -r packages/sdk/tools packages/sdk/langs/python/superdoc/tools
207+
rm -f packages/sdk/langs/python/superdoc/tools/__init__.py
208+
209+
- name: Build companion Python wheels
210+
if: steps.sdk_release.outputs.released == 'true'
211+
run: node packages/sdk/scripts/build-python-companion-wheels.mjs
212+
213+
- name: Verify companion wheels
214+
if: steps.sdk_release.outputs.released == 'true'
215+
run: node packages/sdk/scripts/verify-python-companion-wheels.mjs --companions-only
216+
217+
- name: Build main Python SDK wheel
218+
if: steps.sdk_release.outputs.released == 'true'
219+
run: python -m build
220+
working-directory: packages/sdk/langs/python
221+
222+
- name: Verify main wheel
223+
if: steps.sdk_release.outputs.released == 'true'
224+
run: node packages/sdk/scripts/verify-python-companion-wheels.mjs --root-only
225+
226+
- name: Smoke test (wheelhouse install + marker resolution)
227+
if: steps.sdk_release.outputs.released == 'true'
228+
run: |
229+
python3 -m venv /tmp/sdk-smoke
230+
mkdir -p /tmp/sdk-wheelhouse
231+
cp packages/sdk/langs/python/dist/*.whl /tmp/sdk-wheelhouse/
232+
cp packages/sdk/langs/python/companion-dist/*.whl /tmp/sdk-wheelhouse/
233+
/tmp/sdk-smoke/bin/pip install superdoc-sdk --find-links /tmp/sdk-wheelhouse --no-index
234+
/tmp/sdk-smoke/bin/python -c "from superdoc import SuperDocClient; SuperDocClient()"
235+
/tmp/sdk-smoke/bin/python -c "from superdoc.embedded_cli import resolve_embedded_cli_path; import subprocess; p = resolve_embedded_cli_path(); r = subprocess.run([p, '--help'], capture_output=True, text=True, timeout=10); assert r.returncode == 0, f'CLI exited {r.returncode}: {r.stderr}'; print(f'CLI binary OK: {p}')"
236+
echo "Smoke test passed."
237+
rm -rf /tmp/sdk-smoke /tmp/sdk-wheelhouse
238+
239+
- name: Publish companion Python packages to PyPI
240+
if: steps.sdk_release.outputs.released == 'true'
241+
uses: pypa/gh-action-pypi-publish@release/v1
242+
with:
243+
packages-dir: packages/sdk/langs/python/companion-dist/
244+
skip-existing: true
245+
246+
- name: Publish main Python SDK to PyPI
247+
if: steps.sdk_release.outputs.released == 'true'
248+
uses: pypa/gh-action-pypi-publish@release/v1
249+
with:
250+
packages-dir: packages/sdk/langs/python/dist/
251+
skip-existing: true

.github/workflows/release-sdk.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
name: "\U0001F4E6 Release SDK"
1+
# MANUAL FALLBACK — normal SDK releases are handled by release-cli-sdk.yml.
2+
# Use this workflow only for emergency manual releases or when the orchestrated
3+
# workflow has issues. See release-cli-sdk.yml for the automated flow.
4+
name: "\U0001F4E6 Release SDK (manual fallback)"
25

36
on:
47
workflow_dispatch:

apps/cli/.releaserc.cjs

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,22 @@
11
/* eslint-env node */
2+
3+
/*
4+
* Commit filter: CLI bundles multiple sub-packages, so git log must include
5+
* commits touching any of them. This shared helper patches git-log-parser to
6+
* expand path coverage. It REPLACES semantic-release-commit-filter — do not
7+
* use both (the filter restricts to CWD, which undoes the expansion).
8+
*/
9+
require('../../scripts/semantic-release/patch-commit-filter.cjs')([
10+
'apps/cli',
11+
'packages/document-api',
12+
'packages/superdoc',
13+
'packages/super-editor',
14+
'packages/layout-engine',
15+
'packages/ai',
16+
'packages/word-layout',
17+
'packages/preset-geometry',
18+
]);
19+
220
const branch = process.env.GITHUB_REF_NAME || process.env.CI_COMMIT_BRANCH;
321

422
const config = {
@@ -8,38 +26,57 @@ const config = {
826
],
927
tagFormat: 'cli-v${version}',
1028
plugins: [
11-
'semantic-release-commit-filter',
1229
'@semantic-release/commit-analyzer',
1330
'@semantic-release/release-notes-generator',
1431
['@semantic-release/npm', { npmPublish: false }],
32+
[
33+
'@semantic-release/exec',
34+
{
35+
prepareCmd: 'pnpm run build:prepublish',
36+
publishCmd: 'node scripts/publish.js --tag ${nextRelease.channel || "latest"}',
37+
},
38+
],
1539
],
1640
};
1741

18-
const isPrerelease = config.branches.some((b) => typeof b === 'object' && b.name === branch && b.prerelease);
42+
const isPrerelease = config.branches.some(
43+
(b) => typeof b === 'object' && b.name === branch && b.prerelease,
44+
);
1945

2046
if (!isPrerelease) {
2147
config.plugins.push([
2248
'@semantic-release/git',
2349
{
24-
assets: ['package.json'],
50+
assets: [
51+
'package.json',
52+
'platforms/cli-darwin-arm64/package.json',
53+
'platforms/cli-darwin-x64/package.json',
54+
'platforms/cli-linux-x64/package.json',
55+
'platforms/cli-linux-arm64/package.json',
56+
'platforms/cli-windows-x64/package.json',
57+
],
2558
message: 'chore(cli): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}',
2659
},
2760
]);
2861
}
2962

3063
// Linear integration - labels issues with version on release
31-
config.plugins.push(['semantic-release-linear-app', {
32-
teamKeys: ['SD'],
33-
addComment: true,
34-
packageName: 'superdoc-cli',
35-
commentTemplate: 'shipped in {package} {releaseLink} {channel}'
36-
}]);
64+
config.plugins.push([
65+
'semantic-release-linear-app',
66+
{
67+
teamKeys: ['SD'],
68+
addComment: true,
69+
packageName: 'superdoc-cli',
70+
commentTemplate: 'shipped in {package} {releaseLink} {channel}',
71+
},
72+
]);
3773

3874
config.plugins.push([
3975
'@semantic-release/github',
4076
{
41-
successComment: ':tada: This ${issue.pull_request ? "PR" : "issue"} is included in **superdoc-cli** v${nextRelease.version}\n\nThe release is available on [GitHub release](${releases.find(release => release.pluginName === "@semantic-release/github").url})',
42-
}
77+
successComment:
78+
':tada: This ${issue.pull_request ? "PR" : "issue"} is included in **superdoc-cli** v${nextRelease.version}\n\nThe release is available on [GitHub release](${releases.find(release => release.pluginName === "@semantic-release/github").url})',
79+
},
4380
]);
4481

4582
module.exports = config;

apps/cli/src/cli/operation-params.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ const EXPECTED_REVISION_PARAM: CliOperationParamSpec = {
6767
// ---------------------------------------------------------------------------
6868

6969
type JsonSchema = Record<string, unknown>;
70+
const AGENT_HIDDEN_PARAM_NAMES = new Set(['out', 'expectedRevision', 'changeMode', 'dryRun']);
7071

7172
function schemaToParamType(schema: JsonSchema): CliOperationParamSpec['type'] {
7273
if (schema.type === 'string') return 'string';
@@ -154,6 +155,10 @@ function deriveParamsFromInputSchema(inputSchema: JsonSchema): {
154155
required: required.has(name),
155156
};
156157

158+
if (AGENT_HIDDEN_PARAM_NAMES.has(name)) {
159+
param.agentVisible = false;
160+
}
161+
157162
if (isComplex || (!isSimpleType(propSchema) && paramType !== 'json')) {
158163
param.schema = jsonSchemaToTypeSpec(propSchema);
159164
}

apps/docs/document-api/reference/_generated-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,5 +141,5 @@
141141
}
142142
],
143143
"marker": "{/* GENERATED FILE: DO NOT EDIT. Regenerate via `pnpm run docapi:sync`. */}",
144-
"sourceHash": "def6d3b7f9e73d872b213cde06be8293f5f607501e777f9cb5f76c6d8e3f252a"
144+
"sourceHash": "79c55014e0db6c85c573b0fc674ff61adfc6d6ff9682148b3f83794cb188f2d7"
145145
}

0 commit comments

Comments
 (0)