@valbuild/server 0.130.0 → 0.132.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,102 @@
1
1
  # @valbuild/server
2
2
 
3
+ ## 0.132.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#689](https://github.com/valbuild/val/pull/689) [`72cc676`](https://github.com/valbuild/val/commit/72cc6765e92a6e72b5c09ddd9eed8efa7ce899f2) Thanks [@freekh](https://github.com/freekh)! - `VAL_ENV=app` selects `http` mode.
8
+
9
+ A host knows WHERE it is running; which Val mode that implies is Val's to
10
+ derive. `VAL_ENV=app` says "this is the Val app" — a project built in a browser
11
+ and served from a Worker isolate — and Val reads that as http mode: there is no
12
+ disk, so `fs` is never the right fall-through, and the content is Val's own,
13
+ read over HTTP at a commit like any other deployed app.
14
+
15
+ Unlike `VAL_MODE=memory`, this **selects** the mode rather than only refusing a
16
+ fall-through, because everything http mode needs is an environment variable. The
17
+ point is what happens when one is missing: inference reads an absent
18
+ `VAL_API_KEY` as "not a proxy" and resolves `fs` mode, which in an isolate fails
19
+ on `.val/patches.lock` — a path, two layers below the actual mistake. Now each
20
+ of `VAL_API_KEY`, `VAL_SECRET`, `VAL_PROJECT`, `VAL_GIT_COMMIT` and
21
+ `VAL_GIT_BRANCH` is named when it is the one that is not set, and the message
22
+ says which variable put the app in http mode.
23
+
24
+ An explicit `VAL_MODE` still wins, including when it is a typo that has to be
25
+ refused, and `http` is still not a value `VAL_MODE` accepts. A host that passes
26
+ `sourceFiles` still gets memory mode: that is checked before the environment is
27
+ consulted at all, so a build published by an older platform keeps working.
28
+
29
+ ## 0.131.0
30
+
31
+ ### Minor Changes
32
+
33
+ - [#686](https://github.com/valbuild/val/pull/686) [`0d5857b`](https://github.com/valbuild/val/commit/0d5857b731e11f7e6a011f79297df6485908c31f) Thanks [@freekh](https://github.com/freekh)! - Say `VAL_MODE=memory` where there is no disk, and get a sentence instead of an `EPERM`
34
+
35
+ Memory mode — the one for a host that holds the project's source itself — is
36
+ selected by passing `sourceFiles`, and it has to be: nothing in an environment
37
+ can supply a project's source, so a mode that an env var could switch on would
38
+ be a server with no content in it.
39
+
40
+ The cost was the failure when a host forgot. Val inferred `fs` mode, `fs` mode
41
+ went looking for a working tree, and in a Worker isolate the first thing to
42
+ touch the disk failed:
43
+
44
+ ```
45
+ patch-error /bundle/.val/patches.lock: EPERM
46
+ ```
47
+
48
+ That names a path two layers below the decision that caused it, and nobody
49
+ reading it would guess "your server was configured for the wrong mode".
50
+
51
+ So an environment can now DECLARE that it has no disk:
52
+
53
+ ```
54
+ VAL_MODE=memory
55
+ ```
56
+
57
+ It does not turn memory mode on. It says the host is supposed to be supplying
58
+ `sourceFiles`, so if none arrive, Val refuses at configuration time and says
59
+ where to pass them. `VAL_MODE=` counts as unset, the way a shell means it; any
60
+ other value is refused rather than ignored, since leaving you in `fs` mode is
61
+ the exact failure this is meant to catch.
62
+
63
+ **`initValContent` takes the same options, and this is the release that
64
+ noticed.** It builds a Val server of its own — these readers resolve content by
65
+ asking it, not by calling the API over HTTP — so configuring `initValServer`
66
+ alone left them inferring `fs` mode. On a host with no filesystem that is a
67
+ reader looking for a working tree that is not there; it went unnoticed because
68
+ published reads still worked.
69
+
70
+ ```ts
71
+ const patchStore = new InMemoryPatchStore(); // now exported from this package
72
+
73
+ const { valApiHandler, draftMode } = initValServer(valModules, config, {
74
+ sourceFiles: FILES,
75
+ patchStore,
76
+ unsafelyAllowUnauthenticated: true,
77
+ });
78
+
79
+ const { fetchValStega } = initValContent(config, valModules, {
80
+ draftMode,
81
+ // The same three. Two patch stores are two sets of pending edits, and a
82
+ // reader that checks a session the host never issues answers itself 401 and
83
+ // falls back to published content — a draft render showing the live site.
84
+ sourceFiles: FILES,
85
+ patchStore,
86
+ unsafelyAllowUnauthenticated: true,
87
+ });
88
+ ```
89
+
90
+ All three are optional. Left out, this reader gets its own store and its own
91
+ answer about authentication, which is right for published content.
92
+
93
+ `@valbuild/next` has no memory mode: its `initValServer` takes neither option,
94
+ so for a Next app `VAL_MODE=memory` names an environment Val cannot serve from,
95
+ and the error says so.
96
+
97
+ Nothing changes for an app that sets none of this: `http` when `VAL_API_KEY`
98
+ and `VAL_SECRET` are both present, `fs` otherwise, as before.
99
+
3
100
  ## 0.130.0
4
101
 
5
102
  ### Minor Changes
@@ -61,6 +61,11 @@ type ValServerOverrides = Partial<{
61
61
  * and "proxy" this one cannot be inferred from the environment -- there is
62
62
  * nothing to infer it FROM, and a mode that can be turned on without
63
63
  * supplying the source would be a server with no content in it.
64
+ *
65
+ * An environment that has no disk can still say it EXPECTS this, by setting
66
+ * `VAL_MODE=memory`. That does not select the mode; it makes forgetting to
67
+ * pass the source an error here rather than an `EPERM` from `fs` mode two
68
+ * layers down.
64
69
  */
65
70
  sourceFiles: Record<string, string>;
66
71
  /**
@@ -10051,6 +10051,67 @@ function parsePersonalAccessTokenFile(content) {
10051
10051
 
10052
10052
  const DEFAULT_VAL_BUILD_URL = "https://admin.val.build";
10053
10053
 
10054
+ /**
10055
+ * The value of `VAL_ENV` that means "this is the Val app".
10056
+ *
10057
+ * The Val app builds a project in a browser and runs it in a Worker isolate.
10058
+ * There is no disk there and there never will be, so `fs` mode is never the
10059
+ * right fall-through -- and the content is Val's own, reached over HTTP at a
10060
+ * commit, exactly as it is for any other deployed app. So this names `http`.
10061
+ *
10062
+ * What makes the app unusual is not where its content comes from but what
10063
+ * publishing means: the browser rebuilds the site and the new build is served
10064
+ * immediately, instead of a host noticing a commit and redeploying. That is a
10065
+ * difference in what happens AFTER the commit, and `publishOverride` is where
10066
+ * a host says so -- not a difference in where patches, files or sources live.
10067
+ *
10068
+ * A host says WHERE it runs, which is a fact it knows. Which Val mode that
10069
+ * implies is Val's to derive, and that is the whole reason this exists next to
10070
+ * `VAL_MODE` rather than the platform naming a mode itself: one is a
10071
+ * description of an environment, the other an assertion about Val's internals,
10072
+ * and only the first stays true when the internals move. They have already
10073
+ * moved once -- this meant `memory` while the app kept its own patch store --
10074
+ * and no platform had to be changed to follow.
10075
+ */
10076
+ const VAL_APP_ENV = "app";
10077
+
10078
+ /** Which mode the environment SAYS this is, and which variable said so. */
10079
+
10080
+ /**
10081
+ * `null` is "the environment did not say", which is the normal case.
10082
+ *
10083
+ * The two variables differ in what can be DONE with an answer, and the
10084
+ * difference is whether the environment holds everything the mode needs.
10085
+ * `http` does -- an api key, a secret, a project, a commit and a branch are all
10086
+ * env vars -- so `VAL_ENV=app` SELECTS it, and the checks in
10087
+ * {@link initHandlerOptions} name whichever one is missing. `memory` does not:
10088
+ * it needs the host's own source files, which nothing in an environment can
10089
+ * supply, so `VAL_MODE=memory` can only ever turn a fall-through into an error.
10090
+ */
10091
+ function namedMode() {
10092
+ const declared = process.env.VAL_MODE;
10093
+ /*
10094
+ * An empty value counts as unset, which is what `VAL_MODE=` in a shell or a
10095
+ * CI settings page means. An explicit `VAL_MODE` otherwise wins over
10096
+ * `VAL_ENV`: naming a mode outright says something more specific than naming
10097
+ * an environment does, including when what it names is wrong and has to be
10098
+ * refused.
10099
+ */
10100
+ if (declared !== undefined && declared !== "") {
10101
+ return {
10102
+ mode: declared,
10103
+ from: "VAL_MODE"
10104
+ };
10105
+ }
10106
+ if (process.env.VAL_ENV === VAL_APP_ENV) {
10107
+ return {
10108
+ mode: "http",
10109
+ from: "VAL_ENV"
10110
+ };
10111
+ }
10112
+ return null;
10113
+ }
10114
+
10054
10115
  /**
10055
10116
  * Resolve options plus environment into a concrete {@link ValServerConfig}.
10056
10117
  *
@@ -10103,9 +10164,53 @@ async function initHandlerOptions(route, opts, config) {
10103
10164
  config
10104
10165
  };
10105
10166
  }
10167
+ /*
10168
+ * The environment saying 'memory' means the host MEANT to hold the source,
10169
+ * and did not. Either variable can say it: `VAL_MODE=memory` outright, or
10170
+ * `VAL_ENV=app`, which names an environment that has no disk.
10171
+ *
10172
+ * Neither can SELECT memory mode -- nothing in the environment can supply
10173
+ * `sourceFiles`, and a mode turned on without them is a server with no
10174
+ * content in it. What they do is turn the fall-through into an error.
10175
+ *
10176
+ * Without it, a host that forgot to pass its source got `fs` mode, and `fs`
10177
+ * mode in a Worker isolate reaches for a working tree that is not there: the
10178
+ * failure is an `EPERM` on `.val/patches.lock`, several layers below the
10179
+ * mistake, naming a path rather than the decision that led to it. Every
10180
+ * environment that runs Val without a disk can set this once and get a
10181
+ * sentence instead.
10182
+ */
10183
+ const declared = namedMode();
10184
+ if ((declared === null || declared === void 0 ? void 0 : declared.mode) === "memory") {
10185
+ throw new Error("VAL_MODE is 'memory', but no `sourceFiles` were given here, so there " + "is no source to serve. Memory mode cannot be turned on by the " + "environment: it needs the project's own source, and only the host " + "that holds it can hand it over. On TanStack Start that is the " + "`sourceFiles` option, passed to `initValServer` AND to " + "`initValContent`, which has a Val server of its own and is " + "configured separately. @valbuild/next has no memory mode yet, so " + "for a Next app this variable is set on an environment Val cannot " + "serve from. Unset VAL_MODE to go back to the inferred mode instead " + "('http' when VAL_API_KEY and VAL_SECRET are both set, 'fs' " + "otherwise).");
10186
+ }
10187
+ /*
10188
+ * Every other value is refused rather than ignored: ignoring `VAL_MODE=memry`
10189
+ * would leave the app in `fs` mode, which is the exact failure this variable
10190
+ * exists to catch.
10191
+ *
10192
+ * `VAL_ENV` is excluded by name rather than by its value happening to pass:
10193
+ * it names 'http', which is selected below, and a reader who sees only
10194
+ * `declared !== null` here would reasonably conclude that 'http' is a
10195
+ * `VAL_MODE` value -- it is not, and the message below says so.
10196
+ */
10197
+ if (declared !== null && declared.from === "VAL_MODE") {
10198
+ throw new Error(`VAL_MODE is '${declared.mode}', which is not a mode Val knows. The only ` + "value it accepts is 'memory', which asserts that the host supplies " + "`sourceFiles`. 'fs' and 'http' are inferred rather than named: " + "'http' when VAL_API_KEY and VAL_SECRET are both set, 'fs' otherwise.");
10199
+ }
10106
10200
  const maybeApiKey = opts.apiKey || process.env.VAL_API_KEY;
10107
10201
  const maybeValSecret = opts.valSecret || process.env.VAL_SECRET;
10108
- const isProxyMode = opts.mode === "proxy" || opts.mode === undefined && (maybeApiKey || maybeValSecret);
10202
+ /*
10203
+ * The app's environment selects http mode, rather than leaving it to be
10204
+ * inferred from a credential being present.
10205
+ *
10206
+ * The difference shows when something is MISSING. Inference reads an absent
10207
+ * api key as "not a proxy" and falls through to `fs`, which in an isolate
10208
+ * reaches for a working tree that is not there -- an `EPERM` on
10209
+ * `.val/patches.lock`, several layers below the mistake. Selecting the mode
10210
+ * means the checks below run instead, and each one names what it wanted.
10211
+ */
10212
+ const isAppEnv = (declared === null || declared === void 0 ? void 0 : declared.from) === "VAL_ENV";
10213
+ const isProxyMode = opts.mode === "proxy" || isAppEnv || opts.mode === undefined && (maybeApiKey || maybeValSecret);
10109
10214
  const valEnableRedirectUrl = opts.valEnableRedirectUrl || process.env.VAL_ENABLE_REDIRECT_URL;
10110
10215
  const valDisableRedirectUrl = opts.valDisableRedirectUrl || process.env.VAL_DISABLE_REDIRECT_URL;
10111
10216
  const maybeValProject = opts.project || process.env.VAL_PROJECT;
@@ -10117,19 +10222,28 @@ async function initHandlerOptions(route, opts, config) {
10117
10222
  });
10118
10223
  if (isProxyMode) {
10119
10224
  var _opts$versions, _opts$versions2;
10225
+ /*
10226
+ * Why this app is in http mode, in the message that says what is missing.
10227
+ *
10228
+ * "must be set in proxy mode" is a fine sentence for a developer who wrote
10229
+ * `mode: "proxy"` and a poor one for an app that never mentioned a mode:
10230
+ * there, the answer to "why am I in proxy mode?" is a variable set by the
10231
+ * platform, in a file the reader of this error is not looking at.
10232
+ */
10233
+ const because = isAppEnv ? " (VAL_ENV is 'app', which is the Val app: its content is Val's own " + "and is read over HTTP at a commit, so http mode is the mode and " + "these are what it needs)" : "";
10120
10234
  if (!maybeApiKey || !maybeValSecret) {
10121
- throw new Error("VAL_API_KEY and VAL_SECRET env vars must both be set in proxy mode");
10235
+ throw new Error("VAL_API_KEY and VAL_SECRET env vars must both be set in proxy mode" + because);
10122
10236
  }
10123
10237
  const maybeGitCommit = opts.gitCommit || process.env.VAL_GIT_COMMIT;
10124
10238
  if (!maybeGitCommit) {
10125
- throw new Error("VAL_GIT_COMMIT env var must be set in proxy mode");
10239
+ throw new Error("VAL_GIT_COMMIT env var must be set in proxy mode" + because);
10126
10240
  }
10127
10241
  const maybeGitBranch = opts.gitBranch || process.env.VAL_GIT_BRANCH;
10128
10242
  if (!maybeGitBranch) {
10129
- throw new Error("VAL_GIT_BRANCH env var must be set in proxy mode");
10243
+ throw new Error("VAL_GIT_BRANCH env var must be set in proxy mode" + because);
10130
10244
  }
10131
10245
  if (!maybeValProject) {
10132
- throw new Error("Proxy mode does not work unless the 'project' option in val.config is defined or the VAL_PROJECT env var is set.");
10246
+ throw new Error("Proxy mode does not work unless the 'project' option in val.config is defined or the VAL_PROJECT env var is set." + because);
10133
10247
  }
10134
10248
  const coreVersion = (_opts$versions = opts.versions) === null || _opts$versions === void 0 ? void 0 : _opts$versions.core;
10135
10249
  if (!coreVersion) {
@@ -10051,6 +10051,67 @@ function parsePersonalAccessTokenFile(content) {
10051
10051
 
10052
10052
  const DEFAULT_VAL_BUILD_URL = "https://admin.val.build";
10053
10053
 
10054
+ /**
10055
+ * The value of `VAL_ENV` that means "this is the Val app".
10056
+ *
10057
+ * The Val app builds a project in a browser and runs it in a Worker isolate.
10058
+ * There is no disk there and there never will be, so `fs` mode is never the
10059
+ * right fall-through -- and the content is Val's own, reached over HTTP at a
10060
+ * commit, exactly as it is for any other deployed app. So this names `http`.
10061
+ *
10062
+ * What makes the app unusual is not where its content comes from but what
10063
+ * publishing means: the browser rebuilds the site and the new build is served
10064
+ * immediately, instead of a host noticing a commit and redeploying. That is a
10065
+ * difference in what happens AFTER the commit, and `publishOverride` is where
10066
+ * a host says so -- not a difference in where patches, files or sources live.
10067
+ *
10068
+ * A host says WHERE it runs, which is a fact it knows. Which Val mode that
10069
+ * implies is Val's to derive, and that is the whole reason this exists next to
10070
+ * `VAL_MODE` rather than the platform naming a mode itself: one is a
10071
+ * description of an environment, the other an assertion about Val's internals,
10072
+ * and only the first stays true when the internals move. They have already
10073
+ * moved once -- this meant `memory` while the app kept its own patch store --
10074
+ * and no platform had to be changed to follow.
10075
+ */
10076
+ const VAL_APP_ENV = "app";
10077
+
10078
+ /** Which mode the environment SAYS this is, and which variable said so. */
10079
+
10080
+ /**
10081
+ * `null` is "the environment did not say", which is the normal case.
10082
+ *
10083
+ * The two variables differ in what can be DONE with an answer, and the
10084
+ * difference is whether the environment holds everything the mode needs.
10085
+ * `http` does -- an api key, a secret, a project, a commit and a branch are all
10086
+ * env vars -- so `VAL_ENV=app` SELECTS it, and the checks in
10087
+ * {@link initHandlerOptions} name whichever one is missing. `memory` does not:
10088
+ * it needs the host's own source files, which nothing in an environment can
10089
+ * supply, so `VAL_MODE=memory` can only ever turn a fall-through into an error.
10090
+ */
10091
+ function namedMode() {
10092
+ const declared = process.env.VAL_MODE;
10093
+ /*
10094
+ * An empty value counts as unset, which is what `VAL_MODE=` in a shell or a
10095
+ * CI settings page means. An explicit `VAL_MODE` otherwise wins over
10096
+ * `VAL_ENV`: naming a mode outright says something more specific than naming
10097
+ * an environment does, including when what it names is wrong and has to be
10098
+ * refused.
10099
+ */
10100
+ if (declared !== undefined && declared !== "") {
10101
+ return {
10102
+ mode: declared,
10103
+ from: "VAL_MODE"
10104
+ };
10105
+ }
10106
+ if (process.env.VAL_ENV === VAL_APP_ENV) {
10107
+ return {
10108
+ mode: "http",
10109
+ from: "VAL_ENV"
10110
+ };
10111
+ }
10112
+ return null;
10113
+ }
10114
+
10054
10115
  /**
10055
10116
  * Resolve options plus environment into a concrete {@link ValServerConfig}.
10056
10117
  *
@@ -10103,9 +10164,53 @@ async function initHandlerOptions(route, opts, config) {
10103
10164
  config
10104
10165
  };
10105
10166
  }
10167
+ /*
10168
+ * The environment saying 'memory' means the host MEANT to hold the source,
10169
+ * and did not. Either variable can say it: `VAL_MODE=memory` outright, or
10170
+ * `VAL_ENV=app`, which names an environment that has no disk.
10171
+ *
10172
+ * Neither can SELECT memory mode -- nothing in the environment can supply
10173
+ * `sourceFiles`, and a mode turned on without them is a server with no
10174
+ * content in it. What they do is turn the fall-through into an error.
10175
+ *
10176
+ * Without it, a host that forgot to pass its source got `fs` mode, and `fs`
10177
+ * mode in a Worker isolate reaches for a working tree that is not there: the
10178
+ * failure is an `EPERM` on `.val/patches.lock`, several layers below the
10179
+ * mistake, naming a path rather than the decision that led to it. Every
10180
+ * environment that runs Val without a disk can set this once and get a
10181
+ * sentence instead.
10182
+ */
10183
+ const declared = namedMode();
10184
+ if ((declared === null || declared === void 0 ? void 0 : declared.mode) === "memory") {
10185
+ throw new Error("VAL_MODE is 'memory', but no `sourceFiles` were given here, so there " + "is no source to serve. Memory mode cannot be turned on by the " + "environment: it needs the project's own source, and only the host " + "that holds it can hand it over. On TanStack Start that is the " + "`sourceFiles` option, passed to `initValServer` AND to " + "`initValContent`, which has a Val server of its own and is " + "configured separately. @valbuild/next has no memory mode yet, so " + "for a Next app this variable is set on an environment Val cannot " + "serve from. Unset VAL_MODE to go back to the inferred mode instead " + "('http' when VAL_API_KEY and VAL_SECRET are both set, 'fs' " + "otherwise).");
10186
+ }
10187
+ /*
10188
+ * Every other value is refused rather than ignored: ignoring `VAL_MODE=memry`
10189
+ * would leave the app in `fs` mode, which is the exact failure this variable
10190
+ * exists to catch.
10191
+ *
10192
+ * `VAL_ENV` is excluded by name rather than by its value happening to pass:
10193
+ * it names 'http', which is selected below, and a reader who sees only
10194
+ * `declared !== null` here would reasonably conclude that 'http' is a
10195
+ * `VAL_MODE` value -- it is not, and the message below says so.
10196
+ */
10197
+ if (declared !== null && declared.from === "VAL_MODE") {
10198
+ throw new Error(`VAL_MODE is '${declared.mode}', which is not a mode Val knows. The only ` + "value it accepts is 'memory', which asserts that the host supplies " + "`sourceFiles`. 'fs' and 'http' are inferred rather than named: " + "'http' when VAL_API_KEY and VAL_SECRET are both set, 'fs' otherwise.");
10199
+ }
10106
10200
  const maybeApiKey = opts.apiKey || process.env.VAL_API_KEY;
10107
10201
  const maybeValSecret = opts.valSecret || process.env.VAL_SECRET;
10108
- const isProxyMode = opts.mode === "proxy" || opts.mode === undefined && (maybeApiKey || maybeValSecret);
10202
+ /*
10203
+ * The app's environment selects http mode, rather than leaving it to be
10204
+ * inferred from a credential being present.
10205
+ *
10206
+ * The difference shows when something is MISSING. Inference reads an absent
10207
+ * api key as "not a proxy" and falls through to `fs`, which in an isolate
10208
+ * reaches for a working tree that is not there -- an `EPERM` on
10209
+ * `.val/patches.lock`, several layers below the mistake. Selecting the mode
10210
+ * means the checks below run instead, and each one names what it wanted.
10211
+ */
10212
+ const isAppEnv = (declared === null || declared === void 0 ? void 0 : declared.from) === "VAL_ENV";
10213
+ const isProxyMode = opts.mode === "proxy" || isAppEnv || opts.mode === undefined && (maybeApiKey || maybeValSecret);
10109
10214
  const valEnableRedirectUrl = opts.valEnableRedirectUrl || process.env.VAL_ENABLE_REDIRECT_URL;
10110
10215
  const valDisableRedirectUrl = opts.valDisableRedirectUrl || process.env.VAL_DISABLE_REDIRECT_URL;
10111
10216
  const maybeValProject = opts.project || process.env.VAL_PROJECT;
@@ -10117,19 +10222,28 @@ async function initHandlerOptions(route, opts, config) {
10117
10222
  });
10118
10223
  if (isProxyMode) {
10119
10224
  var _opts$versions, _opts$versions2;
10225
+ /*
10226
+ * Why this app is in http mode, in the message that says what is missing.
10227
+ *
10228
+ * "must be set in proxy mode" is a fine sentence for a developer who wrote
10229
+ * `mode: "proxy"` and a poor one for an app that never mentioned a mode:
10230
+ * there, the answer to "why am I in proxy mode?" is a variable set by the
10231
+ * platform, in a file the reader of this error is not looking at.
10232
+ */
10233
+ const because = isAppEnv ? " (VAL_ENV is 'app', which is the Val app: its content is Val's own " + "and is read over HTTP at a commit, so http mode is the mode and " + "these are what it needs)" : "";
10120
10234
  if (!maybeApiKey || !maybeValSecret) {
10121
- throw new Error("VAL_API_KEY and VAL_SECRET env vars must both be set in proxy mode");
10235
+ throw new Error("VAL_API_KEY and VAL_SECRET env vars must both be set in proxy mode" + because);
10122
10236
  }
10123
10237
  const maybeGitCommit = opts.gitCommit || process.env.VAL_GIT_COMMIT;
10124
10238
  if (!maybeGitCommit) {
10125
- throw new Error("VAL_GIT_COMMIT env var must be set in proxy mode");
10239
+ throw new Error("VAL_GIT_COMMIT env var must be set in proxy mode" + because);
10126
10240
  }
10127
10241
  const maybeGitBranch = opts.gitBranch || process.env.VAL_GIT_BRANCH;
10128
10242
  if (!maybeGitBranch) {
10129
- throw new Error("VAL_GIT_BRANCH env var must be set in proxy mode");
10243
+ throw new Error("VAL_GIT_BRANCH env var must be set in proxy mode" + because);
10130
10244
  }
10131
10245
  if (!maybeValProject) {
10132
- throw new Error("Proxy mode does not work unless the 'project' option in val.config is defined or the VAL_PROJECT env var is set.");
10246
+ throw new Error("Proxy mode does not work unless the 'project' option in val.config is defined or the VAL_PROJECT env var is set." + because);
10133
10247
  }
10134
10248
  const coreVersion = (_opts$versions = opts.versions) === null || _opts$versions === void 0 ? void 0 : _opts$versions.core;
10135
10249
  if (!coreVersion) {
@@ -10017,6 +10017,67 @@ function parsePersonalAccessTokenFile(content) {
10017
10017
 
10018
10018
  const DEFAULT_VAL_BUILD_URL = "https://admin.val.build";
10019
10019
 
10020
+ /**
10021
+ * The value of `VAL_ENV` that means "this is the Val app".
10022
+ *
10023
+ * The Val app builds a project in a browser and runs it in a Worker isolate.
10024
+ * There is no disk there and there never will be, so `fs` mode is never the
10025
+ * right fall-through -- and the content is Val's own, reached over HTTP at a
10026
+ * commit, exactly as it is for any other deployed app. So this names `http`.
10027
+ *
10028
+ * What makes the app unusual is not where its content comes from but what
10029
+ * publishing means: the browser rebuilds the site and the new build is served
10030
+ * immediately, instead of a host noticing a commit and redeploying. That is a
10031
+ * difference in what happens AFTER the commit, and `publishOverride` is where
10032
+ * a host says so -- not a difference in where patches, files or sources live.
10033
+ *
10034
+ * A host says WHERE it runs, which is a fact it knows. Which Val mode that
10035
+ * implies is Val's to derive, and that is the whole reason this exists next to
10036
+ * `VAL_MODE` rather than the platform naming a mode itself: one is a
10037
+ * description of an environment, the other an assertion about Val's internals,
10038
+ * and only the first stays true when the internals move. They have already
10039
+ * moved once -- this meant `memory` while the app kept its own patch store --
10040
+ * and no platform had to be changed to follow.
10041
+ */
10042
+ const VAL_APP_ENV = "app";
10043
+
10044
+ /** Which mode the environment SAYS this is, and which variable said so. */
10045
+
10046
+ /**
10047
+ * `null` is "the environment did not say", which is the normal case.
10048
+ *
10049
+ * The two variables differ in what can be DONE with an answer, and the
10050
+ * difference is whether the environment holds everything the mode needs.
10051
+ * `http` does -- an api key, a secret, a project, a commit and a branch are all
10052
+ * env vars -- so `VAL_ENV=app` SELECTS it, and the checks in
10053
+ * {@link initHandlerOptions} name whichever one is missing. `memory` does not:
10054
+ * it needs the host's own source files, which nothing in an environment can
10055
+ * supply, so `VAL_MODE=memory` can only ever turn a fall-through into an error.
10056
+ */
10057
+ function namedMode() {
10058
+ const declared = process.env.VAL_MODE;
10059
+ /*
10060
+ * An empty value counts as unset, which is what `VAL_MODE=` in a shell or a
10061
+ * CI settings page means. An explicit `VAL_MODE` otherwise wins over
10062
+ * `VAL_ENV`: naming a mode outright says something more specific than naming
10063
+ * an environment does, including when what it names is wrong and has to be
10064
+ * refused.
10065
+ */
10066
+ if (declared !== undefined && declared !== "") {
10067
+ return {
10068
+ mode: declared,
10069
+ from: "VAL_MODE"
10070
+ };
10071
+ }
10072
+ if (process.env.VAL_ENV === VAL_APP_ENV) {
10073
+ return {
10074
+ mode: "http",
10075
+ from: "VAL_ENV"
10076
+ };
10077
+ }
10078
+ return null;
10079
+ }
10080
+
10020
10081
  /**
10021
10082
  * Resolve options plus environment into a concrete {@link ValServerConfig}.
10022
10083
  *
@@ -10069,9 +10130,53 @@ async function initHandlerOptions(route, opts, config) {
10069
10130
  config
10070
10131
  };
10071
10132
  }
10133
+ /*
10134
+ * The environment saying 'memory' means the host MEANT to hold the source,
10135
+ * and did not. Either variable can say it: `VAL_MODE=memory` outright, or
10136
+ * `VAL_ENV=app`, which names an environment that has no disk.
10137
+ *
10138
+ * Neither can SELECT memory mode -- nothing in the environment can supply
10139
+ * `sourceFiles`, and a mode turned on without them is a server with no
10140
+ * content in it. What they do is turn the fall-through into an error.
10141
+ *
10142
+ * Without it, a host that forgot to pass its source got `fs` mode, and `fs`
10143
+ * mode in a Worker isolate reaches for a working tree that is not there: the
10144
+ * failure is an `EPERM` on `.val/patches.lock`, several layers below the
10145
+ * mistake, naming a path rather than the decision that led to it. Every
10146
+ * environment that runs Val without a disk can set this once and get a
10147
+ * sentence instead.
10148
+ */
10149
+ const declared = namedMode();
10150
+ if ((declared === null || declared === void 0 ? void 0 : declared.mode) === "memory") {
10151
+ throw new Error("VAL_MODE is 'memory', but no `sourceFiles` were given here, so there " + "is no source to serve. Memory mode cannot be turned on by the " + "environment: it needs the project's own source, and only the host " + "that holds it can hand it over. On TanStack Start that is the " + "`sourceFiles` option, passed to `initValServer` AND to " + "`initValContent`, which has a Val server of its own and is " + "configured separately. @valbuild/next has no memory mode yet, so " + "for a Next app this variable is set on an environment Val cannot " + "serve from. Unset VAL_MODE to go back to the inferred mode instead " + "('http' when VAL_API_KEY and VAL_SECRET are both set, 'fs' " + "otherwise).");
10152
+ }
10153
+ /*
10154
+ * Every other value is refused rather than ignored: ignoring `VAL_MODE=memry`
10155
+ * would leave the app in `fs` mode, which is the exact failure this variable
10156
+ * exists to catch.
10157
+ *
10158
+ * `VAL_ENV` is excluded by name rather than by its value happening to pass:
10159
+ * it names 'http', which is selected below, and a reader who sees only
10160
+ * `declared !== null` here would reasonably conclude that 'http' is a
10161
+ * `VAL_MODE` value -- it is not, and the message below says so.
10162
+ */
10163
+ if (declared !== null && declared.from === "VAL_MODE") {
10164
+ throw new Error(`VAL_MODE is '${declared.mode}', which is not a mode Val knows. The only ` + "value it accepts is 'memory', which asserts that the host supplies " + "`sourceFiles`. 'fs' and 'http' are inferred rather than named: " + "'http' when VAL_API_KEY and VAL_SECRET are both set, 'fs' otherwise.");
10165
+ }
10072
10166
  const maybeApiKey = opts.apiKey || process.env.VAL_API_KEY;
10073
10167
  const maybeValSecret = opts.valSecret || process.env.VAL_SECRET;
10074
- const isProxyMode = opts.mode === "proxy" || opts.mode === undefined && (maybeApiKey || maybeValSecret);
10168
+ /*
10169
+ * The app's environment selects http mode, rather than leaving it to be
10170
+ * inferred from a credential being present.
10171
+ *
10172
+ * The difference shows when something is MISSING. Inference reads an absent
10173
+ * api key as "not a proxy" and falls through to `fs`, which in an isolate
10174
+ * reaches for a working tree that is not there -- an `EPERM` on
10175
+ * `.val/patches.lock`, several layers below the mistake. Selecting the mode
10176
+ * means the checks below run instead, and each one names what it wanted.
10177
+ */
10178
+ const isAppEnv = (declared === null || declared === void 0 ? void 0 : declared.from) === "VAL_ENV";
10179
+ const isProxyMode = opts.mode === "proxy" || isAppEnv || opts.mode === undefined && (maybeApiKey || maybeValSecret);
10075
10180
  const valEnableRedirectUrl = opts.valEnableRedirectUrl || process.env.VAL_ENABLE_REDIRECT_URL;
10076
10181
  const valDisableRedirectUrl = opts.valDisableRedirectUrl || process.env.VAL_DISABLE_REDIRECT_URL;
10077
10182
  const maybeValProject = opts.project || process.env.VAL_PROJECT;
@@ -10083,19 +10188,28 @@ async function initHandlerOptions(route, opts, config) {
10083
10188
  });
10084
10189
  if (isProxyMode) {
10085
10190
  var _opts$versions, _opts$versions2;
10191
+ /*
10192
+ * Why this app is in http mode, in the message that says what is missing.
10193
+ *
10194
+ * "must be set in proxy mode" is a fine sentence for a developer who wrote
10195
+ * `mode: "proxy"` and a poor one for an app that never mentioned a mode:
10196
+ * there, the answer to "why am I in proxy mode?" is a variable set by the
10197
+ * platform, in a file the reader of this error is not looking at.
10198
+ */
10199
+ const because = isAppEnv ? " (VAL_ENV is 'app', which is the Val app: its content is Val's own " + "and is read over HTTP at a commit, so http mode is the mode and " + "these are what it needs)" : "";
10086
10200
  if (!maybeApiKey || !maybeValSecret) {
10087
- throw new Error("VAL_API_KEY and VAL_SECRET env vars must both be set in proxy mode");
10201
+ throw new Error("VAL_API_KEY and VAL_SECRET env vars must both be set in proxy mode" + because);
10088
10202
  }
10089
10203
  const maybeGitCommit = opts.gitCommit || process.env.VAL_GIT_COMMIT;
10090
10204
  if (!maybeGitCommit) {
10091
- throw new Error("VAL_GIT_COMMIT env var must be set in proxy mode");
10205
+ throw new Error("VAL_GIT_COMMIT env var must be set in proxy mode" + because);
10092
10206
  }
10093
10207
  const maybeGitBranch = opts.gitBranch || process.env.VAL_GIT_BRANCH;
10094
10208
  if (!maybeGitBranch) {
10095
- throw new Error("VAL_GIT_BRANCH env var must be set in proxy mode");
10209
+ throw new Error("VAL_GIT_BRANCH env var must be set in proxy mode" + because);
10096
10210
  }
10097
10211
  if (!maybeValProject) {
10098
- throw new Error("Proxy mode does not work unless the 'project' option in val.config is defined or the VAL_PROJECT env var is set.");
10212
+ throw new Error("Proxy mode does not work unless the 'project' option in val.config is defined or the VAL_PROJECT env var is set." + because);
10099
10213
  }
10100
10214
  const coreVersion = (_opts$versions = opts.versions) === null || _opts$versions === void 0 ? void 0 : _opts$versions.core;
10101
10215
  if (!coreVersion) {
package/package.json CHANGED
@@ -16,7 +16,7 @@
16
16
  "./package.json": "./package.json"
17
17
  },
18
18
  "types": "dist/valbuild-server.cjs.d.ts",
19
- "version": "0.130.0",
19
+ "version": "0.132.0",
20
20
  "devDependencies": {
21
21
  "@prettier/sync": "^0.6.1",
22
22
  "@types/jest": "^30.0.0"
@@ -29,9 +29,9 @@
29
29
  "typescript": "^6.0.3",
30
30
  "zod": "^4.4.3",
31
31
  "zod-validation-error": "^5.0.0",
32
- "@valbuild/shared": "0.130.0",
33
32
  "@valbuild/core": "0.130.0",
34
- "@valbuild/ui": "0.130.0"
33
+ "@valbuild/ui": "0.130.0",
34
+ "@valbuild/shared": "0.130.0"
35
35
  },
36
36
  "engines": {
37
37
  "node": "^20.19.0 || >=22"