@decocms/blocks-cli 7.21.1 → 7.22.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/blocks-cli",
3
- "version": "7.21.1",
3
+ "version": "7.22.0",
4
4
  "type": "module",
5
5
  "description": "Deco codegen (generate-blocks, generate-schema, generate-invoke) and Fresh-to-TanStack migration tooling",
6
6
  "repository": {
@@ -16,7 +16,8 @@
16
16
  "deco-cf-observability": "./scripts/migrate-to-cf-observability.ts",
17
17
  "deco-audit-observability": "./scripts/audit-observability-config.ts",
18
18
  "deco-migrate-blocks-to-kv": "./scripts/migrate-blocks-to-kv.ts",
19
- "deco-sync-blocks-to-kv": "./scripts/sync-blocks-to-kv.ts"
19
+ "deco-sync-blocks-to-kv": "./scripts/sync-blocks-to-kv.ts",
20
+ "deco-upgrade-6-to-7": "./scripts/upgrade-6-to-7.ts"
20
21
  },
21
22
  "//exports": "Deliberately narrow. ./generate is the ONE public module entry (unified orchestrator); ./generate-blocks stays only because @decocms/tanstack's vite plugin tsImports it (readBlockDelta + programmatic generateBlocks) — each surviving entry must name its consumer here. The other scripts remain shipped FILES (the orchestrator spawns them; sites' existing `tsx node_modules/@decocms/blocks-cli/scripts/generate-*.ts` invocations keep working) but are internal implementation details of ./generate, not module subpaths. CLIs are exposed via bin, not exports.",
22
23
  "exports": {
@@ -30,7 +31,7 @@
30
31
  "lint:unused": "knip"
31
32
  },
32
33
  "dependencies": {
33
- "@decocms/blocks": "7.21.1",
34
+ "@decocms/blocks": "7.22.0",
34
35
  "ts-morph": "^27.0.0",
35
36
  "tsx": "^4.22.5"
36
37
  },
@@ -0,0 +1,145 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { upgradeFileToV7 } from "./upgrade-6-to-7";
3
+
4
+ describe("upgradeFileToV7 (#367) — simple 1:1 specifier rewrites", () => {
5
+ it("rewrites @decocms/start/vite", () => {
6
+ const r = upgradeFileToV7(`import { decoVitePlugin } from "@decocms/start/vite";`);
7
+ expect(r.changed).toBe(true);
8
+ expect(r.content).toBe(`import { decoVitePlugin } from "@decocms/tanstack/vite";`);
9
+ });
10
+
11
+ it("rewrites @decocms/start/sdk/cookiePassthrough", () => {
12
+ const r = upgradeFileToV7(
13
+ `import { getRequestCookieHeader } from "@decocms/start/sdk/cookiePassthrough";`,
14
+ );
15
+ expect(r.content).toBe(
16
+ `import { getRequestCookieHeader } from "@decocms/tanstack/sdk/cookiePassthrough";`,
17
+ );
18
+ });
19
+
20
+ it("rewrites @decocms/start/sdk/workerEntry and sdk/router to @decocms/tanstack root", () => {
21
+ const r = upgradeFileToV7(
22
+ [
23
+ `import { createDecoWorkerEntry } from "@decocms/start/sdk/workerEntry";`,
24
+ `import { createDecoRouter } from "@decocms/start/sdk/router";`,
25
+ ].join("\n"),
26
+ );
27
+ expect(r.content).toBe(
28
+ [
29
+ `import { createDecoWorkerEntry } from "@decocms/tanstack";`,
30
+ `import { createDecoRouter } from "@decocms/tanstack";`,
31
+ ].join("\n"),
32
+ );
33
+ });
34
+
35
+ it("rewrites generic @decocms/start/sdk/* to @decocms/blocks/sdk/*", () => {
36
+ const r = upgradeFileToV7(
37
+ [
38
+ `import { useScript } from "@decocms/start/sdk/useScript";`,
39
+ `import { useDevice } from "@decocms/start/sdk/useDevice";`,
40
+ `import { clx } from "@decocms/start/sdk/clx";`,
41
+ ].join("\n"),
42
+ );
43
+ expect(r.content).toBe(
44
+ [
45
+ `import { useScript } from "@decocms/blocks/sdk/useScript";`,
46
+ `import { useDevice } from "@decocms/blocks/sdk/useDevice";`,
47
+ `import { clx } from "@decocms/blocks/sdk/clx";`,
48
+ ].join("\n"),
49
+ );
50
+ });
51
+
52
+ it("rewrites @decocms/start/cms, /setup, /types/widgets, /admin", () => {
53
+ const r = upgradeFileToV7(
54
+ [
55
+ `import { resolveDecoPage } from "@decocms/start/cms";`,
56
+ `import { createSiteSetup } from "@decocms/start/setup";`,
57
+ `import type { ImageWidget } from "@decocms/start/types/widgets";`,
58
+ `import { handleMeta } from "@decocms/start/admin";`,
59
+ ].join("\n"),
60
+ );
61
+ expect(r.content).toContain(`from "@decocms/blocks/cms"`);
62
+ expect(r.content).toContain(`from "@decocms/blocks/setup"`);
63
+ expect(r.content).toContain(`from "@decocms/blocks/types/widgets"`);
64
+ expect(r.content).toContain(`from "@decocms/blocks-admin"`);
65
+ });
66
+
67
+ it("rewrites @decocms/apps/commerce/components/Image to @decocms/blocks/hooks", () => {
68
+ const r = upgradeFileToV7(`import { Image } from "@decocms/apps/commerce/components/Image";`);
69
+ expect(r.content).toBe(`import { Image } from "@decocms/blocks/hooks";`);
70
+ });
71
+
72
+ it("rewrites @decocms/apps/commerce/* to @decocms/apps-commerce/*", () => {
73
+ const r = upgradeFileToV7(`import type { Product } from "@decocms/apps/commerce/types";`);
74
+ expect(r.content).toBe(`import type { Product } from "@decocms/apps-commerce/types";`);
75
+ });
76
+
77
+ it("rewrites @decocms/apps/vtex/* to @decocms/apps-vtex/*", () => {
78
+ const r = upgradeFileToV7(`import { getUser } from "@decocms/apps/vtex/loaders/user";`);
79
+ expect(r.content).toBe(`import { getUser } from "@decocms/apps-vtex/loaders/user";`);
80
+ });
81
+
82
+ it("is a no-op for a file already on the 7.x split packages", () => {
83
+ const src = `import { createDecoWorkerEntry } from "@decocms/tanstack";\nimport { resolveDecoPage } from "@decocms/blocks/cms";\n`;
84
+ const r = upgradeFileToV7(src);
85
+ expect(r.changed).toBe(false);
86
+ expect(r.content).toBe(src);
87
+ expect(r.notes).toEqual([]);
88
+ });
89
+ });
90
+
91
+ describe("upgradeFileToV7 (#367) — @decocms/start/routes fan-out", () => {
92
+ it("moves cmsRouteConfig/cmsHomeRouteConfig to @decocms/tanstack root", () => {
93
+ const r = upgradeFileToV7(
94
+ `import { cmsRouteConfig, cmsHomeRouteConfig } from "@decocms/start/routes";`,
95
+ );
96
+ expect(r.content).toBe(
97
+ `import { cmsRouteConfig, cmsHomeRouteConfig } from "@decocms/tanstack";`,
98
+ );
99
+ });
100
+
101
+ it("splits deferredSectionLoader into its own subpath import", () => {
102
+ const r = upgradeFileToV7(
103
+ `import { cmsRouteConfig, deferredSectionLoader } from "@decocms/start/routes";`,
104
+ );
105
+ expect(r.content).toContain(`import { cmsRouteConfig } from "@decocms/tanstack";`);
106
+ expect(r.content).toContain(
107
+ `import { deferredSectionLoader } from "@decocms/tanstack/sdk/deferredSectionLoader";`,
108
+ );
109
+ });
110
+
111
+ it("renames decoMetaRoute/decoRenderRoute/decoInvokeRoute to their *RouteConfig factory names and flags a MANUAL call-site note", () => {
112
+ const src = [
113
+ `import { decoMetaRoute, decoRenderRoute, decoInvokeRoute } from "@decocms/start/routes";`,
114
+ `export const Route = createFileRoute("/deco/meta")({ ...decoMetaRoute });`,
115
+ ].join("\n");
116
+ const r = upgradeFileToV7(src);
117
+ expect(r.content).toContain(
118
+ `import { decoMetaRouteConfig, decoRenderRouteConfig, decoInvokeRouteConfig } from "@decocms/tanstack";`,
119
+ );
120
+ expect(r.content).toContain("...decoMetaRouteConfig }");
121
+ expect(r.notes.some((n) => n.includes("decoMetaRoute") && n.includes("factory"))).toBe(true);
122
+ });
123
+ });
124
+
125
+ describe("upgradeFileToV7 (#367) — @decocms/start/hooks fan-out", () => {
126
+ it("moves DecoPageRenderer/DecoRootLayout/PreviewProviders to @decocms/tanstack root", () => {
127
+ const r = upgradeFileToV7(
128
+ `import { DecoPageRenderer, DecoRootLayout, PreviewProviders } from "@decocms/start/hooks";`,
129
+ );
130
+ expect(r.content).toBe(
131
+ `import { DecoPageRenderer, DecoRootLayout, PreviewProviders } from "@decocms/tanstack";`,
132
+ );
133
+ });
134
+
135
+ it("renames RenderSection to SectionRenderer at the import and every usage", () => {
136
+ const src = [
137
+ `import { RenderSection } from "@decocms/start/hooks";`,
138
+ `export const x = <RenderSection section={s} />;`,
139
+ ].join("\n");
140
+ const r = upgradeFileToV7(src);
141
+ expect(r.content).toContain(`import { SectionRenderer } from "@decocms/tanstack";`);
142
+ expect(r.content).toContain("<SectionRenderer section={s} />");
143
+ expect(r.content).not.toContain("RenderSection");
144
+ });
145
+ });
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Codemod: upgrade a site off the frozen pre-split `@decocms/start@6.30.0` /
3
+ * `@decocms/apps@5.4.0` packages onto the current 7.x split
4
+ * (`@decocms/tanstack` + `@decocms/blocks` + `@decocms/blocks-admin` +
5
+ * `@decocms/apps-*`). See #367 — this is a DIFFERENT migration than
6
+ * Fresh→TanStack (`deco-migrate`): the site is already on TanStack, it's
7
+ * only the package dependency that needs to move.
8
+ *
9
+ * Mapping derived by hand against a real production site
10
+ * (montecarlo-tanstack) — see the issue for the full worked table. Symbol
11
+ * renames and relocations mean a pure specifier-string rewrite is not
12
+ * enough for every case; those are called out below.
13
+ */
14
+
15
+ export interface UpgradeResult {
16
+ content: string;
17
+ changed: boolean;
18
+ notes: string[];
19
+ }
20
+
21
+ /** Straightforward 1:1 specifier rewrites — no symbol splitting needed. */
22
+ const SIMPLE_SPECIFIER_MAP: Array<{ re: RegExp; to: string }> = [
23
+ { re: /@decocms\/start\/vite\b/g, to: "@decocms/tanstack/vite" },
24
+ { re: /@decocms\/start\/sdk\/cookiePassthrough\b/g, to: "@decocms/tanstack/sdk/cookiePassthrough" },
25
+ { re: /@decocms\/start\/sdk\/workerEntry\b/g, to: "@decocms/tanstack" },
26
+ { re: /@decocms\/start\/sdk\/router\b/g, to: "@decocms/tanstack" },
27
+ { re: /@decocms\/start\/cms\b/g, to: "@decocms/blocks/cms" },
28
+ { re: /@decocms\/start\/setup\b/g, to: "@decocms/blocks/setup" },
29
+ { re: /@decocms\/start\/types\/widgets\b/g, to: "@decocms/blocks/types/widgets" },
30
+ { re: /@decocms\/start\/admin\b/g, to: "@decocms/blocks-admin" },
31
+ { re: /@decocms\/apps\/commerce\/components\/Image\b/g, to: "@decocms/blocks/hooks" },
32
+ { re: /@decocms\/apps\/commerce\//g, to: "@decocms/apps-commerce/" },
33
+ { re: /@decocms\/apps\/vtex\//g, to: "@decocms/apps-vtex/" },
34
+ ];
35
+
36
+ /**
37
+ * `@decocms/start/sdk/*` (useScript, useDevice, clx, signal, ...) → the
38
+ * matching `@decocms/blocks/sdk/*` subpath. Runs AFTER the more specific
39
+ * sdk/workerEntry, sdk/router, sdk/cookiePassthrough rewrites above so
40
+ * those aren't double-handled by this catch-all.
41
+ */
42
+ const GENERIC_SDK_RE = /@decocms\/start\/sdk\/([\w-]+)/g;
43
+
44
+ /** Named-import line: `import { A, B as C } from "spec";` (captures the clause + spec). */
45
+ const NAMED_IMPORT_RE = /import\s+\{([^}]*)\}\s+from\s+["']([^"']+)["'];?/g;
46
+
47
+ /** Symbols from `@decocms/start/routes` that move to `@decocms/tanstack/sdk/deferredSectionLoader`. */
48
+ const DEFERRED_LOADER_SYMBOL = "deferredSectionLoader";
49
+
50
+ /** Symbols renamed on the way from `@decocms/start/{routes,hooks}` to `@decocms/tanstack`. */
51
+ const ROUTE_FACTORY_RENAMES: Record<string, string> = {
52
+ decoMetaRoute: "decoMetaRouteConfig",
53
+ decoRenderRoute: "decoRenderRouteConfig",
54
+ decoInvokeRoute: "decoInvokeRouteConfig",
55
+ };
56
+ const HOOKS_RENAMES: Record<string, string> = {
57
+ RenderSection: "SectionRenderer",
58
+ };
59
+
60
+ function splitNamedClause(clause: string): string[] {
61
+ return clause
62
+ .split(",")
63
+ .map((s) => s.trim())
64
+ .filter(Boolean);
65
+ }
66
+
67
+ /** Handles the `@decocms/start/routes` and `@decocms/start/hooks` imports, which
68
+ * fan out to `@decocms/tanstack` (root) plus, for routes, a dedicated subpath
69
+ * for `deferredSectionLoader`. Symbol renames are applied to both the import
70
+ * clause and every usage in the file. */
71
+ function rewriteRoutesAndHooksImports(
72
+ content: string,
73
+ notes: string[],
74
+ ): { content: string; changed: boolean } {
75
+ let changed = false;
76
+ // Collected here during the import-line pass, applied to the WHOLE file
77
+ // afterward — must not mutate `content`/the replace target mid-scan.
78
+ const pendingRenames: Array<{ from: string; to: string }> = [];
79
+
80
+ let result = content.replace(NAMED_IMPORT_RE, (line, clauseRaw: string, spec: string) => {
81
+ if (spec !== "@decocms/start/routes" && spec !== "@decocms/start/hooks") return line;
82
+
83
+ const isRoutes = spec === "@decocms/start/routes";
84
+ const renames = isRoutes ? ROUTE_FACTORY_RENAMES : HOOKS_RENAMES;
85
+ const names = splitNamedClause(clauseRaw);
86
+
87
+ const deferred: string[] = [];
88
+ const rootImports: string[] = [];
89
+
90
+ for (const name of names) {
91
+ if (isRoutes && name === DEFERRED_LOADER_SYMBOL) {
92
+ deferred.push(name);
93
+ continue;
94
+ }
95
+ const rename = renames[name];
96
+ if (rename) {
97
+ pendingRenames.push({ from: name, to: rename });
98
+ rootImports.push(rename);
99
+ if (isRoutes) {
100
+ notes.push(
101
+ `MANUAL: \`${name}\` is now the factory \`${rename}()\` in @decocms/tanstack — ` +
102
+ `add the call at every usage site (e.g. \`...${rename}()\` instead of \`...${rename}\`).`,
103
+ );
104
+ }
105
+ continue;
106
+ }
107
+ rootImports.push(name);
108
+ }
109
+
110
+ changed = true;
111
+ const lines: string[] = [];
112
+ if (rootImports.length > 0) {
113
+ lines.push(`import { ${rootImports.join(", ")} } from "@decocms/tanstack";`);
114
+ }
115
+ if (deferred.length > 0) {
116
+ lines.push(`import { ${deferred.join(", ")} } from "@decocms/tanstack/sdk/deferredSectionLoader";`);
117
+ }
118
+ return lines.join("\n");
119
+ });
120
+
121
+ for (const { from, to } of pendingRenames) {
122
+ result = result.replace(new RegExp(`\\b${from}\\b`, "g"), to);
123
+ }
124
+
125
+ return { content: result, changed };
126
+ }
127
+
128
+ /**
129
+ * Upgrade a single file's source from `@decocms/start` / `@decocms/apps`
130
+ * (pre-7.x-split) specifiers to the current split packages. Pure and
131
+ * side-effect-free — the CLI wrapper handles file I/O and package.json.
132
+ */
133
+ export function upgradeFileToV7(content: string): UpgradeResult {
134
+ const notes: string[] = [];
135
+ let result = content;
136
+ let changed = false;
137
+
138
+ if (result.includes("@decocms/start/routes") || result.includes("@decocms/start/hooks")) {
139
+ const r = rewriteRoutesAndHooksImports(result, notes);
140
+ result = r.content;
141
+ changed = changed || r.changed;
142
+ }
143
+
144
+ for (const { re, to } of SIMPLE_SPECIFIER_MAP) {
145
+ const before = result;
146
+ result = result.replace(re, to);
147
+ if (result !== before) changed = true;
148
+ }
149
+
150
+ {
151
+ const before = result;
152
+ result = result.replace(GENERIC_SDK_RE, "@decocms/blocks/sdk/$1");
153
+ if (result !== before) changed = true;
154
+ }
155
+
156
+ return { content: result, changed, notes };
157
+ }
@@ -0,0 +1,134 @@
1
+ import * as fs from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
+ import { checks } from "./phase-verify";
6
+ import type { MigrationContext } from "./types";
7
+
8
+ /**
9
+ * #367: blocks-cli must never leave the frozen pre-7.x-split package
10
+ * specifiers (@decocms/start@6.30.0, @decocms/apps@5.4.0) in a migrated
11
+ * site's src/ — including via a transform hand-off marker (jsx.ts emits
12
+ * `@decocms/start/hooks` as a same-run marker that phase-cleanup.ts is
13
+ * supposed to resolve to `@decocms/blocks/hooks`) that leaks past cleanup
14
+ * if the pipeline order ever changes.
15
+ */
16
+
17
+ function makeCtx(sourceDir: string): MigrationContext {
18
+ return {
19
+ sourceDir,
20
+ siteName: "test-site",
21
+ platform: "custom",
22
+ vtexAccount: null,
23
+ gtmId: null,
24
+ importMap: {},
25
+ discoveredNpmDeps: {},
26
+ themeColors: {},
27
+ fontFamily: null,
28
+ files: [],
29
+ sectionMetas: [],
30
+ islandClassifications: [],
31
+ islandWrapperTargets: new Map(),
32
+ loaderInventory: [],
33
+ scaffoldedFiles: [],
34
+ transformedFiles: [],
35
+ deletedFiles: [],
36
+ movedFiles: [],
37
+ manualReviewItems: [],
38
+ frameworkFindings: [],
39
+ dryRun: false,
40
+ verbose: false,
41
+ };
42
+ }
43
+
44
+ const legacySpecifierCheck = checks.find(
45
+ (c) => c.name === "No frozen pre-split package specifiers in src/ (@decocms/start, @decocms/apps/*)",
46
+ );
47
+ if (!legacySpecifierCheck) {
48
+ throw new Error("verify check not found — name changed?");
49
+ }
50
+
51
+ function runCheck(ctx: MigrationContext): { ok: boolean; output: string } {
52
+ const lines: string[] = [];
53
+ const spy = vi.spyOn(console, "log").mockImplementation((...args: unknown[]) => {
54
+ lines.push(args.map((a) => String(a)).join(" "));
55
+ });
56
+ try {
57
+ const ok = legacySpecifierCheck!.fn(ctx);
58
+ return { ok, output: lines.join("\n") };
59
+ } finally {
60
+ spy.mockRestore();
61
+ }
62
+ }
63
+
64
+ describe("verify check: 'No frozen pre-split package specifiers in src/' (#367)", () => {
65
+ let tmp: string;
66
+
67
+ beforeEach(() => {
68
+ tmp = fs.mkdtempSync(path.join(os.tmpdir(), "verify-legacy-specifiers-"));
69
+ fs.mkdirSync(path.join(tmp, "src", "hooks"), { recursive: true });
70
+ });
71
+
72
+ afterEach(() => {
73
+ fs.rmSync(tmp, { recursive: true, force: true });
74
+ });
75
+
76
+ it("passes when src/ only imports from the current split packages", () => {
77
+ fs.writeFileSync(
78
+ path.join(tmp, "src", "hooks", "useUser.ts"),
79
+ `import { SectionRenderer } from "@decocms/blocks/hooks";\n` +
80
+ `import { invoke } from "@decocms/blocks/sdk/invoke";\n` +
81
+ `export const x = 1;\n`,
82
+ );
83
+ const { ok, output } = runCheck(makeCtx(tmp));
84
+ expect(ok).toBe(true);
85
+ expect(output).toBe("");
86
+ });
87
+
88
+ it("fails when a `@decocms/start/hooks` hand-off marker leaked past cleanup", () => {
89
+ fs.writeFileSync(
90
+ path.join(tmp, "src", "hooks", "useUser.ts"),
91
+ `import { SectionRenderer } from "@decocms/start/hooks";\nexport const x = 1;\n`,
92
+ );
93
+ const { ok, output } = runCheck(makeCtx(tmp));
94
+ expect(ok).toBe(false);
95
+ expect(output).toContain("@decocms/start");
96
+ });
97
+
98
+ it("fails on a bare `@decocms/start` import (no subpath)", () => {
99
+ fs.writeFileSync(
100
+ path.join(tmp, "src", "hooks", "setup.ts"),
101
+ `import "@decocms/start";\nexport const x = 1;\n`,
102
+ );
103
+ const { ok } = runCheck(makeCtx(tmp));
104
+ expect(ok).toBe(false);
105
+ });
106
+
107
+ it("fails on a `@decocms/apps/vtex/...` monolith subpath import", () => {
108
+ fs.writeFileSync(
109
+ path.join(tmp, "src", "hooks", "old.ts"),
110
+ `import { getUser } from "@decocms/apps/vtex/loaders/user";\nexport const x = 1;\n`,
111
+ );
112
+ const { ok, output } = runCheck(makeCtx(tmp));
113
+ expect(ok).toBe(false);
114
+ expect(output).toContain("@decocms/apps/");
115
+ });
116
+
117
+ it("does not flag the current @decocms/apps-vtex split package", () => {
118
+ fs.writeFileSync(
119
+ path.join(tmp, "src", "hooks", "useUser.ts"),
120
+ `import { getUser } from "@decocms/apps-vtex/loaders/user";\nexport const x = 1;\n`,
121
+ );
122
+ const { ok } = runCheck(makeCtx(tmp));
123
+ expect(ok).toBe(true);
124
+ });
125
+
126
+ it("ignores commented-out references", () => {
127
+ fs.writeFileSync(
128
+ path.join(tmp, "src", "hooks", "Docs.ts"),
129
+ `// import { x } from "@decocms/start/hooks"\nexport const x = 1;\n`,
130
+ );
131
+ const { ok } = runCheck(makeCtx(tmp));
132
+ expect(ok).toBe(true);
133
+ });
134
+ });
@@ -410,6 +410,30 @@ export const checks: Check[] = [
410
410
  return true;
411
411
  },
412
412
  },
