@decocms/tanstack 7.71.3 → 7.71.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/tanstack",
3
- "version": "7.71.3",
3
+ "version": "7.71.4",
4
4
  "type": "module",
5
5
  "description": "Deco framework binding for TanStack Start + Cloudflare Workers",
6
6
  "repository": {
@@ -27,9 +27,9 @@
27
27
  "lint:unused": "knip"
28
28
  },
29
29
  "dependencies": {
30
- "@decocms/blocks": "7.71.3",
31
- "@decocms/blocks-admin": "7.71.3",
32
- "@decocms/blocks-cli": "7.71.3",
30
+ "@decocms/blocks": "7.71.4",
31
+ "@decocms/blocks-admin": "7.71.4",
32
+ "@decocms/blocks-cli": "7.71.4",
33
33
  "@deco-cx/warp-node": "^0.3.16",
34
34
  "fast-json-patch": "^3.1.0",
35
35
  "ws": "^8.18.0"
@@ -84,6 +84,12 @@ function resolveBuildHash() {
84
84
  return Date.now().toString(36);
85
85
  }
86
86
 
87
+ // Virtual id for meta.gen.json on the server: the real path wrapped so the id
88
+ // does NOT end in `.json` (Vite's `vite:json` would re-parse our JS output).
89
+ const META_GEN_PREFIX = "\0deco-meta:";
90
+ const META_GEN_SUFFIX = ".deco-meta.js";
91
+ const metaGenSsrId = (file) => META_GEN_PREFIX + file + META_GEN_SUFFIX;
92
+
87
93
  // Bare-specifier stubs resolved by ID before Vite touches them.
88
94
  /** @type {Record<string, string>} */
89
95
  const CLIENT_STUBS = {
@@ -235,6 +241,16 @@ export function decoVitePlugin({ fastDeploy = "auto" } = {}) {
235
241
  // SSR-only stubs — must be checked first since the client guard below
236
242
  // returns undefined for everything that hasn't matched yet on SSR.
237
243
  if (options?.ssr && SSR_STUBS[id]) return SSR_STUBS[id];
244
+ // meta.gen.json on the SERVER → a virtual id that does NOT end in
245
+ // `.json`. `load` below emits JS (`JSON.parse("...")`), and Vite's
246
+ // `vite:json` transform runs on every id matching `\.json$` regardless
247
+ // of who loaded it — it would try to JSON.parse that JS and fail the
248
+ // build ("Failed to parse JSON file").
249
+ if (options?.ssr && importer && id.endsWith("meta.gen.json")) {
250
+ const file = path.isAbsolute(id) ? id : path.resolve(path.dirname(importer), id);
251
+ if (existsSync(file)) return metaGenSsrId(file);
252
+ // Absent (pre-generate-schema) — let Vite report it normally.
253
+ }
238
254
  // Server builds keep the real modules.
239
255
  if (options?.ssr) return undefined;
240
256
  // Bare-specifier exact-match stubs (react-dom/server, node:stream, etc.).
@@ -294,12 +310,12 @@ export function decoVitePlugin({ fastDeploy = "auto" } = {}) {
294
310
  // materializes the object graph at all.
295
311
  //
296
312
  // The client is stubbed in `resolveId` above and never reaches here.
297
- if (id.endsWith("meta.gen.json") && options?.ssr) {
298
- if (existsSync(id)) {
299
- const raw = readFileSync(id, "utf-8");
300
- return `export default JSON.parse(${JSON.stringify(raw)});`;
301
- }
302
- // Absent (pre-generate-schema) — let Vite report it normally.
313
+ if (id.startsWith(META_GEN_PREFIX) && id.endsWith(META_GEN_SUFFIX)) {
314
+ const file = id.slice(META_GEN_PREFIX.length, -META_GEN_SUFFIX.length);
315
+ // Virtual id → Vite won't watch the file on its own.
316
+ this.addWatchFile?.(file);
317
+ const raw = readFileSync(file, "utf-8");
318
+ return `export default JSON.parse(${JSON.stringify(raw)});`;
303
319
  }
304
320
 
305
321
  // loaders.gen.ts — the site's invoke registry (`export const siteLoaders`).
@@ -588,7 +604,7 @@ export function decoVitePlugin({ fastDeploy = "auto" } = {}) {
588
604
  console.log(`[deco] meta.gen.json updated (${Date.now() - start}ms)`);
589
605
  // Invalidate the meta.gen.json module so SSR picks up fresh schema
590
606
  const mod =
591
- server.environments?.ssr?.moduleGraph?.getModuleById(schemaOutFile);
607
+ server.environments?.ssr?.moduleGraph?.getModuleById(metaGenSsrId(schemaOutFile));
592
608
  if (mod) {
593
609
  server.environments.ssr.moduleGraph.invalidateModule(mod);
594
610
  }
@@ -1,3 +1,6 @@
1
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import path from "node:path";
1
4
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
5
  import { decoVitePlugin, generateSchemaArgs } from "./plugin.js";
3
6
 
@@ -122,3 +125,69 @@ describe("decoVitePlugin __DECO_BUILD_HASH__ injection", () => {
122
125
  }
123
126
  });
124
127
  });
128
+
129
+ describe("decoVitePlugin meta.gen.json on SSR (regression: vite:json double-parse)", () => {
130
+ // Vite's json plugin transforms every `.json` id — even when another plugin
131
+ // already loaded it as JS — so the virtual id must not end in `.json`.
132
+ let dir;
133
+
134
+ beforeEach(() => {
135
+ dir = mkdtempSync(path.join(tmpdir(), "deco-meta-"));
136
+ mkdirSync(path.join(dir, ".deco"));
137
+ mkdirSync(path.join(dir, "src"));
138
+ writeFileSync(path.join(dir, ".deco", "meta.gen.json"), '{"major":1,"schema":{}}');
139
+ });
140
+
141
+ afterEach(() => {
142
+ rmSync(dir, { recursive: true, force: true });
143
+ });
144
+
145
+ it("resolves to a virtual id vite:json does not transform, and loads it as JSON.parse", () => {
146
+ const p = getPlugin();
147
+ const importer = path.join(dir, "src", "setup.ts");
148
+ const id = p.resolveId.call({}, "../.deco/meta.gen.json", importer, { ssr: true });
149
+ expect(id).toBeTypeOf("string");
150
+ expect(id.endsWith(".json")).toBe(false);
151
+
152
+ const watched = [];
153
+ const code = p.load.call({ addWatchFile: (f) => watched.push(f) }, id, { ssr: true });
154
+ expect(code).toBe(`export default JSON.parse(${JSON.stringify('{"major":1,"schema":{}}')});`);
155
+ expect(watched).toEqual([path.join(dir, ".deco", "meta.gen.json")]);
156
+ });
157
+
158
+ it("gives each meta.gen.json its own module id (no shared state)", () => {
159
+ const p = getPlugin();
160
+ const other = mkdtempSync(path.join(tmpdir(), "deco-meta-other-"));
161
+ try {
162
+ writeFileSync(path.join(other, "meta.gen.json"), '{"major":2}');
163
+ const a = p.resolveId.call({}, "../.deco/meta.gen.json", path.join(dir, "src", "setup.ts"), {
164
+ ssr: true,
165
+ });
166
+ const b = p.resolveId.call({}, path.join(other, "meta.gen.json"), path.join(dir, "x.ts"), {
167
+ ssr: true,
168
+ });
169
+ expect(a).not.toBe(b);
170
+ // Loading in reverse order still returns each file's own content.
171
+ expect(p.load.call({}, b, { ssr: true })).toContain('\\"major\\":2');
172
+ expect(p.load.call({}, a, { ssr: true })).toContain('\\"major\\":1');
173
+ } finally {
174
+ rmSync(other, { recursive: true, force: true });
175
+ }
176
+ });
177
+
178
+ it("falls through to Vite when meta.gen.json does not exist yet", () => {
179
+ const p = getPlugin();
180
+ const importer = path.join(dir, "src", "setup.ts");
181
+ expect(
182
+ p.resolveId.call({}, "../missing/meta.gen.json", importer, { ssr: true }),
183
+ ).toBeUndefined();
184
+ });
185
+
186
+ it("still stubs meta.gen.json on the client", () => {
187
+ const p = getPlugin();
188
+ const importer = path.join(dir, "src", "setup.ts");
189
+ expect(p.resolveId.call({}, "../.deco/meta.gen.json", importer, { ssr: false })).toBe(
190
+ "\0stub:meta-gen",
191
+ );
192
+ });
193
+ });