@valbuild/shared 0.134.1 → 0.136.1
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/CHANGELOG.md +101 -0
- package/client/dist/valbuild-shared-client.cjs.dev.js +1 -1
- package/client/dist/valbuild-shared-client.cjs.prod.js +1 -1
- package/client/dist/valbuild-shared-client.esm.js +1 -1
- package/dist/declarations/src/internal/ApiRoutes.d.ts +60 -0
- package/dist/declarations/src/internal/index.d.ts +3 -0
- package/dist/declarations/src/internal/publish/contentApi.d.ts +201 -0
- package/dist/declarations/src/internal/publish/protocol.d.ts +123 -0
- package/dist/declarations/src/internal/server/requestScopedMemo.d.ts +47 -0
- package/dist/{getSitemapTree-99839b02.cjs.prod.js → getSitemapTree-0ed3deac.cjs.prod.js} +1 -0
- package/dist/{getSitemapTree-878d3888.esm.js → getSitemapTree-6fcdf745.esm.js} +1 -1
- package/dist/{getSitemapTree-99c97c62.cjs.dev.js → getSitemapTree-c732e09a.cjs.dev.js} +1 -0
- package/internal/dist/valbuild-shared-internal.cjs.dev.js +511 -1
- package/internal/dist/valbuild-shared-internal.cjs.prod.js +511 -1
- package/internal/dist/valbuild-shared-internal.esm.js +502 -3
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,106 @@
|
|
|
1
1
|
# @valbuild/shared
|
|
2
2
|
|
|
3
|
+
## 0.136.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#716](https://github.com/valbuild/val/pull/716) [`e673b43`](https://github.com/valbuild/val/commit/e673b43feaf48b7f30881d6786c858a0cb6a44a2) Thanks [@freekh](https://github.com/freekh)! - The Studio can run a whole publish: read what to build and what to build it from, build it, and hand the result to the content service.
|
|
8
|
+
|
|
9
|
+
The two reads go through the same proxy and the same credential as the publish conversation, and both are validated rather than cast — an error page from a gateway in between would otherwise fail several layers down inside the bundler with a message about a module specifier.
|
|
10
|
+
|
|
11
|
+
Generating a route tree for a file-based project is a capability the deployment supplies, like `routeSplitter` and `loadCssModule` already are. Without one, such a project is refused by name rather than failing with `UNRESOLVED_ENTRY`.
|
|
12
|
+
|
|
13
|
+
- [#697](https://github.com/valbuild/val/pull/697) [`5c82928`](https://github.com/valbuild/val/commit/5c82928992d29f133f81cf0704a273c0449b18f1) Thanks [@freekh](https://github.com/freekh)! - Read draft content once per request instead of once per `fetchVal`
|
|
14
|
+
|
|
15
|
+
In draft mode, `fetchVal` resolves its selector by asking the Val server for the
|
|
16
|
+
whole module tree. It did that on every call, so a page with three reads made
|
|
17
|
+
three requests for three identical answers — and `fetchValRouteUrl` added a
|
|
18
|
+
fourth by calling `fetchVal` again on top of the caller's own. The answer cannot
|
|
19
|
+
differ between reads in one request, so it is now read once and shared. On a
|
|
20
|
+
60-module project a three-read draft render goes from ~187ms to ~70ms.
|
|
21
|
+
|
|
22
|
+
The memo is scoped to the request and nothing wider, because the response it
|
|
23
|
+
holds contains the caller's own unpublished patches: React's `cache()` in a
|
|
24
|
+
Next RSC, a `WeakMap` keyed on the `Request` in TanStack Start, and no caching
|
|
25
|
+
at all where neither is available.
|
|
26
|
+
|
|
27
|
+
What this does **not** change:
|
|
28
|
+
|
|
29
|
+
- **Production is untouched.** With Val disabled the reader never calls the
|
|
30
|
+
server; it resolves against the statically imported module, as before.
|
|
31
|
+
- **What a draft shows is unchanged.** Same query, same patch scoping — a draft
|
|
32
|
+
still shows the caller's own staged work and nobody else's.
|
|
33
|
+
- **The client readers are untouched.** `useVal` already subscribed to exactly
|
|
34
|
+
the modules its selector names.
|
|
35
|
+
- **The server still evaluates every module per request.** Narrowing the
|
|
36
|
+
request's path was measured and does not help: `/sources/~` evaluates,
|
|
37
|
+
previews and validates everything and only then filters the response, so a
|
|
38
|
+
narrow path returns less for the same milliseconds. That remains open.
|
|
39
|
+
|
|
40
|
+
- [#716](https://github.com/valbuild/val/pull/716) [`d2f385a`](https://github.com/valbuild/val/commit/d2f385a04978992a5036379235e447ba7775faef) Thanks [@freekh](https://github.com/freekh)! - The Studio works again when served from the published package, and a managed project's publish now puts the saved edit on the site.
|
|
41
|
+
|
|
42
|
+
- **The Studio did not start in 0.136.0.** Its main bundle imports sibling chunks by relative path, and the Studio is served at `/api/val/static/<version>/app`, so those imports resolved to paths the handler did not know and came back as the HTML fallback — which a browser refuses as a module script. Preview was gone with it. The handler now serves those chunks, and the package build follows every import the bundle makes before it lets a release through.
|
|
43
|
+
- **The in-browser builder could not load.** The Studio's bundle carried rolldown's Node WASI binding, which throws `process is not defined` in a tab. It now uses the browser binding, and the build refuses a bundle that contains the Node one.
|
|
44
|
+
- **A managed publish builds what was just saved.** `/save` returns the source files the commit wrote, the Studio builds with them laid over the project's stored source, and publishes that source back with the build — so the next edit starts from this one rather than undoing it. The save's commit is also passed through to the build, which previously ran as though no commit had been made.
|
|
45
|
+
- **The bundler's WebAssembly is served from `content.val.build/v1/static`** (`DEFAULT_STATIC_HOST`), still addressed by its SHA-256. `globalThis.__VAL_ROLLDOWN_WASM_URL__` still overrides it.
|
|
46
|
+
- The `val.server.ts` that `@valbuild/tanstack-build` generates no longer hands a save's files to the platform (`platform.internal/__api/source`). A managed project's Studio builds in its own tab, and the platform has closed that door. `isWired` recognises files wired either way.
|
|
47
|
+
- `@valbuild/tanstack-build/constants` exports the contract paths and the artifact key namespace from an entrypoint that imports nothing else, for callers that must not load the bundler. The namespace gains a `source` key.
|
|
48
|
+
|
|
49
|
+
- Updated dependencies [[`d2f385a`](https://github.com/valbuild/val/commit/d2f385a04978992a5036379235e447ba7775faef)]:
|
|
50
|
+
- @valbuild/core@0.136.1
|
|
51
|
+
|
|
52
|
+
## 0.136.0
|
|
53
|
+
|
|
54
|
+
### Minor Changes
|
|
55
|
+
|
|
56
|
+
- [#713](https://github.com/valbuild/val/pull/713) [`f3a4bb7`](https://github.com/valbuild/val/commit/f3a4bb7aea604943b92545c122243798c759715c) Thanks [@freekh](https://github.com/freekh)! - The Studio can load a bundler, and it stops telling a managed project that its
|
|
57
|
+
publish is on its way out.
|
|
58
|
+
|
|
59
|
+
**A managed project is one with no repository and no host watching one.** There
|
|
60
|
+
is nothing outside the browser to pick a commit up, so the Studio has been
|
|
61
|
+
showing it a story that belongs to a project with a repository: a `Building`
|
|
62
|
+
spinner, waiting for an event that can never arrive. It does not resolve on a
|
|
63
|
+
reload, on a retry, or tomorrow, and there is no way to tell it from a deploy
|
|
64
|
+
that is merely slow.
|
|
65
|
+
|
|
66
|
+
The content service now reports which kind of project this is, as `sourceMode`
|
|
67
|
+
on `/stat`, and the Studio narrates accordingly:
|
|
68
|
+
|
|
69
|
+
- a **connected** project keeps the deploy feed and keeps `Building`, because
|
|
70
|
+
there a host genuinely does pick the commit up;
|
|
71
|
+
- a **managed** one says `Saved, not yet live` instead — a durable condition,
|
|
72
|
+
not a phase, because nothing else will ever resolve it.
|
|
73
|
+
|
|
74
|
+
A server that reports no source mode keeps the behaviour it has today. Absence
|
|
75
|
+
means "not reported", never "managed": `fs` mode has no project to have a mode,
|
|
76
|
+
and neither does a content service that predates the field.
|
|
77
|
+
|
|
78
|
+
**The Studio also loads `@valbuild/tanstack-build` in the tab**, at mount and at
|
|
79
|
+
idle, for the managed projects that will need it — a browser that is the
|
|
80
|
+
deployer needs a bundler. This is the groundwork for browser-side publishing;
|
|
81
|
+
the publish path itself is unchanged in this release.
|
|
82
|
+
|
|
83
|
+
That bundler is `@rolldown/browser`, whose WebAssembly binary is 10.9 MB and is
|
|
84
|
+
**not** in the npm package. It is served from `static.val.build`, addressed by
|
|
85
|
+
the SHA-256 of its own bytes, so the build and the binary it needs cannot drift
|
|
86
|
+
apart. A deployment that must not reach that host — an air-gapped install, a
|
|
87
|
+
mirror — sets `globalThis.__VAL_ROLLDOWN_WASM_URL__` before the Studio loads and
|
|
88
|
+
needs no rebuild.
|
|
89
|
+
|
|
90
|
+
**Building in the browser requires a cross-origin isolated page.** Rolldown runs
|
|
91
|
+
WebAssembly on worker threads that share memory, and a browser will not hand a
|
|
92
|
+
`SharedArrayBuffer` to a worker otherwise. Without
|
|
93
|
+
`Cross-Origin-Opener-Policy: same-origin` and
|
|
94
|
+
`Cross-Origin-Embedder-Policy: require-corp` on the document Val is mounted in,
|
|
95
|
+
loading the bundler now fails with a message that says exactly that, instead of
|
|
96
|
+
a `DataCloneError` thrown from inside a worker. Nothing else in this release is
|
|
97
|
+
affected: a Studio that never builds in the browser never asks.
|
|
98
|
+
|
|
99
|
+
### Patch Changes
|
|
100
|
+
|
|
101
|
+
- Updated dependencies [[`f3a4bb7`](https://github.com/valbuild/val/commit/f3a4bb7aea604943b92545c122243798c759715c)]:
|
|
102
|
+
- @valbuild/core@0.136.0
|
|
103
|
+
|
|
3
104
|
## 0.134.1
|
|
4
105
|
|
|
5
106
|
### Patch Changes
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { D as DEFAULT_CANVAS_SELECTION, e as DEFAULT_CANVAS_SELECTION_SOFT, b as VAL_AI_SESSION_STORAGE_KEY, d as VAL_CANVAS_MESSAGE, c as VAL_CANVAS_PARAM, V as VAL_CONFIG_SESSION_STORAGE_KEY, a as VAL_THEME_SESSION_STORAGE_KEY, h as getNextAppRouterSourceFolder, m as getPageRouterSitemapTree, k as getPageRouterSourceFolder, n as getPatternFromModuleFilePath, j as getTanStackRouterSourceFolder, l as isTanStackRoutesFolder, g as isValCanvasFrame, i as isValCanvasPageMessage, f as isValCanvasStudioMessage, p as parseRoutePattern, w as withValCanvasParam } from '../../dist/getSitemapTree-
|
|
1
|
+
export { D as DEFAULT_CANVAS_SELECTION, e as DEFAULT_CANVAS_SELECTION_SOFT, b as VAL_AI_SESSION_STORAGE_KEY, d as VAL_CANVAS_MESSAGE, c as VAL_CANVAS_PARAM, V as VAL_CONFIG_SESSION_STORAGE_KEY, a as VAL_THEME_SESSION_STORAGE_KEY, h as getNextAppRouterSourceFolder, m as getPageRouterSitemapTree, k as getPageRouterSourceFolder, n as getPatternFromModuleFilePath, j as getTanStackRouterSourceFolder, l as isTanStackRoutesFolder, g as isValCanvasFrame, i as isValCanvasPageMessage, f as isValCanvasStudioMessage, p as parseRoutePattern, w as withValCanvasParam } from '../../dist/getSitemapTree-6fcdf745.esm.js';
|
|
2
2
|
import '@valbuild/core';
|
|
@@ -423,6 +423,7 @@ export declare const Api: {
|
|
|
423
423
|
profileId: z.ZodNullable<z.ZodString>;
|
|
424
424
|
mode: z.ZodUnion<readonly [z.ZodLiteral<"http">, z.ZodLiteral<"fs">]>;
|
|
425
425
|
jsonEntriesSha: z.ZodOptional<z.ZodString>;
|
|
426
|
+
sourceMode: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"managed">, z.ZodLiteral<"connected">]>>;
|
|
426
427
|
publishRefusal: z.ZodOptional<z.ZodObject<{
|
|
427
428
|
code: z.ZodLiteral<"no-base">;
|
|
428
429
|
message: z.ZodString;
|
|
@@ -466,6 +467,7 @@ export declare const Api: {
|
|
|
466
467
|
}, z.core.$strip>;
|
|
467
468
|
profileId: z.ZodNullable<z.ZodString>;
|
|
468
469
|
mode: z.ZodUnion<readonly [z.ZodLiteral<"http">, z.ZodLiteral<"fs">]>;
|
|
470
|
+
sourceMode: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"managed">, z.ZodLiteral<"connected">]>>;
|
|
469
471
|
publishRefusal: z.ZodOptional<z.ZodObject<{
|
|
470
472
|
code: z.ZodLiteral<"no-base">;
|
|
471
473
|
message: z.ZodString;
|
|
@@ -474,6 +476,63 @@ export declare const Api: {
|
|
|
474
476
|
}, z.core.$strip>]>;
|
|
475
477
|
};
|
|
476
478
|
};
|
|
479
|
+
/**
|
|
480
|
+
* The content service's publish API, reached through this deployment.
|
|
481
|
+
*
|
|
482
|
+
* A managed project is built in the Studio's own tab -- there is no host
|
|
483
|
+
* watching a repository to pick a commit up -- and what it builds then has to
|
|
484
|
+
* be published. The browser cannot do that itself: it holds a session cookie
|
|
485
|
+
* for this origin and no credential content would accept. So the publish
|
|
486
|
+
* conversation goes through here, exactly as patches already do.
|
|
487
|
+
*
|
|
488
|
+
* ## Deliberately not typed past this point
|
|
489
|
+
*
|
|
490
|
+
* No `res`, and a body this does not describe. The shapes belong to content,
|
|
491
|
+
* and they are already written down twice -- once in content's own `Api.ts`
|
|
492
|
+
* and once in the copy `@valbuild/cli` keeps beside its parser so the two can
|
|
493
|
+
* be diffed by eye. A third copy here would be a third thing to keep in step,
|
|
494
|
+
* and this layer has no decision to make that would need one: it authenticates
|
|
495
|
+
* the caller, swaps the credential, and carries status and body back verbatim.
|
|
496
|
+
* The Studio parses the answer with the same module the CLI does.
|
|
497
|
+
*
|
|
498
|
+
* That means this route answers with statuses the generic result type does not
|
|
499
|
+
* model -- a 409 when a publish has already gone live, for one. `/save` has
|
|
500
|
+
* always done the same; the dispatch boundary erases the type and the adapter
|
|
501
|
+
* sends what it is given.
|
|
502
|
+
*
|
|
503
|
+
* ## What it may reach
|
|
504
|
+
*
|
|
505
|
+
* The sub-path is content's, minus `/v1`: `/publish`, `/publish/{id}` and its
|
|
506
|
+
* three steps, and `/build-target`. `ValOpsHttp.publishApi` holds the allow
|
|
507
|
+
* list, beside the credential, because the browser chooses this path.
|
|
508
|
+
*/
|
|
509
|
+
"/publish-api": {
|
|
510
|
+
GET: {
|
|
511
|
+
req: {
|
|
512
|
+
path: z.ZodOptional<z.ZodString>;
|
|
513
|
+
cookies: {
|
|
514
|
+
val_session: z.ZodOptional<z.ZodString>;
|
|
515
|
+
};
|
|
516
|
+
};
|
|
517
|
+
res: z.ZodObject<{
|
|
518
|
+
status: z.ZodNumber;
|
|
519
|
+
json: z.ZodUnknown;
|
|
520
|
+
}, z.core.$strip>;
|
|
521
|
+
};
|
|
522
|
+
POST: {
|
|
523
|
+
req: {
|
|
524
|
+
path: z.ZodOptional<z.ZodString>;
|
|
525
|
+
body: z.ZodOptional<z.ZodUnknown>;
|
|
526
|
+
cookies: {
|
|
527
|
+
val_session: z.ZodOptional<z.ZodString>;
|
|
528
|
+
};
|
|
529
|
+
};
|
|
530
|
+
res: z.ZodObject<{
|
|
531
|
+
status: z.ZodNumber;
|
|
532
|
+
json: z.ZodUnknown;
|
|
533
|
+
}, z.core.$strip>;
|
|
534
|
+
};
|
|
535
|
+
};
|
|
477
536
|
"/upload/patches": {
|
|
478
537
|
POST: {
|
|
479
538
|
req: {
|
|
@@ -1017,6 +1076,7 @@ export declare const Api: {
|
|
|
1017
1076
|
status: z.ZodLiteral<200>;
|
|
1018
1077
|
json: z.ZodObject<{
|
|
1019
1078
|
commitSha: z.ZodOptional<z.ZodString>;
|
|
1079
|
+
sourceFiles: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNullable<z.ZodString>>>;
|
|
1020
1080
|
removed: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
1021
1081
|
patchId: z.ZodString & z.ZodType<import("@valbuild/core").PatchId, string, z.core.$ZodTypeInternals<import("@valbuild/core").PatchId, string>>;
|
|
1022
1082
|
moduleFilePath: z.ZodString & z.ZodType<ModuleFilePath, string, z.core.$ZodTypeInternals<ModuleFilePath, string>>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from "./richtext/conversion/index.js";
|
|
2
2
|
export * from "./server/types.js";
|
|
3
|
+
export * from "./server/requestScopedMemo.js";
|
|
3
4
|
export * from "./ValClient.js";
|
|
4
5
|
export * from "./ValUrls.js";
|
|
5
6
|
export * from "./ApiRoutes.js";
|
|
@@ -32,3 +33,5 @@ export * from "./search/traverseSchemaSource.js";
|
|
|
32
33
|
export * from "./search/getFilenameFromRef.js";
|
|
33
34
|
export * from "./search/sourcePath.js";
|
|
34
35
|
export * from "./theme/accentRamp.js";
|
|
36
|
+
export * from "./publish/contentApi.js";
|
|
37
|
+
export * from "./publish/protocol.js";
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The publish API's types, COPIED from the service that serves them.
|
|
3
|
+
*
|
|
4
|
+
* Source: `content/src/handlers/Api.ts` in valbuild/home, branch
|
|
5
|
+
* `claude/new-project-studio-saves-0g9ksb`, commit `ecf3b8d`. The routes below
|
|
6
|
+
* are that file's `/publish*` entries and the two types they use, verbatim,
|
|
7
|
+
* comments included, so that the two can be diffed by eye.
|
|
8
|
+
*
|
|
9
|
+
* **Copied rather than imported, because it cannot be imported.** That file
|
|
10
|
+
* lives in a private repository which publishes nothing to npm, and its own
|
|
11
|
+
* header says this is how it is kept in step: "We have also used it (by just
|
|
12
|
+
* copying it in and setting the types there) in the @valbuild/server package to
|
|
13
|
+
* check that we are more or less in sync."
|
|
14
|
+
*
|
|
15
|
+
* **When home's `Api.ts` changes, change this with it.** Everything in
|
|
16
|
+
* `protocol.ts` is checked against these types, so a copy brought up to date
|
|
17
|
+
* fails to compile wherever this CLI has not caught up. That is the whole
|
|
18
|
+
* value: without it a wire change is a runtime 500 in somebody's CI, which is
|
|
19
|
+
* how `home` and `@valbuild/server` have already diverged three times - see
|
|
20
|
+
* `homeWireContract.test.ts` in `@valbuild/server` for the last one, and
|
|
21
|
+
* `publishWireContract.test.ts` here for the fixtures that go with these types.
|
|
22
|
+
*
|
|
23
|
+
* A copy is not a guarantee, only a tripwire: nothing checks it against the
|
|
24
|
+
* service, and the parsers in `protocol.ts` are what actually holds at runtime.
|
|
25
|
+
*/
|
|
26
|
+
export type ContentPublishApi = {
|
|
27
|
+
/**
|
|
28
|
+
* Publishing a build, as a resource with a lifecycle.
|
|
29
|
+
*
|
|
30
|
+
* ```
|
|
31
|
+
* POST /publish declare -> an upload slot per MISSING artifact
|
|
32
|
+
* PUT <presigned url> upload -> each artifact, straight to storage
|
|
33
|
+
* POST /publish/{id}/artifacts confirm -> the uploads are checked
|
|
34
|
+
* POST /publish/{id}/verify render -> a canary build, server side
|
|
35
|
+
* POST /publish/{id}/promote go live -> the pointer moves
|
|
36
|
+
* GET /publish/{id} status
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* Four steps rather than one `POST /publish`, because they fail differently
|
|
40
|
+
* and one call cannot say "the bytes are fine but it did not render" -- which
|
|
41
|
+
* is the sentence a publisher most needs. `promote` is separate from `verify`
|
|
42
|
+
* so that a dry run is the absence of a call rather than a flag.
|
|
43
|
+
*
|
|
44
|
+
* Not keyed by a project, like `/publish-target` above and for the same
|
|
45
|
+
* reason: a project token names one. That is what lets a generated repository
|
|
46
|
+
* hold one secret and no variables at all.
|
|
47
|
+
*
|
|
48
|
+
* ## What is deliberately not here
|
|
49
|
+
*
|
|
50
|
+
* No loader URL, no `vendorRev`, no `x-platform-project`. This service holds
|
|
51
|
+
* the operator relationship with the build platform and calls it; a publisher
|
|
52
|
+
* talks to this API and nothing else. The one exception is the presigned
|
|
53
|
+
* upload URL, which points at object storage -- bytes do not travel through
|
|
54
|
+
* here.
|
|
55
|
+
*
|
|
56
|
+
* It is also what makes this publishable by a project token at all. The
|
|
57
|
+
* platform's own verify step publishes a canary to a throwaway project, and a
|
|
58
|
+
* throwaway has no secrets, so the loader has nothing to check a caller
|
|
59
|
+
* against and refuses with a 503 naming a project nobody has heard of. Behind
|
|
60
|
+
* this API the caller is never involved in that exchange.
|
|
61
|
+
*/
|
|
62
|
+
"/publish": {
|
|
63
|
+
POST: {
|
|
64
|
+
body: {
|
|
65
|
+
/** The build's own hash. Repeating it returns the same publish. */
|
|
66
|
+
buildHash: string;
|
|
67
|
+
/** Null only for a seed publish. See `publishPlan.ts`. */
|
|
68
|
+
commit: string | null;
|
|
69
|
+
/** The branch content saves commit to. Required when `commit` is set. */
|
|
70
|
+
branch: string | null;
|
|
71
|
+
/** Which dependency layer this was built against, sent or not. */
|
|
72
|
+
layerRev: string | null;
|
|
73
|
+
/**
|
|
74
|
+
* Whether the app links its own CSS.
|
|
75
|
+
*
|
|
76
|
+
* Build metadata the loader needs and no artifact carries, so it has to
|
|
77
|
+
* be declared. Null is a third answer -- "this build did not say" --
|
|
78
|
+
* and is not false.
|
|
79
|
+
*/
|
|
80
|
+
linksOwnCss: boolean | null;
|
|
81
|
+
artifacts: {
|
|
82
|
+
/** See `publishPlan.ts` for the namespace. */
|
|
83
|
+
key: string;
|
|
84
|
+
sha256: string;
|
|
85
|
+
bytes: number;
|
|
86
|
+
}[];
|
|
87
|
+
};
|
|
88
|
+
res: {
|
|
89
|
+
publishId: string;
|
|
90
|
+
state: PublishState;
|
|
91
|
+
project: {
|
|
92
|
+
publicProjectId: string;
|
|
93
|
+
/** Null when the project has no site yet -- reported, not refused. */
|
|
94
|
+
siteUrl: string | null;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* One per artifact this project does not already hold, and nothing else.
|
|
98
|
+
*
|
|
99
|
+
* So this doubles as the answer to "what is missing": there is no
|
|
100
|
+
* separate field to keep in step with it. Slots expire; a presigned PUT
|
|
101
|
+
* that answers 403 means declare again, not that the publish failed.
|
|
102
|
+
*/
|
|
103
|
+
uploads: {
|
|
104
|
+
key: string;
|
|
105
|
+
url: string;
|
|
106
|
+
method: "PUT";
|
|
107
|
+
headers: Record<string, string>;
|
|
108
|
+
/** ISO 8601. */
|
|
109
|
+
expiresAt: string;
|
|
110
|
+
}[];
|
|
111
|
+
/** Keys already held, so a caller can see what it did not have to send. */
|
|
112
|
+
have: string[];
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
"/publish/:publishId": {
|
|
117
|
+
GET: {
|
|
118
|
+
res: {
|
|
119
|
+
publishId: string;
|
|
120
|
+
state: PublishState;
|
|
121
|
+
buildHash: string;
|
|
122
|
+
/** Artifact keys still not uploaded. */
|
|
123
|
+
missing: string[];
|
|
124
|
+
problems: PublishProblem[];
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
/** Everything asked for has been uploaded. The uploads are checked here. */
|
|
129
|
+
"/publish/:publishId/artifacts": {
|
|
130
|
+
POST: {
|
|
131
|
+
res: {
|
|
132
|
+
state: PublishState;
|
|
133
|
+
problems: PublishProblem[];
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
/** A canary build and render, on the build platform, with our credential. */
|
|
138
|
+
"/publish/:publishId/verify": {
|
|
139
|
+
POST: {
|
|
140
|
+
res: {
|
|
141
|
+
state: PublishState;
|
|
142
|
+
ok: boolean;
|
|
143
|
+
/** Where the canary can be looked at, when it rendered. */
|
|
144
|
+
previewUrl: string | null;
|
|
145
|
+
problems: PublishProblem[];
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Move the project's pointer to this build.
|
|
151
|
+
*
|
|
152
|
+
* Refused when the commit is no longer the branch head -- which this service
|
|
153
|
+
* is the authority on (see `getGitHead`), so the rule is enforced where the
|
|
154
|
+
* data already is rather than a round trip away.
|
|
155
|
+
*/
|
|
156
|
+
"/publish/:publishId/promote": {
|
|
157
|
+
POST: {
|
|
158
|
+
res: {
|
|
159
|
+
state: PublishState;
|
|
160
|
+
url: string | null;
|
|
161
|
+
commit: string | null;
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
/** Exchange a personal access token for a short-lived publish token. */
|
|
166
|
+
"/publish-token": {
|
|
167
|
+
POST: {
|
|
168
|
+
res: {
|
|
169
|
+
/** Shown once, here. Nothing can produce it again. */
|
|
170
|
+
token: string;
|
|
171
|
+
/** ISO 8601, because a `Date` does not survive JSON as one. */
|
|
172
|
+
expiresAt: string | null;
|
|
173
|
+
publicProjectId: string;
|
|
174
|
+
productionUrl: string | null;
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
/**
|
|
180
|
+
* Where a publish is. Mirrors `PublishState` in `utils/publishPlan.ts`, which
|
|
181
|
+
* owns the transition table; this is the wire spelling of it.
|
|
182
|
+
*/
|
|
183
|
+
export type PublishState = "awaiting-artifacts" | "ready" | "verified" | "live" | "failed" | "expired";
|
|
184
|
+
/**
|
|
185
|
+
* Why a publish is not going anywhere.
|
|
186
|
+
*
|
|
187
|
+
* `code` is the stable half -- a pipeline gates on it -- and carries both this
|
|
188
|
+
* API's own codes (`ARTIFACT_MISMATCH`, `POINTER_STALE`, the declaration codes
|
|
189
|
+
* in `publishPlan.ts`) and the build platform's `PLATFORM*` codes passed
|
|
190
|
+
* through from a verify, because rewording those would lose the only sentence
|
|
191
|
+
* that says what to change.
|
|
192
|
+
*
|
|
193
|
+
* `hint` is not decoration. A gate that merely fails is useless in somebody
|
|
194
|
+
* else's pipeline: they get a red build and no idea why.
|
|
195
|
+
*/
|
|
196
|
+
export type PublishProblem = {
|
|
197
|
+
code: string;
|
|
198
|
+
message: string;
|
|
199
|
+
hint?: string;
|
|
200
|
+
keys?: string[];
|
|
201
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ContentPublishApi, PublishProblem, PublishState } from "./contentApi.js";
|
|
3
|
+
/**
|
|
4
|
+
* Reading what content answers.
|
|
5
|
+
*
|
|
6
|
+
* Every type here comes from `contentApi.ts`, which is a copy of the
|
|
7
|
+
* service's own `Api.ts` - so the shapes are not re-derived from what this end
|
|
8
|
+
* wishes it received, and a copy brought up to date fails to compile wherever
|
|
9
|
+
* this CLI has not caught up.
|
|
10
|
+
*
|
|
11
|
+
* The schemas are the runtime half, and they are needed as well as the types:
|
|
12
|
+
* what arrives is JSON over HTTP from a service that versions separately, so a
|
|
13
|
+
* type alone asserts a shape rather than checking one. Each schema is
|
|
14
|
+
* annotated with the copied type it must produce (`z.ZodType<DeclareResponse>`
|
|
15
|
+
* and so on), which is what ties the two halves together - a schema that drifts
|
|
16
|
+
* from the copy does not compile, and an answer that drifts from the schema
|
|
17
|
+
* does not parse. `ValOpsHttp` in `@valbuild/server` reads every other content
|
|
18
|
+
* route the same way.
|
|
19
|
+
*
|
|
20
|
+
* Nothing here is laxer than the copy. An optional field is optional because
|
|
21
|
+
* the service says so; making a required one optional here would hide exactly
|
|
22
|
+
* the drift this file exists to catch.
|
|
23
|
+
*/
|
|
24
|
+
export type { PublishProblem, PublishState };
|
|
25
|
+
export type DeclareBody = ContentPublishApi["/publish"]["POST"]["body"];
|
|
26
|
+
export type DeclareResponse = ContentPublishApi["/publish"]["POST"]["res"];
|
|
27
|
+
export type ArtifactsResponse = ContentPublishApi["/publish/:publishId/artifacts"]["POST"]["res"];
|
|
28
|
+
export type VerifyResponse = ContentPublishApi["/publish/:publishId/verify"]["POST"]["res"];
|
|
29
|
+
export type PromoteResponse = ContentPublishApi["/publish/:publishId/promote"]["POST"]["res"];
|
|
30
|
+
export type StatusResponse = ContentPublishApi["/publish/:publishId"]["GET"]["res"];
|
|
31
|
+
export type PublishTokenResponse = ContentPublishApi["/publish-token"]["POST"]["res"];
|
|
32
|
+
/** An artifact, as it is declared: by key, by hash, by size. */
|
|
33
|
+
export type DeclaredArtifact = DeclareBody["artifacts"][number];
|
|
34
|
+
/** Permission to write one artifact's bytes to object storage. */
|
|
35
|
+
export type UploadSlot = DeclareResponse["uploads"][number];
|
|
36
|
+
/**
|
|
37
|
+
* Content answered, and the answer was not one this CLI can act on.
|
|
38
|
+
*
|
|
39
|
+
* Separate from a refusal, which is content saying no in a sentence meant for
|
|
40
|
+
* a person. This is two programs disagreeing - the copy in `contentApi.ts` and
|
|
41
|
+
* the service that has moved on from it - and the two want opposite things
|
|
42
|
+
* said about them.
|
|
43
|
+
*/
|
|
44
|
+
export declare class PublishProtocolError extends Error {
|
|
45
|
+
constructor(message: string);
|
|
46
|
+
}
|
|
47
|
+
export declare const parseDeclare: (body: unknown) => DeclareResponse;
|
|
48
|
+
export declare const parseArtifacts: (body: unknown) => ArtifactsResponse;
|
|
49
|
+
export declare const parseVerify: (body: unknown) => VerifyResponse;
|
|
50
|
+
export declare const parsePromote: (body: unknown) => PromoteResponse;
|
|
51
|
+
export declare const parseStatus: (body: unknown) => StatusResponse;
|
|
52
|
+
export declare const parsePublishToken: (body: unknown, call: string) => PublishTokenResponse;
|
|
53
|
+
/**
|
|
54
|
+
* What a build targets, and the project's files, as the Studio asks for them.
|
|
55
|
+
*
|
|
56
|
+
* Both are read through `/publish-api` rather than fetched from the loader,
|
|
57
|
+
* because a browser has neither the loader's address nor a credential it would
|
|
58
|
+
* accept. `fetchBuildTarget` in `@valbuild/tanstack-build` is the other half --
|
|
59
|
+
* same shape, a different caller, and it CASTS where this parses.
|
|
60
|
+
*
|
|
61
|
+
* Parsed rather than cast here because of what sits in between: a proxy, a
|
|
62
|
+
* gateway and a service boundary. An HTML error page cast to `BuildTarget`
|
|
63
|
+
* fails several layers down inside rolldown with a message about a module
|
|
64
|
+
* specifier, and the publish that follows would label a build against a layer
|
|
65
|
+
* that was never there.
|
|
66
|
+
*
|
|
67
|
+
* The TYPE is inferred from this schema instead of imported. `BuildTarget` is
|
|
68
|
+
* declared in `@valbuild/tanstack-build`, which has no zod and deliberately
|
|
69
|
+
* almost no dependencies -- it goes into a browser bundle -- and this package
|
|
70
|
+
* is imported by everything. The declarations are kept honest at the one place
|
|
71
|
+
* the value is used: it is passed straight into `buildUserApp({ target })`, so
|
|
72
|
+
* a drift between them is a compile error at that call rather than a runtime
|
|
73
|
+
* surprise.
|
|
74
|
+
*/
|
|
75
|
+
declare const buildTargetResponse: z.ZodObject<{
|
|
76
|
+
base: z.ZodObject<{
|
|
77
|
+
rev: z.ZodString;
|
|
78
|
+
shellRev: z.ZodString;
|
|
79
|
+
rscShellRev: z.ZodString;
|
|
80
|
+
modules: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
81
|
+
rscModules: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
82
|
+
paths: z.ZodObject<{
|
|
83
|
+
baseFromProject: z.ZodString;
|
|
84
|
+
projectVendorDir: z.ZodString;
|
|
85
|
+
rscVendorBase: z.ZodString;
|
|
86
|
+
rscRuntimeSpecifier: z.ZodString;
|
|
87
|
+
rscRuntimePath: z.ZodString;
|
|
88
|
+
flightServer: z.ZodString;
|
|
89
|
+
serverFnBase: z.ZodString;
|
|
90
|
+
}, z.core.$strip>;
|
|
91
|
+
}, z.core.$strip>;
|
|
92
|
+
project: z.ZodObject<{
|
|
93
|
+
rev: z.ZodNullable<z.ZodString>;
|
|
94
|
+
rsc: z.ZodBoolean;
|
|
95
|
+
modules: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
96
|
+
css: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
97
|
+
workerOnly: z.ZodArray<z.ZodString>;
|
|
98
|
+
}, z.core.$strip>;
|
|
99
|
+
}, z.core.$strip>;
|
|
100
|
+
export type BuildTargetResponse = z.infer<typeof buildTargetResponse>;
|
|
101
|
+
/**
|
|
102
|
+
* `null` files is a real answer: a project that has never published.
|
|
103
|
+
*
|
|
104
|
+
* Distinguished from an empty record, which would be a project that published
|
|
105
|
+
* nothing -- and which a build would take as "no entry point" rather than as
|
|
106
|
+
* "there is nothing here yet".
|
|
107
|
+
*/
|
|
108
|
+
declare const projectSourceResponse: z.ZodObject<{
|
|
109
|
+
files: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
110
|
+
}, z.core.$strip>;
|
|
111
|
+
export type ProjectSourceResponse = z.infer<typeof projectSourceResponse>;
|
|
112
|
+
export declare const parseBuildTarget: (body: unknown) => BuildTargetResponse;
|
|
113
|
+
export declare const parseProjectSource: (body: unknown) => ProjectSourceResponse;
|
|
114
|
+
/**
|
|
115
|
+
* The `details` of a refusal, when it carries a list of problems.
|
|
116
|
+
*
|
|
117
|
+
* Every non-2xx is `{ statusCode, message, details? }`, and the publish routes
|
|
118
|
+
* put `PublishProblem[]` in `details` - a declaration is answered with EVERY
|
|
119
|
+
* problem rather than the first, because finding them one round trip at a time
|
|
120
|
+
* is how a publish takes six attempts. Anything else in there is somebody
|
|
121
|
+
* else's `details` and is left to the caller's own message.
|
|
122
|
+
*/
|
|
123
|
+
export declare function parseProblems(details: unknown): PublishProblem[];
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One value, computed once per REQUEST.
|
|
3
|
+
*
|
|
4
|
+
* `fetchVal` in draft mode reads the whole module tree out of `/sources/~`, and
|
|
5
|
+
* a page that calls it three times used to ask three times — the answer being
|
|
6
|
+
* byte-identical each time, because the query and the session are fixed for the
|
|
7
|
+
* length of a request. This is what makes the second and third call free.
|
|
8
|
+
*
|
|
9
|
+
* ## Why the scope is the REQUEST and can never be wider
|
|
10
|
+
*
|
|
11
|
+
* The memoised response is `own_patch_groups_only: true` — it contains the
|
|
12
|
+
* CALLER'S OWN unpublished work, resolved from their session cookie. A memo
|
|
13
|
+
* that outlived the request would serve one author's staged edits to the next
|
|
14
|
+
* visitor, which is the exact failure `own_patch_groups_only` exists to
|
|
15
|
+
* prevent, and it would be invisible: the draft would simply render somebody
|
|
16
|
+
* else's half-finished sentence.
|
|
17
|
+
*
|
|
18
|
+
* So this module holds NO state of its own. The caller supplies the box, and
|
|
19
|
+
* the box has to come from something the framework already scopes to the
|
|
20
|
+
* request — React's `cache()` in an RSC, a `WeakMap` keyed on the `Request` in
|
|
21
|
+
* TanStack Start. `null` means "no request scope available", and that is not an
|
|
22
|
+
* error: it falls back to computing every time, which is what the code did
|
|
23
|
+
* before this existed. Ineffective is a safe failure here; leaking is not.
|
|
24
|
+
*
|
|
25
|
+
* The `key` is the second half of that guarantee. It carries whatever the
|
|
26
|
+
* answer depends on — the session — so a box that somehow outlived its request,
|
|
27
|
+
* or is shared by two sessions, MISSES rather than serving the wrong draft.
|
|
28
|
+
*
|
|
29
|
+
* ## The promise is cached, not the value
|
|
30
|
+
*
|
|
31
|
+
* React renders siblings concurrently, so three `fetchVal` calls in one page
|
|
32
|
+
* are usually in flight at the same time rather than one after another.
|
|
33
|
+
* Caching the resolved value would dedupe none of them; caching the promise
|
|
34
|
+
* dedupes both orderings.
|
|
35
|
+
*/
|
|
36
|
+
export type RequestScopedMemo<T> = {
|
|
37
|
+
key?: string;
|
|
38
|
+
promise?: Promise<T>;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Returns the memoised promise for `key`, computing it only on a miss.
|
|
42
|
+
*
|
|
43
|
+
* A rejection is memoised along with everything else, on purpose: one failed
|
|
44
|
+
* read per request is the same answer for every caller in it, and retrying it
|
|
45
|
+
* two more times only makes a broken draft slower to render.
|
|
46
|
+
*/
|
|
47
|
+
export declare function memoizePerRequest<T>(box: RequestScopedMemo<T> | null, key: string, compute: () => Promise<T>): Promise<T>;
|
|
@@ -488,6 +488,7 @@ exports._arrayLikeToArray = _arrayLikeToArray;
|
|
|
488
488
|
exports._createForOfIteratorHelper = _createForOfIteratorHelper;
|
|
489
489
|
exports._defineProperty = _defineProperty;
|
|
490
490
|
exports._objectSpread2 = _objectSpread2;
|
|
491
|
+
exports._toPropertyKey = _toPropertyKey;
|
|
491
492
|
exports._typeof = _typeof;
|
|
492
493
|
exports._unsupportedIterableToArray = _unsupportedIterableToArray;
|
|
493
494
|
exports.getNextAppRouterSourceFolder = getNextAppRouterSourceFolder;
|
|
@@ -475,4 +475,4 @@ function getPatternFromModuleFilePath(moduleFilePath, srcFolder) {
|
|
|
475
475
|
.replace("/page.val.ts", "").replace("/page.val.js", "");
|
|
476
476
|
}
|
|
477
477
|
|
|
478
|
-
export { DEFAULT_CANVAS_SELECTION as D, VAL_CONFIG_SESSION_STORAGE_KEY as V, _createForOfIteratorHelper as _, VAL_THEME_SESSION_STORAGE_KEY as a, VAL_AI_SESSION_STORAGE_KEY as b, VAL_CANVAS_PARAM as c, VAL_CANVAS_MESSAGE as d, DEFAULT_CANVAS_SELECTION_SOFT as e, isValCanvasStudioMessage as f, isValCanvasFrame as g, getNextAppRouterSourceFolder as h, isValCanvasPageMessage as i, getTanStackRouterSourceFolder as j, getPageRouterSourceFolder as k, isTanStackRoutesFolder as l, getPageRouterSitemapTree as m, getPatternFromModuleFilePath as n, _objectSpread2 as o, parseRoutePattern as p, _arrayLikeToArray as q, _unsupportedIterableToArray as r, _typeof as s, _defineProperty as t, withValCanvasParam as w };
|
|
478
|
+
export { DEFAULT_CANVAS_SELECTION as D, VAL_CONFIG_SESSION_STORAGE_KEY as V, _createForOfIteratorHelper as _, VAL_THEME_SESSION_STORAGE_KEY as a, VAL_AI_SESSION_STORAGE_KEY as b, VAL_CANVAS_PARAM as c, VAL_CANVAS_MESSAGE as d, DEFAULT_CANVAS_SELECTION_SOFT as e, isValCanvasStudioMessage as f, isValCanvasFrame as g, getNextAppRouterSourceFolder as h, isValCanvasPageMessage as i, getTanStackRouterSourceFolder as j, getPageRouterSourceFolder as k, isTanStackRoutesFolder as l, getPageRouterSitemapTree as m, getPatternFromModuleFilePath as n, _objectSpread2 as o, parseRoutePattern as p, _arrayLikeToArray as q, _unsupportedIterableToArray as r, _typeof as s, _defineProperty as t, _toPropertyKey as u, withValCanvasParam as w };
|
|
@@ -488,6 +488,7 @@ exports._arrayLikeToArray = _arrayLikeToArray;
|
|
|
488
488
|
exports._createForOfIteratorHelper = _createForOfIteratorHelper;
|
|
489
489
|
exports._defineProperty = _defineProperty;
|
|
490
490
|
exports._objectSpread2 = _objectSpread2;
|
|
491
|
+
exports._toPropertyKey = _toPropertyKey;
|
|
491
492
|
exports._typeof = _typeof;
|
|
492
493
|
exports._unsupportedIterableToArray = _unsupportedIterableToArray;
|
|
493
494
|
exports.getNextAppRouterSourceFolder = getNextAppRouterSourceFolder;
|