From 7dab72aea38e2b2ec874c3430907f62aaeba0cb6 Mon Sep 17 00:00:00 2001 From: dazzatronus Date: Wed, 30 Sep 2026 10:03:30 +1000 Subject: [PATCH 1/3] fix: replace deprecated assets in the API examples and cover every request body --- .github/workflows/ci.yml | 3 + definitions/edit.yaml | 40 ++++++++------ package.json | 3 +- paths/assets.yaml | 5 ++ paths/generate.yaml | 5 ++ paths/renderid.yaml | 55 ++++++++++++++++++- paths/templates.yaml | 29 ++++++++++ paths/templatesid.yaml | 29 ++++++++++ paths/templatesrender.yaml | 5 ++ .../responses/templatedataresponsedata.yaml | 55 ++++++++++++++++++- tests/request-examples.cjs | 49 +++++++++++++++++ 11 files changed, 259 insertions(+), 19 deletions(-) create mode 100644 tests/request-examples.cjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 42b0aa7..0a25b80 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,9 @@ jobs: - name: Smoke tests run: pnpm test:smoke + - name: Request examples + run: pnpm test:examples + - name: Install reference renderer working-directory: .shins run: npm ci --omit=dev --no-audit --no-fund diff --git a/definitions/edit.yaml b/definitions/edit.yaml index 0256612..ed623d6 100644 --- a/definitions/edit.yaml +++ b/definitions/edit.yaml @@ -1,46 +1,54 @@ timeline: - soundtrack: - src: "https://s3-ap-northeast-1.amazonaws.com/my-bucket/music.mp3" - effect: fadeInFadeOut background: "#000000" + fonts: + - src: "https://fonts.gstatic.com/s/montserrat/v31/JTUSjIg1_i6t8kCHKm45xW5rygbi49c.ttf" tracks: - clips: - asset: - type: title + type: rich-text text: Hello World - style: minimal + font: + family: JTUSjIg1_i6t8kCHKm45xW5rygbi49c + size: 64 + weight: "700" + color: "#ffffff" + align: + horizontal: center + vertical: middle start: 0 length: 4 transition: in: fade out: fade - effect: slideRight - asset: type: image - src: >- - https://s3-ap-northeast-1.amazonaws.com/my-bucket/my-image.jpg - start: 3 - length: 4 + src: "https://shotstack-assets.s3.amazonaws.com/images/earth.jpg" + start: 4 + length: 3 effect: zoomIn filter: greyscale - clips: - asset: type: video - src: >- - https://s3-ap-northeast-1.amazonaws.com/my-bucket/my-clip-1.mp4 - trim: 10.5 + src: "https://shotstack-assets.s3.amazonaws.com/footage/beach.mp4" transcode: true start: 7 length: 4.5 - asset: type: video - src: >- - https://s3-ap-northeast-1.amazonaws.com/my-bucket/my-clip-2.mp4 + src: "https://shotstack-assets.s3.amazonaws.com/footage/city-timelapse.mp4" volume: 0.5 start: 11.5 length: 5 transition: out: wipeLeft + - clips: + - asset: + type: audio + src: "https://shotstack-assets.s3.amazonaws.com/music/unminus/lit.mp3" + effect: fadeInFadeOut + start: 0 + length: end output: format: mp4 - resolution: sd \ No newline at end of file + resolution: sd diff --git a/package.json b/package.json index b3a9245..82ff015 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,8 @@ "start": "cross-env ./build-docs.sh && http-server build/docs/ -o -c-1", "deploy:docs": "aws s3 sync build/docs/ s3://shotstack.io/docs/api", "prepublishOnly": "node scripts/publish-guard.cjs && pnpm build && pnpm test", - "test:smoke": "node tests/smoke.cjs" + "test:smoke": "node tests/smoke.cjs", + "test:examples": "node tests/request-examples.cjs" }, "repository": { "type": "git", diff --git a/paths/assets.yaml b/paths/assets.yaml index 130d144..0781065 100644 --- a/paths/assets.yaml +++ b/paths/assets.yaml @@ -11,6 +11,11 @@ post: Fetch an asset from a URL and send it to one or more destinations. content: application/json: + example: + url: 'https://s3-ap-southeast-2.amazonaws.com/shotstack-assets/music/moment.mp3' + id: 018e8937-5015-75ee-aab6-03f214981133 + destinations: + - provider: shotstack schema: $ref: "../schemas/serve/transfer.yaml#/Transfer" required: true diff --git a/paths/generate.yaml b/paths/generate.yaml index 0406e76..d03012f 100644 --- a/paths/generate.yaml +++ b/paths/generate.yaml @@ -94,6 +94,11 @@ A prompt-bearing image, video or audio asset to generate. content: application/json: + example: + asset: + type: image + prompt: 'A lighthouse on a rocky coast at sunset, cinematic lighting' + model: flux-schnell schema: type: object properties: diff --git a/paths/renderid.yaml b/paths/renderid.yaml index 304398b..1938a63 100644 --- a/paths/renderid.yaml +++ b/paths/renderid.yaml @@ -14,7 +14,60 @@ url: >- https://shotstack-api-v1-output.s3-ap-southeast-2.amazonaws.com/5ca6hu7s9k/2abd5c11-0f3d-4c6d-ba20-235fc9b8e8b7.mp4 data: - $ref: "../definitions/edit.yaml" + timeline: + background: '#000000' + fonts: + - src: 'https://fonts.gstatic.com/s/montserrat/v31/JTUSjIg1_i6t8kCHKm45xW5rygbi49c.ttf' + tracks: + - clips: + - asset: + type: rich-text + text: Hello World + font: + family: JTUSjIg1_i6t8kCHKm45xW5rygbi49c + size: 64 + weight: '700' + color: '#ffffff' + align: + horizontal: center + vertical: middle + start: 0 + length: 4 + transition: + in: fade + out: fade + - asset: + type: image + src: 'https://shotstack-assets.s3.amazonaws.com/images/earth.jpg' + start: 4 + length: 3 + effect: zoomIn + filter: greyscale + - clips: + - asset: + type: video + src: 'https://shotstack-assets.s3.amazonaws.com/footage/beach.mp4' + transcode: true + start: 7 + length: 4.5 + - asset: + type: video + src: 'https://shotstack-assets.s3.amazonaws.com/footage/city-timelapse.mp4' + volume: 0.5 + start: 11.5 + length: 5 + transition: + out: wipeLeft + - clips: + - asset: + type: audio + src: 'https://shotstack-assets.s3.amazonaws.com/music/unminus/lit.mp3' + effect: fadeInFadeOut + start: 0 + length: end + output: + format: mp4 + resolution: sd created: "2020-10-30T09:42:29.446Z" updated: "2020-10-30T09:42:39.168Z" schema: diff --git a/paths/templates.yaml b/paths/templates.yaml index 130db0b..b685c9f 100644 --- a/paths/templates.yaml +++ b/paths/templates.yaml @@ -12,6 +12,35 @@ Create a template with a name and [Edit](#tocs_edit). content: application/json: + example: + name: Jane's welcome video + template: + timeline: + fonts: + - src: 'https://fonts.gstatic.com/s/montserrat/v31/JTUSjIg1_i6t8kCHKm45xW5rygbi49c.ttf' + tracks: + - clips: + - asset: + type: rich-text + text: 'HELLO {{NAME}}' + font: + family: JTUSjIg1_i6t8kCHKm45xW5rygbi49c + size: 72 + weight: '800' + color: '#ffffff' + align: + horizontal: center + vertical: middle + start: 0 + length: 5 + output: + format: mp4 + size: + width: 1024 + height: 576 + merge: + - find: NAME + replace: World schema: $ref: "../schemas/template.yaml#/Template" required: true diff --git a/paths/templatesid.yaml b/paths/templatesid.yaml index 32918e9..5a5b4eb 100644 --- a/paths/templatesid.yaml +++ b/paths/templatesid.yaml @@ -30,6 +30,35 @@ provided. If the template parameter is omitted a blank template will be saved. content: application/json: + example: + name: Jane's welcome video (updated) + template: + timeline: + fonts: + - src: 'https://fonts.gstatic.com/s/montserrat/v31/JTUSjIg1_i6t8kCHKm45xW5rygbi49c.ttf' + tracks: + - clips: + - asset: + type: rich-text + text: 'HELLO {{NAME}}' + font: + family: JTUSjIg1_i6t8kCHKm45xW5rygbi49c + size: 72 + weight: '800' + color: '#ffffff' + align: + horizontal: center + vertical: middle + start: 0 + length: 5 + output: + format: mp4 + size: + width: 1024 + height: 576 + merge: + - find: NAME + replace: World schema: $ref: "../schemas/template.yaml#/Template" required: true diff --git a/paths/templatesrender.yaml b/paths/templatesrender.yaml index 2766cea..23bb359 100644 --- a/paths/templatesrender.yaml +++ b/paths/templatesrender.yaml @@ -11,6 +11,11 @@ Render a template by template id. content: application/json: + example: + id: f5493c17-d01f-445c-bb49-535fae65f219 + merge: + - find: NAME + replace: Jane schema: $ref: "../schemas/templaterender.yaml#/TemplateRender" required: true diff --git a/schemas/responses/templatedataresponsedata.yaml b/schemas/responses/templatedataresponsedata.yaml index 6002c0f..92b38db 100644 --- a/schemas/responses/templatedataresponsedata.yaml +++ b/schemas/responses/templatedataresponsedata.yaml @@ -17,7 +17,60 @@ description: The [Edit](#tocs_edit) template. $ref: "../edit.yaml#/Edit" example: - $ref: "../../definitions/edit.yaml" + timeline: + background: '#000000' + fonts: + - src: 'https://fonts.gstatic.com/s/montserrat/v31/JTUSjIg1_i6t8kCHKm45xW5rygbi49c.ttf' + tracks: + - clips: + - asset: + type: rich-text + text: Hello World + font: + family: JTUSjIg1_i6t8kCHKm45xW5rygbi49c + size: 64 + weight: '700' + color: '#ffffff' + align: + horizontal: center + vertical: middle + start: 0 + length: 4 + transition: + in: fade + out: fade + - asset: + type: image + src: 'https://shotstack-assets.s3.amazonaws.com/images/earth.jpg' + start: 4 + length: 3 + effect: zoomIn + filter: greyscale + - clips: + - asset: + type: video + src: 'https://shotstack-assets.s3.amazonaws.com/footage/beach.mp4' + transcode: true + start: 7 + length: 4.5 + - asset: + type: video + src: 'https://shotstack-assets.s3.amazonaws.com/footage/city-timelapse.mp4' + volume: 0.5 + start: 11.5 + length: 5 + transition: + out: wipeLeft + - clips: + - asset: + type: audio + src: 'https://shotstack-assets.s3.amazonaws.com/music/unminus/lit.mp3' + effect: fadeInFadeOut + start: 0 + length: end + output: + format: mp4 + resolution: sd required: - id - name diff --git a/tests/request-examples.cjs b/tests/request-examples.cjs new file mode 100644 index 0000000..73f3dab --- /dev/null +++ b/tests/request-examples.cjs @@ -0,0 +1,49 @@ +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +// Reads the package produced by `pnpm build`. +const dist = path.resolve(__dirname, '..', 'dist'); +const api = require(path.join(dist, 'api.bundled.json')); +const z = require(path.join(dist, 'zod/zod.gen.cjs')); + +// Serve's transfer operation has no generated request validator, so its body is checked against Transfer. +const bodySchema = (operationId) => + z[`${operationId}Request`]?.shape.body ?? { postServeAsset: z.transferSchema }[operationId]; + +// The bundler turns a second use of an example file into a `$ref` pointer, which the reference and the +// published types then show as-is. +const hasPointer = (value) => JSON.stringify(value ?? null).includes('"$ref"'); + +const failures = []; +let checked = 0; +for (const [route, operations] of Object.entries(api.paths)) { + for (const [method, operation] of Object.entries(operations)) { + const content = operation?.requestBody?.content?.['application/json']; + if (!content) continue; + const label = `${method.toUpperCase()} ${route}`; + if (content.example === undefined) { + failures.push(`${label}: no example`); + continue; + } + if (hasPointer(content.example)) { + failures.push(`${label}: example is a $ref pointer`); + continue; + } + const schema = bodySchema(operation.operationId); + assert.ok(schema, `${label}: no body schema for ${operation.operationId}`); + checked++; + const result = schema.safeParse(content.example); + if (!result.success) { + failures.push(`${label}: ${result.error.issues.map((i) => `${i.path.join('.')} ${i.message}`).join('; ')}`); + } + } +} + +// openapi-typescript copies examples into JSDoc verbatim, pointers included. +if (/^\s*\*\s+"\$ref":/m.test(fs.readFileSync(path.join(dist, 'schema.d.ts'), 'utf8'))) { + failures.push('schema.d.ts: an @example shows a $ref pointer'); +} + +assert.equal(failures.length, 0, `\n${failures.join('\n')}`); +console.log(`Request examples: ${checked} valid`); From 2c103b416731ec10052dc41368358980d0991cc4 Mon Sep 17 00:00:00 2001 From: dazzatronus Date: Wed, 30 Sep 2026 10:18:33 +1000 Subject: [PATCH 2/3] fix: use nano-banana-2 as the example image generation model --- paths/generate.yaml | 2 +- schemas/imageasset.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/paths/generate.yaml b/paths/generate.yaml index d03012f..ad96638 100644 --- a/paths/generate.yaml +++ b/paths/generate.yaml @@ -98,7 +98,7 @@ asset: type: image prompt: 'A lighthouse on a rocky coast at sunset, cinematic lighting' - model: flux-schnell + model: nano-banana-2 schema: type: object properties: diff --git a/schemas/imageasset.yaml b/schemas/imageasset.yaml index 5ed108a..32567ef 100644 --- a/schemas/imageasset.yaml +++ b/schemas/imageasset.yaml @@ -42,7 +42,7 @@ `nano-banana-2`). Defaults to `nano-banana-2` if omitted. Each model's available options are defined by the model registry. type: string - example: flux-schnell + example: nano-banana-2 options: description: >- Model-specific generation settings. Valid keys and values depend on From 57a09bd87a6ac74aa4971d47aaa46b3cdcc1371b Mon Sep 17 00:00:00 2001 From: dazzatronus Date: Wed, 30 Sep 2026 10:29:09 +1000 Subject: [PATCH 3/3] fix: use the create example's template name in the update example --- paths/templatesid.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paths/templatesid.yaml b/paths/templatesid.yaml index 5a5b4eb..47def38 100644 --- a/paths/templatesid.yaml +++ b/paths/templatesid.yaml @@ -31,7 +31,7 @@ content: application/json: example: - name: Jane's welcome video (updated) + name: Jane's welcome video template: timeline: fonts: