@decocms/blocks-cli 7.67.0 → 7.67.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/blocks-cli",
|
|
3
|
-
"version": "7.67.
|
|
3
|
+
"version": "7.67.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Deco codegen (generate-blocks, generate-schema, generate-invoke) and Fresh-to-TanStack migration tooling",
|
|
6
6
|
"repository": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"lint:unused": "knip"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@decocms/blocks": "7.67.
|
|
36
|
+
"@decocms/blocks": "7.67.1",
|
|
37
37
|
"ts-morph": "^27.0.0",
|
|
38
38
|
"tsx": "^4.22.5"
|
|
39
39
|
},
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
snapshotKey,
|
|
11
11
|
} from "@decocms/blocks/cms";
|
|
12
12
|
import { createKvRestClient, type KvRestClient, kvConfigFromEnv } from "./lib/cf-kv-rest";
|
|
13
|
+
import { readDecofileFromDir } from "./lib/read-decofile";
|
|
13
14
|
import { kvNamespaceIdFromToml, kvNamespaceIdFromWrangler } from "./lib/wrangler-config";
|
|
14
15
|
import {
|
|
15
16
|
buildSnapshot,
|
|
@@ -341,3 +342,71 @@ describe("sync-helpers", () => {
|
|
|
341
342
|
expect(paths).not.toContain(undefined);
|
|
342
343
|
});
|
|
343
344
|
});
|
|
345
|
+
|
|
346
|
+
describe("readDecofileFromDir — CSV redirects", () => {
|
|
347
|
+
/** A site tree: `.deco/blocks/*.json` + `public/*.csv`. Returns the blocks dir. */
|
|
348
|
+
function makeSite(blocks: Record<string, unknown>, csv: Record<string, string>): string {
|
|
349
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "decofile-csv-"));
|
|
350
|
+
const blocksDir = path.join(root, ".deco", "blocks");
|
|
351
|
+
fs.mkdirSync(blocksDir, { recursive: true });
|
|
352
|
+
fs.mkdirSync(path.join(root, "public"), { recursive: true });
|
|
353
|
+
for (const [name, value] of Object.entries(blocks)) {
|
|
354
|
+
fs.writeFileSync(path.join(blocksDir, name), JSON.stringify(value));
|
|
355
|
+
}
|
|
356
|
+
for (const [name, value] of Object.entries(csv)) {
|
|
357
|
+
fs.writeFileSync(path.join(root, "public", name), value);
|
|
358
|
+
}
|
|
359
|
+
return blocksDir;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
it("materializes a CSV loader nested under site.routes[] into a top-level block", () => {
|
|
363
|
+
// The shape that broke production: the CSV loader is nested inside
|
|
364
|
+
// `site.json -> routes[]`, where `loadRedirects` (top-level only) can't see
|
|
365
|
+
// it, and the CSV itself was only ever read by the unported Fresh loader.
|
|
366
|
+
const blocksDir = makeSite(
|
|
367
|
+
{
|
|
368
|
+
"site.json": {
|
|
369
|
+
__resolveType: "site/apps/site.ts",
|
|
370
|
+
routes: [{ __resolveType: "website/loaders/redirectsFromCsv.ts", from: "static/r.csv" }],
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
{ "r.csv": "from,to,type\n/old,/new,permanent\n/tmp,/other\n" },
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
const { blocks } = readDecofileFromDir(blocksDir, { silent: true });
|
|
377
|
+
|
|
378
|
+
expect(blocks["__csv_redirects__r.csv"]).toEqual({
|
|
379
|
+
__resolveType: "website/loaders/redirects.ts",
|
|
380
|
+
redirects: [
|
|
381
|
+
{ from: "/old", to: "/new", type: "permanent" },
|
|
382
|
+
{ from: "/tmp", to: "/other", type: "temporary" },
|
|
383
|
+
],
|
|
384
|
+
});
|
|
385
|
+
// The real blocks are untouched.
|
|
386
|
+
expect(blocks.site).toBeDefined();
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it("lets a real block win over the synthetic one on a key collision", () => {
|
|
390
|
+
const blocksDir = makeSite(
|
|
391
|
+
{
|
|
392
|
+
"site.json": {
|
|
393
|
+
routes: [{ __resolveType: "website/loaders/redirectsFromCsv.ts", from: "r.csv" }],
|
|
394
|
+
},
|
|
395
|
+
// Decodes to exactly the synthetic key the CSV materializer produces.
|
|
396
|
+
"__csv_redirects__r.csv.json": { curated: true },
|
|
397
|
+
},
|
|
398
|
+
{ "r.csv": "/old,/new\n" },
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
const { blocks } = readDecofileFromDir(blocksDir, { silent: true });
|
|
402
|
+
|
|
403
|
+
// `{ ...csv, ...blocks }` — the curated block wins, never the synthetic one.
|
|
404
|
+
expect(blocks["__csv_redirects__r.csv"]).toEqual({ curated: true });
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it("is a no-op when no CSV loader is referenced", () => {
|
|
408
|
+
const blocksDir = makeSite({ "site.json": { __resolveType: "site/apps/site.ts" } }, {});
|
|
409
|
+
const { blocks } = readDecofileFromDir(blocksDir, { silent: true });
|
|
410
|
+
expect(Object.keys(blocks)).toEqual(["site"]);
|
|
411
|
+
});
|
|
412
|
+
});
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* the fast-deploy CI scripts (`migrate-blocks-to-kv.ts`, `sync-blocks-to-kv.ts`)
|
|
7
7
|
* produce a decofile byte-identical to the bundled `blocks.gen` snapshot.
|
|
8
8
|
*
|
|
9
|
-
* Reuses `scripts/lib/blocks-dedupe.ts`.
|
|
9
|
+
* Reuses `scripts/lib/blocks-dedupe.ts` and `scripts/lib/csv-redirects.ts`.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import * as fs from "node:fs";
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
decodeBlockNameWithPasses,
|
|
19
19
|
mergeCandidates,
|
|
20
20
|
} from "./blocks-dedupe";
|
|
21
|
+
import { buildCsvRedirectBlocks } from "./csv-redirects";
|
|
21
22
|
|
|
22
23
|
export interface ReadDecofileResult {
|
|
23
24
|
/** Decoded block key → parsed block JSON. */
|
|
@@ -66,5 +67,15 @@ export function readDecofileFromDir(blocksDir: string, opts: { silent?: boolean
|
|
|
66
67
|
for (const [key, candidate] of Object.entries(winners)) {
|
|
67
68
|
blocks[key] = candidate.parsed;
|
|
68
69
|
}
|
|
69
|
-
|
|
70
|
+
|
|
71
|
+
// Synthetic `__csv_redirects__*` blocks, exactly as `generate-blocks` builds
|
|
72
|
+
// them. Without this the KV snapshot is NOT byte-identical to `blocks.gen`:
|
|
73
|
+
// a site whose redirects come from a CSV would lose every one of them the
|
|
74
|
+
// moment the bundled snapshot stops being the fallback (the fastDeploy stub
|
|
75
|
+
// makes KV the only source), turning migration 301s into silent 404s.
|
|
76
|
+
// Same precedence as the generator — a curated CMS redirect wins over CSV.
|
|
77
|
+
const csvBlocks = buildCsvRedirectBlocks(blocks, { blocksDir, silent: opts.silent });
|
|
78
|
+
const merged = Object.keys(csvBlocks).length ? { ...csvBlocks, ...blocks } : blocks;
|
|
79
|
+
|
|
80
|
+
return { blocks: merged, collisions };
|
|
70
81
|
}
|