@valbuild/cli 0.144.12 → 0.145.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # @valbuild/cli
2
2
 
3
+ ## 0.145.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#860](https://github.com/valbuild/val/pull/860) [`a45f595`](https://github.com/valbuild/val/commit/a45f595a5e20b009d4110bbef8ebdc08b2d731d7) Thanks [@freekh](https://github.com/freekh)! - `val publish` now refuses to build a site whose installed TanStack disagrees with the platform's. A project's CI builds its dependency layer from its own `node_modules`, and a lockfile two patches away from the platform's `@tanstack/*` used to publish fine and then fail on the first request that reached the mismatched code — typically signing in — with "does not provide an export named …". The build now stops with a message naming the package, the version the project resolved and the version the platform carries, and the fix is to pin the package to that version (with `pnpm.overrides` for a transitive one) and reinstall.
8
+
9
+ The same build also checks every import a dependency takes from the platform's React and TanStack against what the platform says those modules export, which until now ran only in the platform's own checkout.
10
+
11
+ Both checks use what the platform reports about itself, so they run once the platform does; until then `val publish` builds as before and says in its output that the check was skipped. They run in the `@valbuild/tanstack-build` the project has installed, so upgrade it alongside `@valbuild/cli`.
12
+
13
+ - [#860](https://github.com/valbuild/val/pull/860) [`a45f595`](https://github.com/valbuild/val/commit/a45f595a5e20b009d4110bbef8ebdc08b2d731d7) Thanks [@freekh](https://github.com/freekh)! - `val publish` and the Studio now tell Val Build which version of the platform's React and TanStack a build was made against. If the platform is updated while a build is running — a project's CI can take several minutes — that build is no longer published onto a platform it was never checked against: the publish stops with a message saying the platform was updated and to build again, and the next build is checked against what it will run on. A build published with `val publish --artifacts` was made elsewhere and does not say, and publishes as before.
14
+ - Updated dependencies [[`a45f595`](https://github.com/valbuild/val/commit/a45f595a5e20b009d4110bbef8ebdc08b2d731d7), [`a45f595`](https://github.com/valbuild/val/commit/a45f595a5e20b009d4110bbef8ebdc08b2d731d7), [`a45f595`](https://github.com/valbuild/val/commit/a45f595a5e20b009d4110bbef8ebdc08b2d731d7)]:
15
+ - @valbuild/tanstack-build@0.145.1
16
+ - @valbuild/shared@0.145.1
17
+ - @valbuild/language-server@0.145.1
18
+ - @valbuild/server@0.145.1
19
+
20
+ ## 0.145.0
21
+
22
+ ### Patch Changes
23
+
24
+ - Updated dependencies [[`b6cb939`](https://github.com/valbuild/val/commit/b6cb9395d8bdcd5182e9c6ba84f5284930de2fa5), [`f085be0`](https://github.com/valbuild/val/commit/f085be0eb5bdc195e9d8cdb15dd5f8acfdabe23c)]:
25
+ - @valbuild/core@0.145.0
26
+ - @valbuild/shared@0.145.0
27
+ - @valbuild/server@0.145.0
28
+ - @valbuild/language-server@0.145.0
29
+
3
30
  ## 0.144.12
4
31
 
5
32
  ### Patch Changes
@@ -2677,14 +2677,41 @@ async function buildArtifacts(options) {
2677
2677
  // published with the site under the names their `url()`s now use.
2678
2678
  const dependencyAssets = {};
2679
2679
  const css = await node.collectDependencyCss(root, node.cssSpecifiersOf(sources), dependencyAssets);
2680
+ /*
2681
+ * There is no platform checkout here, so nothing can be PINNED to the
2682
+ * platform's copies and no built base layer can be read. What stands in
2683
+ * for both is what the platform says about its base set -- `base.pins`,
2684
+ * which refuses a layer carrying a pinned package at another version, and
2685
+ * `base.exports`, which the export audit reads. A platform that predates
2686
+ * them sends neither, and the layer builds unchecked, as it always did
2687
+ * here; that is worth a line in the log rather than silence.
2688
+ */
2689
+ if (deps.length > 0 && !target.base.pins) {
2690
+ warnings.push("The platform did not say which versions its base layer carries, so " + "this build could not check that the project's TanStack and React " + "agree with them.");
2691
+ }
2692
+ if (deps.length > 0 && !target.base.exports) {
2693
+ warnings.push("The platform did not say what its base layer exports, so this build " + "could not check that the project's dependencies import only what it has.");
2694
+ }
2680
2695
  const layer = await node.buildVendorLayer(root, deps, css, {
2681
2696
  base: target.base,
2682
- // A project on the platform layers its own copies; only the platform's
2683
- // own checkout carries pinned ones.
2684
2697
  resolvePlatformModule: () => null,
2685
- // No built base layer here: the export audit is skipped, not failed.
2686
2698
  baseLayerDir: null
2687
2699
  });
2700
+
2701
+ /*
2702
+ * What the layer left out, and why. A dependency the layer could not build
2703
+ * is dropped rather than fatal -- a Vite plugin nobody imports, say -- and
2704
+ * one the app DOES import then fails the site build naming only the
2705
+ * specifier. The reason is what says whether to fix the import or the
2706
+ * package: an export the base layer does not have, most of all, which is a
2707
+ * version disagreement with the platform rather than a missing package.
2708
+ */
2709
+ for (const {
2710
+ specifier,
2711
+ reason
2712
+ } of (layer === null || layer === void 0 ? void 0 : layer.skipped) ?? []) {
2713
+ warnings.push(`${specifier} is not in the dependency layer: ${reason}`);
2714
+ }
2688
2715
  log("Building the site");
2689
2716
  const fileBased = await node.isFileBased(root);
2690
2717
  const files = fileBased ? await node.generateRouteTree(root, sources) : sources;
@@ -2737,6 +2764,7 @@ async function buildArtifacts(options) {
2737
2764
  ...(layerRev !== undefined ? {
2738
2765
  layerRev
2739
2766
  } : {}),
2767
+ baseRev: target.base.rev,
2740
2768
  warnings
2741
2769
  };
2742
2770
  } catch (e) {
@@ -3387,6 +3415,11 @@ async function runPublish(options) {
3387
3415
  commit: git.commit,
3388
3416
  branch: git.branch,
3389
3417
  layerRev: options.layerRev ?? layerRev,
3418
+ // Only for a build made here: one handed over with `--artifacts` was
3419
+ // built against a base layer this run never saw.
3420
+ ...(built ? {
3421
+ baseRev: built.baseRev
3422
+ } : {}),
3390
3423
  linksOwnCss: options.linksOwnCss ?? ((_built2 = built) === null || _built2 === void 0 ? void 0 : _built2.linksOwnCss) ?? null,
3391
3424
  artifacts: collected.artifacts.map(({
3392
3425
  key,
@@ -2677,14 +2677,41 @@ async function buildArtifacts(options) {
2677
2677
  // published with the site under the names their `url()`s now use.
2678
2678
  const dependencyAssets = {};
2679
2679
  const css = await node.collectDependencyCss(root, node.cssSpecifiersOf(sources), dependencyAssets);
2680
+ /*
2681
+ * There is no platform checkout here, so nothing can be PINNED to the
2682
+ * platform's copies and no built base layer can be read. What stands in
2683
+ * for both is what the platform says about its base set -- `base.pins`,
2684
+ * which refuses a layer carrying a pinned package at another version, and
2685
+ * `base.exports`, which the export audit reads. A platform that predates
2686
+ * them sends neither, and the layer builds unchecked, as it always did
2687
+ * here; that is worth a line in the log rather than silence.
2688
+ */
2689
+ if (deps.length > 0 && !target.base.pins) {
2690
+ warnings.push("The platform did not say which versions its base layer carries, so " + "this build could not check that the project's TanStack and React " + "agree with them.");
2691
+ }
2692
+ if (deps.length > 0 && !target.base.exports) {
2693
+ warnings.push("The platform did not say what its base layer exports, so this build " + "could not check that the project's dependencies import only what it has.");
2694
+ }
2680
2695
  const layer = await node.buildVendorLayer(root, deps, css, {
2681
2696
  base: target.base,
2682
- // A project on the platform layers its own copies; only the platform's
2683
- // own checkout carries pinned ones.
2684
2697
  resolvePlatformModule: () => null,
2685
- // No built base layer here: the export audit is skipped, not failed.
2686
2698
  baseLayerDir: null
2687
2699
  });
2700
+
2701
+ /*
2702
+ * What the layer left out, and why. A dependency the layer could not build
2703
+ * is dropped rather than fatal -- a Vite plugin nobody imports, say -- and
2704
+ * one the app DOES import then fails the site build naming only the
2705
+ * specifier. The reason is what says whether to fix the import or the
2706
+ * package: an export the base layer does not have, most of all, which is a
2707
+ * version disagreement with the platform rather than a missing package.
2708
+ */
2709
+ for (const {
2710
+ specifier,
2711
+ reason
2712
+ } of (layer === null || layer === void 0 ? void 0 : layer.skipped) ?? []) {
2713
+ warnings.push(`${specifier} is not in the dependency layer: ${reason}`);
2714
+ }
2688
2715
  log("Building the site");
2689
2716
  const fileBased = await node.isFileBased(root);
2690
2717
  const files = fileBased ? await node.generateRouteTree(root, sources) : sources;
@@ -2737,6 +2764,7 @@ async function buildArtifacts(options) {
2737
2764
  ...(layerRev !== undefined ? {
2738
2765
  layerRev
2739
2766
  } : {}),
2767
+ baseRev: target.base.rev,
2740
2768
  warnings
2741
2769
  };
2742
2770
  } catch (e) {
@@ -3387,6 +3415,11 @@ async function runPublish(options) {
3387
3415
  commit: git.commit,
3388
3416
  branch: git.branch,
3389
3417
  layerRev: options.layerRev ?? layerRev,
3418
+ // Only for a build made here: one handed over with `--artifacts` was
3419
+ // built against a base layer this run never saw.
3420
+ ...(built ? {
3421
+ baseRev: built.baseRev
3422
+ } : {}),
3390
3423
  linksOwnCss: options.linksOwnCss ?? ((_built2 = built) === null || _built2 === void 0 ? void 0 : _built2.linksOwnCss) ?? null,
3391
3424
  artifacts: collected.artifacts.map(({
3392
3425
  key,
@@ -2642,14 +2642,41 @@ async function buildArtifacts(options) {
2642
2642
  // published with the site under the names their `url()`s now use.
2643
2643
  const dependencyAssets = {};
2644
2644
  const css = await node.collectDependencyCss(root, node.cssSpecifiersOf(sources), dependencyAssets);
2645
+ /*
2646
+ * There is no platform checkout here, so nothing can be PINNED to the
2647
+ * platform's copies and no built base layer can be read. What stands in
2648
+ * for both is what the platform says about its base set -- `base.pins`,
2649
+ * which refuses a layer carrying a pinned package at another version, and
2650
+ * `base.exports`, which the export audit reads. A platform that predates
2651
+ * them sends neither, and the layer builds unchecked, as it always did
2652
+ * here; that is worth a line in the log rather than silence.
2653
+ */
2654
+ if (deps.length > 0 && !target.base.pins) {
2655
+ warnings.push("The platform did not say which versions its base layer carries, so " + "this build could not check that the project's TanStack and React " + "agree with them.");
2656
+ }
2657
+ if (deps.length > 0 && !target.base.exports) {
2658
+ warnings.push("The platform did not say what its base layer exports, so this build " + "could not check that the project's dependencies import only what it has.");
2659
+ }
2645
2660
  const layer = await node.buildVendorLayer(root, deps, css, {
2646
2661
  base: target.base,
2647
- // A project on the platform layers its own copies; only the platform's
2648
- // own checkout carries pinned ones.
2649
2662
  resolvePlatformModule: () => null,
2650
- // No built base layer here: the export audit is skipped, not failed.
2651
2663
  baseLayerDir: null
2652
2664
  });
2665
+
2666
+ /*
2667
+ * What the layer left out, and why. A dependency the layer could not build
2668
+ * is dropped rather than fatal -- a Vite plugin nobody imports, say -- and
2669
+ * one the app DOES import then fails the site build naming only the
2670
+ * specifier. The reason is what says whether to fix the import or the
2671
+ * package: an export the base layer does not have, most of all, which is a
2672
+ * version disagreement with the platform rather than a missing package.
2673
+ */
2674
+ for (const {
2675
+ specifier,
2676
+ reason
2677
+ } of (layer === null || layer === void 0 ? void 0 : layer.skipped) ?? []) {
2678
+ warnings.push(`${specifier} is not in the dependency layer: ${reason}`);
2679
+ }
2653
2680
  log("Building the site");
2654
2681
  const fileBased = await node.isFileBased(root);
2655
2682
  const files = fileBased ? await node.generateRouteTree(root, sources) : sources;
@@ -2702,6 +2729,7 @@ async function buildArtifacts(options) {
2702
2729
  ...(layerRev !== undefined ? {
2703
2730
  layerRev
2704
2731
  } : {}),
2732
+ baseRev: target.base.rev,
2705
2733
  warnings
2706
2734
  };
2707
2735
  } catch (e) {
@@ -3352,6 +3380,11 @@ async function runPublish(options) {
3352
3380
  commit: git.commit,
3353
3381
  branch: git.branch,
3354
3382
  layerRev: options.layerRev ?? layerRev,
3383
+ // Only for a build made here: one handed over with `--artifacts` was
3384
+ // built against a base layer this run never saw.
3385
+ ...(built ? {
3386
+ baseRev: built.baseRev
3387
+ } : {}),
3355
3388
  linksOwnCss: options.linksOwnCss ?? ((_built2 = built) === null || _built2 === void 0 ? void 0 : _built2.linksOwnCss) ?? null,
3356
3389
  artifacts: collected.artifacts.map(({
3357
3390
  key,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@valbuild/cli",
3
3
  "private": false,
4
- "version": "0.144.12",
4
+ "version": "0.145.1",
5
5
  "description": "Val CLI tools",
6
6
  "repository": {
7
7
  "type": "git",
@@ -26,11 +26,11 @@
26
26
  "picocolors": "^1.1.1",
27
27
  "zod": "^4.4.3",
28
28
  "zod-validation-error": "^5.0.0",
29
- "@valbuild/core": "0.144.0",
29
+ "@valbuild/core": "0.145.0",
30
30
  "@valbuild/eslint-plugin": "0.125.0",
31
- "@valbuild/language-server": "0.144.12",
32
- "@valbuild/server": "0.144.12",
33
- "@valbuild/shared": "0.144.9"
31
+ "@valbuild/language-server": "0.145.1",
32
+ "@valbuild/server": "0.145.1",
33
+ "@valbuild/shared": "0.145.1"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "prettier": "*",
@@ -38,7 +38,7 @@
38
38
  "rolldown": ">=1.2.5",
39
39
  "@tanstack/router-generator": ">=1.167.0",
40
40
  "@tanstack/router-plugin": ">=1.168.0",
41
- "@valbuild/tanstack-build": "^0.144.4"
41
+ "@valbuild/tanstack-build": "^0.145.1"
42
42
  },
43
43
  "peerDependenciesMeta": {
44
44
  "@valbuild/tanstack-build": {
@@ -70,7 +70,7 @@
70
70
  "prettier": "~3.8.5",
71
71
  "rolldown": "^1.2.5",
72
72
  "tsx": "^4.23.12",
73
- "@valbuild/tanstack-build": "0.144.4"
73
+ "@valbuild/tanstack-build": "0.145.1"
74
74
  },
75
75
  "scripts": {
76
76
  "start": "tsx src/cli.ts --",
@@ -81,6 +81,7 @@ type Report = {
81
81
  commit: string | null;
82
82
  branch: string | null;
83
83
  layerRev: string | null;
84
+ baseRev: string | null;
84
85
  linksOwnCss: boolean | null;
85
86
  keys: string[];
86
87
  }>;
@@ -105,6 +106,8 @@ type Report = {
105
106
  wiredBranch: string | null;
106
107
  /** Whether the run warned that the checkout's branch is not the project's. */
107
108
  warnedAboutBranch: boolean;
109
+ /** Whether it said the platform sent no pins or exports -- which it did. */
110
+ warnedAboutPlatformFacts: boolean;
108
111
  };
109
112
 
110
113
  /** A 1x1 PNG: small enough to inline, so the page carries it as a data URI. */
@@ -166,6 +169,7 @@ async function publishOnce(
166
169
  commit: string | null;
167
170
  branch: string | null;
168
171
  layerRev: string | null;
172
+ baseRev?: string | null;
169
173
  linksOwnCss: boolean | null;
170
174
  artifacts: Array<{ key: string }>;
171
175
  };
@@ -174,6 +178,7 @@ async function publishOnce(
174
178
  commit: d.commit,
175
179
  branch: d.branch,
176
180
  layerRev: d.layerRev,
181
+ baseRev: d.baseRev ?? null,
177
182
  linksOwnCss: d.linksOwnCss,
178
183
  keys: d.artifacts.map((a) => a.key).sort(),
179
184
  };
@@ -218,6 +223,9 @@ async function publishOnce(
218
223
  : false),
219
224
  checkoutUntouched: JSON.stringify(listed(root)) === JSON.stringify(before),
220
225
  wiredBranch: source ? wiredBranchOf(source) : null,
226
+ warnedAboutPlatformFacts: logged.some((line) =>
227
+ /did not say (which versions|what its base layer exports)/.test(line),
228
+ ),
221
229
  warnedAboutBranch: logged.some(
222
230
  (line) => line.startsWith("warn:") && line.includes("publishes"),
223
231
  ),
@@ -38,6 +38,8 @@ export type BuiltArtifacts =
38
38
  * `undefined` when the layer artifact is in `dir`, which names it.
39
39
  */
40
40
  layerRev?: string | null;
41
+ /** The base layer this was built against: the build target's `base.rev`. */
42
+ baseRev: string;
41
43
  warnings: string[];
42
44
  }
43
45
  | { status: "error"; message: string };
@@ -287,15 +289,46 @@ export async function buildArtifacts(options: {
287
289
  node.cssSpecifiersOf(sources),
288
290
  dependencyAssets,
289
291
  );
292
+ /*
293
+ * There is no platform checkout here, so nothing can be PINNED to the
294
+ * platform's copies and no built base layer can be read. What stands in
295
+ * for both is what the platform says about its base set -- `base.pins`,
296
+ * which refuses a layer carrying a pinned package at another version, and
297
+ * `base.exports`, which the export audit reads. A platform that predates
298
+ * them sends neither, and the layer builds unchecked, as it always did
299
+ * here; that is worth a line in the log rather than silence.
300
+ */
301
+ if (deps.length > 0 && !target.base.pins) {
302
+ warnings.push(
303
+ "The platform did not say which versions its base layer carries, so " +
304
+ "this build could not check that the project's TanStack and React " +
305
+ "agree with them.",
306
+ );
307
+ }
308
+ if (deps.length > 0 && !target.base.exports) {
309
+ warnings.push(
310
+ "The platform did not say what its base layer exports, so this build " +
311
+ "could not check that the project's dependencies import only what it has.",
312
+ );
313
+ }
290
314
  const layer = await node.buildVendorLayer(root, deps, css, {
291
315
  base: target.base,
292
- // A project on the platform layers its own copies; only the platform's
293
- // own checkout carries pinned ones.
294
316
  resolvePlatformModule: () => null,
295
- // No built base layer here: the export audit is skipped, not failed.
296
317
  baseLayerDir: null,
297
318
  });
298
319
 
320
+ /*
321
+ * What the layer left out, and why. A dependency the layer could not build
322
+ * is dropped rather than fatal -- a Vite plugin nobody imports, say -- and
323
+ * one the app DOES import then fails the site build naming only the
324
+ * specifier. The reason is what says whether to fix the import or the
325
+ * package: an export the base layer does not have, most of all, which is a
326
+ * version disagreement with the platform rather than a missing package.
327
+ */
328
+ for (const { specifier, reason } of layer?.skipped ?? []) {
329
+ warnings.push(`${specifier} is not in the dependency layer: ${reason}`);
330
+ }
331
+
299
332
  log("Building the site");
300
333
  const fileBased = await node.isFileBased(root);
301
334
  const files = fileBased
@@ -357,6 +390,7 @@ export async function buildArtifacts(options: {
357
390
  buildHash: built.hash,
358
391
  linksOwnCss: built.linksOwnCss,
359
392
  ...(layerRev !== undefined ? { layerRev } : {}),
393
+ baseRev: target.base.rev,
360
394
  warnings,
361
395
  };
362
396
  } catch (e) {
@@ -20,6 +20,7 @@ type Declared = {
20
20
  commit: string | null;
21
21
  branch: string | null;
22
22
  layerRev: string | null;
23
+ baseRev: string | null;
23
24
  linksOwnCss: boolean | null;
24
25
  keys: string[];
25
26
  };
@@ -37,6 +38,7 @@ type Run = {
37
38
  checkoutUntouched: boolean;
38
39
  wiredBranch: string | null;
39
40
  warnedAboutBranch: boolean;
41
+ warnedAboutPlatformFacts: boolean;
40
42
  };
41
43
 
42
44
  let probe: { fresh: Run; held: Run; elsewhere: Run };
@@ -101,6 +103,9 @@ test("a layer content does not hold is built and sent, and named by its own rev"
101
103
  expect(fresh.declared[0]!.keys).toContain("layer");
102
104
  expect(fresh.uploadedLayerRev).toEqual(expect.any(String));
103
105
  expect(fresh.declared[0]!.layerRev).toBe(fresh.uploadedLayerRev);
106
+ // The base layer it was built against -- the fake's build target says
107
+ // "base" -- so the platform can refuse it if it has been deployed since.
108
+ expect(fresh.declared[0]!.baseRev).toBe("base");
104
109
  });
105
110
 
106
111
  test("a layer content already holds is named, not sent again", () => {
@@ -124,6 +129,12 @@ test("a checkout on another branch is wired at the project's, and says so", () =
124
129
  expect(probe.fresh.warnedAboutBranch).toBe(false);
125
130
  });
126
131
 
132
+ test("the platform's pins and exports reach the build through the client", () => {
133
+ // content sends them (FAKE_BUILD_TARGET); a parser that dropped them made
134
+ // every publish skip both checks and say the platform had not sent them.
135
+ expect(probe.fresh.warnedAboutPlatformFacts).toBe(false);
136
+ });
137
+
127
138
  test("the checkout is left as it was found", () => {
128
139
  // The route tree is generated on disk and put back; the build is written
129
140
  // to a temporary directory, not into `.val/publish`.
@@ -92,6 +92,11 @@ export const FAKE_BUILD_TARGET: BuildTargetResponse = {
92
92
  flightServer: "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
93
93
  serverFnBase: "/_serverFn/",
94
94
  },
95
+ // What the platform says its base layer carries (home's base-facts.ts).
96
+ // Empty: nothing in the probe's project is pinned or imports from a base
97
+ // chunk by name -- what matters is that they arrive at all.
98
+ pins: {},
99
+ exports: { worker: {}, browser: {} },
95
100
  },
96
101
  project: { rev: null, rsc: false, modules: {}, css: {}, workerOnly: [] },
97
102
  };
@@ -261,6 +261,9 @@ export async function runPublish(
261
261
  commit: git.commit,
262
262
  branch: git.branch,
263
263
  layerRev: options.layerRev ?? layerRev,
264
+ // Only for a build made here: one handed over with `--artifacts` was
265
+ // built against a base layer this run never saw.
266
+ ...(built ? { baseRev: built.baseRev } : {}),
264
267
  linksOwnCss: options.linksOwnCss ?? built?.linksOwnCss ?? null,
265
268
  artifacts: collected.artifacts.map(({ key, sha256, bytes }) => ({
266
269
  key,