Skip to content

Commit b4f7b1d

Browse files
authored
Add bidirectional API types to frontend (Stirling-Tools#6867)
# Description of Changes Fix Stirling-Tools/Stirling-PDF-SaaS#281. Add generated backend API mappings to the frontend code, and the logic to convert from a backend API to frontend parameters objects. Previously, it was impossible to tell if changing the backend API would require a change to the frontend to support it because the frontend had no static type information about the backend API. This PR adds autogenerated tool API types to the frontend (in `toolApiTypes.ts`) and adds explicit typed mappings between the frontend parameter types and the backend API types, so theoretically the type checker should be able to catch issues when changing one puts us in an invalid state with the other. During development, it pointed out several inconsistencies that we have between the frontend and backend types, some of which were genuine bugs, and others were only happening to work because the backend is more permissive than its API claims to be. This also unlocks the ability for us to render the frontend settings on saved backend API structures, which we've previously had to avoid doing because we had no reverse mapping.
1 parent f881828 commit b4f7b1d

73 files changed

Lines changed: 5165 additions & 744 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/config/.files.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,21 @@ engine: &engine
8787
- Taskfile.yml
8888
- .taskfiles/engine.yml
8989

90+
# Files that can make the committed generated API models (frontend tool API
91+
# types + engine tool models) go stale: the Java tool surfaces they derive from,
92+
# the generators, the generated files themselves (to catch a hand-edit), and the
93+
# tasks that drive generation. Deliberately excludes the broad frontend/docker/
94+
# testing globs, so a CSS-only PR does not boot the backend to rebuild the spec.
95+
generated-models: &generated-models
96+
- *openapi
97+
- frontend/editor/scripts/generate-tool-api-types.mts
98+
- frontend/editor/src/core/types/toolApiTypes.ts
99+
- engine/scripts/generate_tool_models.py
100+
- engine/src/stirling/models/tool_models.py
101+
- .taskfiles/frontend.yml
102+
- .taskfiles/engine.yml
103+
- .github/workflows/check-generated-models.yml
104+
90105
licenses-frontend: &licenses-frontend
91106
- ".github/workflows/frontend-backend-licenses-update.yml"
92107
- "frontend/package.json"

.github/workflows/ai-engine.yml

Lines changed: 4 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
name: AI Engine CI
22

3-
# Validates the Python AI engine: regenerates tool models and runs the
4-
# engine quality gate (lint, type-check, format-check, tests). Called from
5-
# build.yml on PRs and merge_group; also runs directly on push to main as
6-
# a post-merge safety net.
3+
# Runs the engine quality gate (lint, type-check, format-check, tests). Called
4+
# from build.yml on PRs and merge_group; also runs directly on push to main as
5+
# a post-merge safety net. Freshness of the generated tool_models.py is checked
6+
# by the shared check-generated-models workflow.
77
on:
88
workflow_call:
99
push:
@@ -34,104 +34,9 @@ jobs:
3434
with:
3535
enable-cache: true
3636

37-
- name: Set up JDK 25
38-
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
39-
with:
40-
java-version: "25"
41-
distribution: "temurin"
42-
43-
- name: Setup Gradle
44-
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
45-
with:
46-
gradle-version: 9.6.0
47-
4837
- name: Install Task
4938
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
5039

51-
- name: Regenerate tool models
52-
run: task engine:tool-models
53-
54-
- name: Verify tool models are up to date
55-
id: tool-models-check
56-
continue-on-error: true
57-
run: git diff --exit-code engine/src/stirling/models/tool_models.py
58-
59-
- name: Comment on tool models check failure
60-
# Only post a comment on PRs. github-script's PR helpers need an
61-
# issue/PR number, which doesn't exist on merge_group runs.
62-
if: steps.tool-models-check.outcome == 'failure' && github.event_name == 'pull_request'
63-
continue-on-error: true
64-
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
65-
with:
66-
script: |
67-
const marker = '<!-- tool-models-check -->';
68-
const body = [
69-
marker,
70-
'### Tool Models Check Failed',
71-
'',
72-
'The generated `engine/src/stirling/models/tool_models.py` is out of date with the Java OpenAPI spec and will need to be regenerated before it can be merged in.',
73-
'',
74-
'Run `task engine:tool-models` to regenerate, then commit the updated file.',
75-
].join('\n');
76-
const { data: comments } = await github.rest.issues.listComments({
77-
owner: context.repo.owner,
78-
repo: context.repo.repo,
79-
issue_number: context.issue.number,
80-
});
81-
const existing = comments.find(c => c.body.includes(marker));
82-
if (existing) {
83-
await github.rest.issues.updateComment({
84-
owner: context.repo.owner,
85-
repo: context.repo.repo,
86-
comment_id: existing.id,
87-
body,
88-
});
89-
} else {
90-
await github.rest.issues.createComment({
91-
owner: context.repo.owner,
92-
repo: context.repo.repo,
93-
issue_number: context.issue.number,
94-
body,
95-
});
96-
}
97-
98-
- name: Fail if tool models check failed
99-
if: steps.tool-models-check.outcome == 'failure'
100-
run: |
101-
echo "============================================"
102-
echo " Tool Models Check Failed"
103-
echo "============================================"
104-
echo ""
105-
echo "The generated engine/src/stirling/models/tool_models.py"
106-
echo "is out of date with the Java OpenAPI spec and will"
107-
echo "need to be regenerated before it can be merged in."
108-
echo ""
109-
echo "Run 'task engine:tool-models' to regenerate, then"
110-
echo "commit the updated file."
111-
echo "============================================"
112-
exit 1
113-
114-
- name: Remove tool models check comment on success
115-
if: steps.tool-models-check.outcome == 'success' && github.event_name == 'pull_request'
116-
continue-on-error: true
117-
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
118-
with:
119-
script: |
120-
const marker = '<!-- tool-models-check -->';
121-
const { data: comments } = await github.rest.issues.listComments({
122-
owner: context.repo.owner,
123-
repo: context.repo.repo,
124-
issue_number: context.issue.number,
125-
});
126-
const existing = comments.find(c => c.body.includes(marker));
127-
if (existing) {
128-
await github.rest.issues.deleteComment({
129-
owner: context.repo.owner,
130-
repo: context.repo.repo,
131-
comment_id: existing.id,
132-
});
133-
}
134-
13540
- name: Quality-check engine
13641
id: engine-check
13742
run: task engine:check

.github/workflows/build.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ jobs:
4343
docker-base: ${{ steps.changes.outputs.docker-base }}
4444
tauri: ${{ steps.changes.outputs.tauri }}
4545
engine: ${{ steps.changes.outputs.engine }}
46+
generated-models: ${{ steps.changes.outputs.generated-models }}
4647
proprietary: ${{ steps.changes.outputs.proprietary }}
4748
steps:
4849
- name: Harden the runner (Audit all outbound calls)
@@ -171,6 +172,20 @@ jobs:
171172
uses: ./.github/workflows/ai-engine.yml
172173
secrets: inherit
173174

175+
# The generated frontend types and engine tool models are both derived from
176+
# the Java OpenAPI spec. This job regenerates and diffs them; it boots the
177+
# backend, so it is gated on the narrow generated-models filter (spec source,
178+
# generators, generated files, generation tasks) rather than the broad
179+
# frontend filter, so a CSS-only PR does not pay for a backend build.
180+
generated-models:
181+
if: needs.files-changed.outputs.generated-models == 'true'
182+
needs: [files-changed]
183+
permissions:
184+
contents: read
185+
pull-requests: write
186+
uses: ./.github/workflows/check-generated-models.yml
187+
secrets: inherit
188+
174189
pre-commit:
175190
needs: [files-changed]
176191
permissions:
@@ -228,6 +243,7 @@ jobs:
228243
- test-build-docker-images
229244
- tauri-build
230245
- ai-engine
246+
- generated-models
231247
- pre-commit
232248
- dependency-review
233249
runs-on: ubuntu-latest
@@ -253,6 +269,7 @@ jobs:
253269
test-build-docker-images=${{ needs.test-build-docker-images.result }}
254270
tauri-build=${{ needs.tauri-build.result }}
255271
ai-engine=${{ needs.ai-engine.result }}
272+
generated-models=${{ needs.generated-models.result }}
256273
pre-commit=${{ needs.pre-commit.result }}
257274
dependency-review=${{ needs.dependency-review.result }}
258275
run: |
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
name: Check generated models
2+
3+
# Verifies the committed generated API models are still in sync with the Java
4+
# OpenAPI spec: the frontend tool API types
5+
# (frontend/editor/src/core/types/toolApiTypes.ts) and the engine tool
6+
# models (engine/src/stirling/models/tool_models.py). Regenerates both with the
7+
# single top-level `task tool-models` and fails if either committed file is
8+
# out of date. Called from build.yml when the backend Java, frontend, or engine
9+
# changes; also runs on push to main as a post-merge safety net.
10+
on:
11+
workflow_call:
12+
push:
13+
branches: [main]
14+
15+
permissions:
16+
contents: read
17+
18+
jobs:
19+
generated-models:
20+
runs-on: ubuntu-latest
21+
permissions:
22+
contents: read
23+
pull-requests: write
24+
env:
25+
DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }}
26+
steps:
27+
- name: Harden the runner (Audit all outbound calls)
28+
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
29+
with:
30+
egress-policy: audit
31+
32+
- name: Checkout code
33+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
34+
35+
- name: Install uv
36+
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
37+
with:
38+
enable-cache: true
39+
40+
- name: Set up JDK 25
41+
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
42+
with:
43+
java-version: "25"
44+
distribution: "temurin"
45+
46+
- name: Setup Gradle
47+
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
48+
with:
49+
gradle-version: 9.6.0
50+
51+
- name: Set up Node
52+
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
53+
with:
54+
node-version: "22"
55+
cache: "npm"
56+
cache-dependency-path: frontend/package-lock.json
57+
58+
- name: Install Task
59+
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
60+
61+
# Rebuilds the OpenAPI spec from the current Java and regenerates both the
62+
# frontend types and the engine tool models from it.
63+
- name: Regenerate generated models
64+
run: task tool-models
65+
66+
- name: Verify generated models are up to date
67+
id: models-check
68+
continue-on-error: true
69+
run: |
70+
git diff --exit-code \
71+
frontend/editor/src/core/types/toolApiTypes.ts \
72+
engine/src/stirling/models/tool_models.py
73+
74+
- name: Comment on generated models check failure
75+
# Only post a comment on PRs. github-script's PR helpers need an
76+
# issue/PR number, which doesn't exist on merge_group runs.
77+
if: steps.models-check.outcome == 'failure' && github.event_name == 'pull_request'
78+
continue-on-error: true
79+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
80+
with:
81+
script: |
82+
const marker = '<!-- generated-models-check -->';
83+
const body = [
84+
marker,
85+
'### Generated Models Check Failed',
86+
'',
87+
'The generated `frontend/editor/src/core/types/toolApiTypes.ts` and/or `engine/src/stirling/models/tool_models.py` are out of date with the Java OpenAPI spec and will need to be regenerated before they can be merged in.',
88+
'',
89+
'Run `task tool-models` to regenerate both, then commit the updated files.',
90+
].join('\n');
91+
const { data: comments } = await github.rest.issues.listComments({
92+
owner: context.repo.owner,
93+
repo: context.repo.repo,
94+
issue_number: context.issue.number,
95+
});
96+
const existing = comments.find(c => c.body.includes(marker));
97+
if (existing) {
98+
await github.rest.issues.updateComment({
99+
owner: context.repo.owner,
100+
repo: context.repo.repo,
101+
comment_id: existing.id,
102+
body,
103+
});
104+
} else {
105+
await github.rest.issues.createComment({
106+
owner: context.repo.owner,
107+
repo: context.repo.repo,
108+
issue_number: context.issue.number,
109+
body,
110+
});
111+
}
112+
113+
- name: Fail if generated models check failed
114+
if: steps.models-check.outcome == 'failure'
115+
run: |
116+
echo "============================================"
117+
echo " Generated Models Check Failed"
118+
echo "============================================"
119+
echo ""
120+
echo "The generated frontend API types and/or engine tool"
121+
echo "models are out of date with the Java OpenAPI spec and"
122+
echo "will need to be regenerated before they can be merged in."
123+
echo ""
124+
echo "Run 'task tool-models' to regenerate both, then"
125+
echo "commit the updated files."
126+
echo "============================================"
127+
exit 1
128+
129+
- name: Remove generated models check comment on success
130+
if: steps.models-check.outcome == 'success' && github.event_name == 'pull_request'
131+
continue-on-error: true
132+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
133+
with:
134+
script: |
135+
const marker = '<!-- generated-models-check -->';
136+
const { data: comments } = await github.rest.issues.listComments({
137+
owner: context.repo.owner,
138+
repo: context.repo.repo,
139+
issue_number: context.issue.number,
140+
});
141+
const existing = comments.find(c => c.body.includes(marker));
142+
if (existing) {
143+
await github.rest.issues.deleteComment({
144+
owner: context.repo.owner,
145+
repo: context.repo.repo,
146+
comment_id: existing.id,
147+
});
148+
}

