@field/cascade 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +17 -1
- package/CHANGELOG.md +214 -0
- package/DESIGN.md +14 -0
- package/README.md +41 -25
- package/dist/assets/CodeEditor-DSZBpzdy.js +623 -0
- package/dist/assets/CodePanel-sY6sZa7B.js +2 -0
- package/dist/assets/{CustomNodeDialog-BQbEhIwL.js → CustomNodeDialog-BRKnqZhp.js} +1 -1
- package/dist/assets/{ExportDialog-CjTbbTZJ.js → ExportDialog-C7dVGua4.js} +2 -2
- package/dist/assets/OpenGraphDialog-ZsgL-iwr.js +1 -0
- package/dist/assets/ProjectSettings-Cw17nNe3.js +2 -0
- package/dist/assets/SettingsDialog-p25h3tj0.js +1 -0
- package/dist/assets/{VersionHistoryDialog-D6VBzSQ0.js → VersionHistoryDialog-DzFLUotB.js} +2 -2
- package/dist/assets/{core-BInLG4gw.js → core-BICrEt0y.js} +8 -8
- package/dist/assets/{cssMode-DfR2qmYG.js → cssMode-D7TXw57I.js} +1 -1
- package/dist/assets/definitions-DUww2Rt-.js +1 -0
- package/dist/assets/{editor.api-Ck8_vgQV.js → editor.api-DvkCO3ko.js} +1 -1
- package/dist/assets/{freemarker2-fVn10Wn3.js → freemarker2-BA2lxsF1.js} +1 -1
- package/dist/assets/geo-C2RHOVHC.js +1 -0
- package/dist/assets/{handlebars-BCSchLT2.js → handlebars-BcDiyPd8.js} +1 -1
- package/dist/assets/{html-CVpSv_k9.js → html-C7O4TyhX.js} +1 -1
- package/dist/assets/{htmlMode-Da4ESTv0.js → htmlMode-WaDGF3Mj.js} +1 -1
- package/dist/assets/{image-BLn--oSF.js → image-DR4oeyJB.js} +3 -3
- package/dist/assets/index-7vae6YFC.css +1 -0
- package/dist/assets/index-DyvDC6pS.js +219 -0
- package/dist/assets/index-client-DqYmKstU.js +1 -0
- package/dist/assets/{javascript-BpX0Gvcf.js → javascript-CpIEK7tz.js} +1 -1
- package/dist/assets/{jsonMode-CsLqaou7.js → jsonMode-B2FltW0f.js} +1 -1
- package/dist/assets/legacy-CTEeXeqF.js +2 -0
- package/dist/assets/{liquid-efUz51Wf.js → liquid-CN6FFHZE.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-DCi5-K0n.js → lspLanguageFeatures-QhV9XtLp.js} +1 -1
- package/dist/assets/math-DaDEgWKt.js +1 -0
- package/dist/assets/{mdx-DP_sjGVy.js → mdx-DE89pafV.js} +1 -1
- package/dist/assets/pop-pLH7L0PW.js +1 -0
- package/dist/assets/{python-aAuP3LUi.js → python-ByTllTB9.js} +1 -1
- package/dist/assets/{razor-7M6sMmC5.js → razor-USYlRRCE.js} +1 -1
- package/dist/assets/studioScenes-8sq9FfFg.js +1 -0
- package/dist/assets/{tsMode-Bf4CnoYK.js → tsMode-BXpN8iUz.js} +1 -1
- package/dist/assets/{typescript-ddhYBGyT.js → typescript-DInBjGQQ.js} +1 -1
- package/dist/assets/{workers-X5la8DDT.js → workers-C7Ye5B7D.js} +1 -1
- package/dist/assets/{xml-BzbXa3Ps.js → xml-U0Uocqyo.js} +1 -1
- package/dist/assets/{yaml-CXs-VzxL.js → yaml-CBw_PVYf.js} +1 -1
- package/dist/cli/index.js +19978 -3423
- package/dist/cli/runtime/stage.ts +70 -10
- package/dist/contracts/bundle.d.ts +75 -0
- package/dist/contracts/bundle.js +112 -0
- package/dist/contracts/definition.d.ts +67 -4
- package/dist/contracts/document.d.ts +29 -0
- package/dist/contracts/editor.d.ts +172 -0
- package/dist/contracts/editor.js +2 -0
- package/dist/contracts/index.d.ts +2 -0
- package/dist/contracts/index.js +2 -0
- package/dist/contracts/validate.js +59 -3
- package/dist/contracts/values.d.ts +3 -3
- package/dist/contracts/values.js +1 -0
- package/dist/index.html +4 -4
- package/dist/player/app.js +17 -5
- package/dist/player/editor.js +1894 -0
- package/dist/player/editorEngine.js +477 -0
- package/dist/runtime/builtins/core/bundle.d.ts +70 -0
- package/dist/runtime/builtins/core/bundle.js +81 -0
- package/dist/runtime/builtins/core/index.d.ts +3 -2
- package/dist/runtime/builtins/core/index.js +6 -2
- package/dist/runtime/builtins/core/routing.d.ts +27 -0
- package/dist/runtime/builtins/core/routing.js +28 -0
- package/dist/runtime/edit/document.d.ts +94 -0
- package/dist/runtime/edit/document.js +194 -0
- package/dist/runtime/edit/graphEditor.d.ts +223 -0
- package/dist/runtime/edit/graphEditor.js +893 -0
- package/dist/runtime/edit/index.d.ts +12 -0
- package/dist/runtime/edit/index.js +6 -0
- package/dist/runtime/edit/layout.d.ts +50 -0
- package/dist/runtime/edit/layout.js +126 -0
- package/dist/runtime/edit/operations.d.ts +65 -0
- package/dist/runtime/edit/operations.js +57 -0
- package/dist/runtime/edit/parameters.d.ts +75 -0
- package/dist/runtime/edit/parameters.js +284 -0
- package/dist/runtime/edit/read.d.ts +35 -0
- package/dist/runtime/edit/read.js +65 -0
- package/dist/runtime/editor/index.d.ts +104 -0
- package/dist/runtime/editor/index.js +439 -0
- package/dist/runtime/expressions/bundlePaths.d.ts +37 -0
- package/dist/runtime/expressions/bundlePaths.js +55 -0
- package/dist/runtime/expressions/index.d.ts +2 -0
- package/dist/runtime/expressions/index.js +1 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/params/animator.d.ts +5 -0
- package/dist/runtime/params/animator.js +16 -0
- package/dist/runtime/runtime.js +238 -12
- package/dist/runtime/seed.d.ts +29 -0
- package/dist/runtime/seed.js +52 -0
- package/dist/runtime/types.d.ts +12 -0
- package/dist/stage.js +40 -9
- package/dist/studio/panel.d.ts +2 -0
- package/doc/CLI_REFERENCE.md +100 -0
- package/doc/EDITOR_EXPORT.md +226 -0
- package/doc/HEADLESS_GPU.md +27 -0
- package/doc/MCP.md +53 -0
- package/doc/NODE_REFERENCE.md +66 -0
- package/doc/PROJECT_AUTHORING.md +137 -0
- package/package.json +15 -6
- package/dist/assets/CodeEditor-BEk-kIBd.js +0 -623
- package/dist/assets/CodePanel-CeSdjXkg.js +0 -2
- package/dist/assets/OpenGraphDialog-DCwH9itM.js +0 -1
- package/dist/assets/ProjectSettings-Dg7M5xt9.js +0 -2
- package/dist/assets/SettingsDialog-e7GgsjB9.js +0 -1
- package/dist/assets/definitions-DZpOjWKR.js +0 -1
- package/dist/assets/geo-D2VPjAdt.js +0 -1
- package/dist/assets/index-CMiBdEuT.js +0 -212
- package/dist/assets/index-DMIuml9a.css +0 -1
- package/dist/assets/index-client-BI2H82FJ.js +0 -1
- package/dist/assets/legacy-ZA7textG.js +0 -2
- package/dist/assets/math-B6hpxPJO.js +0 -1
- package/dist/assets/pop-CYUIjT6x.js +0 -1
- package/dist/assets/projectSettingsApi-C1WvIwfP.js +0 -1
package/doc/PROJECT_AUTHORING.md
CHANGED
|
@@ -269,6 +269,58 @@ The reason is not tidiness. Two floats cannot connect to a `vec2` output, take t
|
|
|
269
269
|
|
|
270
270
|
Keep them separate only when the components differ in kind or in range — a `width` and a `depth` that mean different things are two props, not a `vec2`.
|
|
271
271
|
|
|
272
|
+
#### Bundle ports
|
|
273
|
+
|
|
274
|
+
When a node would fan out more than three or four outputs that travel together, export one `bundle` instead: a single port carrying named fields. Each field is any Cascade type; an image or cache entry travels as its reference (`ImageRef`, `AssetRef`), never its pixels.
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
// Producer: declares the fields it carries, and optionally a schema name.
|
|
278
|
+
outputs: {
|
|
279
|
+
format: {
|
|
280
|
+
kind: 'data', type: 'bundle', schema: 'format',
|
|
281
|
+
fields: {
|
|
282
|
+
size: 'vec2i',
|
|
283
|
+
size_mm: 'vec2',
|
|
284
|
+
dpi: { type: 'float', description: 'Resolution of the working canvas' },
|
|
285
|
+
paper: 'string',
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
// in execute:
|
|
290
|
+
context.outputs.format.set({ size, size_mm, dpi, paper });
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
// Consumer: declares the fields it reads.
|
|
295
|
+
inputs: {
|
|
296
|
+
format: { kind: 'data', type: 'bundle', schema: 'format', fields: ['size', 'paper'] },
|
|
297
|
+
},
|
|
298
|
+
// in execute:
|
|
299
|
+
const { size, paper } = context.inputs.format ?? {};
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
- **`fields` on an output** maps each field to its type. A producer may set only declared fields; a declared field may be left unset. Without `fields` the bundle is untyped and nothing can check it.
|
|
303
|
+
- **`fields` on an input** lists the fields the node reads. The node then sees only those, and Studio re-cooks it only when one of them changed: a change to `dpi` does not re-cook a consumer that reads `size` and `paper`. Without `fields` a consumer depends on the whole bundle. Field access is not tracked at run time, so declare the selection when the node is expensive.
|
|
304
|
+
- **`schema`** names the bundle's shape. Two named schemas must match to connect; a port without one takes any bundle.
|
|
305
|
+
- **Expressions** read a field of a wired bundle input with a dotted path: `ch("format.dpi")`, `chv("format.size_mm")`, `chs("format.paper")`. The path names an input on the same node, not another node's output, so the wire is the dependency and the producer always cooks first. `ch("/spread-format/format.dpi")` is refused by `cascade check`, which says which wire to add. A field read in an expression counts as a declared field for caching.
|
|
306
|
+
- **`cascade.core.Pick`** reads one field (`field` prop) for a node that wants a single value, and **`cascade.core.Pack`** builds a bundle from loose values, one name per input in its comma-separated `names` prop. Pick is cheap and its output only changes when the picked field does, so it is also how a node that cannot declare fields depends on one.
|
|
307
|
+
- **`cascade check`** reports a field that the producer does not declare, in a consumer's `fields`, in an expression, or on a Pick, with the nearest declared name: `dot-layer.radius reads unknown bundle field "dpu" — did you mean "dpi"?`.
|
|
308
|
+
- **Studio** draws a bundle as one port in the neutral wire colour. Hovering it lists every field with its type and current value, and the Inspector shows each field with its own control.
|
|
309
|
+
|
|
310
|
+
A bundle passed to a Python stage is an ordinary JSON object, so the worker receives a `dict`; reference fields arrive as their small JSON form, e.g. `args["format"]["preview"]["path"]`. A stage that returns a dict can be set straight onto a bundle output:
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
const result = await runStage('signals', { format: context.inputs.format });
|
|
314
|
+
context.outputs.signals.set(result as Record<string, unknown>);
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
```python
|
|
318
|
+
def signals(args):
|
|
319
|
+
fmt = args["format"] # a dict
|
|
320
|
+
w, h = fmt["size"]
|
|
321
|
+
return {"brightness": {"path": cache_png(...), "size": [w, h], ...}, "edge_count": 412}
|
|
322
|
+
```
|
|
323
|
+
|
|
272
324
|
### dynamic
|
|
273
325
|
|
|
274
326
|
A dynamic module declares its interface imperatively, inside `execute`, which runs once to discover the interface and again on every cook. There is no `definition` export; `runsOn` and `icon` are plain exports.
|
|
@@ -512,6 +564,60 @@ It may also accept inline JSON after `--args` for manual compatibility. Projects
|
|
|
512
564
|
with expensive imports may additionally declare a newline-delimited JSON
|
|
513
565
|
`worker`; Cascade keeps that process warm and correlates requests by `id`.
|
|
514
566
|
|
|
567
|
+
Cascade sends the worker one request at a time. When a newer edit supersedes a
|
|
568
|
+
running stage, Cascade withdraws it: a queued request is never sent, and the
|
|
569
|
+
one in flight gets `SIGINT`. A worker that catches `KeyboardInterrupt` around a
|
|
570
|
+
request should answer `{"id": <id>, "ok": false, "cancelled": true}` and keep
|
|
571
|
+
serving, so warm models survive a scrub; it should also ignore an interrupt
|
|
572
|
+
that arrives between requests. A worker that does not handle it exits and is
|
|
573
|
+
respawned on the next call, and one that has not answered within three seconds
|
|
574
|
+
is killed. Whatever a cancelled request returns is discarded. Write cache
|
|
575
|
+
entries atomically (temp file plus rename), since a stage can now be
|
|
576
|
+
interrupted part-way. Nodes can pass `{ signal }` as `runStage`'s third
|
|
577
|
+
argument; without it the signal of the cook in flight is used.
|
|
578
|
+
|
|
579
|
+
#### Stage code, cache keys and the run trace
|
|
580
|
+
|
|
581
|
+
Cascade keys every stage call on four things: the stage code (a hash of every
|
|
582
|
+
`.py` file under `exec.stages.sources`), the arguments with each input file
|
|
583
|
+
replaced by a hash of its bytes, those input hashes, and the Cascade version.
|
|
584
|
+
The key and the cache state are reported on every call, hit or miss: in the
|
|
585
|
+
server log (`[stage] ink: miss (no entry) key 3f2a… code 99dad29d… 1204 ms`),
|
|
586
|
+
in `cascade run --json` under `stages`, and in the run trace.
|
|
587
|
+
|
|
588
|
+
```json
|
|
589
|
+
{
|
|
590
|
+
"exec": {
|
|
591
|
+
"stages": {
|
|
592
|
+
"entrypoint": "runtime/node_cli.py",
|
|
593
|
+
"worker": "runtime/worker.py",
|
|
594
|
+
"sources": ["runtime", "src", "shared/bridge"],
|
|
595
|
+
"cache": { "exclude": ["plot-status"], "outputs": ["out", "svg_out"] }
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
- `sources` lists the files and folders whose `.py` files are the stage code.
|
|
602
|
+
Without it: the folders of the entrypoint and the worker, plus `src/`. A
|
|
603
|
+
symlinked folder (such as `shared/`) is followed only when it is listed.
|
|
604
|
+
- The warm worker fingerprints those sources when it starts and again before
|
|
605
|
+
every request, and restarts first when they changed, so an edited stage never
|
|
606
|
+
keeps running its old code in a live Studio. Its bytecode cache is keyed by
|
|
607
|
+
the code hash, because Python accepts a `.pyc` whose source kept its size
|
|
608
|
+
within the same second.
|
|
609
|
+
- `cache` turns on Cascade's own result cache: `true`, or an object with
|
|
610
|
+
`exclude` (stages whose answer changes under an identical call) and
|
|
611
|
+
`outputs` (arguments naming files a stage writes, which are not inputs). Off
|
|
612
|
+
by default. A stored result is served only while every file it names still
|
|
613
|
+
has the bytes it had, so a stage that writes a fixed path and was re-run with
|
|
614
|
+
other inputs misses rather than serving the overwritten file.
|
|
615
|
+
- A request carries `"cascade": {"key", "code", "cache"}` beside `args` (the
|
|
616
|
+
worker), or `CASCADE_STAGE_ENVELOPE` in the environment (one-shot). A
|
|
617
|
+
project that keeps its own cache should stand aside when `cache` is `"off"`,
|
|
618
|
+
which `cascade parity` sets, and may answer `"cache": "hit" | "miss"` beside
|
|
619
|
+
`result` so the trace shows its state too.
|
|
620
|
+
|
|
515
621
|
## Work with `.cascade` files
|
|
516
622
|
|
|
517
623
|
A graph document stores authored elements separately from runtime state. Nodes and annotations remain flat even inside subnets:
|
|
@@ -550,6 +656,11 @@ cascade run index.cascade --frames 1-100x2 --fps 25 --out frames
|
|
|
550
656
|
cascade run index.cascade --frames 42 --entry-node logo-1024
|
|
551
657
|
```
|
|
552
658
|
|
|
659
|
+
Every run writes a trace to `.cascade/runs/<time>-<graph>.json` (the last 200
|
|
660
|
+
are kept): each node that cooked, its time, and every stage call it made with
|
|
661
|
+
the key and cache state. `--verbose` prints the stage lines as they happen, and
|
|
662
|
+
`--json` prints the status, the seed, the trace path and the stage calls.
|
|
663
|
+
|
|
553
664
|
`--frames` takes a range, a stepped range, or a single frame, and refuses a range it cannot parse rather than repairing it. Files are written as `<out>/<node id>.<frame>.<ext>`, zero-padded to at least four digits, into `renders/` unless `--out` says otherwise. One rule whether the graph has one output or three. Every image output with nothing connected downstream is saved, or the outputs of `--entry-node` when one is named. Cascade stops at the image sequence; a directory of numbered frames is what an encoder takes.
|
|
554
665
|
|
|
555
666
|
Offline, drawing goes through Skia: the optional `@napi-rs/canvas` supplies `OffscreenCanvas`, `Image`, `Path2D` and the rest as globals in the Node process, deliberately not `document`, because that is how a node tells which host it is in. This is why a node that draws on an `OffscreenCanvas` needs no second implementation for headless runs, and why the built-in `cascade.image.*` nodes render under `cascade run` as well as in Studio. A graph that draws nothing runs without the renderer installed.
|
|
@@ -583,6 +694,32 @@ testing the exported artifact for the particular graph. Prefer `cascade build`
|
|
|
583
694
|
for definition-v1 browser graphs, or embed `cascade/runtime` directly when an
|
|
584
695
|
application needs custom capabilities and presentation.
|
|
585
696
|
|
|
697
|
+
## Seeds and parity
|
|
698
|
+
|
|
699
|
+
A graph has one `seed` (an unsigned 32-bit integer, 0 when absent) and each
|
|
700
|
+
node gets `context.seed`, derived from it and the node id, plus
|
|
701
|
+
`context.random()`, a stream seeded with it that restarts at every cook. Both
|
|
702
|
+
hosts derive them the same way, so a node that draws with `context.random`
|
|
703
|
+
draws the same thing headless and in Studio. `Math.random` cannot be
|
|
704
|
+
reproduced by any host; pass `context.seed` on to a Python stage instead of
|
|
705
|
+
seeding there from the clock. `cascade run --seed N` cooks with another seed
|
|
706
|
+
without editing the file.
|
|
707
|
+
|
|
708
|
+
```bash
|
|
709
|
+
cascade parity index.cascade # every node
|
|
710
|
+
cascade parity index.cascade --node ink # one node and what feeds it
|
|
711
|
+
cascade parity index.cascade --tolerance 2 --json
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
`parity` cooks three times with every cache off: headless, headless again, and
|
|
715
|
+
through Studio's own executor (the compatibility graph and its definition
|
|
716
|
+
adapter) run headlessly with the same Dawn GPU and project capabilities. It
|
|
717
|
+
prints one row per node output with both comparisons side by side (`same`,
|
|
718
|
+
`within tolerance`, or the difference: pixels and the largest channel delta
|
|
719
|
+
for an image, both values for a number) and exits 1 when anything differs.
|
|
720
|
+
See [Headless GPU](HEADLESS_GPU.md#determinism) for what makes a GPU stage
|
|
721
|
+
repeatable.
|
|
722
|
+
|
|
586
723
|
## Verify a project
|
|
587
724
|
|
|
588
725
|
Before committing a custom node or graph:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@field/cascade",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Visual programming for generative art",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "FIELD.IO <marcus@field.io>",
|
|
@@ -35,6 +35,9 @@
|
|
|
35
35
|
"doc/PROJECT_AUTHORING.md",
|
|
36
36
|
"doc/HEADLESS_GPU.md",
|
|
37
37
|
"doc/WEB_PLAYER.md",
|
|
38
|
+
"doc/EDITOR_EXPORT.md",
|
|
39
|
+
"doc/CLI_REFERENCE.md",
|
|
40
|
+
"doc/MCP.md",
|
|
38
41
|
"README.md",
|
|
39
42
|
"LICENSE"
|
|
40
43
|
],
|
|
@@ -86,6 +89,10 @@
|
|
|
86
89
|
"types": "./dist/runtime/scene/index.d.ts",
|
|
87
90
|
"default": "./dist/runtime/scene/index.js"
|
|
88
91
|
},
|
|
92
|
+
"./runtime/editor": {
|
|
93
|
+
"types": "./dist/runtime/editor/index.d.ts",
|
|
94
|
+
"default": "./dist/runtime/editor/index.js"
|
|
95
|
+
},
|
|
89
96
|
"./runtime/definition/extract": {
|
|
90
97
|
"types": "./dist/runtime/definition/extract.d.ts",
|
|
91
98
|
"default": "./dist/runtime/definition/extract.js"
|
|
@@ -121,12 +128,12 @@
|
|
|
121
128
|
"scripts": {
|
|
122
129
|
"dev": "vite",
|
|
123
130
|
"dev:client": "vite",
|
|
124
|
-
"build": "
|
|
125
|
-
"
|
|
126
|
-
"build:packages": "npm run build
|
|
131
|
+
"build": "node scripts/build.mjs",
|
|
132
|
+
"build:workspaces": "npm run build --workspace @cascade/contracts && npm run build --workspace @cascade/runtime",
|
|
133
|
+
"build:packages": "npm run build:workspaces && node scripts/copy-package-builds.mjs",
|
|
127
134
|
"check:packages": "npm run check --workspace @cascade/contracts && npm run build --workspace @cascade/contracts && npm run check --workspace @cascade/runtime",
|
|
128
135
|
"test:packages": "npm run test --workspace @cascade/contracts && npm run test --workspace @cascade/runtime",
|
|
129
|
-
"build:cli": "
|
|
136
|
+
"build:cli": "node scripts/build.mjs --cli",
|
|
130
137
|
"build:server": "npm --prefix server run build",
|
|
131
138
|
"prepack": "npm run build",
|
|
132
139
|
"preview": "vite preview",
|
|
@@ -136,7 +143,9 @@
|
|
|
136
143
|
"test:coverage": "vitest run --coverage",
|
|
137
144
|
"test:package": "node scripts/smoke-package.mjs",
|
|
138
145
|
"test:player": "node scripts/smoke-player.mjs",
|
|
139
|
-
"build:node-reference": "tsx scripts/build-node-reference.mjs"
|
|
146
|
+
"build:node-reference": "tsx scripts/build-node-reference.mjs",
|
|
147
|
+
"site:versions": "node site/build-versions.mjs",
|
|
148
|
+
"site:deploy": "node site/build-versions.mjs && vercel deploy --prod --yes --cwd site"
|
|
140
149
|
},
|
|
141
150
|
"dependencies": {
|
|
142
151
|
"esbuild": "^0.28.2",
|