@decocms/start 0.36.3 → 0.36.5
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
|
@@ -72,6 +72,24 @@ export function transform(ctx: MigrationContext): void {
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
// Flag files with HTMX patterns for manual React migration
|
|
76
|
+
if (/\bhx-(?:get|post|put|delete|trigger|target|swap|on|indicator|sync|select)\b/.test(result.content)) {
|
|
77
|
+
ctx.manualReviewItems.push({
|
|
78
|
+
file: targetPath,
|
|
79
|
+
reason: "HTMX attributes (hx-*) found — needs manual migration to React state/effects. HTMX server-side rendering (hx-get/hx-post with useSection) must be converted to React components with useState/useEffect or server functions.",
|
|
80
|
+
severity: "warning",
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Flag files with hx-on:click that use useScript (simpler pattern)
|
|
85
|
+
if (/hx-on:click=\{useScript/.test(result.content)) {
|
|
86
|
+
ctx.manualReviewItems.push({
|
|
87
|
+
file: targetPath,
|
|
88
|
+
reason: "hx-on:click with useScript found — convert to onClick with React event handler. The useScript serialization won't work as onClick value.",
|
|
89
|
+
severity: "warning",
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
75
93
|
if (ctx.dryRun) {
|
|
76
94
|
if (result.changed) {
|
|
77
95
|
log(ctx, `[DRY] Would transform: ${record.path} → ${targetPath}`);
|
|
@@ -325,6 +325,20 @@ const checks: Check[] = [
|
|
|
325
325
|
return true;
|
|
326
326
|
},
|
|
327
327
|
},
|
|
328
|
+
{
|
|
329
|
+
name: "No HTMX attributes (hx-*) in components",
|
|
330
|
+
severity: "warning",
|
|
331
|
+
fn: (ctx) => {
|
|
332
|
+
const srcDir = path.join(ctx.sourceDir, "src");
|
|
333
|
+
if (!fs.existsSync(srcDir)) return true;
|
|
334
|
+
const bad = findFilesWithPattern(srcDir, /\bhx-(?:get|post|put|delete|patch|trigger|target|swap|on|indicator|sync|select)\b/);
|
|
335
|
+
if (bad.length > 0) {
|
|
336
|
+
console.log(` HTMX attributes found (needs manual React migration): ${bad.join(", ")}`);
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
return true;
|
|
340
|
+
},
|
|
341
|
+
},
|
|
328
342
|
];
|
|
329
343
|
|
|
330
344
|
function findFilesWithPattern(
|
|
@@ -38,6 +38,13 @@ export function transformDenoIsms(content: string): TransformResult {
|
|
|
38
38
|
notes.push("Removed npm: prefix from imports");
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
// @ts-ignore → @ts-expect-error (TypeScript 5+ prefers @ts-expect-error)
|
|
42
|
+
if (/@ts-ignore/.test(result)) {
|
|
43
|
+
result = result.replace(/@ts-ignore/g, "@ts-expect-error");
|
|
44
|
+
changed = true;
|
|
45
|
+
notes.push("Replaced @ts-ignore with @ts-expect-error");
|
|
46
|
+
}
|
|
47
|
+
|
|
41
48
|
// Remove Deno.* API usages — flag for manual review
|
|
42
49
|
if (result.includes("Deno.")) {
|
|
43
50
|
notes.push("MANUAL: Deno.* API usage found — needs Node.js equivalent");
|
|
@@ -171,6 +171,55 @@ export function transformJsx(content: string): TransformResult {
|
|
|
171
171
|
notes.push("Replaced 'class' in interface definitions with 'className'");
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
// Remove `alt` prop from non-img elements (<a>, <iframe>, <div>, etc.)
|
|
175
|
+
// In React, `alt` is only valid on <img>, <input type="image">, <area>
|
|
176
|
+
const altOnNonImgRegex = /(<(?:a|iframe|div|span|button|section)\s[^>]*?)\s+alt=(?:\{[^}]*\}|"[^"]*"|'[^']*')/g;
|
|
177
|
+
if (altOnNonImgRegex.test(result)) {
|
|
178
|
+
result = result.replace(
|
|
179
|
+
/(<(?:a|iframe|div|span|button|section)\s[^>]*?)\s+alt=(?:\{[^}]*\}|"[^"]*"|'[^']*')/g,
|
|
180
|
+
"$1",
|
|
181
|
+
);
|
|
182
|
+
changed = true;
|
|
183
|
+
notes.push("Removed invalid alt prop from non-img elements");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Remove `type` prop from non-form elements (<span>, <div>, etc.)
|
|
187
|
+
// In React, `type` is only valid on <input>, <button>, <select>, <textarea>, <script>, <style>, <link>
|
|
188
|
+
const typeOnInvalidRegex = /(<(?:span|div|p|section|header|footer|nav|main|article|aside)\s[^>]*?)\s+type=(?:\{[^}]*\}|"[^"]*"|'[^']*')/g;
|
|
189
|
+
if (typeOnInvalidRegex.test(result)) {
|
|
190
|
+
result = result.replace(
|
|
191
|
+
/(<(?:span|div|p|section|header|footer|nav|main|article|aside)\s[^>]*?)\s+type=(?:\{[^}]*\}|"[^"]*"|'[^']*')/g,
|
|
192
|
+
"$1",
|
|
193
|
+
);
|
|
194
|
+
changed = true;
|
|
195
|
+
notes.push("Removed invalid type prop from non-form elements");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// setTimeout/setInterval return type: use window.setTimeout for correct typing
|
|
199
|
+
// In Node/CF Workers, setTimeout returns Timeout object, not number
|
|
200
|
+
// window.setTimeout always returns number
|
|
201
|
+
if (/\bsetTimeout\b/.test(result) && /:\s*number/.test(result)) {
|
|
202
|
+
// Only replace bare setTimeout when it's assigned to a typed variable
|
|
203
|
+
result = result.replace(
|
|
204
|
+
/\b(?<!window\.)setTimeout\(/g,
|
|
205
|
+
"window.setTimeout(",
|
|
206
|
+
);
|
|
207
|
+
result = result.replace(
|
|
208
|
+
/\b(?<!window\.)setInterval\(/g,
|
|
209
|
+
"window.setInterval(",
|
|
210
|
+
);
|
|
211
|
+
result = result.replace(
|
|
212
|
+
/\b(?<!window\.)clearTimeout\(/g,
|
|
213
|
+
"window.clearTimeout(",
|
|
214
|
+
);
|
|
215
|
+
result = result.replace(
|
|
216
|
+
/\b(?<!window\.)clearInterval\(/g,
|
|
217
|
+
"window.clearInterval(",
|
|
218
|
+
);
|
|
219
|
+
changed = true;
|
|
220
|
+
notes.push("Prefixed setTimeout/setInterval with window. for correct typing");
|
|
221
|
+
}
|
|
222
|
+
|
|
174
223
|
// Ensure React import exists if we introduced React.* references
|
|
175
224
|
if (
|
|
176
225
|
(result.includes("React.") || result.includes("React,")) &&
|
package/src/apps/autoconfig.ts
CHANGED
|
@@ -25,7 +25,7 @@ interface AppAutoconfigurator {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const KNOWN_APPS: Record<string, AppAutoconfigurator> = {
|
|
28
|
-
"deco-resend": async (block: any) => {
|
|
28
|
+
"deco-resend": async (block: any): Promise<Record<string, InvokeAction>> => {
|
|
29
29
|
try {
|
|
30
30
|
const [resendClient, resendActions] = await Promise.all([
|
|
31
31
|
import("@decocms/apps/resend/client" as string),
|
|
@@ -40,7 +40,7 @@ const KNOWN_APPS: Record<string, AppAutoconfigurator> = {
|
|
|
40
40
|
"[autoconfig] deco-resend: no API key found." +
|
|
41
41
|
" Set DECO_CRYPTO_KEY to decrypt CMS secrets, or set RESEND_API_KEY as fallback.",
|
|
42
42
|
);
|
|
43
|
-
return {}
|
|
43
|
+
return {} as Record<string, InvokeAction>;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
configureResend({
|