@decocms/start 0.33.0 → 0.34.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/start",
3
- "version": "0.33.0",
3
+ "version": "0.34.0",
4
4
  "type": "module",
5
5
  "description": "Deco framework for TanStack Start - CMS bridge, admin protocol, hooks, schema generation",
6
6
  "main": "./src/index.ts",
@@ -7,6 +7,7 @@ import { transformJsx } from "./transforms/jsx.ts";
7
7
  import { transformFreshApis } from "./transforms/fresh-apis.ts";
8
8
  import { transformDenoIsms } from "./transforms/deno-isms.ts";
9
9
  import { transformTailwind } from "./transforms/tailwind.ts";
10
+ import { transformDeadCode } from "./transforms/dead-code.ts";
10
11
 
11
12
  /**
12
13
  * Apply all transforms to a file's content in the correct order.
@@ -22,11 +23,12 @@ function applyTransforms(content: string, filePath: string): TransformResult {
22
23
  return { content, changed: false, notes: [] };
23
24
  }
24
25
 
25
- // Pipeline: imports → jsx → fresh-apis → deno-isms → tailwind
26
+ // Pipeline: imports → jsx → fresh-apis → dead-code → deno-isms → tailwind
26
27
  const pipeline = [
27
28
  { name: "imports", fn: transformImports },
28
29
  { name: "jsx", fn: transformJsx },
29
30
  { name: "fresh-apis", fn: transformFreshApis },
31
+ { name: "dead-code", fn: transformDeadCode },
30
32
  { name: "deno-isms", fn: transformDenoIsms },
31
33
  { name: "tailwind", fn: transformTailwind },
32
34
  ];
@@ -260,6 +260,67 @@ const checks: Check[] = [
260
260
  return true;
261
261
  },
262
262
  },
263
+ {
264
+ name: "No dead cache/cacheKey/loader exports",
265
+ severity: "warning",
266
+ fn: (ctx) => {
267
+ const srcDir = path.join(ctx.sourceDir, "src");
268
+ if (!fs.existsSync(srcDir)) return true;
269
+ const bad = findFilesWithPattern(srcDir, /^export\s+const\s+(?:cache|cacheKey|loader)\s*=/m);
270
+ if (bad.length > 0) {
271
+ console.log(` Dead exports found (old cache/loader system): ${bad.join(", ")}`);
272
+ return false;
273
+ }
274
+ return true;
275
+ },
276
+ },
277
+ {
278
+ name: "No invoke.resend calls (should use sendEmail)",
279
+ severity: "warning",
280
+ fn: (ctx) => {
281
+ const srcDir = path.join(ctx.sourceDir, "src");
282
+ if (!fs.existsSync(srcDir)) return true;
283
+ const bad = findFilesWithPattern(srcDir, /invoke\.resend\./);
284
+ if (bad.length > 0) {
285
+ console.log(` Still uses invoke.resend: ${bad.join(", ")}`);
286
+ return false;
287
+ }
288
+ return true;
289
+ },
290
+ },
291
+ {
292
+ name: "No Deno-only crypto.subtle.digestSync",
293
+ severity: "warning",
294
+ fn: (ctx) => {
295
+ const srcDir = path.join(ctx.sourceDir, "src");
296
+ if (!fs.existsSync(srcDir)) return true;
297
+ const bad = findFilesWithPattern(srcDir, /digestSync/);
298
+ if (bad.length > 0) {
299
+ console.log(` Deno-only digestSync found: ${bad.join(", ")}`);
300
+ return false;
301
+ }
302
+ return true;
303
+ },
304
+ },
305
+ {
306
+ name: ".gitignore exists with new stack entries",
307
+ severity: "warning",
308
+ fn: (ctx) => {
309
+ const gitignorePath = path.join(ctx.sourceDir, ".gitignore");
310
+ if (!fs.existsSync(gitignorePath)) {
311
+ console.log(" .gitignore missing");
312
+ return false;
313
+ }
314
+ const content = fs.readFileSync(gitignorePath, "utf-8");
315
+ const required = [".wrangler/", "node_modules/", ".tanstack/", "src/routeTree.gen.ts"];
316
+ const missing = required.filter((r) => !content.includes(r));
317
+ if (missing.length > 0) {
318
+ console.log(` .gitignore missing entries: ${missing.join(", ")}`);
319
+ return false;
320
+ }
321
+ return true;
322
+ },
323
+ },
263
324
  ];
264
325
 
265
326
  function findFilesWithPattern(
@@ -0,0 +1,103 @@
1
+ import type { TransformResult } from "../types.ts";
2
+
3
+ /**
4
+ * Removes dead code patterns from the old Deco stack that don't work
5
+ * in TanStack Start:
6
+ *
7
+ * - `export const cache = "stale-while-revalidate"` (old cache system)
8
+ * - `export const cacheKey = ...` (old cache key generation)
9
+ * - `export const loader = (props, req, ctx) => ...` (old section loader pattern)
10
+ * - `crypto.subtle.digestSync(...)` (Deno-only sync API)
11
+ * - `invoke.resend.actions.emails.send(...)` → `sendEmail(...)` from @decocms/apps
12
+ */
13
+ export function transformDeadCode(content: string): TransformResult {
14
+ const notes: string[] = [];
15
+ let changed = false;
16
+ let result = content;
17
+
18
+ // Remove old cache export: export const cache = "stale-while-revalidate";
19
+ if (/^export\s+const\s+cache\s*=\s*["'][^"']*["']/m.test(result)) {
20
+ result = result.replace(
21
+ /^export\s+const\s+cache\s*=\s*["'][^"']*["'];?\s*\n?/gm,
22
+ "",
23
+ );
24
+ changed = true;
25
+ notes.push("Removed dead `export const cache` (old caching system)");
26
+ }
27
+
28
+ // Remove old cacheKey export (can be multiline)
29
+ if (/^export\s+const\s+cacheKey\s*=/m.test(result)) {
30
+ // Try to remove the entire cacheKey function — find matching closing brace/semicolon
31
+ result = result.replace(
32
+ /^export\s+const\s+cacheKey\s*=\s*\([^)]*\)\s*(?::\s*\w+\s*)?=>\s*\{[\s\S]*?\n\};\s*\n?/gm,
33
+ "",
34
+ );
35
+ // Also handle simpler inline forms
36
+ result = result.replace(
37
+ /^export\s+const\s+cacheKey\s*=[^;]*;\s*\n?/gm,
38
+ "",
39
+ );
40
+ changed = true;
41
+ notes.push("Removed dead `export const cacheKey` (old caching system)");
42
+ }
43
+
44
+ // Remove old section loader export: export const loader = (props, req, ctx) => { ... };
45
+ // This is the old pattern where sections had co-located loaders.
46
+ // In TanStack Start, section loaders are handled differently.
47
+ if (/^export\s+const\s+loader\s*=\s*\(/m.test(result)) {
48
+ result = result.replace(
49
+ /^export\s+const\s+loader\s*=\s*\([^)]*\)\s*(?::\s*[\w<>[\]|&\s]+)?\s*=>\s*\{[\s\S]*?\n\};\s*\n?/gm,
50
+ "",
51
+ );
52
+ // Also handle simpler inline forms
53
+ result = result.replace(
54
+ /^export\s+const\s+loader\s*=\s*\([^)]*\)\s*=>[^;]*;\s*\n?/gm,
55
+ "",
56
+ );
57
+ changed = true;
58
+ notes.push("Removed dead `export const loader` (old section loader — use section loaders in @decocms/start)");
59
+ }
60
+
61
+ // Replace crypto.subtle.digestSync (Deno-only) with a note
62
+ if (result.includes("digestSync")) {
63
+ result = result.replace(
64
+ /crypto\.subtle\.digestSync\(/g,
65
+ "/* MIGRATION: digestSync is Deno-only, use await crypto.subtle.digest( */ crypto.subtle.digest(",
66
+ );
67
+ changed = true;
68
+ notes.push("MANUAL: crypto.subtle.digestSync is Deno-only — replaced with crypto.subtle.digest (needs await)");
69
+ }
70
+
71
+ // Migrate invoke.resend.actions.emails.send → sendEmail from @decocms/apps
72
+ if (result.includes("invoke.resend.actions.emails.send")) {
73
+ // Replace the invoke call with sendEmail
74
+ result = result.replace(
75
+ /(?:await\s+)?invoke\.resend\.actions\.emails\.send\(/g,
76
+ "await sendEmail(",
77
+ );
78
+
79
+ // Remove the old runtime import if only used for resend
80
+ result = result.replace(
81
+ /^import\s+\{\s*invoke\s*\}\s+from\s+["'][^"']*runtime["'];?\s*\n?/gm,
82
+ "",
83
+ );
84
+
85
+ // Add the sendEmail import if not present
86
+ if (!result.includes('"@decocms/apps/resend/actions/send"')) {
87
+ result = `import { sendEmail } from "@decocms/apps/resend/actions/send";\n${result}`;
88
+ }
89
+
90
+ changed = true;
91
+ notes.push("Migrated invoke.resend.actions.emails.send → sendEmail from @decocms/apps/resend");
92
+ }
93
+
94
+ // Generic invoke.X pattern — flag for manual review
95
+ if (/\binvoke\.\w+\.\w+/.test(result) && !result.includes("sendEmail")) {
96
+ notes.push("MANUAL: invoke.* calls found — need manual migration to server functions or @decocms/apps actions");
97
+ }
98
+
99
+ // Clean up blank lines
100
+ result = result.replace(/\n{3,}/g, "\n\n");
101
+
102
+ return { content: result, changed, notes };
103
+ }