.taskfiles/frontend.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,23 @@ tasks:
396396
# Code Generation
397397
# ============================================================
398398

399+
tool-models:
400+
desc: "Generate tool API types from the Java OpenAPI spec"
401+
deps: [install, ":backend:swagger"]
402+
cmds:
403+
- npx tsx editor/scripts/generate-tool-api-types.mts --spec ../SwaggerDoc.json --output editor/src/core/types/toolApiTypes.ts
404+
sources:
405+
- editor/scripts/generate-tool-api-types.mts
406+
- ../SwaggerDoc.json
407+
generates:
408+
- editor/src/core/types/toolApiTypes.ts
409+
410+
tool-models:check:
411+
desc: "Fail if committed tool API types are out of date"
412+
deps: [install, ":backend:swagger"]
413+
cmds:
414+
- npx tsx editor/scripts/generate-tool-api-types.mts --spec ../SwaggerDoc.json --output editor/src/core/types/toolApiTypes.ts --check
415+
399416
licenses:generate:
400417
desc: "Generate frontend license report"
401418
deps: [install]

Taskfile.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,16 @@ tasks:
185185
- task: frontend:format:check
186186
- task: engine:format:check
187187

188+
# ============================================================
189+
# Code generation
190+
# ============================================================
191+
192+
tool-models:
193+
desc: "Generate all API models from the Java OpenAPI spec"
194+
cmds:
195+
- task: frontend:tool-models
196+
- task: engine:tool-models
197+
188198
# ============================================================
189199
# Quality Gate
190200
# ============================================================

0 commit comments

Comments
 (0)