@valbuild/server 0.135.0 → 0.136.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,91 @@
1
1
  # @valbuild/server
2
2
 
3
+ ## 0.136.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#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
8
+ publish is on its way out.
9
+
10
+ **A managed project is one with no repository and no host watching one.** There
11
+ is nothing outside the browser to pick a commit up, so the Studio has been
12
+ showing it a story that belongs to a project with a repository: a `Building`
13
+ spinner, waiting for an event that can never arrive. It does not resolve on a
14
+ reload, on a retry, or tomorrow, and there is no way to tell it from a deploy
15
+ that is merely slow.
16
+
17
+ The content service now reports which kind of project this is, as `sourceMode`
18
+ on `/stat`, and the Studio narrates accordingly:
19
+
20
+ - a **connected** project keeps the deploy feed and keeps `Building`, because
21
+ there a host genuinely does pick the commit up;
22
+ - a **managed** one says `Saved, not yet live` instead — a durable condition,
23
+ not a phase, because nothing else will ever resolve it.
24
+
25
+ A server that reports no source mode keeps the behaviour it has today. Absence
26
+ means "not reported", never "managed": `fs` mode has no project to have a mode,
27
+ and neither does a content service that predates the field.
28
+
29
+ **The Studio also loads `@valbuild/tanstack-build` in the tab**, at mount and at
30
+ idle, for the managed projects that will need it — a browser that is the
31
+ deployer needs a bundler. This is the groundwork for browser-side publishing;
32
+ the publish path itself is unchanged in this release.
33
+
34
+ That bundler is `@rolldown/browser`, whose WebAssembly binary is 10.9 MB and is
35
+ **not** in the npm package. It is served from `static.val.build`, addressed by
36
+ the SHA-256 of its own bytes, so the build and the binary it needs cannot drift
37
+ apart. A deployment that must not reach that host — an air-gapped install, a
38
+ mirror — sets `globalThis.__VAL_ROLLDOWN_WASM_URL__` before the Studio loads and
39
+ needs no rebuild.
40
+
41
+ **Building in the browser requires a cross-origin isolated page.** Rolldown runs
42
+ WebAssembly on worker threads that share memory, and a browser will not hand a
43
+ `SharedArrayBuffer` to a worker otherwise. Without
44
+ `Cross-Origin-Opener-Policy: same-origin` and
45
+ `Cross-Origin-Embedder-Policy: require-corp` on the document Val is mounted in,
46
+ loading the bundler now fails with a message that says exactly that, instead of
47
+ a `DataCloneError` thrown from inside a worker. Nothing else in this release is
48
+ affected: a Studio that never builds in the browser never asks.
49
+
50
+ ### Patch Changes
51
+
52
+ - [#699](https://github.com/valbuild/val/pull/699) [`1d72d00`](https://github.com/valbuild/val/commit/1d72d00a1b036959ae0f1540121701cbb2694dcb) Thanks [@freekh](https://github.com/freekh)! - A `*.val.ts` with no default export is no longer reported as a missing module
53
+
54
+ `*.val.ts` is a naming convention, not a promise: plenty of files under it hold
55
+ only the schemas and helpers the modules beside them import. Every one of those
56
+ was getting two errors in the editor — `Module '…' was not found in
57
+ val.modules` and `… is not registered in val.modules, so Val will not serve it`
58
+ — both on line 1, telling their author to register a file that has nothing to
59
+ register. `val validate` has never reported them; only the editor did.
60
+
61
+ The default export is what makes a `*.val.ts` a module, so that is what the
62
+ diagnostic now asks about:
63
+
64
+ - **No default export → nothing is reported.** The file is not a module, so it
65
+ is not a module Val is failing to serve.
66
+ - **A default export → one diagnostic, on the `export default` itself** rather
67
+ than on line 1, so it is next to the thing that has to change, and the
68
+ duplicate fatal beside it is gone. The message now gives both remedies: add
69
+ the file to `val.modules`, or export what it holds by name instead. The "Val:
70
+ register … in val.modules" quick fix is offered there.
71
+
72
+ The rule is `findDefaultExport` in `@valbuild/server`, which `val validate`
73
+ already used to decide the same question — so the editor and the CLI now agree
74
+ about which files are modules, including the cases that are easy to get wrong
75
+ (`export * from …` carries no default; `export type { T as default }` and
76
+ `export default interface T {}` are both gone after transpilation).
77
+
78
+ Because the diagnostic now replaces a module's own findings rather than adding
79
+ to them, the editor also stops guessing about registration it cannot read: a
80
+ `val.modules` that registers modules through a tsconfig path alias
81
+ (`import("_/content/page.val")`), or that builds its list in another file, is no
82
+ longer taken to register nothing.
83
+
84
+ - Updated dependencies [[`f3a4bb7`](https://github.com/valbuild/val/commit/f3a4bb7aea604943b92545c122243798c759715c)]:
85
+ - @valbuild/ui@0.136.0
86
+ - @valbuild/core@0.136.0
87
+ - @valbuild/shared@0.136.0
88
+
3
89
  ## 0.135.0
4
90
 
5
91
  ### Patch Changes
@@ -417,6 +417,22 @@ export declare abstract class ValOps {
417
417
  * project requires.
418
418
  */
419
419
  publishRefusal(): PublishRefusal | null;
420
+ /**
421
+ * How this project's SOURCE is kept, or `null` when it is nobody's question.
422
+ *
423
+ * `"managed"` -- the content service is the store of record, there is no
424
+ * repository, and nothing outside the browser will ever pick a commit up.
425
+ * `"connected"` -- commits are mirrored into a repository a host watches.
426
+ *
427
+ * `null` is the honest answer for `fs` and memory mode, where publishing is
428
+ * writing to disk and there is no project to have a mode, and for an `http`
429
+ * project whose content service predates the field. The Studio treats it as
430
+ * "the story I have always told", which is the connected one -- the feed, and
431
+ * a `building` state that something outside resolves. Guessing `managed`
432
+ * instead would take the deploy feed away from every project running against
433
+ * an older service.
434
+ */
435
+ sourceMode(): "managed" | "connected" | null;
420
436
  /**
421
437
  * Whether a commit here produces `.val.ts` TEXT as well as data.
422
438
  *
@@ -126,6 +126,21 @@ export declare class ValOpsHttp extends ValOps {
126
126
  * checking.
127
127
  */
128
128
  publishRefusal(): PublishRefusal | null;
129
+ /**
130
+ * What the content service last said this project's source mode is.
131
+ *
132
+ * Read off the same remembered expectation {@link publishRefusal} uses. It is
133
+ * CURRENT wherever it matters rather than a poll behind, and by construction
134
+ * rather than by luck: the only caller is `/stat`, which awaits `getStat`
135
+ * first, and that fetches the patches -- which is the response the expectation
136
+ * is recorded from.
137
+ *
138
+ * `null` before anything has fetched patches, which is the same "not
139
+ * reported" the wire field means, and reads as connected. The alternative
140
+ * would be to ask for it separately, which is a round trip for a field that
141
+ * has just arrived.
142
+ */
143
+ sourceMode(): "managed" | "connected" | null;
129
144
  onInit(): Promise<void>;
130
145
  getPresignedAuthNonce(profileId: string, corsOrigin: string): Promise<{
131
146
  status: "success";
@@ -44,7 +44,7 @@ export type { ModulePathMap } from "./modulePathMap.js";
44
44
  export { ValOpsFS } from "./ValOpsFS.js";
45
45
  export { ValOpsHttp } from "./ValOpsHttp.js";
46
46
  export { ValOpsMemory, InMemoryPatchStore, type ValPatchStore, type StoredPatch, type ValOpsMemoryOptions, } from "./ValOpsMemory.js";
47
- export { loadValModules, createValModuleFileInspector } from "./loadValModules.js";
47
+ export { loadValModules, createValModuleFileInspector, findDefaultExport, } from "./loadValModules.js";
48
48
  export type { ValModuleFileInspection } from "./loadValModules.js";
49
49
  export { formatPatchSourceError } from "./ValOps.js";
50
50
  export { compareWithCapturedReport, readCapturedReport, replaySnapshot, } from "./debug/replaySnapshot.js";
@@ -71,3 +71,26 @@ export type ValModuleFileInspection =
71
71
  * project's own first-party files.
72
72
  */
73
73
  export declare function createValModuleFileInspector(projectRoot: string, host?: ValModulesHost): (absPath: string) => ValModuleFileInspection;
74
+ /**
75
+ * The statement that exports a RUNTIME value as `default`, without evaluating
76
+ * it — or `undefined` when the file has no such export.
77
+ *
78
+ * This is the one rule that separates a Val module from a `*.val.ts` that
79
+ * merely wears the naming convention: a shared schema or helper is exported by
80
+ * name, a module is exported by default. Every caller that needs to tell those
81
+ * apart goes through this — `val validate` to decide whether an unregistered
82
+ * file is worth reporting, and the language server to decide the same thing and
83
+ * to put its diagnostic on the export rather than on line 1.
84
+ *
85
+ * Two things deliberately do not count, because neither exists once the file is
86
+ * transpiled — and treating either as a default export would send a pure helper
87
+ * off to be evaluated and reported:
88
+ *
89
+ * - `export * from "./x"`, since a star re-export never carries the default;
90
+ * - a type-only export, in any of its three spellings
91
+ * (`export type { T as default }`, `export { type T as default }` and
92
+ * `export default interface T {}` — the last one parses as a declaration
93
+ * carrying a `default` modifier, exactly like `export default class`, and is
94
+ * the one that looks like a runtime export and is not).
95
+ */
96
+ export declare function findDefaultExport(sourceFile: ts.SourceFile): ts.Statement | undefined;
@@ -1007,18 +1007,29 @@ function createValModuleFileInspector(projectRoot, host = ts__default["default"]
1007
1007
  }
1008
1008
 
1009
1009
  /**
1010
- * Whether the file exports a RUNTIME value as `default`, without evaluating it.
1010
+ * The statement that exports a RUNTIME value as `default`, without evaluating
1011
+ * it — or `undefined` when the file has no such export.
1012
+ *
1013
+ * This is the one rule that separates a Val module from a `*.val.ts` that
1014
+ * merely wears the naming convention: a shared schema or helper is exported by
1015
+ * name, a module is exported by default. Every caller that needs to tell those
1016
+ * apart goes through this — `val validate` to decide whether an unregistered
1017
+ * file is worth reporting, and the language server to decide the same thing and
1018
+ * to put its diagnostic on the export rather than on line 1.
1011
1019
  *
1012
1020
  * Two things deliberately do not count, because neither exists once the file is
1013
1021
  * transpiled — and treating either as a default export would send a pure helper
1014
1022
  * off to be evaluated and reported:
1015
1023
  *
1016
1024
  * - `export * from "./x"`, since a star re-export never carries the default;
1017
- * - a type-only export, in either of its spellings
1018
- * (`export type { T as default }` and `export { type T as default }`).
1025
+ * - a type-only export, in any of its three spellings
1026
+ * (`export type { T as default }`, `export { type T as default }` and
1027
+ * `export default interface T {}` — the last one parses as a declaration
1028
+ * carrying a `default` modifier, exactly like `export default class`, and is
1029
+ * the one that looks like a runtime export and is not).
1019
1030
  */
1020
- function hasDefaultExport(sourceFile) {
1021
- return sourceFile.statements.some(statement => {
1031
+ function findDefaultExport(sourceFile) {
1032
+ return sourceFile.statements.find(statement => {
1022
1033
  // `export default <expr>` — but not `export = x`, which shares this node.
1023
1034
  if (ts__default["default"].isExportAssignment(statement)) {
1024
1035
  return !statement.isExportEquals;
@@ -1029,10 +1040,17 @@ function hasDefaultExport(sourceFile) {
1029
1040
  }
1030
1041
  // `export default function f() {}` / `export default class C {}`, which are
1031
1042
  // declarations carrying a `default` modifier rather than export assignments.
1032
- return ts__default["default"].canHaveModifiers(statement) && (ts__default["default"].getModifiers(statement) ?? []).some(modifier => modifier.kind === ts__default["default"].SyntaxKind.DefaultKeyword);
1043
+ // `export default interface T {}` is spelled the same way and is a type, so
1044
+ // it is excluded here rather than by the `isTypeOnly` checks above.
1045
+ return !ts__default["default"].isInterfaceDeclaration(statement) && ts__default["default"].canHaveModifiers(statement) && (ts__default["default"].getModifiers(statement) ?? []).some(modifier => modifier.kind === ts__default["default"].SyntaxKind.DefaultKeyword);
1033
1046
  });
1034
1047
  }
1035
1048
 
1049
+ /** Whether the file exports a runtime value as `default`. */
1050
+ function hasDefaultExport(sourceFile) {
1051
+ return findDefaultExport(sourceFile) !== undefined;
1052
+ }
1053
+
1036
1054
  /** A short, human-readable "what you exported instead" for the error message. */
1037
1055
  function describeDefaultExport(value) {
1038
1056
  if (value === null) {
@@ -4558,6 +4576,25 @@ class ValOps {
4558
4576
  return null;
4559
4577
  }
4560
4578
 
4579
+ /**
4580
+ * How this project's SOURCE is kept, or `null` when it is nobody's question.
4581
+ *
4582
+ * `"managed"` -- the content service is the store of record, there is no
4583
+ * repository, and nothing outside the browser will ever pick a commit up.
4584
+ * `"connected"` -- commits are mirrored into a repository a host watches.
4585
+ *
4586
+ * `null` is the honest answer for `fs` and memory mode, where publishing is
4587
+ * writing to disk and there is no project to have a mode, and for an `http`
4588
+ * project whose content service predates the field. The Studio treats it as
4589
+ * "the story I have always told", which is the connected one -- the feed, and
4590
+ * a `building` state that something outside resolves. Guessing `managed`
4591
+ * instead would take the deploy feed away from every project running against
4592
+ * an older service.
4593
+ */
4594
+ sourceMode() {
4595
+ return null;
4596
+ }
4597
+
4561
4598
  /**
4562
4599
  * Whether a commit here produces `.val.ts` TEXT as well as data.
4563
4600
  *
@@ -7917,6 +7954,25 @@ class ValOpsHttp extends ValOps {
7917
7954
  message: `This project mirrors its content into a git repository (branch ` + `'${this.projectExpectation.branch}'), but this deployment was not ` + "built from one, so it does not know which commit to write that " + "mirror against. Publishing would save the content and silently " + "leave the repository behind. Deploy this project again from its " + "repository, and publishing will work from that build on."
7918
7955
  };
7919
7956
  }
7957
+
7958
+ /**
7959
+ * What the content service last said this project's source mode is.
7960
+ *
7961
+ * Read off the same remembered expectation {@link publishRefusal} uses. It is
7962
+ * CURRENT wherever it matters rather than a poll behind, and by construction
7963
+ * rather than by luck: the only caller is `/stat`, which awaits `getStat`
7964
+ * first, and that fetches the patches -- which is the response the expectation
7965
+ * is recorded from.
7966
+ *
7967
+ * `null` before anything has fetched patches, which is the same "not
7968
+ * reported" the wire field means, and reads as connected. The alternative
7969
+ * would be to ask for it separately, which is a round trip for a field that
7970
+ * has just arrived.
7971
+ */
7972
+ sourceMode() {
7973
+ var _this$projectExpectat2;
7974
+ return ((_this$projectExpectat2 = this.projectExpectation) === null || _this$projectExpectat2 === void 0 ? void 0 : _this$projectExpectat2.sourceMode) ?? null;
7975
+ }
7920
7976
  async onInit() {
7921
7977
  // TODO: unused for now. Implement or remove
7922
7978
  }
@@ -11898,6 +11954,16 @@ const ValServer = (valModules, options, callbacks) => {
11898
11954
  * it failed to compute.
11899
11955
  */
11900
11956
  const publishRefusal = serverOps.publishRefusal();
11957
+ /*
11958
+ * How this project's source is kept, when the store knows.
11959
+ *
11960
+ * Spread for the same reason `publishRefusal` is: absent has to keep
11961
+ * meaning "not reported" rather than becoming a mode. The Studio
11962
+ * narrates a publish differently for a managed project -- there is
11963
+ * nothing outside the browser to finish it -- so a wrong default here
11964
+ * shows one story to a project that lives by the other.
11965
+ */
11966
+ const sourceMode = serverOps.sourceMode();
11901
11967
  return {
11902
11968
  status: 200,
11903
11969
  json: {
@@ -11907,6 +11973,9 @@ const ValServer = (valModules, options, callbacks) => {
11907
11973
  ...(publishRefusal ? {
11908
11974
  publishRefusal
11909
11975
  } : {}),
11976
+ ...(sourceMode ? {
11977
+ sourceMode
11978
+ } : {}),
11910
11979
  config: options.config
11911
11980
  }
11912
11981
  };
@@ -18039,6 +18108,7 @@ exports.extractFileMetadata = extractFileMetadata;
18039
18108
  exports.extractImageMetadata = extractImageMetadata;
18040
18109
  exports.extractJsonValuesEntry = extractJsonValuesEntry;
18041
18110
  exports.findAndEvalValConfigFile = findAndEvalValConfigFile;
18111
+ exports.findDefaultExport = findDefaultExport;
18042
18112
  exports.findJsonEntryFilePath = findJsonEntryFilePath;
18043
18113
  exports.fixHandlers = fixHandlers;
18044
18114
  exports.formatPatchSourceError = formatPatchSourceError;
@@ -1007,18 +1007,29 @@ function createValModuleFileInspector(projectRoot, host = ts__default["default"]
1007
1007
  }
1008
1008
 
1009
1009
  /**
1010
- * Whether the file exports a RUNTIME value as `default`, without evaluating it.
1010
+ * The statement that exports a RUNTIME value as `default`, without evaluating
1011
+ * it — or `undefined` when the file has no such export.
1012
+ *
1013
+ * This is the one rule that separates a Val module from a `*.val.ts` that
1014
+ * merely wears the naming convention: a shared schema or helper is exported by
1015
+ * name, a module is exported by default. Every caller that needs to tell those
1016
+ * apart goes through this — `val validate` to decide whether an unregistered
1017
+ * file is worth reporting, and the language server to decide the same thing and
1018
+ * to put its diagnostic on the export rather than on line 1.
1011
1019
  *
1012
1020
  * Two things deliberately do not count, because neither exists once the file is
1013
1021
  * transpiled — and treating either as a default export would send a pure helper
1014
1022
  * off to be evaluated and reported:
1015
1023
  *
1016
1024
  * - `export * from "./x"`, since a star re-export never carries the default;
1017
- * - a type-only export, in either of its spellings
1018
- * (`export type { T as default }` and `export { type T as default }`).
1025
+ * - a type-only export, in any of its three spellings
1026
+ * (`export type { T as default }`, `export { type T as default }` and
1027
+ * `export default interface T {}` — the last one parses as a declaration
1028
+ * carrying a `default` modifier, exactly like `export default class`, and is
1029
+ * the one that looks like a runtime export and is not).
1019
1030
  */
1020
- function hasDefaultExport(sourceFile) {
1021
- return sourceFile.statements.some(statement => {
1031
+ function findDefaultExport(sourceFile) {
1032
+ return sourceFile.statements.find(statement => {
1022
1033
  // `export default <expr>` — but not `export = x`, which shares this node.
1023
1034
  if (ts__default["default"].isExportAssignment(statement)) {
1024
1035
  return !statement.isExportEquals;
@@ -1029,10 +1040,17 @@ function hasDefaultExport(sourceFile) {
1029
1040
  }
1030
1041
  // `export default function f() {}` / `export default class C {}`, which are
1031
1042
  // declarations carrying a `default` modifier rather than export assignments.
1032
- return ts__default["default"].canHaveModifiers(statement) && (ts__default["default"].getModifiers(statement) ?? []).some(modifier => modifier.kind === ts__default["default"].SyntaxKind.DefaultKeyword);
1043
+ // `export default interface T {}` is spelled the same way and is a type, so
1044
+ // it is excluded here rather than by the `isTypeOnly` checks above.
1045
+ return !ts__default["default"].isInterfaceDeclaration(statement) && ts__default["default"].canHaveModifiers(statement) && (ts__default["default"].getModifiers(statement) ?? []).some(modifier => modifier.kind === ts__default["default"].SyntaxKind.DefaultKeyword);
1033
1046
  });
1034
1047
  }
1035
1048
 
1049
+ /** Whether the file exports a runtime value as `default`. */
1050
+ function hasDefaultExport(sourceFile) {
1051
+ return findDefaultExport(sourceFile) !== undefined;
1052
+ }
1053
+
1036
1054
  /** A short, human-readable "what you exported instead" for the error message. */
1037
1055
  function describeDefaultExport(value) {
1038
1056
  if (value === null) {
@@ -4558,6 +4576,25 @@ class ValOps {
4558
4576
  return null;
4559
4577
  }
4560
4578
 
4579
+ /**
4580
+ * How this project's SOURCE is kept, or `null` when it is nobody's question.
4581
+ *
4582
+ * `"managed"` -- the content service is the store of record, there is no
4583
+ * repository, and nothing outside the browser will ever pick a commit up.
4584
+ * `"connected"` -- commits are mirrored into a repository a host watches.
4585
+ *
4586
+ * `null` is the honest answer for `fs` and memory mode, where publishing is
4587
+ * writing to disk and there is no project to have a mode, and for an `http`
4588
+ * project whose content service predates the field. The Studio treats it as
4589
+ * "the story I have always told", which is the connected one -- the feed, and
4590
+ * a `building` state that something outside resolves. Guessing `managed`
4591
+ * instead would take the deploy feed away from every project running against
4592
+ * an older service.
4593
+ */
4594
+ sourceMode() {
4595
+ return null;
4596
+ }
4597
+
4561
4598
  /**
4562
4599
  * Whether a commit here produces `.val.ts` TEXT as well as data.
4563
4600
  *
@@ -7917,6 +7954,25 @@ class ValOpsHttp extends ValOps {
7917
7954
  message: `This project mirrors its content into a git repository (branch ` + `'${this.projectExpectation.branch}'), but this deployment was not ` + "built from one, so it does not know which commit to write that " + "mirror against. Publishing would save the content and silently " + "leave the repository behind. Deploy this project again from its " + "repository, and publishing will work from that build on."
7918
7955
  };
7919
7956
  }
7957
+
7958
+ /**
7959
+ * What the content service last said this project's source mode is.
7960
+ *
7961
+ * Read off the same remembered expectation {@link publishRefusal} uses. It is
7962
+ * CURRENT wherever it matters rather than a poll behind, and by construction
7963
+ * rather than by luck: the only caller is `/stat`, which awaits `getStat`
7964
+ * first, and that fetches the patches -- which is the response the expectation
7965
+ * is recorded from.
7966
+ *
7967
+ * `null` before anything has fetched patches, which is the same "not
7968
+ * reported" the wire field means, and reads as connected. The alternative
7969
+ * would be to ask for it separately, which is a round trip for a field that
7970
+ * has just arrived.
7971
+ */
7972
+ sourceMode() {
7973
+ var _this$projectExpectat2;
7974
+ return ((_this$projectExpectat2 = this.projectExpectation) === null || _this$projectExpectat2 === void 0 ? void 0 : _this$projectExpectat2.sourceMode) ?? null;
7975
+ }
7920
7976
  async onInit() {
7921
7977
  // TODO: unused for now. Implement or remove
7922
7978
  }
@@ -11898,6 +11954,16 @@ const ValServer = (valModules, options, callbacks) => {
11898
11954
  * it failed to compute.
11899
11955
  */
11900
11956
  const publishRefusal = serverOps.publishRefusal();
11957
+ /*
11958
+ * How this project's source is kept, when the store knows.
11959
+ *
11960
+ * Spread for the same reason `publishRefusal` is: absent has to keep
11961
+ * meaning "not reported" rather than becoming a mode. The Studio
11962
+ * narrates a publish differently for a managed project -- there is
11963
+ * nothing outside the browser to finish it -- so a wrong default here
11964
+ * shows one story to a project that lives by the other.
11965
+ */
11966
+ const sourceMode = serverOps.sourceMode();
11901
11967
  return {
11902
11968
  status: 200,
11903
11969
  json: {
@@ -11907,6 +11973,9 @@ const ValServer = (valModules, options, callbacks) => {
11907
11973
  ...(publishRefusal ? {
11908
11974
  publishRefusal
11909
11975
  } : {}),
11976
+ ...(sourceMode ? {
11977
+ sourceMode
11978
+ } : {}),
11910
11979
  config: options.config
11911
11980
  }
11912
11981
  };
@@ -18039,6 +18108,7 @@ exports.extractFileMetadata = extractFileMetadata;
18039
18108
  exports.extractImageMetadata = extractImageMetadata;
18040
18109
  exports.extractJsonValuesEntry = extractJsonValuesEntry;
18041
18110
  exports.findAndEvalValConfigFile = findAndEvalValConfigFile;
18111
+ exports.findDefaultExport = findDefaultExport;
18042
18112
  exports.findJsonEntryFilePath = findJsonEntryFilePath;
18043
18113
  exports.fixHandlers = fixHandlers;
18044
18114
  exports.formatPatchSourceError = formatPatchSourceError;
@@ -973,18 +973,29 @@ function createValModuleFileInspector(projectRoot, host = ts.sys) {
973
973
  }
974
974
 
975
975
  /**
976
- * Whether the file exports a RUNTIME value as `default`, without evaluating it.
976
+ * The statement that exports a RUNTIME value as `default`, without evaluating
977
+ * it — or `undefined` when the file has no such export.
978
+ *
979
+ * This is the one rule that separates a Val module from a `*.val.ts` that
980
+ * merely wears the naming convention: a shared schema or helper is exported by
981
+ * name, a module is exported by default. Every caller that needs to tell those
982
+ * apart goes through this — `val validate` to decide whether an unregistered
983
+ * file is worth reporting, and the language server to decide the same thing and
984
+ * to put its diagnostic on the export rather than on line 1.
977
985
  *
978
986
  * Two things deliberately do not count, because neither exists once the file is
979
987
  * transpiled — and treating either as a default export would send a pure helper
980
988
  * off to be evaluated and reported:
981
989
  *
982
990
  * - `export * from "./x"`, since a star re-export never carries the default;
983
- * - a type-only export, in either of its spellings
984
- * (`export type { T as default }` and `export { type T as default }`).
991
+ * - a type-only export, in any of its three spellings
992
+ * (`export type { T as default }`, `export { type T as default }` and
993
+ * `export default interface T {}` — the last one parses as a declaration
994
+ * carrying a `default` modifier, exactly like `export default class`, and is
995
+ * the one that looks like a runtime export and is not).
985
996
  */
986
- function hasDefaultExport(sourceFile) {
987
- return sourceFile.statements.some(statement => {
997
+ function findDefaultExport(sourceFile) {
998
+ return sourceFile.statements.find(statement => {
988
999
  // `export default <expr>` — but not `export = x`, which shares this node.
989
1000
  if (ts.isExportAssignment(statement)) {
990
1001
  return !statement.isExportEquals;
@@ -995,10 +1006,17 @@ function hasDefaultExport(sourceFile) {
995
1006
  }
996
1007
  // `export default function f() {}` / `export default class C {}`, which are
997
1008
  // declarations carrying a `default` modifier rather than export assignments.
998
- return ts.canHaveModifiers(statement) && (ts.getModifiers(statement) ?? []).some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword);
1009
+ // `export default interface T {}` is spelled the same way and is a type, so
1010
+ // it is excluded here rather than by the `isTypeOnly` checks above.
1011
+ return !ts.isInterfaceDeclaration(statement) && ts.canHaveModifiers(statement) && (ts.getModifiers(statement) ?? []).some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword);
999
1012
  });
1000
1013
  }
1001
1014
 
1015
+ /** Whether the file exports a runtime value as `default`. */
1016
+ function hasDefaultExport(sourceFile) {
1017
+ return findDefaultExport(sourceFile) !== undefined;
1018
+ }
1019
+
1002
1020
  /** A short, human-readable "what you exported instead" for the error message. */
1003
1021
  function describeDefaultExport(value) {
1004
1022
  if (value === null) {
@@ -4524,6 +4542,25 @@ class ValOps {
4524
4542
  return null;
4525
4543
  }
4526
4544
 
4545
+ /**
4546
+ * How this project's SOURCE is kept, or `null` when it is nobody's question.
4547
+ *
4548
+ * `"managed"` -- the content service is the store of record, there is no
4549
+ * repository, and nothing outside the browser will ever pick a commit up.
4550
+ * `"connected"` -- commits are mirrored into a repository a host watches.
4551
+ *
4552
+ * `null` is the honest answer for `fs` and memory mode, where publishing is
4553
+ * writing to disk and there is no project to have a mode, and for an `http`
4554
+ * project whose content service predates the field. The Studio treats it as
4555
+ * "the story I have always told", which is the connected one -- the feed, and
4556
+ * a `building` state that something outside resolves. Guessing `managed`
4557
+ * instead would take the deploy feed away from every project running against
4558
+ * an older service.
4559
+ */
4560
+ sourceMode() {
4561
+ return null;
4562
+ }
4563
+
4527
4564
  /**
4528
4565
  * Whether a commit here produces `.val.ts` TEXT as well as data.
4529
4566
  *
@@ -7883,6 +7920,25 @@ class ValOpsHttp extends ValOps {
7883
7920
  message: `This project mirrors its content into a git repository (branch ` + `'${this.projectExpectation.branch}'), but this deployment was not ` + "built from one, so it does not know which commit to write that " + "mirror against. Publishing would save the content and silently " + "leave the repository behind. Deploy this project again from its " + "repository, and publishing will work from that build on."
7884
7921
  };
7885
7922
  }
7923
+
7924
+ /**
7925
+ * What the content service last said this project's source mode is.
7926
+ *
7927
+ * Read off the same remembered expectation {@link publishRefusal} uses. It is
7928
+ * CURRENT wherever it matters rather than a poll behind, and by construction
7929
+ * rather than by luck: the only caller is `/stat`, which awaits `getStat`
7930
+ * first, and that fetches the patches -- which is the response the expectation
7931
+ * is recorded from.
7932
+ *
7933
+ * `null` before anything has fetched patches, which is the same "not
7934
+ * reported" the wire field means, and reads as connected. The alternative
7935
+ * would be to ask for it separately, which is a round trip for a field that
7936
+ * has just arrived.
7937
+ */
7938
+ sourceMode() {
7939
+ var _this$projectExpectat2;
7940
+ return ((_this$projectExpectat2 = this.projectExpectation) === null || _this$projectExpectat2 === void 0 ? void 0 : _this$projectExpectat2.sourceMode) ?? null;
7941
+ }
7886
7942
  async onInit() {
7887
7943
  // TODO: unused for now. Implement or remove
7888
7944
  }
@@ -11864,6 +11920,16 @@ const ValServer = (valModules, options, callbacks) => {
11864
11920
  * it failed to compute.
11865
11921
  */
11866
11922
  const publishRefusal = serverOps.publishRefusal();
11923
+ /*
11924
+ * How this project's source is kept, when the store knows.
11925
+ *
11926
+ * Spread for the same reason `publishRefusal` is: absent has to keep
11927
+ * meaning "not reported" rather than becoming a mode. The Studio
11928
+ * narrates a publish differently for a managed project -- there is
11929
+ * nothing outside the browser to finish it -- so a wrong default here
11930
+ * shows one story to a project that lives by the other.
11931
+ */
11932
+ const sourceMode = serverOps.sourceMode();
11867
11933
  return {
11868
11934
  status: 200,
11869
11935
  json: {
@@ -11873,6 +11939,9 @@ const ValServer = (valModules, options, callbacks) => {
11873
11939
  ...(publishRefusal ? {
11874
11940
  publishRefusal
11875
11941
  } : {}),
11942
+ ...(sourceMode ? {
11943
+ sourceMode
11944
+ } : {}),
11876
11945
  config: options.config
11877
11946
  }
11878
11947
  };
@@ -17961,4 +18030,4 @@ function readCapturedReport(snapshotDir) {
17961
18030
  return JSON.parse(fs.readFileSync(reportPath, "utf-8"));
17962
18031
  }
17963
18032
 
17964
- export { DEFAULT_LOGIN_EXPIRES_IN_SECONDS, DEFAULT_LOGIN_HOST, DEFAULT_LOGIN_POLL_INTERVAL_SECONDS, EXTERNAL_RESULT, InMemoryPatchStore, Service, ValFSHost, ValLoginError, ValModuleLoader, ValOpsFS, ValOpsHttp, ValOpsMemory, ValSourceFileHandler, analyzeValModule, awaitValLoginConfirmation, checkRemoteRef, classifyJsonValuesOp, compareWithCapturedReport, createDefaultValFSHost, createFixPatch, createJsonEntryPathMap, createModulePathMap, createPrettierFormatter, createService, createValApiRouter, createValModuleFileInspector, createValOps, createValServer, currentFixHandlers, decodeJwtWithoutVerifying, defineExternal, describePatchStoreProblems, downloadFileFromRemote, encodeJwt, err, evalValConfigFile, extractFileMetadata, extractImageMetadata, extractJsonValuesEntry, findAndEvalValConfigFile, findJsonEntryFilePath, fixHandlers, formatPatchSourceError, formatSyntaxErrorTree, getCachedRemoteFileDir, getCachedRemoteFilePath, getCompilerOptions, getExpire, getFileExt, getModulePathRange, getPersonalAccessTokenPath, getSettings, getValidationErrorFileRef, handleCheckAllFiles, handleExternalUpload, handleFileMetadata, handleJsonValuesExtractEntry, handleRemoteFileCheck, handleRemoteFileDownload, handleRemoteFileUpload, handleRemoteGalleryFileUpload, handleUniqueFolderCheck, initHandlerOptions, isExternalResult, loadValModules, ok, parsePersonalAccessTokenFile, patchSourceFile, persistPersonalAccessToken, planJsonValuesEntryExtraction, readCapturedReport, readPatchStore, rebaseContentOp, replaySnapshot, resolveRemoteFileAuth, safeReadGit, startValLogin, uploadRemoteFile, validateMetadata, verifyJwt };
18033
+ export { DEFAULT_LOGIN_EXPIRES_IN_SECONDS, DEFAULT_LOGIN_HOST, DEFAULT_LOGIN_POLL_INTERVAL_SECONDS, EXTERNAL_RESULT, InMemoryPatchStore, Service, ValFSHost, ValLoginError, ValModuleLoader, ValOpsFS, ValOpsHttp, ValOpsMemory, ValSourceFileHandler, analyzeValModule, awaitValLoginConfirmation, checkRemoteRef, classifyJsonValuesOp, compareWithCapturedReport, createDefaultValFSHost, createFixPatch, createJsonEntryPathMap, createModulePathMap, createPrettierFormatter, createService, createValApiRouter, createValModuleFileInspector, createValOps, createValServer, currentFixHandlers, decodeJwtWithoutVerifying, defineExternal, describePatchStoreProblems, downloadFileFromRemote, encodeJwt, err, evalValConfigFile, extractFileMetadata, extractImageMetadata, extractJsonValuesEntry, findAndEvalValConfigFile, findDefaultExport, findJsonEntryFilePath, fixHandlers, formatPatchSourceError, formatSyntaxErrorTree, getCachedRemoteFileDir, getCachedRemoteFilePath, getCompilerOptions, getExpire, getFileExt, getModulePathRange, getPersonalAccessTokenPath, getSettings, getValidationErrorFileRef, handleCheckAllFiles, handleExternalUpload, handleFileMetadata, handleJsonValuesExtractEntry, handleRemoteFileCheck, handleRemoteFileDownload, handleRemoteFileUpload, handleRemoteGalleryFileUpload, handleUniqueFolderCheck, initHandlerOptions, isExternalResult, loadValModules, ok, parsePersonalAccessTokenFile, patchSourceFile, persistPersonalAccessToken, planJsonValuesEntryExtraction, readCapturedReport, readPatchStore, rebaseContentOp, replaySnapshot, resolveRemoteFileAuth, safeReadGit, startValLogin, uploadRemoteFile, validateMetadata, verifyJwt };
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.135.0",
19
+ "version": "0.136.0",
20
20
  "devDependencies": {
21
21
  "@prettier/sync": "^0.6.1",
22
22
  "@types/jest": "^30.0.0",
@@ -30,9 +30,9 @@
30
30
  "typescript": "^6.0.3",
31
31
  "zod": "^4.4.3",
32
32
  "zod-validation-error": "^5.0.0",
33
- "@valbuild/core": "0.134.0",
34
- "@valbuild/shared": "0.134.1",
35
- "@valbuild/ui": "0.134.0"
33
+ "@valbuild/core": "0.136.0",
34
+ "@valbuild/shared": "0.136.0",
35
+ "@valbuild/ui": "0.136.0"
36
36
  },
37
37
  "engines": {
38
38
  "node": "^20.19.0 || >=22"