@valbuild/server 0.132.1 → 0.134.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/CHANGELOG.md CHANGED
@@ -1,5 +1,140 @@
1
1
  # @valbuild/server
2
2
 
3
+ ## 0.134.0
4
+
5
+ ### Patch Changes
6
+
7
+ - [#708](https://github.com/valbuild/val/pull/708) [`be78a9b`](https://github.com/valbuild/val/commit/be78a9b51678439f7ecb1c7f3887f9e8e1261a13) Thanks [@freekh](https://github.com/freekh)! - Fix `gitCommit` and `gitBranch` in `val.config.ts` never reaching the server,
8
+ and make them the one way to say it.
9
+
10
+ `val.config.ts` has had `gitCommit` and `gitBranch` for as long as it has had
11
+ anything, and a deployed app that set them resolved to no repository at all.
12
+ Nothing failed and nothing was logged: the commit was simply never sent, and
13
+ every patch the project saved recorded none.
14
+
15
+ ```ts
16
+ // val.config.ts — the normal Vercel shape, and it did nothing
17
+ initVal({
18
+ project: "org/project",
19
+ gitCommit: process.env.VERCEL_GIT_COMMIT_SHA,
20
+ gitBranch: process.env.VERCEL_GIT_COMMIT_REF,
21
+ });
22
+ ```
23
+
24
+ The two halves never met. The server wanted a nested
25
+ `git: { commit, branch }`, `val.config.ts` offered two flat keys, and nothing
26
+ mapped one onto the other — so the only thing that worked was
27
+ `VAL_GIT_COMMIT` / `VAL_GIT_BRANCH` in the environment.
28
+
29
+ Rather than teach the server to read both, the nested option is gone:
30
+ `ValApiOptions` and TanStack's `ValHttpMode` now take `gitCommit` and
31
+ `gitBranch`, the same two names `ValConfig` uses. There is one name for this
32
+ now, wherever it is set. The framework bindings already hand the server
33
+ `{ versions, ...config }`, so a project's config keys arrive with nothing to
34
+ map, and a host passing them directly is saying the same thing in the same
35
+ words.
36
+
37
+ Flat because of where these values come from. A platform supplies them as
38
+ `process.env.VERCEL_GIT_COMMIT_SHA` and friends, typed `string | undefined`,
39
+ and two optional strings take that as it comes — a nested object makes every
40
+ caller write the ternary that turns two maybe-strings into one maybe-object.
41
+
42
+ **Breaking for a host that passed `git` itself**, which is the nested option on
43
+ `ValApiOptions` and on TanStack's `http` mode. An app that only configures
44
+ `val.config.ts` or the environment is unaffected.
45
+
46
+ ```ts
47
+ // before
48
+ initValServer(valModules, config, {
49
+ http: { apiKey, valSecret, git: { commit, branch } },
50
+ });
51
+
52
+ // after
53
+ initValServer(valModules, config, {
54
+ http: { apiKey, valSecret, gitCommit: commit, gitBranch: branch },
55
+ });
56
+ ```
57
+
58
+ **What a commit is for**, and why its absence is worth a release rather than a
59
+ shrug: publishing turns pending patches into new `.val.ts` text, and to patch a
60
+ file you must first read it — at a commit. A project with none is a project
61
+ whose publishes read whatever the content service last had, rather than the
62
+ revision the deployed code was built from.
63
+
64
+ **One more behaviour change.** A commit and a branch have always been taken
65
+ together or not at all, and that check now sees config-supplied values too. A
66
+ project that sets exactly one of `gitCommit` and `gitBranch` used to have both
67
+ quietly ignored and will now be refused at startup, naming the missing half.
68
+ That is the configuration that would otherwise fail later, at a publish.
69
+
70
+ - Updated dependencies [[`1c31dcc`](https://github.com/valbuild/val/commit/1c31dcca7f1bb0199350567fd579de29f48bb26d), [`688b9e3`](https://github.com/valbuild/val/commit/688b9e36b821323cda6870cdff03dd36ec3e782f), [`16c49ea`](https://github.com/valbuild/val/commit/16c49ea6dd97c7a96bbfdf211af9bab5884579e2), [`eaa265e`](https://github.com/valbuild/val/commit/eaa265e78d9f6d2a1b685c38901612b8a3704eed)]:
71
+ - @valbuild/ui@0.134.0
72
+ - @valbuild/core@0.134.0
73
+ - @valbuild/shared@0.134.0
74
+
75
+ ## 0.133.0
76
+
77
+ ### Minor Changes
78
+
79
+ - [#700](https://github.com/valbuild/val/pull/700) [`802412b`](https://github.com/valbuild/val/commit/802412b92c06bc1abbca78c86885e39c8710dd83) Thanks [@freekh](https://github.com/freekh)! - http mode no longer needs a git repository
80
+
81
+ A Val app can now run in http mode with no commit and no branch — its content
82
+ service is the store of record, and git is an optional mirror of the code. This
83
+ is what `fs` mode has always done: it has never had git, and it works.
84
+
85
+ Before this, `VAL_API_KEY` and `VAL_SECRET` were not enough. `VAL_GIT_COMMIT`
86
+ and `VAL_GIT_BRANCH` were required too, so a deployment with no commit to name
87
+ either threw at boot or fell through to `fs` mode and reached for a working
88
+ tree that was not there.
89
+
90
+ **Breaking, if you pass `http` options in code.** `gitCommit` and `gitBranch`
91
+ are replaced by one optional `git`:
92
+
93
+ ```diff
94
+ initValServer(valModules, config, {
95
+ http: {
96
+ apiKey,
97
+ valSecret,
98
+ - gitCommit: process.env.VAL_GIT_COMMIT,
99
+ - gitBranch: "main",
100
+ + // Only for a project whose content is mirrored into a repository.
101
+ + // Omit it entirely otherwise.
102
+ + git: { commit: process.env.VAL_GIT_COMMIT, branch: "main" },
103
+ },
104
+ })
105
+ ```
106
+
107
+ `VAL_GIT_COMMIT` and `VAL_GIT_BRANCH` still work and are still read; they are
108
+ simply no longer required. Set both or neither — a commit without a branch, or
109
+ a branch without a commit, is refused at startup with a message naming the
110
+ missing half, rather than failing later at a publish.
111
+
112
+ **What a commit is for, where you have one.** Turning pending patches into new
113
+ `.val.ts` text means reading the current text first, and that read goes to the
114
+ content service at that commit. It is the publish path, not the serving path: a
115
+ committed render reads the source compiled into the build and asks the content
116
+ service nothing. With no repository there is nothing to write `.val.ts` into,
117
+ so a publish records the module's data and its schema and skips the file — and
118
+ that data is what history reads, so nothing is lost.
119
+
120
+ **A publish can now be refused by name, before it is attempted.** If a project
121
+ mirrors its commits into a repository but the running deployment was built
122
+ before that repository existed, it has no commit to write the mirror against.
123
+ Publishing anyway would save the content and silently leave the repository
124
+ behind. The Studio now disables Publish and shows why, and `/save` refuses with
125
+ a `no-base` code instead of failing partway.
126
+
127
+ **Also:** `ValCommit` and `HistoricalCommit` have nullable `parentCommitSha`
128
+ and `clientCommitSha`, and `/stat`'s `commitSha` is optional. A root commit has
129
+ no parent, and a publisher with no repository does not report where it was. If
130
+ you read these fields, handle `null`.
131
+
132
+ ### Patch Changes
133
+
134
+ - Updated dependencies [[`2b9a51b`](https://github.com/valbuild/val/commit/2b9a51b7dbe2dff53b7686a4f3b7eb9bc5784fae), [`802412b`](https://github.com/valbuild/val/commit/802412b92c06bc1abbca78c86885e39c8710dd83)]:
135
+ - @valbuild/ui@0.133.0
136
+ - @valbuild/shared@0.133.0
137
+
3
138
  ## 0.132.1
4
139
 
5
140
  ### Patch Changes
@@ -137,7 +137,8 @@ export declare abstract class ValOps {
137
137
  nonce: string;
138
138
  baseSha: BaseSha;
139
139
  schemaSha: SchemaSha;
140
- commitSha: CommitSha;
140
+ /** Absent for a project with no repository. See `git` on ValApiOptions. */
141
+ commitSha?: CommitSha;
141
142
  sourcesSha: SourcesSha;
142
143
  patches: PatchId[];
143
144
  } | {
@@ -401,6 +402,42 @@ export declare abstract class ValOps {
401
402
  } | {
402
403
  errorType: "patch-head-conflict";
403
404
  }>>;
405
+ /**
406
+ * Why a publish cannot happen here, or `null` when one can.
407
+ *
408
+ * Named rather than thrown, and asked BEFORE the click: the Studio shows the
409
+ * reason and disables the action, instead of letting someone write a commit
410
+ * message and then meeting a failure from four layers down.
411
+ *
412
+ * `no-base` is the only code so far and means what it says: there is nowhere
413
+ * for this publish's commit to be based. Note which way round that is --
414
+ * a project whose content service is the store of record always HAS a base
415
+ * (the service's own chain, which mints its own shas), so the refusal is not
416
+ * about missing git. It is about a deployment that cannot do what its
417
+ * project requires.
418
+ */
419
+ publishRefusal(): PublishRefusal | null;
420
+ /**
421
+ * Whether a commit here produces `.val.ts` TEXT as well as data.
422
+ *
423
+ * True everywhere there is somewhere to put it: a working tree in `fs` mode,
424
+ * a host holding its own source in memory mode, a git repository in `http`
425
+ * mode. False for an `http` project whose content service is the store of
426
+ * record and which has no repository attached -- see `git` on
427
+ * {@link ValApiOptions}.
428
+ *
429
+ * WHAT IS NOT AFFECTED, and it is the part worth being sure of:
430
+ * `moduleVersions` -- what each changed module IS after the commit, with its
431
+ * schema -- comes from `getSources(analysis)`, which applies the ops to
432
+ * Source in the stores. It does not go near the file text. So a commit with
433
+ * no mirror still records everything history and a later `connect-github`
434
+ * fold need; what it does not record is a rendering of that data as code.
435
+ *
436
+ * WHAT IS: the ops are no longer applied to the file text as well, so a
437
+ * patch that would not fit the `.val.ts` is not reported here. That check
438
+ * only ever existed for the text being produced, and there is none.
439
+ */
440
+ protected readonly mirrorsSourceFiles: boolean;
404
441
  /**
405
442
  * Take the `.val.ts` text a commit produced as the new committed source.
406
443
  *
@@ -624,6 +661,18 @@ export type OpsMetadata<T extends "file" | "image"> = {
624
661
  }))[];
625
662
  };
626
663
  export type BinaryFileType = "file" | "image";
664
+ /**
665
+ * Why a publish is refused, in a form a person can be shown.
666
+ *
667
+ * `code` is for the Studio to branch on and `message` is what it says. Both,
668
+ * rather than a code and a lookup table on the client: the server knows what
669
+ * is actually missing -- which branch, which commit -- and a client-side table
670
+ * could only ever say the generic version.
671
+ */
672
+ export type PublishRefusal = {
673
+ code: "no-base";
674
+ message: string;
675
+ };
627
676
  export type PreparedCommit = {
628
677
  /**
629
678
  * Updated / new source files that are ready to be committed / saved.
@@ -1,6 +1,6 @@
1
1
  import { type PatchId, type ModuleFilePath, ValModules } from "@valbuild/core";
2
2
  import type { Patch as PatchT, ParentRef as ParentRefT } from "@valbuild/core/patch";
3
- import { type AuthorId, type BaseSha, BinaryFileType, type CommitSha, GenericErrorMessage, MetadataOfType, OpsMetadata, PreparedCommit, ValOps, ValOpsOptions, WithGenericError, SaveSourceFilePatchResult, type PatchGroupMembership, SchemaSha, OrderedPatchesMetadata, OrderedPatches, SourcesSha } from "./ValOps.js";
3
+ import { type AuthorId, type BaseSha, BinaryFileType, type CommitSha, GenericErrorMessage, MetadataOfType, OpsMetadata, PreparedCommit, ValOps, ValOpsOptions, WithGenericError, SaveSourceFilePatchResult, type PatchGroupMembership, SchemaSha, OrderedPatchesMetadata, OrderedPatches, SourcesSha, type PublishRefusal } from "./ValOps.js";
4
4
  import { z } from "zod";
5
5
  import type { HistoryError } from "./history/HistoryError.js";
6
6
  import type { AffectedFile, StoredModuleVersion, CommitPage, CommitPatch, HistoricalCommit } from "./history/types.js";
@@ -23,16 +23,69 @@ export type PatchGroupMutationResult = {
23
23
  export declare class ValOpsHttp extends ValOps {
24
24
  private readonly contentUrl;
25
25
  private readonly project;
26
- private readonly commitSha;
27
- private readonly branch;
26
+ /**
27
+ * The repository this project's commits are mirrored into, or `null`.
28
+ *
29
+ * `null` is a project whose content service is the store of record: it
30
+ * mints its own commit shas, and it knows this project's branch from the
31
+ * project itself. Every request below that would have carried a branch and
32
+ * a commit omits them instead, and the service answers from the project's
33
+ * own chain -- which is where those answers always came from.
34
+ *
35
+ * It is NOT a degraded mode. The one thing that genuinely needs a
36
+ * repository is producing the `.val.ts` text a commit mirrors, and a
37
+ * project with no repository has nothing to mirror into. See `git` on
38
+ * {@link ValApiOptions}.
39
+ */
40
+ private readonly git;
28
41
  private readonly authHeaders;
29
42
  private readonly root;
30
43
  /** Val's content service owns the store. See {@link ValOps.patchesAreLocal}. */
31
44
  readonly patchesAreLocal = false;
32
45
  /** See {@link ValOps.requiresAuth}. */
33
46
  readonly requiresAuth = true;
34
- constructor(contentUrl: string, project: string, commitSha: string, // TODO: CommitSha
35
- branch: string,
47
+ /**
48
+ * A commit mirrors into `.val.ts` only when there is a repository.
49
+ *
50
+ * See {@link ValOps.mirrorsSourceFiles}. Set in the constructor rather than
51
+ * as an initialiser because it depends on `git`, and a class field
52
+ * initialiser runs before the constructor body has assigned it.
53
+ */
54
+ protected readonly mirrorsSourceFiles: boolean;
55
+ /**
56
+ * What the content service last said this project expects of its publisher.
57
+ *
58
+ * `null` until something has asked it, which in practice is the first poll.
59
+ * It is remembered rather than asked for on demand because the answer is
60
+ * only wanted on the publish path, and that path already fetches the
61
+ * patches it is publishing -- so a dedicated request would be a second round
62
+ * trip for two fields that just arrived.
63
+ *
64
+ * It can be one poll out of date, and that is the right amount: the thing it
65
+ * changes is whether this deployment can mirror commits into a repository,
66
+ * which changes when a project CONNECTS one -- and a project that has just
67
+ * connected is one whose builds are about to be replaced anyway.
68
+ */
69
+ private projectExpectation;
70
+ constructor(contentUrl: string, project: string,
71
+ /**
72
+ * The repository this project's commits are mirrored into, or `null`.
73
+ *
74
+ * `null` is a project whose content service is the store of record: it
75
+ * mints its own commit shas, and it knows this project's branch from the
76
+ * project itself. Every request below that would have carried a branch and
77
+ * a commit omits them instead, and the service answers from the project's
78
+ * own chain -- which is where those answers always came from.
79
+ *
80
+ * It is NOT a degraded mode. The one thing that genuinely needs a
81
+ * repository is producing the `.val.ts` text a commit mirrors, and a
82
+ * project with no repository has nothing to mirror into. See `git` on
83
+ * {@link ValApiOptions}.
84
+ */
85
+ git: {
86
+ commit: string;
87
+ branch: string;
88
+ } | null,
36
89
  /**
37
90
  * An api key (how the app itself authenticates) or a personal access token
38
91
  * (how the CLI authenticates after `val login`). Same two shapes as
@@ -50,6 +103,29 @@ export declare class ValOpsHttp extends ValOps {
50
103
  */
51
104
  root?: string;
52
105
  });
106
+ /**
107
+ * A deployment that cannot mirror a project which expects to be mirrored.
108
+ *
109
+ * This is the one shape of "no base" that exists, and it is not the one it
110
+ * sounds like. A project with no repository is fine: the content service is
111
+ * the store of record for it and mints its own commit shas, so there is
112
+ * always somewhere for the commit to go. What is refused is the mismatch --
113
+ * a project whose commits are mirrored into a repository, being published by
114
+ * a build that was made before it had one and so has no commit to produce
115
+ * that mirror against.
116
+ *
117
+ * It is a REAL state rather than a defensive check: it is exactly what a
118
+ * deployment looks like between a project connecting a repository and its
119
+ * next build going out. Left unchecked, such a publish writes the data and
120
+ * silently fails to mirror it, and the repository quietly falls behind the
121
+ * content nobody is told about.
122
+ *
123
+ * `null` when the service did not say (see `project` on the response
124
+ * schema): an older content service is not evidence of anything, and
125
+ * refusing every publish against one would be a worse failure than not
126
+ * checking.
127
+ */
128
+ publishRefusal(): PublishRefusal | null;
53
129
  onInit(): Promise<void>;
54
130
  getPresignedAuthNonce(profileId: string, corsOrigin: string): Promise<{
55
131
  status: "success";
@@ -80,7 +156,8 @@ export declare class ValOpsHttp extends ValOps {
80
156
  baseSha: BaseSha;
81
157
  schemaSha: SchemaSha;
82
158
  sourcesSha: SourcesSha;
83
- commitSha: CommitSha;
159
+ /** Absent for a project with no repository. See `git` on ValApiOptions. */
160
+ commitSha?: CommitSha;
84
161
  commits: ValCommit[];
85
162
  deployments: ValDeployment[];
86
163
  patches: PatchId[];
@@ -84,21 +84,50 @@ type ValServerOverrides = Partial<{
84
84
  */
85
85
  patchStore: ValPatchStore;
86
86
  /**
87
- * Current git commit.
88
- *
89
- * Required if mode is "proxy".
87
+ * The git commit this code was built from, and the branch a publish mirrors
88
+ * into -- for a project that HAS a repository.
89
+ *
90
+ * OPTIONAL, including in http mode, and absent is the normal case for a
91
+ * project whose content service is the store of record. It used to be
92
+ * required, which made a repository a precondition for editing anything: a
93
+ * deployment with no commit to name fell through to `fs` mode and reached
94
+ * for a working tree that was not there.
95
+ *
96
+ * What it is FOR, where there is one: a publish turns pending patches into
97
+ * new `.val.ts` text, and to patch a file you must first read it. That read
98
+ * goes to the content service AT THIS COMMIT. Give it a commit the deployed
99
+ * code did not come from and the publish writes over a different version of
100
+ * the file than the one the site is running.
101
+ *
102
+ * It is NOT what a committed render reads -- that reads the source compiled
103
+ * into the build and asks the content service nothing.
104
+ *
105
+ * A normal deploy bakes this at build time, because the commit really is a
106
+ * property of those bytes. `VAL_GIT_COMMIT` / `VAL_GIT_BRANCH` supply it
107
+ * where a build system sets environment variables instead.
108
+ *
109
+ * FLAT, and the same two names `val.config.ts` uses, so that there is one
110
+ * way to say this rather than two. An app reads these off its platform --
111
+ * `process.env.VERCEL_GIT_COMMIT_SHA` and friends, which are typed
112
+ * `string | undefined` -- and a pair of optional strings takes that as it
113
+ * comes. A nested `{ commit, branch }` would make every caller write the
114
+ * ternary that turns two maybe-strings into one maybe-object. Sharing the
115
+ * names with `ValConfig` is what makes the bindings' `{ versions,
116
+ * ...config }` carry them here with nothing to map.
117
+ *
118
+ * Taken together or not at all: `initHandlerOptions` refuses one without
119
+ * the other rather than resolving half a repository.
90
120
  *
91
121
  * @example "e83c5163316f89bfbde7d9ab23ca2e25604af290"
92
122
  */
93
- gitCommit: string;
123
+ gitCommit?: string;
94
124
  /**
95
- * Current git branch.
96
- *
97
- * Required if mode is "proxy".
125
+ * The branch a publish mirrors into. See {@link ValApiOptions.gitCommit},
126
+ * which this is required with and meaningless without.
98
127
  *
99
128
  * @example "main"
100
129
  */
101
- gitBranch: string;
130
+ gitBranch?: string;
102
131
  /**
103
132
  * The base url of Val.
104
133
  *
@@ -121,8 +121,17 @@ export type ValServerConfig = ValServerOptions & ({
121
121
  mode: "http";
122
122
  apiKey: string;
123
123
  project: string;
124
- commit: string;
125
- branch: string;
124
+ /**
125
+ * The repository this project's commits are mirrored into, if any.
126
+ *
127
+ * Absent is a project whose content service is the store of record --
128
+ * which is every project that has not attached a repository, and the
129
+ * normal case. See `git` on {@link ValApiOptions}.
130
+ */
131
+ git?: {
132
+ commit: string;
133
+ branch: string;
134
+ };
126
135
  root?: string;
127
136
  config: ValConfig;
128
137
  }
@@ -205,6 +205,14 @@ export declare function handleJsonValuesExtractEntry(ctx: FixHandlerContext): Pr
205
205
  * `fixableErrorMessage` rather than a plain error, because the error IS fixable
206
206
  * — just not by this command.
207
207
  */
208
+ /**
209
+ * A view pointer that names a module its schema does not.
210
+ *
211
+ * Nothing to look up and nothing to ask: the schema names the module, so the
212
+ * one correct value is already known. `createFixPatch` writes it — this handler
213
+ * exists to send it there, and to say what `--fix` would do when it is off.
214
+ */
215
+ export declare function handleViewCheckModule(ctx: FixHandlerContext): Promise<FixHandlerResult>;
208
216
  export declare function handleExternalUpload(): Promise<FixHandlerResult>;
209
217
  export declare const currentFixHandlers: Record<Exclude<ValidationFix, "keyof:check-keys" | "router:check-route" | "locale:check-locale" | "record:fill-keys">, FixHandler>;
210
218
  export declare const fixHandlers: Record<string, FixHandler>;
@@ -4,8 +4,10 @@ import type { HistoryError } from "./HistoryError.js";
4
4
  /** One commit, as history lists it. */
5
5
  export type HistoricalCommit = {
6
6
  commitSha: string;
7
- parentCommitSha: string;
8
- clientCommitSha: string;
7
+ /** `null` for a root commit. See `ValCommit`. */
8
+ parentCommitSha: string | null;
9
+ /** `null` when the publisher did not say where it was. See `ValCommit`. */
10
+ clientCommitSha: string | null;
9
11
  branch: string;
10
12
  createdBranch: string | null;
11
13
  creator: string | null;