413
+ {
414
+ // Fleet-wide regression guard, complementing the scaffolder-only check
415
+ // in templates/no-legacy-packages.test.ts (#367): the frozen
416
+ // pre-7.x-split packages (@decocms/start@6.30.0, @decocms/apps@5.4.0)
417
+ // must never appear in a migrated site's src/, even via a transform
418
+ // hand-off marker that leaked past cleanup (see jsx.ts's
419
+ // `@decocms/start/hooks` same-run hand-off to phase-cleanup.ts).
420
+ name: "No frozen pre-split package specifiers in src/ (@decocms/start, @decocms/apps/*)",
421
+ severity: "error",
422
+ fn: (ctx) => {
423
+ const srcDir = path.join(ctx.sourceDir, "src");
424
+ if (!fs.existsSync(srcDir)) return true;
425
+ const bad = [
426
+ ...findFilesWithPattern(srcDir, /(?:from|import)\s+["']@decocms\/start(?:\/|["'])/),
427
+ ...findFilesWithPattern(srcDir, /from\s+["']@decocms\/apps\//),
428
+ ];
429
+ const unique = [...new Set(bad)];
430
+ if (unique.length > 0) {
431
+ console.log(` Still references frozen @decocms/start or @decocms/apps/* specifiers: ${unique.join(", ")}`);
432
+ return false;
433
+ }
434
+ return true;
435
+ },
436
+ },
413
437
  {
414
438
  name: "Setup infrastructure is complete",
415
439
  severity: "error",