@decocms/start 2.10.0 → 2.12.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/.agents/skills/deco-migrate-script/SKILL.md +38 -0
- package/.agents/skills/deco-to-tanstack-migration/references/post-migration-cleanup.md +37 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/post-migration-cleanup.md +37 -0
- package/package.json +4 -2
- package/scripts/migrate/post-cleanup/rules.ts +371 -0
- package/scripts/migrate/post-cleanup/runner.test.ts +460 -0
- package/scripts/migrate/post-cleanup/runner.ts +137 -0
- package/scripts/migrate/post-cleanup/types.ts +106 -0
- package/scripts/migrate/source-layout.test.ts +111 -0
- package/scripts/migrate/source-layout.ts +103 -0
- package/scripts/migrate-post-cleanup.ts +186 -0
- package/scripts/migrate.ts +24 -7
|
@@ -485,3 +485,41 @@ This script handles **Phases 0-6** of the [migration playbook](../deco-to-tansta
|
|
|
485
485
|
- Phase 7-12 — Section registry tuning, route customization, matchers, async rendering, search
|
|
486
486
|
|
|
487
487
|
The script gets you from "raw Fresh site" to "builds with `npm run build` and has ~0 old imports". Human work starts at runtime debugging and feature wiring.
|
|
488
|
+
|
|
489
|
+
## Post-Migration Audit (`deco-post-cleanup`)
|
|
490
|
+
|
|
491
|
+
After the migration script's compile phase passes, run the
|
|
492
|
+
**`deco-post-cleanup`** audit to catch the residual cleanup the
|
|
493
|
+
script leaves behind on existing-but-pre-framework-helpers sites:
|
|
494
|
+
|
|
495
|
+
```bash
|
|
496
|
+
# Read-only audit (default)
|
|
497
|
+
npx -p @decocms/start deco-post-cleanup
|
|
498
|
+
|
|
499
|
+
# Auto-fix the safe rules (dead-lib-shims, dead-runtime-shim, local-widgets-types)
|
|
500
|
+
npx -p @decocms/start deco-post-cleanup --fix
|
|
501
|
+
|
|
502
|
+
# CI gate: auto-fix safe rules, exit 2 if any warnings remain
|
|
503
|
+
npx -p @decocms/start deco-post-cleanup --fix --strict
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
The audit covers 7 rules (delete dead lib shims, drop obsolete inline
|
|
507
|
+
Vite plugins, delete dead `runtime.ts` invoke shim, delete site-local
|
|
508
|
+
`withSiteGlobals` wrapper, repoint `~/lib/vtex-*` shim regressions,
|
|
509
|
+
delete shadowed `widgets.ts`, surface orphan framework TODOs). The
|
|
510
|
+
detection logic mirrors the canonical checklist at
|
|
511
|
+
[`deco-to-tanstack-migration/references/post-migration-cleanup.md`](../deco-to-tanstack-migration/references/post-migration-cleanup.md).
|
|
512
|
+
|
|
513
|
+
**Why `compile` and `audit` are complementary:**
|
|
514
|
+
|
|
515
|
+
| Tool | Catches |
|
|
516
|
+
|------|---------|
|
|
517
|
+
| `phase-compile` (in this script) | TS5097, missing exports, type bugs — anything `tsc --noEmit` finds |
|
|
518
|
+
| `deco-post-cleanup` (separate CLI) | Silent runtime stubs (e.g. dead `~/lib/vtex-*` shims that typecheck cleanly but resolve to `{}` at runtime) |
|
|
519
|
+
|
|
520
|
+
`tsc` doesn't catch the silent-stub class of bug because the dead
|
|
521
|
+
shim files have valid TypeScript signatures. The audit's pattern
|
|
522
|
+
matches surface what compilation cannot.
|
|
523
|
+
|
|
524
|
+
Source: `scripts/migrate-post-cleanup.ts` + `scripts/migrate/post-cleanup/`.
|
|
525
|
+
Tests: `scripts/migrate/post-cleanup/runner.test.ts`.
|
|
@@ -5,6 +5,43 @@ recurring set of dead-code and boilerplate cleanup that every migrated
|
|
|
5
5
|
site benefits from. Run this checklist before the first PR review, not
|
|
6
6
|
after the site has been shipping for weeks.
|
|
7
7
|
|
|
8
|
+
## Run the audit first
|
|
9
|
+
|
|
10
|
+
This whole checklist is now automated by the **`deco-post-cleanup`**
|
|
11
|
+
audit script (added in `@decocms/start >= 2.11.0`, `--fix` mode in
|
|
12
|
+
`>= 2.12.0`). Run it from the site repo to get a structured report
|
|
13
|
+
of which sections below actually apply to your codebase:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Pretty text output, exits 0 unless --strict is passed
|
|
17
|
+
npx -p @decocms/start deco-post-cleanup
|
|
18
|
+
|
|
19
|
+
# Auto-apply mechanical fixes for the safe rules, then report what's left.
|
|
20
|
+
# Safe rules: dead-lib-shims, dead-runtime-shim, local-widgets-types.
|
|
21
|
+
# Other rules stay detect-only — they require human judgment.
|
|
22
|
+
npx -p @decocms/start deco-post-cleanup --fix
|
|
23
|
+
|
|
24
|
+
# Combine for CI: auto-fix safe rules, fail (exit 2) if warnings remain.
|
|
25
|
+
npx -p @decocms/start deco-post-cleanup --fix --strict
|
|
26
|
+
|
|
27
|
+
# Machine-readable JSON for dashboards
|
|
28
|
+
npx -p @decocms/start deco-post-cleanup --json
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The audit covers all 7 rules below and prints the exact file path +
|
|
32
|
+
suggested fix for each finding. With `--fix`, the three safe rules
|
|
33
|
+
auto-apply (`rm` for dead files, regex-anchored import rewrites for
|
|
34
|
+
shadowed shims). The output explicitly tags rules that require manual
|
|
35
|
+
work as `(0 fixed, manual)`, so you always know what's left after
|
|
36
|
+
auto-fix runs.
|
|
37
|
+
|
|
38
|
+
Real-world signal: on baggagio, `--fix` produced a byte-identical
|
|
39
|
+
diff to the manual cleanup PR a human had just made (45 files,
|
|
40
|
+
+45/-53). On casaevideo-storefront (production), the audit caught
|
|
41
|
+
six silent VTEX shim regressions that no `tsc --noEmit` run can
|
|
42
|
+
detect — those still require manual cleanup until rule 5 gains a
|
|
43
|
+
per-shim mapping table.
|
|
44
|
+
|
|
8
45
|
## 1. Delete unused `src/lib/*` shims
|
|
9
46
|
|
|
10
47
|
The migration script's `templates/lib-utils.ts` generates 11 shim files
|
|
@@ -5,6 +5,43 @@ recurring set of dead-code and boilerplate cleanup that every migrated
|
|
|
5
5
|
site benefits from. Run this checklist before the first PR review, not
|
|
6
6
|
after the site has been shipping for weeks.
|
|
7
7
|
|
|
8
|
+
## Run the audit first
|
|
9
|
+
|
|
10
|
+
This whole checklist is now automated by the **`deco-post-cleanup`**
|
|
11
|
+
audit script (added in `@decocms/start >= 2.11.0`, `--fix` mode in
|
|
12
|
+
`>= 2.12.0`). Run it from the site repo to get a structured report
|
|
13
|
+
of which sections below actually apply to your codebase:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Pretty text output, exits 0 unless --strict is passed
|
|
17
|
+
npx -p @decocms/start deco-post-cleanup
|
|
18
|
+
|
|
19
|
+
# Auto-apply mechanical fixes for the safe rules, then report what's left.
|
|
20
|
+
# Safe rules: dead-lib-shims, dead-runtime-shim, local-widgets-types.
|
|
21
|
+
# Other rules stay detect-only — they require human judgment.
|
|
22
|
+
npx -p @decocms/start deco-post-cleanup --fix
|
|
23
|
+
|
|
24
|
+
# Combine for CI: auto-fix safe rules, fail (exit 2) if warnings remain.
|
|
25
|
+
npx -p @decocms/start deco-post-cleanup --fix --strict
|
|
26
|
+
|
|
27
|
+
# Machine-readable JSON for dashboards
|
|
28
|
+
npx -p @decocms/start deco-post-cleanup --json
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The audit covers all 7 rules below and prints the exact file path +
|
|
32
|
+
suggested fix for each finding. With `--fix`, the three safe rules
|
|
33
|
+
auto-apply (`rm` for dead files, regex-anchored import rewrites for
|
|
34
|
+
shadowed shims). The output explicitly tags rules that require manual
|
|
35
|
+
work as `(0 fixed, manual)`, so you always know what's left after
|
|
36
|
+
auto-fix runs.
|
|
37
|
+
|
|
38
|
+
Real-world signal: on baggagio, `--fix` produced a byte-identical
|
|
39
|
+
diff to the manual cleanup PR a human had just made (45 files,
|
|
40
|
+
+45/-53). On casaevideo-storefront (production), the audit caught
|
|
41
|
+
six silent VTEX shim regressions that no `tsc --noEmit` run can
|
|
42
|
+
detect — those still require manual cleanup until rule 5 gains a
|
|
43
|
+
per-shim mapping table.
|
|
44
|
+
|
|
8
45
|
## 1. Delete unused `src/lib/*` shims
|
|
9
46
|
|
|
10
47
|
The migration script's `templates/lib-utils.ts` generates 11 shim files
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/start",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.12.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
7
|
"bin": {
|
|
8
|
-
"deco-migrate": "./scripts/migrate.ts"
|
|
8
|
+
"deco-migrate": "./scripts/migrate.ts",
|
|
9
|
+
"deco-post-cleanup": "./scripts/migrate-post-cleanup.ts"
|
|
9
10
|
},
|
|
10
11
|
"exports": {
|
|
11
12
|
".": "./src/index.ts",
|
|
@@ -59,6 +60,7 @@
|
|
|
59
60
|
"./scripts/generate-schema": "./scripts/generate-schema.ts",
|
|
60
61
|
"./scripts/generate-invoke": "./scripts/generate-invoke.ts",
|
|
61
62
|
"./scripts/migrate": "./scripts/migrate.ts",
|
|
63
|
+
"./scripts/migrate-post-cleanup": "./scripts/migrate-post-cleanup.ts",
|
|
62
64
|
"./scripts/tailwind-lint": "./scripts/tailwind-lint.ts",
|
|
63
65
|
"./vite": "./src/vite/plugin.js",
|
|
64
66
|
"./daemon": "./src/daemon/index.ts"
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-migration cleanup audit — rule implementations.
|
|
3
|
+
*
|
|
4
|
+
* Each rule mirrors a section in
|
|
5
|
+
* `.agents/skills/deco-to-tanstack-migration/references/post-migration-cleanup.md`.
|
|
6
|
+
* The intent is to take the human checklist and make it programmatically
|
|
7
|
+
* detectable so future migrations get the same scrubbing automatically.
|
|
8
|
+
*
|
|
9
|
+
* Rules are intentionally read-only here — `--fix` is a follow-up.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { Finding, FixAction, FsWriter, Rule, RuleContext } from "./types";
|
|
13
|
+
|
|
14
|
+
const SRC_GLOB_EXCLUDES = ["node_modules", "dist", ".wrangler", ".vite", ".tanstack", "build"];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Rewrite all `from "<oldSpec>"` (or `from '<oldSpec>'`) imports in
|
|
18
|
+
* `src/**` to `from "<newSpec>"`. Returns the list of site-relative
|
|
19
|
+
* paths actually changed so fix-action summaries can quote a count.
|
|
20
|
+
* Uses the write side of the FS adapter — never touches disk in unit
|
|
21
|
+
* tests.
|
|
22
|
+
*
|
|
23
|
+
* Intentionally string-anchored on the exact spec; will not pick up
|
|
24
|
+
* partial-prefix matches like `~/types/widgets-extra`.
|
|
25
|
+
*/
|
|
26
|
+
function rewriteImportSpec(
|
|
27
|
+
ctx: RuleContext,
|
|
28
|
+
writer: FsWriter,
|
|
29
|
+
oldSpec: string,
|
|
30
|
+
newSpec: string,
|
|
31
|
+
): string[] {
|
|
32
|
+
const { siteDir, fs } = ctx;
|
|
33
|
+
const tsFiles = fs.glob(siteDir, "src/**/*.{ts,tsx}", SRC_GLOB_EXCLUDES);
|
|
34
|
+
const escaped = oldSpec.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
35
|
+
const re = new RegExp(`from\\s+(['"])${escaped}\\1`, "g");
|
|
36
|
+
const updated: string[] = [];
|
|
37
|
+
for (const abs of tsFiles) {
|
|
38
|
+
const content = fs.readText(abs);
|
|
39
|
+
if (!re.test(content)) {
|
|
40
|
+
re.lastIndex = 0;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
re.lastIndex = 0;
|
|
44
|
+
const next = content.replace(re, (_m, q) => `from ${q}${newSpec}${q}`);
|
|
45
|
+
if (next !== content) {
|
|
46
|
+
writer.writeText(abs, next);
|
|
47
|
+
updated.push(abs.slice(siteDir.length + 1));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return updated;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* ------------------------------------------------------------------ */
|
|
54
|
+
/* Rule 1 — dead `src/lib/*` shims */
|
|
55
|
+
/* ------------------------------------------------------------------ */
|
|
56
|
+
|
|
57
|
+
const EXPORT_RE = /^export\s+(?:function|const|interface|type|class)\s+([A-Za-z_][A-Za-z0-9_]*)/gm;
|
|
58
|
+
|
|
59
|
+
function extractExports(content: string): string[] {
|
|
60
|
+
const out: string[] = [];
|
|
61
|
+
for (const m of content.matchAll(EXPORT_RE)) {
|
|
62
|
+
out.push(m[1]);
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function symbolUsedOutsideLib(siteDir: string, fs: RuleContext["fs"], symbol: string): boolean {
|
|
68
|
+
const tsFiles = fs.glob(siteDir, "src/**/*.{ts,tsx}", SRC_GLOB_EXCLUDES);
|
|
69
|
+
const re = new RegExp(`\\b${symbol}\\b`);
|
|
70
|
+
for (const file of tsFiles) {
|
|
71
|
+
if (file.includes("/src/lib/")) continue;
|
|
72
|
+
const content = fs.readText(file);
|
|
73
|
+
if (re.test(content)) return true;
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const ruleDeadLibShims: Rule = {
|
|
79
|
+
id: "dead-lib-shims",
|
|
80
|
+
title: "Dead src/lib/* shims",
|
|
81
|
+
run({ siteDir, fs }: RuleContext): Finding[] {
|
|
82
|
+
const libFiles = fs.glob(siteDir, "src/lib/*.ts", SRC_GLOB_EXCLUDES);
|
|
83
|
+
if (libFiles.length === 0) return [];
|
|
84
|
+
|
|
85
|
+
const findings: Finding[] = [];
|
|
86
|
+
for (const abs of libFiles) {
|
|
87
|
+
const rel = abs.slice(siteDir.length + 1);
|
|
88
|
+
const content = fs.readText(abs);
|
|
89
|
+
const exports = extractExports(content);
|
|
90
|
+
if (exports.length === 0) continue;
|
|
91
|
+
const allDead = exports.every((s) => !symbolUsedOutsideLib(siteDir, fs, s));
|
|
92
|
+
if (!allDead) continue;
|
|
93
|
+
findings.push({
|
|
94
|
+
rule: "dead-lib-shims",
|
|
95
|
+
severity: "info",
|
|
96
|
+
file: rel,
|
|
97
|
+
message: `${exports.length} export(s), 0 external imports`,
|
|
98
|
+
fix: `rm ${rel}`,
|
|
99
|
+
meta: { exports },
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return findings;
|
|
103
|
+
},
|
|
104
|
+
applyFix({ siteDir }, findings, writer): FixAction[] {
|
|
105
|
+
const actions: FixAction[] = [];
|
|
106
|
+
for (const f of findings) {
|
|
107
|
+
writer.deleteFile(`${siteDir}/${f.file}`);
|
|
108
|
+
actions.push({
|
|
109
|
+
file: f.file,
|
|
110
|
+
kind: "delete",
|
|
111
|
+
detail: "deleted (all exports verified unused)",
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return actions;
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/* ------------------------------------------------------------------ */
|
|
119
|
+
/* Rule 2 — obsolete inline vite plugins */
|
|
120
|
+
/* ------------------------------------------------------------------ */
|
|
121
|
+
|
|
122
|
+
const OBSOLETE_VITE_PLUGINS: { name: string; reason: string }[] = [
|
|
123
|
+
{
|
|
124
|
+
name: "site-manual-chunks",
|
|
125
|
+
reason: "framework's decoVitePlugin() now owns chunking",
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: "deco-stub-meta-gen",
|
|
129
|
+
reason: "framework now stubs meta.gen.{json,ts} on the client by default",
|
|
130
|
+
},
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
const ruleObsoleteVitePlugins: Rule = {
|
|
134
|
+
id: "obsolete-vite-plugins",
|
|
135
|
+
title: "Obsolete inline Vite plugins",
|
|
136
|
+
run({ siteDir, fs }: RuleContext): Finding[] {
|
|
137
|
+
const findings: Finding[] = [];
|
|
138
|
+
const candidates = ["vite.config.ts", "vite.config.js", "vite.config.mjs"];
|
|
139
|
+
for (const rel of candidates) {
|
|
140
|
+
const abs = `${siteDir}/${rel}`;
|
|
141
|
+
if (!fs.exists(abs)) continue;
|
|
142
|
+
const content = fs.readText(abs);
|
|
143
|
+
for (const plugin of OBSOLETE_VITE_PLUGINS) {
|
|
144
|
+
const re = new RegExp(`name:\\s*["']${plugin.name}["']`);
|
|
145
|
+
if (!re.test(content)) continue;
|
|
146
|
+
findings.push({
|
|
147
|
+
rule: "obsolete-vite-plugins",
|
|
148
|
+
severity: "warning",
|
|
149
|
+
file: rel,
|
|
150
|
+
message: `'${plugin.name}' plugin is obsolete — ${plugin.reason}`,
|
|
151
|
+
fix: `delete the inline '${plugin.name}' plugin from ${rel}`,
|
|
152
|
+
meta: { plugin: plugin.name },
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return findings;
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/* ------------------------------------------------------------------ */
|
|
161
|
+
/* Rule 3 — dead `src/runtime.ts` invoke shim */
|
|
162
|
+
/* ------------------------------------------------------------------ */
|
|
163
|
+
|
|
164
|
+
const ruleDeadRuntimeShim: Rule = {
|
|
165
|
+
id: "dead-runtime-shim",
|
|
166
|
+
title: "Dead src/runtime.ts invoke shim",
|
|
167
|
+
run({ siteDir, fs }: RuleContext): Finding[] {
|
|
168
|
+
const abs = `${siteDir}/src/runtime.ts`;
|
|
169
|
+
if (!fs.exists(abs)) return [];
|
|
170
|
+
const content = fs.readText(abs);
|
|
171
|
+
// Heuristic: if the file's only meaningful exports are `invoke` /
|
|
172
|
+
// `createNestedInvokeProxy`, it's purely a shim.
|
|
173
|
+
const exports = extractExports(content);
|
|
174
|
+
const onlyInvokeShim =
|
|
175
|
+
exports.length > 0 && exports.every((e) => ["invoke", "createNestedInvokeProxy"].includes(e));
|
|
176
|
+
if (!onlyInvokeShim) return [];
|
|
177
|
+
return [
|
|
178
|
+
{
|
|
179
|
+
rule: "dead-runtime-shim",
|
|
180
|
+
severity: "info",
|
|
181
|
+
file: "src/runtime.ts",
|
|
182
|
+
message: `Only re-exports invoke (${exports.join(", ")}) — replace with @decocms/start/sdk`,
|
|
183
|
+
fix: 'rg -l "from \\"~/runtime\\"" src/ | xargs sed -i \'\' \'s|from "~/runtime"|from "@decocms/start/sdk"|g\' && rm src/runtime.ts',
|
|
184
|
+
},
|
|
185
|
+
];
|
|
186
|
+
},
|
|
187
|
+
applyFix(ctx, findings, writer): FixAction[] {
|
|
188
|
+
if (findings.length === 0) return [];
|
|
189
|
+
const updated = rewriteImportSpec(ctx, writer, "~/runtime", "@decocms/start/sdk");
|
|
190
|
+
writer.deleteFile(`${ctx.siteDir}/src/runtime.ts`);
|
|
191
|
+
return [
|
|
192
|
+
{
|
|
193
|
+
file: "src/runtime.ts",
|
|
194
|
+
kind: "rewrite-imports+delete",
|
|
195
|
+
detail: `rewrote ${updated.length} import(s) → @decocms/start/sdk and deleted src/runtime.ts`,
|
|
196
|
+
},
|
|
197
|
+
];
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
/* ------------------------------------------------------------------ */
|
|
202
|
+
/* Rule 4 — site-local `withSiteGlobals` workaround */
|
|
203
|
+
/* ------------------------------------------------------------------ */
|
|
204
|
+
|
|
205
|
+
const ruleSiteLocalGlobals: Rule = {
|
|
206
|
+
id: "site-local-with-globals",
|
|
207
|
+
title: "Site-local withSiteGlobals wrapper",
|
|
208
|
+
run({ siteDir, fs }: RuleContext): Finding[] {
|
|
209
|
+
const findings: Finding[] = [];
|
|
210
|
+
const candidates = fs.glob(siteDir, "src/**/withSiteGlobals.ts", SRC_GLOB_EXCLUDES);
|
|
211
|
+
for (const abs of candidates) {
|
|
212
|
+
const content = fs.readText(abs);
|
|
213
|
+
// Heuristic: any local definition (function/const) of withSiteGlobals or
|
|
214
|
+
// cmsRouteWithGlobals indicates a local wrapper, not a re-export from
|
|
215
|
+
// the framework. The framework version would just re-export.
|
|
216
|
+
const definesWrapper =
|
|
217
|
+
/(?:export\s+)?(?:function|const)\s+(?:withSiteGlobals|cmsRouteWithGlobals)\b/.test(
|
|
218
|
+
content,
|
|
219
|
+
);
|
|
220
|
+
const reExportsFromFramework = /from\s+['"]@decocms\/start\/routes['"]/.test(content);
|
|
221
|
+
if (!definesWrapper || reExportsFromFramework) continue;
|
|
222
|
+
const rel = abs.slice(siteDir.length + 1);
|
|
223
|
+
const lineCount = content.split("\n").length;
|
|
224
|
+
findings.push({
|
|
225
|
+
rule: "site-local-with-globals",
|
|
226
|
+
severity: "warning",
|
|
227
|
+
file: rel,
|
|
228
|
+
message: `Local wrapper (~${lineCount} LOC) — framework now exports withSiteGlobals from @decocms/start/routes`,
|
|
229
|
+
fix: "delete the local wrapper and import { withSiteGlobals } from '@decocms/start/routes'",
|
|
230
|
+
meta: { lineCount },
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
return findings;
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
/* ------------------------------------------------------------------ */
|
|
238
|
+
/* Rule 5 — `~/lib/vtex-*` shim regression */
|
|
239
|
+
/* ------------------------------------------------------------------ */
|
|
240
|
+
|
|
241
|
+
const ruleVtexShimRegression: Rule = {
|
|
242
|
+
id: "vtex-shim-regression",
|
|
243
|
+
title: "Imports from ~/lib/vtex-* (silent stub regression)",
|
|
244
|
+
run({ siteDir, fs }: RuleContext): Finding[] {
|
|
245
|
+
const tsFiles = fs.glob(siteDir, "src/**/*.{ts,tsx}", SRC_GLOB_EXCLUDES);
|
|
246
|
+
const findings: Finding[] = [];
|
|
247
|
+
const re = /from\s+['"]~\/lib\/vtex-([A-Za-z0-9-]+)['"]/g;
|
|
248
|
+
for (const abs of tsFiles) {
|
|
249
|
+
if (abs.includes("/src/lib/")) continue;
|
|
250
|
+
const content = fs.readText(abs);
|
|
251
|
+
const matches = [...content.matchAll(re)];
|
|
252
|
+
if (matches.length === 0) continue;
|
|
253
|
+
const rel = abs.slice(siteDir.length + 1);
|
|
254
|
+
const shims = [...new Set(matches.map((m) => `vtex-${m[1]}`))];
|
|
255
|
+
findings.push({
|
|
256
|
+
rule: "vtex-shim-regression",
|
|
257
|
+
severity: "warning",
|
|
258
|
+
file: rel,
|
|
259
|
+
message: `Imports from dead shim(s): ${shims.join(", ")} — runtime is silently stubbed`,
|
|
260
|
+
fix: "Repoint imports to '@decocms/apps/vtex/...' or 'apps/commerce/utils/...'",
|
|
261
|
+
meta: { shims },
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
return findings;
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
/* ------------------------------------------------------------------ */
|
|
269
|
+
/* Rule 6 — local `src/types/widgets.ts` shadowing framework */
|
|
270
|
+
/* ------------------------------------------------------------------ */
|
|
271
|
+
|
|
272
|
+
const ruleLocalWidgetsTypes: Rule = {
|
|
273
|
+
id: "local-widgets-types",
|
|
274
|
+
title: "Local src/types/widgets.ts shadowing framework",
|
|
275
|
+
run({ siteDir, fs }: RuleContext): Finding[] {
|
|
276
|
+
const abs = `${siteDir}/src/types/widgets.ts`;
|
|
277
|
+
if (!fs.exists(abs)) return [];
|
|
278
|
+
const tsFiles = fs.glob(siteDir, "src/**/*.{ts,tsx}", SRC_GLOB_EXCLUDES);
|
|
279
|
+
const re = /from\s+['"]~\/types\/widgets['"]/;
|
|
280
|
+
let importCount = 0;
|
|
281
|
+
for (const f of tsFiles) {
|
|
282
|
+
if (f === abs) continue;
|
|
283
|
+
if (re.test(fs.readText(f))) importCount++;
|
|
284
|
+
}
|
|
285
|
+
return [
|
|
286
|
+
{
|
|
287
|
+
rule: "local-widgets-types",
|
|
288
|
+
severity: "info",
|
|
289
|
+
file: "src/types/widgets.ts",
|
|
290
|
+
message: `Local file shadows @decocms/start/types/widgets (used in ${importCount} place(s))`,
|
|
291
|
+
fix: 'rewrite imports to "@decocms/start/types/widgets" and rm src/types/widgets.ts',
|
|
292
|
+
meta: { importCount },
|
|
293
|
+
},
|
|
294
|
+
];
|
|
295
|
+
},
|
|
296
|
+
applyFix(ctx, findings, writer): FixAction[] {
|
|
297
|
+
if (findings.length === 0) return [];
|
|
298
|
+
const updated = rewriteImportSpec(
|
|
299
|
+
ctx,
|
|
300
|
+
writer,
|
|
301
|
+
"~/types/widgets",
|
|
302
|
+
"@decocms/start/types/widgets",
|
|
303
|
+
);
|
|
304
|
+
writer.deleteFile(`${ctx.siteDir}/src/types/widgets.ts`);
|
|
305
|
+
return [
|
|
306
|
+
{
|
|
307
|
+
file: "src/types/widgets.ts",
|
|
308
|
+
kind: "rewrite-imports+delete",
|
|
309
|
+
detail: `rewrote ${updated.length} import(s) → @decocms/start/types/widgets and deleted src/types/widgets.ts`,
|
|
310
|
+
},
|
|
311
|
+
];
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
/* ------------------------------------------------------------------ */
|
|
316
|
+
/* Rule 7 — orphan "TODO: framework" comments */
|
|
317
|
+
/* ------------------------------------------------------------------ */
|
|
318
|
+
|
|
319
|
+
const ruleFrameworkTodos: Rule = {
|
|
320
|
+
id: "framework-todos",
|
|
321
|
+
title: "Orphan TODOs deferring to the framework",
|
|
322
|
+
run({ siteDir, fs }: RuleContext): Finding[] {
|
|
323
|
+
const tsFiles = [
|
|
324
|
+
...fs.glob(siteDir, "src/**/*.{ts,tsx}", SRC_GLOB_EXCLUDES),
|
|
325
|
+
...fs.glob(siteDir, "vite.config.ts", SRC_GLOB_EXCLUDES),
|
|
326
|
+
];
|
|
327
|
+
const findings: Finding[] = [];
|
|
328
|
+
const re = /TODO[^\n]*?(?:deco|framework|move into)/i;
|
|
329
|
+
for (const abs of tsFiles) {
|
|
330
|
+
const content = fs.readText(abs);
|
|
331
|
+
const lines = content.split("\n");
|
|
332
|
+
for (let i = 0; i < lines.length; i++) {
|
|
333
|
+
if (!re.test(lines[i])) continue;
|
|
334
|
+
const rel = abs.slice(siteDir.length + 1);
|
|
335
|
+
findings.push({
|
|
336
|
+
rule: "framework-todos",
|
|
337
|
+
severity: "info",
|
|
338
|
+
file: `${rel}:${i + 1}`,
|
|
339
|
+
message: lines[i].trim().slice(0, 120),
|
|
340
|
+
fix: "Triage: shipped → adopt; deferred → file issue; obsolete → delete",
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return findings;
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
export const ALL_RULES: Rule[] = [
|
|
349
|
+
ruleDeadLibShims,
|
|
350
|
+
ruleObsoleteVitePlugins,
|
|
351
|
+
ruleDeadRuntimeShim,
|
|
352
|
+
ruleSiteLocalGlobals,
|
|
353
|
+
ruleVtexShimRegression,
|
|
354
|
+
ruleLocalWidgetsTypes,
|
|
355
|
+
ruleFrameworkTodos,
|
|
356
|
+
];
|
|
357
|
+
|
|
358
|
+
/** Exported for direct unit tests. */
|
|
359
|
+
export const _internals = {
|
|
360
|
+
extractExports,
|
|
361
|
+
symbolUsedOutsideLib,
|
|
362
|
+
rules: {
|
|
363
|
+
ruleDeadLibShims,
|
|
364
|
+
ruleObsoleteVitePlugins,
|
|
365
|
+
ruleDeadRuntimeShim,
|
|
366
|
+
ruleSiteLocalGlobals,
|
|
367
|
+
ruleVtexShimRegression,
|
|
368
|
+
ruleLocalWidgetsTypes,
|
|
369
|
+
ruleFrameworkTodos,
|
|
370
|
+
},
|
|
371
|
+
};
|