@decocms/start 0.31.0 → 0.32.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 +3 -1
- package/scripts/migrate/colors.ts +46 -0
- package/scripts/migrate/phase-analyze.ts +335 -0
- package/scripts/migrate/phase-cleanup.ts +203 -0
- package/scripts/migrate/phase-report.ts +171 -0
- package/scripts/migrate/phase-scaffold.ts +133 -0
- package/scripts/migrate/phase-transform.ts +102 -0
- package/scripts/migrate/phase-verify.ts +248 -0
- package/scripts/migrate/templates/knip-config.ts +27 -0
- package/scripts/migrate/templates/package-json.ts +59 -0
- package/scripts/migrate/templates/routes.ts +280 -0
- package/scripts/migrate/templates/server-entry.ts +148 -0
- package/scripts/migrate/templates/setup.ts +32 -0
- package/scripts/migrate/templates/tsconfig.ts +21 -0
- package/scripts/migrate/templates/vite-config.ts +108 -0
- package/scripts/migrate/templates/wrangler.ts +25 -0
- package/scripts/migrate/transforms/deno-isms.ts +64 -0
- package/scripts/migrate/transforms/fresh-apis.ts +111 -0
- package/scripts/migrate/transforms/imports.ts +132 -0
- package/scripts/migrate/transforms/jsx.ts +109 -0
- package/scripts/migrate/transforms/tailwind.ts +409 -0
- package/scripts/migrate/types.ts +137 -0
- package/scripts/migrate.ts +135 -0
- package/scripts/tailwind-lint.ts +518 -0
- package/src/cms/resolve.ts +27 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/start",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.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",
|
|
@@ -46,6 +46,8 @@
|
|
|
46
46
|
"./scripts/generate-blocks": "./scripts/generate-blocks.ts",
|
|
47
47
|
"./scripts/generate-schema": "./scripts/generate-schema.ts",
|
|
48
48
|
"./scripts/generate-invoke": "./scripts/generate-invoke.ts",
|
|
49
|
+
"./scripts/migrate": "./scripts/migrate.ts",
|
|
50
|
+
"./scripts/tailwind-lint": "./scripts/tailwind-lint.ts",
|
|
49
51
|
"./vite": "./src/vite/plugin.js"
|
|
50
52
|
},
|
|
51
53
|
"scripts": {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal color utilities — keeps output readable without adding dependencies.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const isColorSupported =
|
|
6
|
+
typeof process !== "undefined" &&
|
|
7
|
+
process.stdout?.isTTY &&
|
|
8
|
+
!process.env.NO_COLOR;
|
|
9
|
+
|
|
10
|
+
function wrap(code: number, resetCode: number) {
|
|
11
|
+
return (text: string) =>
|
|
12
|
+
isColorSupported ? `\x1b[${code}m${text}\x1b[${resetCode}m` : text;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const bold = wrap(1, 22);
|
|
16
|
+
export const dim = wrap(2, 22);
|
|
17
|
+
export const red = wrap(31, 39);
|
|
18
|
+
export const green = wrap(32, 39);
|
|
19
|
+
export const yellow = wrap(33, 39);
|
|
20
|
+
export const blue = wrap(34, 39);
|
|
21
|
+
export const cyan = wrap(36, 39);
|
|
22
|
+
export const gray = wrap(90, 39);
|
|
23
|
+
|
|
24
|
+
export const icons = {
|
|
25
|
+
success: isColorSupported ? "\x1b[32m✓\x1b[0m" : "[OK]",
|
|
26
|
+
error: isColorSupported ? "\x1b[31m✗\x1b[0m" : "[FAIL]",
|
|
27
|
+
warning: isColorSupported ? "\x1b[33m⚠\x1b[0m" : "[WARN]",
|
|
28
|
+
info: isColorSupported ? "\x1b[34mℹ\x1b[0m" : "[INFO]",
|
|
29
|
+
arrow: isColorSupported ? "\x1b[36m→\x1b[0m" : "->",
|
|
30
|
+
bullet: isColorSupported ? "\x1b[90m•\x1b[0m" : "-",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function banner(text: string) {
|
|
34
|
+
const line = "═".repeat(58);
|
|
35
|
+
console.log(`\n${cyan(`╔${line}╗`)}`);
|
|
36
|
+
console.log(`${cyan("║")} ${bold(text.padEnd(56))}${cyan("║")}`);
|
|
37
|
+
console.log(`${cyan(`╚${line}╝`)}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function phase(name: string) {
|
|
41
|
+
console.log(`\n${bold(blue(`━━━ ${name} ━━━`))}\n`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function stat(label: string, value: string | number) {
|
|
45
|
+
console.log(` ${gray(label + ":")} ${bold(String(value))}`);
|
|
46
|
+
}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type {
|
|
4
|
+
DetectedPattern,
|
|
5
|
+
FileRecord,
|
|
6
|
+
MigrationContext,
|
|
7
|
+
Platform,
|
|
8
|
+
} from "./types.ts";
|
|
9
|
+
import { log, logPhase } from "./types.ts";
|
|
10
|
+
|
|
11
|
+
const PATTERN_DETECTORS: Array<[DetectedPattern, RegExp]> = [
|
|
12
|
+
["preact-hooks", /from\s+["']preact\/hooks["']/],
|
|
13
|
+
["preact-signals", /from\s+["']@preact\/signals/],
|
|
14
|
+
["fresh-runtime", /from\s+["']\$fresh\/runtime/],
|
|
15
|
+
["fresh-server", /from\s+["']\$fresh\/server/],
|
|
16
|
+
["deco-hooks", /from\s+["']@deco\/deco\/hooks["']/],
|
|
17
|
+
["deco-context", /Context\.active\(\)/],
|
|
18
|
+
["deco-web", /from\s+["']@deco\/deco\/web["']/],
|
|
19
|
+
["deco-blocks", /from\s+["']@deco\/deco\/blocks["']/],
|
|
20
|
+
["apps-imports", /from\s+["']apps\//],
|
|
21
|
+
["site-imports", /from\s+["']site\//],
|
|
22
|
+
["class-attr", /<[a-zA-Z][^>]*\sclass\s*=/],
|
|
23
|
+
["onInput-handler", /onInput\s*=/],
|
|
24
|
+
["deno-lint-ignore", /deno-lint-ignore/],
|
|
25
|
+
["npm-prefix", /from\s+["']npm:/],
|
|
26
|
+
["component-children", /ComponentChildren/],
|
|
27
|
+
["jsx-types", /JSX\.(?:SVG|HTML|Generic)/],
|
|
28
|
+
["asset-function", /\basset\(/],
|
|
29
|
+
["head-component", /<Head[\s>]/],
|
|
30
|
+
["define-app", /defineApp\(/],
|
|
31
|
+
["invoke-proxy", /proxy<Manifest/],
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
/** Files/dirs that should be completely skipped during scanning */
|
|
35
|
+
const SKIP_DIRS = new Set([
|
|
36
|
+
"node_modules",
|
|
37
|
+
".git",
|
|
38
|
+
".github",
|
|
39
|
+
".deco",
|
|
40
|
+
".devcontainer",
|
|
41
|
+
".vscode",
|
|
42
|
+
"_fresh",
|
|
43
|
+
"static",
|
|
44
|
+
".context",
|
|
45
|
+
"scripts",
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
const SKIP_FILES = new Set([
|
|
49
|
+
"deno.lock",
|
|
50
|
+
".gitignore",
|
|
51
|
+
"README.md",
|
|
52
|
+
"LICENSE",
|
|
53
|
+
"browserslist",
|
|
54
|
+
"bw_stats.json",
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
/** Files that are generated and should be deleted */
|
|
58
|
+
const GENERATED_FILES = new Set([
|
|
59
|
+
"fresh.gen.ts",
|
|
60
|
+
"manifest.gen.ts",
|
|
61
|
+
"fresh.config.ts",
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
/** SDK files that have framework equivalents */
|
|
65
|
+
const SDK_DELETE = new Set([
|
|
66
|
+
"sdk/clx.ts",
|
|
67
|
+
"sdk/useId.ts",
|
|
68
|
+
"sdk/useOffer.ts",
|
|
69
|
+
"sdk/useVariantPossiblities.ts",
|
|
70
|
+
"sdk/usePlatform.tsx",
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
/** Root config/infra files to delete */
|
|
74
|
+
const ROOT_DELETE = new Set([
|
|
75
|
+
"main.ts",
|
|
76
|
+
"dev.ts",
|
|
77
|
+
"deno.json",
|
|
78
|
+
"deno.lock",
|
|
79
|
+
"tailwind.css",
|
|
80
|
+
"tailwind.config.ts",
|
|
81
|
+
"runtime.ts",
|
|
82
|
+
"fresh.gen.ts",
|
|
83
|
+
"manifest.gen.ts",
|
|
84
|
+
"fresh.config.ts",
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
function detectPatterns(content: string): DetectedPattern[] {
|
|
88
|
+
const patterns: DetectedPattern[] = [];
|
|
89
|
+
for (const [name, regex] of PATTERN_DETECTORS) {
|
|
90
|
+
if (regex.test(content)) {
|
|
91
|
+
patterns.push(name);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return patterns;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function isReExport(content: string): { is: boolean; target?: string } {
|
|
98
|
+
const match = content.match(
|
|
99
|
+
/^export\s+\{\s*default\s*\}\s+from\s+["']([^"']+)["']/m,
|
|
100
|
+
);
|
|
101
|
+
if (match) return { is: true, target: match[1] };
|
|
102
|
+
return { is: false };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function categorizeFile(
|
|
106
|
+
relPath: string,
|
|
107
|
+
): FileRecord["category"] {
|
|
108
|
+
if (relPath.startsWith("sections/")) return "section";
|
|
109
|
+
if (relPath.startsWith("islands/")) return "island";
|
|
110
|
+
if (relPath.startsWith("components/")) return "component";
|
|
111
|
+
if (relPath.startsWith("sdk/")) return "sdk";
|
|
112
|
+
if (relPath.startsWith("loaders/")) return "loader";
|
|
113
|
+
if (relPath.startsWith("actions/")) return "action";
|
|
114
|
+
if (relPath.startsWith("routes/")) return "route";
|
|
115
|
+
if (relPath.startsWith("apps/")) return "app";
|
|
116
|
+
if (relPath.startsWith("static/")) return "static";
|
|
117
|
+
if (GENERATED_FILES.has(relPath)) return "generated";
|
|
118
|
+
if (
|
|
119
|
+
relPath === "deno.json" || relPath === "tsconfig.json" ||
|
|
120
|
+
relPath === "tailwind.config.ts"
|
|
121
|
+
) {
|
|
122
|
+
return "config";
|
|
123
|
+
}
|
|
124
|
+
return "other";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function decideAction(
|
|
128
|
+
record: FileRecord,
|
|
129
|
+
): { action: FileRecord["action"]; targetPath?: string; notes?: string } {
|
|
130
|
+
const { path: relPath, category, isReExport: isReExp } = record;
|
|
131
|
+
|
|
132
|
+
// Generated files → delete
|
|
133
|
+
if (category === "generated") {
|
|
134
|
+
return { action: "delete" };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Root config/infra → delete (will be scaffolded)
|
|
138
|
+
if (ROOT_DELETE.has(relPath)) {
|
|
139
|
+
return { action: "delete", notes: "Replaced by scaffolded config" };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Routes → delete (will be scaffolded)
|
|
143
|
+
if (category === "route") {
|
|
144
|
+
return { action: "delete", notes: "Routes are scaffolded fresh" };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Apps deco/ dir → delete
|
|
148
|
+
if (relPath.startsWith("apps/deco/")) {
|
|
149
|
+
return { action: "delete", notes: "Deco apps not needed in TanStack" };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// apps/site.ts → delete (will be scaffolded)
|
|
153
|
+
if (relPath === "apps/site.ts") {
|
|
154
|
+
return { action: "delete", notes: "Rewritten from scratch" };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// SDK files to delete
|
|
158
|
+
if (SDK_DELETE.has(relPath)) {
|
|
159
|
+
return {
|
|
160
|
+
action: "delete",
|
|
161
|
+
notes: "Use framework equivalent from @decocms/start or @decocms/apps",
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// cart/ directory → delete
|
|
166
|
+
if (relPath.startsWith("sdk/cart/")) {
|
|
167
|
+
return { action: "delete", notes: "Use @decocms/apps cart hooks" };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Islands — if the section is a re-export of this island, island becomes section
|
|
171
|
+
if (category === "island") {
|
|
172
|
+
const sectionPath = relPath.replace("islands/", "sections/");
|
|
173
|
+
return {
|
|
174
|
+
action: "transform",
|
|
175
|
+
targetPath: `src/${sectionPath}`,
|
|
176
|
+
notes: "Island merged into section",
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Sections that are re-exports of islands → delete (island takes their place)
|
|
181
|
+
if (category === "section" && isReExp) {
|
|
182
|
+
return { action: "delete", notes: "Re-export wrapper, island merged" };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Session component → delete (analytics moves to __root.tsx)
|
|
186
|
+
if (
|
|
187
|
+
relPath === "components/Session.tsx" || relPath === "sections/Session.tsx"
|
|
188
|
+
) {
|
|
189
|
+
return {
|
|
190
|
+
action: "delete",
|
|
191
|
+
notes: "Analytics SDK moved to __root.tsx scaffold",
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Static files → move
|
|
196
|
+
if (category === "static") {
|
|
197
|
+
const publicPath = relPath.replace("static/", "public/");
|
|
198
|
+
return { action: "move", targetPath: publicPath };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Everything else → transform into src/
|
|
202
|
+
return { action: "transform", targetPath: `src/${relPath}` };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function scanDir(
|
|
206
|
+
dir: string,
|
|
207
|
+
baseDir: string,
|
|
208
|
+
files: FileRecord[],
|
|
209
|
+
) {
|
|
210
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
211
|
+
|
|
212
|
+
for (const entry of entries) {
|
|
213
|
+
const fullPath = path.join(dir, entry.name);
|
|
214
|
+
const relPath = path.relative(baseDir, fullPath);
|
|
215
|
+
|
|
216
|
+
if (entry.isDirectory()) {
|
|
217
|
+
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
218
|
+
scanDir(fullPath, baseDir, files);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Skip dotfiles and known non-code files
|
|
223
|
+
if (SKIP_FILES.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
224
|
+
|
|
225
|
+
// Only process .ts, .tsx, .css, .json files for transforms
|
|
226
|
+
const ext = path.extname(entry.name);
|
|
227
|
+
const isCode = [".ts", ".tsx", ".css", ".json"].includes(ext);
|
|
228
|
+
|
|
229
|
+
let content = "";
|
|
230
|
+
let patterns: DetectedPattern[] = [];
|
|
231
|
+
let reExport = { is: false, target: undefined as string | undefined };
|
|
232
|
+
|
|
233
|
+
if (isCode) {
|
|
234
|
+
content = fs.readFileSync(fullPath, "utf-8");
|
|
235
|
+
patterns = detectPatterns(content);
|
|
236
|
+
reExport = isReExport(content);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const record: FileRecord = {
|
|
240
|
+
path: relPath,
|
|
241
|
+
absPath: fullPath,
|
|
242
|
+
category: categorizeFile(relPath),
|
|
243
|
+
isReExport: reExport.is,
|
|
244
|
+
reExportTarget: reExport.target,
|
|
245
|
+
patterns,
|
|
246
|
+
action: "transform", // placeholder
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const decision = decideAction(record);
|
|
250
|
+
record.action = decision.action;
|
|
251
|
+
record.targetPath = decision.targetPath;
|
|
252
|
+
record.notes = decision.notes;
|
|
253
|
+
|
|
254
|
+
files.push(record);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function extractGtmId(sourceDir: string): string | null {
|
|
259
|
+
const appPath = path.join(sourceDir, "routes", "_app.tsx");
|
|
260
|
+
if (!fs.existsSync(appPath)) return null;
|
|
261
|
+
|
|
262
|
+
const content = fs.readFileSync(appPath, "utf-8");
|
|
263
|
+
const match = content.match(/GTM-[A-Z0-9]+/);
|
|
264
|
+
return match ? match[0] : null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function extractPlatform(sourceDir: string): Platform {
|
|
268
|
+
const sitePath = path.join(sourceDir, "apps", "site.ts");
|
|
269
|
+
if (!fs.existsSync(sitePath)) return "custom";
|
|
270
|
+
|
|
271
|
+
const content = fs.readFileSync(sitePath, "utf-8");
|
|
272
|
+
|
|
273
|
+
// Check for platform in Props or default
|
|
274
|
+
for (const p of ["vtex", "shopify", "wake", "vnda", "linx", "nuvemshop"] as const) {
|
|
275
|
+
if (content.includes(`"${p}"`) && content.includes("_platform")) {
|
|
276
|
+
// This is just detecting what's available, default is usually "custom"
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return "custom";
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function extractSiteName(sourceDir: string): string {
|
|
284
|
+
// Try to extract from .deco or directory name
|
|
285
|
+
const dirName = path.basename(path.resolve(sourceDir));
|
|
286
|
+
|
|
287
|
+
// Try deno.json
|
|
288
|
+
const denoPath = path.join(sourceDir, "deno.json");
|
|
289
|
+
if (fs.existsSync(denoPath)) {
|
|
290
|
+
const deno = JSON.parse(fs.readFileSync(denoPath, "utf-8"));
|
|
291
|
+
if (deno.name) return deno.name;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return dirName;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function analyze(ctx: MigrationContext): void {
|
|
298
|
+
logPhase("Analyze");
|
|
299
|
+
|
|
300
|
+
// Parse deno.json for import map
|
|
301
|
+
const denoJsonPath = path.join(ctx.sourceDir, "deno.json");
|
|
302
|
+
if (fs.existsSync(denoJsonPath)) {
|
|
303
|
+
const denoJson = JSON.parse(fs.readFileSync(denoJsonPath, "utf-8"));
|
|
304
|
+
ctx.importMap = denoJson.imports || {};
|
|
305
|
+
log(
|
|
306
|
+
ctx,
|
|
307
|
+
`Found ${Object.keys(ctx.importMap).length} import map entries`,
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Extract metadata
|
|
312
|
+
ctx.siteName = extractSiteName(ctx.sourceDir);
|
|
313
|
+
ctx.platform = extractPlatform(ctx.sourceDir);
|
|
314
|
+
ctx.gtmId = extractGtmId(ctx.sourceDir);
|
|
315
|
+
|
|
316
|
+
console.log(` Site: ${ctx.siteName}`);
|
|
317
|
+
console.log(` Platform: ${ctx.platform}`);
|
|
318
|
+
console.log(` GTM ID: ${ctx.gtmId || "none"}`);
|
|
319
|
+
|
|
320
|
+
// Scan all files
|
|
321
|
+
scanDir(ctx.sourceDir, ctx.sourceDir, ctx.files);
|
|
322
|
+
|
|
323
|
+
// Summary
|
|
324
|
+
const byAction = { transform: 0, delete: 0, move: 0, scaffold: 0, "manual-review": 0 };
|
|
325
|
+
const byCategory: Record<string, number> = {};
|
|
326
|
+
|
|
327
|
+
for (const f of ctx.files) {
|
|
328
|
+
byAction[f.action]++;
|
|
329
|
+
byCategory[f.category] = (byCategory[f.category] || 0) + 1;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
console.log(`\n Files found: ${ctx.files.length}`);
|
|
333
|
+
console.log(` By category: ${JSON.stringify(byCategory)}`);
|
|
334
|
+
console.log(` By action: ${JSON.stringify(byAction)}`);
|
|
335
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { MigrationContext } from "./types.ts";
|
|
4
|
+
import { log, logPhase } from "./types.ts";
|
|
5
|
+
|
|
6
|
+
/** Directories to remove entirely after migration */
|
|
7
|
+
const DIRS_TO_DELETE = [
|
|
8
|
+
"islands",
|
|
9
|
+
"routes",
|
|
10
|
+
"apps/deco",
|
|
11
|
+
"sdk/cart",
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
/** Individual root files to delete */
|
|
15
|
+
const ROOT_FILES_TO_DELETE = [
|
|
16
|
+
"main.ts",
|
|
17
|
+
"dev.ts",
|
|
18
|
+
"deno.json",
|
|
19
|
+
"deno.lock",
|
|
20
|
+
"tailwind.css",
|
|
21
|
+
"tailwind.config.ts",
|
|
22
|
+
"runtime.ts",
|
|
23
|
+
"constants.ts",
|
|
24
|
+
"fresh.gen.ts",
|
|
25
|
+
"manifest.gen.ts",
|
|
26
|
+
"fresh.config.ts",
|
|
27
|
+
"browserslist",
|
|
28
|
+
"bw_stats.json",
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
/** SDK files that have framework equivalents */
|
|
32
|
+
const SDK_FILES_TO_DELETE = [
|
|
33
|
+
"sdk/clx.ts",
|
|
34
|
+
"sdk/useId.ts",
|
|
35
|
+
"sdk/useOffer.ts",
|
|
36
|
+
"sdk/useVariantPossiblities.ts",
|
|
37
|
+
"sdk/usePlatform.tsx",
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
/** Section/component wrappers that are no longer needed */
|
|
41
|
+
const WRAPPER_FILES_TO_DELETE = [
|
|
42
|
+
"components/Session.tsx",
|
|
43
|
+
"sections/Session.tsx",
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
function deleteFileIfExists(ctx: MigrationContext, relPath: string) {
|
|
47
|
+
const fullPath = path.join(ctx.sourceDir, relPath);
|
|
48
|
+
if (!fs.existsSync(fullPath)) return;
|
|
49
|
+
|
|
50
|
+
if (ctx.dryRun) {
|
|
51
|
+
log(ctx, `[DRY] Would delete: ${relPath}`);
|
|
52
|
+
ctx.deletedFiles.push(relPath);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
fs.unlinkSync(fullPath);
|
|
57
|
+
ctx.deletedFiles.push(relPath);
|
|
58
|
+
log(ctx, `Deleted: ${relPath}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function deleteDirIfExists(ctx: MigrationContext, relPath: string) {
|
|
62
|
+
const fullPath = path.join(ctx.sourceDir, relPath);
|
|
63
|
+
if (!fs.existsSync(fullPath)) return;
|
|
64
|
+
|
|
65
|
+
if (ctx.dryRun) {
|
|
66
|
+
log(ctx, `[DRY] Would delete dir: ${relPath}/`);
|
|
67
|
+
ctx.deletedFiles.push(`${relPath}/`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
fs.rmSync(fullPath, { recursive: true, force: true });
|
|
72
|
+
ctx.deletedFiles.push(`${relPath}/`);
|
|
73
|
+
log(ctx, `Deleted dir: ${relPath}/`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function moveStaticFiles(ctx: MigrationContext) {
|
|
77
|
+
const staticDir = path.join(ctx.sourceDir, "static");
|
|
78
|
+
if (!fs.existsSync(staticDir)) return;
|
|
79
|
+
|
|
80
|
+
const publicDir = path.join(ctx.sourceDir, "public");
|
|
81
|
+
|
|
82
|
+
function moveRecursive(dir: string) {
|
|
83
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
84
|
+
for (const entry of entries) {
|
|
85
|
+
const srcPath = path.join(dir, entry.name);
|
|
86
|
+
const relFromStatic = path.relative(staticDir, srcPath);
|
|
87
|
+
const destPath = path.join(publicDir, relFromStatic);
|
|
88
|
+
|
|
89
|
+
// Skip generated files
|
|
90
|
+
if (
|
|
91
|
+
entry.name === "tailwind.css" || entry.name === "adminIcons.ts" ||
|
|
92
|
+
entry.name === "generate-icons.ts"
|
|
93
|
+
) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (entry.isDirectory()) {
|
|
98
|
+
moveRecursive(srcPath);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (ctx.dryRun) {
|
|
103
|
+
log(ctx, `[DRY] Would move: static/${relFromStatic} → public/${relFromStatic}`);
|
|
104
|
+
ctx.movedFiles.push({
|
|
105
|
+
from: `static/${relFromStatic}`,
|
|
106
|
+
to: `public/${relFromStatic}`,
|
|
107
|
+
});
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Ensure dest dir exists
|
|
112
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
113
|
+
fs.copyFileSync(srcPath, destPath);
|
|
114
|
+
ctx.movedFiles.push({
|
|
115
|
+
from: `static/${relFromStatic}`,
|
|
116
|
+
to: `public/${relFromStatic}`,
|
|
117
|
+
});
|
|
118
|
+
log(ctx, `Moved: static/${relFromStatic} → public/${relFromStatic}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
moveRecursive(staticDir);
|
|
123
|
+
|
|
124
|
+
// Now delete static/ dir
|
|
125
|
+
if (!ctx.dryRun) {
|
|
126
|
+
fs.rmSync(staticDir, { recursive: true, force: true });
|
|
127
|
+
log(ctx, "Deleted dir: static/");
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function cleanupOldSourceDirs(ctx: MigrationContext) {
|
|
132
|
+
// After transforms, the original top-level dirs have been copied to src/.
|
|
133
|
+
// Delete the old top-level copies if they still exist and src/ has them.
|
|
134
|
+
const dirsToClean = [
|
|
135
|
+
"sections",
|
|
136
|
+
"components",
|
|
137
|
+
"sdk",
|
|
138
|
+
"loaders",
|
|
139
|
+
"actions",
|
|
140
|
+
"apps",
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
for (const dir of dirsToClean) {
|
|
144
|
+
const oldDir = path.join(ctx.sourceDir, dir);
|
|
145
|
+
const newDir = path.join(ctx.sourceDir, "src", dir);
|
|
146
|
+
if (fs.existsSync(oldDir) && fs.existsSync(newDir)) {
|
|
147
|
+
if (ctx.dryRun) {
|
|
148
|
+
log(ctx, `[DRY] Would delete old dir: ${dir}/ (moved to src/${dir}/)`);
|
|
149
|
+
ctx.deletedFiles.push(`${dir}/`);
|
|
150
|
+
} else {
|
|
151
|
+
fs.rmSync(oldDir, { recursive: true, force: true });
|
|
152
|
+
ctx.deletedFiles.push(`${dir}/`);
|
|
153
|
+
log(ctx, `Deleted old dir: ${dir}/ (now at src/${dir}/)`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Delete sections that were re-export wrappers (their islands are now sections) */
|
|
160
|
+
function cleanupReExportSections(ctx: MigrationContext) {
|
|
161
|
+
const reExports = ctx.files.filter(
|
|
162
|
+
(f) => f.category === "section" && f.isReExport && f.action === "delete",
|
|
163
|
+
);
|
|
164
|
+
for (const f of reExports) {
|
|
165
|
+
// These were already not transformed, just make sure we note them
|
|
166
|
+
log(ctx, `Skipped re-export wrapper: ${f.path}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function cleanup(ctx: MigrationContext): void {
|
|
171
|
+
logPhase("Cleanup");
|
|
172
|
+
|
|
173
|
+
// 1. Move static → public
|
|
174
|
+
console.log(" Moving static/ → public/...");
|
|
175
|
+
moveStaticFiles(ctx);
|
|
176
|
+
|
|
177
|
+
// 2. Delete specific files
|
|
178
|
+
console.log(" Deleting old files...");
|
|
179
|
+
for (const file of ROOT_FILES_TO_DELETE) {
|
|
180
|
+
deleteFileIfExists(ctx, file);
|
|
181
|
+
}
|
|
182
|
+
for (const file of SDK_FILES_TO_DELETE) {
|
|
183
|
+
deleteFileIfExists(ctx, file);
|
|
184
|
+
}
|
|
185
|
+
for (const file of WRAPPER_FILES_TO_DELETE) {
|
|
186
|
+
deleteFileIfExists(ctx, file);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// 3. Delete directories
|
|
190
|
+
console.log(" Deleting old directories...");
|
|
191
|
+
for (const dir of DIRS_TO_DELETE) {
|
|
192
|
+
deleteDirIfExists(ctx, dir);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 4. Clean up old source directories
|
|
196
|
+
console.log(" Cleaning up old source dirs...");
|
|
197
|
+
cleanupOldSourceDirs(ctx);
|
|
198
|
+
cleanupReExportSections(ctx);
|
|
199
|
+
|
|
200
|
+
console.log(
|
|
201
|
+
` Deleted ${ctx.deletedFiles.length} files/dirs, moved ${ctx.movedFiles.length} files`,
|
|
202
|
+
);
|
|
203
|
+
}
|