@decocms/start 1.6.1 → 1.6.3
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/.cursor/skills/deco-to-tanstack-migration/SKILL.md +85 -12
- package/.cursor/skills/deco-to-tanstack-migration/references/gotchas.md +98 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/package-json.md +45 -25
- package/.cursor/skills/deco-to-tanstack-migration/templates/root-route.md +56 -39
- package/.cursor/skills/deco-to-tanstack-migration/templates/setup-ts.md +122 -141
- package/package.json +1 -1
- package/scripts/generate-blocks.ts +8 -5
- package/scripts/migrate/analyzers/island-classifier.ts +23 -0
- package/scripts/migrate/analyzers/section-metadata.ts +63 -7
- package/scripts/migrate/phase-analyze.ts +136 -11
- package/scripts/migrate/phase-cleanup.ts +1057 -6
- package/scripts/migrate/phase-scaffold.ts +294 -5
- package/scripts/migrate/phase-transform.ts +14 -3
- package/scripts/migrate/templates/app-css.ts +149 -2
- package/scripts/migrate/templates/commerce-loaders.ts +173 -68
- package/scripts/migrate/templates/lib-utils.ts +255 -0
- package/scripts/migrate/templates/package-json.ts +30 -22
- package/scripts/migrate/templates/routes.ts +81 -11
- package/scripts/migrate/templates/section-loaders.ts +365 -32
- package/scripts/migrate/templates/server-entry.ts +350 -80
- package/scripts/migrate/templates/setup.ts +78 -8
- package/scripts/migrate/templates/types-gen.ts +58 -0
- package/scripts/migrate/templates/ui-components.ts +47 -16
- package/scripts/migrate/templates/vite-config.ts +17 -6
- package/scripts/migrate/templates/wrangler.ts +3 -1
- package/scripts/migrate/transforms/dead-code.ts +330 -4
- package/scripts/migrate/transforms/deno-isms.ts +19 -0
- package/scripts/migrate/transforms/imports.ts +93 -30
- package/scripts/migrate/transforms/jsx.ts +79 -4
- package/scripts/migrate/transforms/section-conventions.ts +105 -3
- package/scripts/migrate/types.ts +6 -0
- package/src/cms/resolve.ts +4 -0
|
@@ -71,7 +71,6 @@ const SKIP_FILES = new Set([
|
|
|
71
71
|
"yarn.lock",
|
|
72
72
|
"bun.lock",
|
|
73
73
|
"bun.lockb",
|
|
74
|
-
"account.json",
|
|
75
74
|
]);
|
|
76
75
|
|
|
77
76
|
/** Files that are generated and should be deleted */
|
|
@@ -85,8 +84,8 @@ const GENERATED_FILES = new Set([
|
|
|
85
84
|
const SDK_DELETE = new Set([
|
|
86
85
|
"sdk/clx.ts",
|
|
87
86
|
"sdk/useId.ts",
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
// sdk/useOffer.ts — kept: sites often customize offer logic
|
|
88
|
+
// sdk/useVariantPossiblities.ts — kept: sites often customize variant logic
|
|
90
89
|
"sdk/usePlatform.tsx",
|
|
91
90
|
"sdk/signal.ts",
|
|
92
91
|
"sdk/format.ts",
|
|
@@ -141,10 +140,27 @@ function extractInlineNpmDeps(content: string): Record<string, string> {
|
|
|
141
140
|
while ((match = regex.exec(content)) !== null) {
|
|
142
141
|
const name = match[1];
|
|
143
142
|
const version = match[2] || "*";
|
|
144
|
-
// Skip framework deps
|
|
145
143
|
if (name.startsWith("preact") || name.startsWith("@preact/")) continue;
|
|
146
144
|
deps[name] = `^${version}`;
|
|
147
145
|
}
|
|
146
|
+
|
|
147
|
+
// Detect well-known third-party packages imported without npm: prefix
|
|
148
|
+
const KNOWN_THIRD_PARTY: Record<string, string> = {
|
|
149
|
+
"@sentry/react": "^10.43.0",
|
|
150
|
+
"dompurify": "^3.3.3",
|
|
151
|
+
"fuse.js": "^7.0.0",
|
|
152
|
+
"swiper": "^11.2.6",
|
|
153
|
+
"lottie-web": "^5.12.2",
|
|
154
|
+
"class-variance-authority": "^0.7.1",
|
|
155
|
+
"clsx": "^2.1.1",
|
|
156
|
+
};
|
|
157
|
+
for (const [pkg, version] of Object.entries(KNOWN_THIRD_PARTY)) {
|
|
158
|
+
const escaped = pkg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
159
|
+
if (new RegExp(`from\\s+["']${escaped}`, "m").test(content)) {
|
|
160
|
+
deps[pkg] = version;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
148
164
|
return deps;
|
|
149
165
|
}
|
|
150
166
|
|
|
@@ -218,6 +234,11 @@ function decideAction(
|
|
|
218
234
|
return { action: "delete", notes: "Rewritten from scratch" };
|
|
219
235
|
}
|
|
220
236
|
|
|
237
|
+
// All remaining apps/ files → delete (platform config is in setup.ts now)
|
|
238
|
+
if (relPath.startsWith("apps/")) {
|
|
239
|
+
return { action: "delete", notes: "Old app config — platform setup is in setup.ts" };
|
|
240
|
+
}
|
|
241
|
+
|
|
221
242
|
// Loaders that depend on deleted admin tooling
|
|
222
243
|
if (LOADER_DELETE.has(relPath)) {
|
|
223
244
|
return {
|
|
@@ -247,6 +268,8 @@ function decideAction(
|
|
|
247
268
|
return { action: "delete", notes: "Use @decocms/apps cart hooks" };
|
|
248
269
|
}
|
|
249
270
|
|
|
271
|
+
// Non-platform cleanup is done in analyze() post-processing (needs ctx.platform)
|
|
272
|
+
|
|
250
273
|
// Islands — classify and route to appropriate target
|
|
251
274
|
if (category === "island") {
|
|
252
275
|
const classification = (record as any).__islandClassification;
|
|
@@ -262,14 +285,19 @@ function decideAction(
|
|
|
262
285
|
};
|
|
263
286
|
}
|
|
264
287
|
|
|
265
|
-
// Sections that re-export from islands/ →
|
|
266
|
-
//
|
|
288
|
+
// Sections that re-export from islands/ → TRANSFORM (rewrite to ~/components/)
|
|
289
|
+
// The CMS still references these section keys, so the file must exist.
|
|
290
|
+
// The island was moved to components/, so we rewrite the re-export target.
|
|
267
291
|
if (category === "section" && isReExp) {
|
|
268
292
|
const target = record.reExportTarget || "";
|
|
269
293
|
const isIslandReExport = target.includes("islands/") ||
|
|
270
294
|
target.includes("islands\\");
|
|
271
295
|
if (isIslandReExport) {
|
|
272
|
-
return {
|
|
296
|
+
return {
|
|
297
|
+
action: "transform",
|
|
298
|
+
targetPath: `src/${relPath}`,
|
|
299
|
+
notes: "Section re-export — island target rewritten to ~/components/",
|
|
300
|
+
};
|
|
273
301
|
}
|
|
274
302
|
// Section re-exports from components/ — keep and transform
|
|
275
303
|
}
|
|
@@ -298,12 +326,13 @@ function decideAction(
|
|
|
298
326
|
// Non-code root files that shouldn't go into src/
|
|
299
327
|
const ext = path.extname(relPath);
|
|
300
328
|
const nonCodeExts = new Set([".md", ".csv", ".json", ".sh", ".lock", ".yml", ".yaml", ".xml", ".html", ".txt", ".log"]);
|
|
301
|
-
|
|
329
|
+
const keepRootFiles = new Set(["account.json"]);
|
|
330
|
+
if (!relPath.includes("/") && nonCodeExts.has(ext) && !keepRootFiles.has(relPath)) {
|
|
302
331
|
return { action: "delete", notes: "Root-level non-code file" };
|
|
303
332
|
}
|
|
304
333
|
|
|
305
|
-
// Root-level
|
|
306
|
-
const rootToolingFiles = new Set(["islands.ts", "
|
|
334
|
+
// Root-level tooling files — not app code
|
|
335
|
+
const rootToolingFiles = new Set(["islands.ts", "sync.sh"]);
|
|
307
336
|
if (!relPath.includes("/") && rootToolingFiles.has(relPath)) {
|
|
308
337
|
return { action: "delete", notes: "Root-level tooling file" };
|
|
309
338
|
}
|
|
@@ -374,6 +403,47 @@ function extractGtmId(sourceDir: string): string | null {
|
|
|
374
403
|
return match ? match[0] : null;
|
|
375
404
|
}
|
|
376
405
|
|
|
406
|
+
function extractVtexAccount(sourceDir: string): string | null {
|
|
407
|
+
// Strategy 1: utils/sitename.ts — DICT_VTEX_AN mapping
|
|
408
|
+
for (const candidate of ["utils/sitename.ts", "sdk/sitename.ts"]) {
|
|
409
|
+
const fp = path.join(sourceDir, candidate);
|
|
410
|
+
if (!fs.existsSync(fp)) continue;
|
|
411
|
+
const content = fs.readFileSync(fp, "utf-8");
|
|
412
|
+
// Match patterns like: casaevideo: "casaevideonewio"
|
|
413
|
+
const match = content.match(/vtexAn|VTEX_AN/i);
|
|
414
|
+
if (match) {
|
|
415
|
+
const dictMatch = content.match(/["'](\w+)["']\s*:\s*["'](\w+myvtex|\w+newio|\w+)["']/);
|
|
416
|
+
if (dictMatch) return dictMatch[2];
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Strategy 2: .myvtex.com references in loaders/routes
|
|
421
|
+
const candidates = [
|
|
422
|
+
"routes/_app.tsx",
|
|
423
|
+
"loaders/reviews/productReviews.ts",
|
|
424
|
+
"apps/vtex.ts",
|
|
425
|
+
];
|
|
426
|
+
for (const candidate of candidates) {
|
|
427
|
+
const fp = path.join(sourceDir, candidate);
|
|
428
|
+
if (!fs.existsSync(fp)) continue;
|
|
429
|
+
const content = fs.readFileSync(fp, "utf-8");
|
|
430
|
+
const match = content.match(/["'](\w+)\.myvtex\.com/);
|
|
431
|
+
if (match) return match[1];
|
|
432
|
+
const match2 = content.match(/account\s*[:=]\s*["'](\w+)["']/);
|
|
433
|
+
if (match2) return match2[1];
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Strategy 3: deno.json import map or apps/ config
|
|
437
|
+
const appsVtexPath = path.join(sourceDir, "apps", "vtex.ts");
|
|
438
|
+
if (fs.existsSync(appsVtexPath)) {
|
|
439
|
+
const content = fs.readFileSync(appsVtexPath, "utf-8");
|
|
440
|
+
const match = content.match(/account\s*:\s*["'](\w+)["']/);
|
|
441
|
+
if (match) return match[1];
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
|
|
377
447
|
function extractPlatform(sourceDir: string): Platform {
|
|
378
448
|
const platforms: Platform[] = ["vtex", "shopify", "wake", "vnda", "linx", "nuvemshop"];
|
|
379
449
|
|
|
@@ -518,6 +588,7 @@ export function analyze(ctx: MigrationContext): void {
|
|
|
518
588
|
// Extract metadata
|
|
519
589
|
ctx.siteName = extractSiteName(ctx.sourceDir);
|
|
520
590
|
ctx.platform = extractPlatform(ctx.sourceDir);
|
|
591
|
+
ctx.vtexAccount = ctx.platform === "vtex" ? extractVtexAccount(ctx.sourceDir) : null;
|
|
521
592
|
ctx.gtmId = extractGtmId(ctx.sourceDir);
|
|
522
593
|
|
|
523
594
|
// Extract theme colors and font from CMS
|
|
@@ -526,7 +597,7 @@ export function analyze(ctx: MigrationContext): void {
|
|
|
526
597
|
ctx.fontFamily = theme.fontFamily;
|
|
527
598
|
|
|
528
599
|
console.log(` Site: ${ctx.siteName}`);
|
|
529
|
-
console.log(` Platform: ${ctx.platform}`);
|
|
600
|
+
console.log(` Platform: ${ctx.platform}${ctx.vtexAccount ? ` (account: ${ctx.vtexAccount})` : ""}`);
|
|
530
601
|
console.log(` GTM ID: ${ctx.gtmId || "none"}`);
|
|
531
602
|
if (Object.keys(ctx.themeColors).length > 0) {
|
|
532
603
|
console.log(` Theme: ${Object.keys(ctx.themeColors).length} colors from CMS`);
|
|
@@ -581,10 +652,64 @@ export function analyze(ctx: MigrationContext): void {
|
|
|
581
652
|
if (classification.type === "wrapper") {
|
|
582
653
|
f.action = "delete";
|
|
583
654
|
f.notes = "Island wrapper — imports repointed to component";
|
|
655
|
+
// Build redirect map: island path → wrapped component's migrated import path
|
|
656
|
+
if (classification.wrapsComponent) {
|
|
657
|
+
let wrappedImport = classification.wrapsComponent;
|
|
658
|
+
if (wrappedImport.startsWith("./") || wrappedImport.startsWith("../")) {
|
|
659
|
+
// Resolve relative to the island's directory
|
|
660
|
+
const islandDir = path.dirname(f.path);
|
|
661
|
+
wrappedImport = path.posix.normalize(path.posix.join(islandDir, wrappedImport));
|
|
662
|
+
wrappedImport = "~/" + wrappedImport;
|
|
663
|
+
} else {
|
|
664
|
+
wrappedImport = wrappedImport.replace(/^(\$store|site)\//, "~/");
|
|
665
|
+
}
|
|
666
|
+
wrappedImport = wrappedImport.replace(/\.tsx?$/, "");
|
|
667
|
+
ctx.islandWrapperTargets.set(f.path, wrappedImport);
|
|
668
|
+
}
|
|
584
669
|
} else {
|
|
585
670
|
f.action = "transform";
|
|
586
671
|
f.targetPath = classification.suggestedTarget;
|
|
587
672
|
f.notes = "Standalone island moved to components";
|
|
588
673
|
}
|
|
589
674
|
}
|
|
675
|
+
|
|
676
|
+
// Fix section re-exports from wrapper islands — rewrite to the wrapper's target component
|
|
677
|
+
if (ctx.islandWrapperTargets.size > 0) {
|
|
678
|
+
for (const f of ctx.files) {
|
|
679
|
+
if (f.category !== "section" || !f.isReExport || !f.reExportTarget) continue;
|
|
680
|
+
let target = f.reExportTarget;
|
|
681
|
+
// Resolve relative paths (../../islands/X) against section's directory
|
|
682
|
+
if (target.startsWith("./") || target.startsWith("../")) {
|
|
683
|
+
target = path.posix.normalize(path.posix.join(path.dirname(f.path), target));
|
|
684
|
+
}
|
|
685
|
+
target = target.replace(/^(\$store|site)\//, "").replace(/\.tsx?$/, "");
|
|
686
|
+
if (!target.startsWith("islands/")) continue;
|
|
687
|
+
const wrappedImport = ctx.islandWrapperTargets.get(target + ".tsx") ||
|
|
688
|
+
ctx.islandWrapperTargets.get(target);
|
|
689
|
+
if (wrappedImport) {
|
|
690
|
+
(f as any).__resolvedReExportTarget = wrappedImport;
|
|
691
|
+
f.notes = `Section re-export — island wrapper resolved to ${wrappedImport}`;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// Delete non-platform files (e.g. shopify/linx/vnda/wake/nuvemshop on VTEX sites)
|
|
697
|
+
const NON_PLATFORM_PATTERNS: Record<string, RegExp> = {
|
|
698
|
+
vtex: /\/(shopify|linx|vnda|wake|nuvemshop)\b/i,
|
|
699
|
+
shopify: /\/(vtex|linx|vnda|wake|nuvemshop)\b/i,
|
|
700
|
+
vnda: /\/(vtex|shopify|linx|wake|nuvemshop)\b/i,
|
|
701
|
+
wake: /\/(vtex|shopify|linx|vnda|nuvemshop)\b/i,
|
|
702
|
+
linx: /\/(vtex|shopify|vnda|wake|nuvemshop)\b/i,
|
|
703
|
+
nuvemshop: /\/(vtex|shopify|linx|vnda|wake)\b/i,
|
|
704
|
+
};
|
|
705
|
+
const nonPlatformRe = NON_PLATFORM_PATTERNS[ctx.platform];
|
|
706
|
+
if (nonPlatformRe) {
|
|
707
|
+
for (const f of ctx.files) {
|
|
708
|
+
if (f.action === "delete") continue;
|
|
709
|
+
if (nonPlatformRe.test("/" + f.path)) {
|
|
710
|
+
f.action = "delete";
|
|
711
|
+
f.notes = `Non-${ctx.platform} platform file`;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
590
715
|
}
|