fimo 0.9.0-experimental.1 → 0.9.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/assets/skills/fimo/SKILL.md +17 -54
- package/assets/skills/fimo-cli/SKILL.md +15 -15
- package/assets/skills/fimo-cli/references/agent-setup.md +36 -53
- package/assets/skills/fimo-cli/references/agents.md +4 -4
- package/assets/skills/fimo-cli/references/content.md +2 -2
- package/assets/skills/fimo-cli/references/deploy.md +1 -1
- package/assets/skills/fimo-cli/references/forms.md +1 -1
- package/assets/skills/fimo-cli/references/init.md +16 -24
- package/assets/skills/fimo-cli/references/project-vars-and-secrets.md +14 -11
- package/assets/skills/fimo-cli/references/projects.md +1 -1
- package/assets/skills/fimo-cli/references/support.md +11 -9
- package/assets/skills/fimo-cli/references/webhooks.md +3 -1
- package/assets/skills/fimo-code/SKILL.md +67 -0
- package/assets/skills/fimo-migration/SKILL.md +2 -22
- package/assets/skills/fimo-migration/references/content.md +0 -24
- package/assets/skills/fimo-migration/references/manifest.md +0 -35
- package/assets/skills/fimo-migration/references/qa.md +2 -24
- package/assets/skills/fimo-migration/references/workflow.md +1 -12
- package/assets/skills/fimo-studio/references/visual-editing.md +3 -5
- package/assets/skills/fimo-studio/references/webhooks.md +1 -1
- package/dist/build/vite/plugins/translations.d.ts +8 -2
- package/dist/build/vite/plugins/translations.js +121 -3
- package/dist/cli/bundle.json +2 -2
- package/dist/cli/index.js +311 -1209
- package/dist/runtime/app/FimoProviders.d.ts +11 -1
- package/dist/runtime/app/FimoProviders.js +23 -2
- package/dist/runtime/content/client/createContentClient.js +2 -1
- package/dist/runtime/i18n/FimoLocaleContext.js +0 -1
- package/dist/runtime/primitives/lib/useFimoLiveValue.d.ts +3 -5
- package/dist/runtime/primitives/lib/useFimoLiveValue.js +4 -11
- package/dist/runtime/primitives/translations.d.ts +27 -1
- package/dist/runtime/primitives/translations.js +85 -7
- package/dist/runtime/shared/fimo-config.server.js +0 -1
- package/dist/runtime/shared/fimo-config.types.d.ts +0 -2
- package/dist/scripts/extract-translations.js +11 -16
- package/dist/scripts/lib/validation-report.d.ts +0 -1
- package/dist/scripts/lib/validation-report.js +0 -3
- package/dist/scripts/validate-route-metadata.js +0 -11
- package/package.json +4 -84
- package/release.json +2 -2
- package/scripts/postinstall.mjs +11 -65
- package/templates/react-router/AGENTS.md +44 -0
- package/templates/react-router/CLAUDE.md +5 -0
- package/templates/react-router/package.json +1 -1
- package/assets/agents-starter/AGENTS.md +0 -66
- package/assets/skills/fimo-migration/references/wrap.md +0 -617
- package/dist/runtime/astro/index.d.ts +0 -2
- package/dist/runtime/astro/index.js +0 -3
- package/dist/runtime/astro/server.d.ts +0 -3
- package/dist/runtime/astro/server.js +0 -6
- package/dist/runtime/next/FimoNextProviders.d.ts +0 -24
- package/dist/runtime/next/FimoNextProviders.js +0 -20
- package/dist/runtime/next/FimoNextScripts.d.ts +0 -10
- package/dist/runtime/next/FimoNextScripts.js +0 -19
- package/dist/runtime/next/index.d.ts +0 -14
- package/dist/runtime/next/index.js +0 -14
- package/dist/runtime/next/server.d.ts +0 -2
- package/dist/runtime/next/server.js +0 -4
- package/dist/runtime/nuxt/composables.d.ts +0 -9
- package/dist/runtime/nuxt/composables.js +0 -57
- package/dist/runtime/nuxt/index.d.ts +0 -3
- package/dist/runtime/nuxt/index.js +0 -6
- package/dist/runtime/nuxt/server.d.ts +0 -3
- package/dist/runtime/nuxt/server.js +0 -6
- package/dist/runtime/primitives/labels-hook.d.ts +0 -33
- package/dist/runtime/primitives/labels-hook.js +0 -41
- package/dist/runtime/primitives/labels-store.d.ts +0 -12
- package/dist/runtime/primitives/labels-store.js +0 -60
- package/dist/runtime/react/FimoSpaProviders.d.ts +0 -36
- package/dist/runtime/react/FimoSpaProviders.js +0 -63
- package/dist/runtime/react/index.d.ts +0 -33
- package/dist/runtime/react/index.js +0 -39
- package/dist/runtime/remix/FimoRemixProviders.d.ts +0 -16
- package/dist/runtime/remix/FimoRemixProviders.js +0 -12
- package/dist/runtime/remix/FimoRemixScripts.d.ts +0 -10
- package/dist/runtime/remix/FimoRemixScripts.js +0 -19
- package/dist/runtime/remix/index.d.ts +0 -12
- package/dist/runtime/remix/index.js +0 -11
- package/dist/runtime/remix/server.d.ts +0 -2
- package/dist/runtime/remix/server.js +0 -2
- package/dist/runtime/shared/display-routes.d.ts +0 -14
- package/dist/runtime/shared/display-routes.js +0 -25
- package/dist/runtime/shared/i18n-messages.d.ts +0 -29
- package/dist/runtime/shared/i18n-messages.js +0 -79
- package/dist/runtime/shared/labels-fetch.pure.d.ts +0 -17
- package/dist/runtime/shared/labels-fetch.pure.js +0 -71
- package/dist/runtime/shared/labels-fetch.server.d.ts +0 -7
- package/dist/runtime/shared/labels-fetch.server.js +0 -97
- package/dist/runtime/shared/labels-t.pure.d.ts +0 -2
- package/dist/runtime/shared/labels-t.pure.js +0 -10
- package/dist/runtime/strapi/client.d.ts +0 -30
- package/dist/runtime/strapi/client.js +0 -89
- package/dist/runtime/strapi/index.d.ts +0 -5
- package/dist/runtime/strapi/index.js +0 -6
- package/dist/runtime/strapi/reshape.d.ts +0 -46
- package/dist/runtime/strapi/reshape.js +0 -114
- package/dist/runtime/svelte/index.d.ts +0 -3
- package/dist/runtime/svelte/index.js +0 -7
- package/dist/runtime/svelte/server.d.ts +0 -3
- package/dist/runtime/svelte/server.js +0 -6
- package/dist/runtime/vue/index.d.ts +0 -3
- package/dist/runtime/vue/index.js +0 -5
- package/dist/runtime/vue/server.d.ts +0 -3
- package/dist/runtime/vue/server.js +0 -6
- package/dist/runtime/wrapped/FimoFileLink.d.ts +0 -17
- package/dist/runtime/wrapped/FimoFileLink.js +0 -19
- package/dist/runtime/wrapped/FimoMediaImage.d.ts +0 -14
- package/dist/runtime/wrapped/FimoMediaImage.js +0 -20
- package/dist/runtime/wrapped/FimoMediaVideo.d.ts +0 -14
- package/dist/runtime/wrapped/FimoMediaVideo.js +0 -20
- package/dist/runtime/wrapped/FimoRichTextBlock.d.ts +0 -23
- package/dist/runtime/wrapped/FimoRichTextBlock.js +0 -22
- package/dist/runtime/wrapped/FimoText.d.ts +0 -22
- package/dist/runtime/wrapped/FimoText.js +0 -20
- package/dist/runtime/wrapped/WrappedContentContext.d.ts +0 -28
- package/dist/runtime/wrapped/WrappedContentContext.js +0 -30
- package/dist/runtime/wrapped/WrappedProviders.d.ts +0 -33
- package/dist/runtime/wrapped/WrappedProviders.js +0 -101
- package/dist/runtime/wrapped/dom.d.ts +0 -4
- package/dist/runtime/wrapped/dom.js +0 -23
- package/dist/runtime/wrapped/server.d.ts +0 -77
- package/dist/runtime/wrapped/server.js +0 -237
- package/dist/scripts/apply-wrap-templates.d.ts +0 -61
- package/dist/scripts/apply-wrap-templates.js +0 -132
- package/dist/scripts/apply-wrap.d.ts +0 -46
- package/dist/scripts/apply-wrap.js +0 -278
- package/dist/scripts/cms/discover.d.ts +0 -2
- package/dist/scripts/cms/discover.js +0 -17
- package/dist/scripts/cms/example-content.d.ts +0 -33
- package/dist/scripts/cms/example-content.js +0 -162
- package/dist/scripts/cms/strapi/detect.d.ts +0 -2
- package/dist/scripts/cms/strapi/detect.js +0 -186
- package/dist/scripts/cms/strapi/index.d.ts +0 -3
- package/dist/scripts/cms/strapi/index.js +0 -25
- package/dist/scripts/cms/strapi/list-source.d.ts +0 -17
- package/dist/scripts/cms/strapi/list-source.js +0 -90
- package/dist/scripts/cms/strapi/schema-map.d.ts +0 -9
- package/dist/scripts/cms/strapi/schema-map.js +0 -277
- package/dist/scripts/cms/strapi/shapes.d.ts +0 -23
- package/dist/scripts/cms/strapi/shapes.js +0 -38
- package/dist/scripts/cms/strapi/touchpoints.d.ts +0 -31
- package/dist/scripts/cms/strapi/touchpoints.js +0 -145
- package/dist/scripts/cms/strapi/wire-client.d.ts +0 -15
- package/dist/scripts/cms/strapi/wire-client.js +0 -107
- package/dist/scripts/cms/strapi/wiring.d.ts +0 -17
- package/dist/scripts/cms/strapi/wiring.js +0 -46
- package/dist/scripts/cms/types.d.ts +0 -145
- package/dist/scripts/cms/types.js +0 -11
- package/dist/scripts/detect-framework.d.ts +0 -39
- package/dist/scripts/detect-framework.js +0 -133
- package/dist/scripts/draft-rows.d.ts +0 -19
- package/dist/scripts/draft-rows.js +0 -135
- package/dist/scripts/draft-schemas.d.ts +0 -27
- package/dist/scripts/draft-schemas.js +0 -188
- package/dist/scripts/frameworks/astro/routes.d.ts +0 -3
- package/dist/scripts/frameworks/astro/routes.js +0 -55
- package/dist/scripts/frameworks/astro/template-style.d.ts +0 -7
- package/dist/scripts/frameworks/astro/template-style.js +0 -19
- package/dist/scripts/frameworks/discover.d.ts +0 -70
- package/dist/scripts/frameworks/discover.js +0 -178
- package/dist/scripts/frameworks/next/rewire.d.ts +0 -30
- package/dist/scripts/frameworks/next/rewire.js +0 -258
- package/dist/scripts/frameworks/next/routes.d.ts +0 -17
- package/dist/scripts/frameworks/next/routes.js +0 -134
- package/dist/scripts/frameworks/next/wiring.d.ts +0 -33
- package/dist/scripts/frameworks/next/wiring.js +0 -186
- package/dist/scripts/frameworks/nuxt/routes.d.ts +0 -3
- package/dist/scripts/frameworks/nuxt/routes.js +0 -54
- package/dist/scripts/frameworks/nuxt/template-style.d.ts +0 -9
- package/dist/scripts/frameworks/nuxt/template-style.js +0 -27
- package/dist/scripts/frameworks/remix/routes.d.ts +0 -3
- package/dist/scripts/frameworks/remix/routes.js +0 -50
- package/dist/scripts/frameworks/support.d.ts +0 -75
- package/dist/scripts/frameworks/support.js +0 -165
- package/dist/scripts/frameworks/sveltekit/routes.d.ts +0 -3
- package/dist/scripts/frameworks/sveltekit/routes.js +0 -50
- package/dist/scripts/frameworks/sveltekit/template-style.d.ts +0 -9
- package/dist/scripts/frameworks/sveltekit/template-style.js +0 -54
- package/dist/scripts/frameworks/vite/routes.d.ts +0 -15
- package/dist/scripts/frameworks/vite/routes.js +0 -93
- package/dist/scripts/frameworks/vite/wiring.d.ts +0 -10
- package/dist/scripts/frameworks/vite/wiring.js +0 -99
- package/dist/scripts/i18n-namespace.d.ts +0 -27
- package/dist/scripts/i18n-namespace.js +0 -155
- package/dist/scripts/render-parity/capture.d.ts +0 -56
- package/dist/scripts/render-parity/capture.js +0 -366
- package/dist/scripts/render-parity/compare.d.ts +0 -59
- package/dist/scripts/render-parity/compare.js +0 -0
- package/dist/scripts/render-parity/normalize-html.d.ts +0 -13
- package/dist/scripts/render-parity/normalize-html.js +0 -223
- package/dist/scripts/render-parity/types.d.ts +0 -41
- package/dist/scripts/render-parity/types.js +0 -1
- package/dist/scripts/scan-wrappable.d.ts +0 -196
- package/dist/scripts/scan-wrappable.js +0 -1232
- /package/assets/skills/{fimo → fimo-code}/assets/react-router-locales/navigation.tsx +0 -0
- /package/assets/skills/{fimo → fimo-code}/assets/react-router-locales/routes.ts +0 -0
- /package/assets/skills/{fimo → fimo-code}/references/assets.md +0 -0
- /package/assets/skills/{fimo → fimo-code}/references/content.md +0 -0
- /package/assets/skills/{fimo → fimo-code}/references/design.md +0 -0
- /package/assets/skills/{fimo → fimo-code}/references/forms.md +0 -0
- /package/assets/skills/{fimo → fimo-code}/references/labels.md +0 -0
- /package/assets/skills/{fimo → fimo-code}/references/locales.md +0 -0
- /package/assets/skills/{fimo → fimo-code}/references/setup-plain-vite.md +0 -0
- /package/assets/skills/{fimo → fimo-code}/references/setup-react-router.md +0 -0
- /package/assets/skills/{fimo → fimo-code}/references/ui.md +0 -0
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { parseSync } from 'oxc-parser';
|
|
4
|
-
import { ensureFimoImport } from '../../apply-wrap.js';
|
|
5
|
-
function applyEdits(code, edits) {
|
|
6
|
-
let next = code;
|
|
7
|
-
for (const edit of [...edits].sort((a, b) => b.start - a.start)) {
|
|
8
|
-
next = next.slice(0, edit.start) + edit.replacement + next.slice(edit.end);
|
|
9
|
-
}
|
|
10
|
-
return next;
|
|
11
|
-
}
|
|
12
|
-
function walkAll(node, visit) {
|
|
13
|
-
if (!node || typeof node !== 'object') {
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
if (Array.isArray(node)) {
|
|
17
|
-
for (const child of node) {
|
|
18
|
-
walkAll(child, visit);
|
|
19
|
-
}
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
const record = node;
|
|
23
|
-
if (typeof record.type === 'string') {
|
|
24
|
-
visit(record);
|
|
25
|
-
}
|
|
26
|
-
for (const [key, value] of Object.entries(record)) {
|
|
27
|
-
if (key !== 'type' && value && typeof value === 'object') {
|
|
28
|
-
walkAll(value, visit);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
function defaultExportFunction(program) {
|
|
33
|
-
const exportDefault = (program.body ?? []).find((node) => node.type === 'ExportDefaultDeclaration');
|
|
34
|
-
if (!exportDefault || exportDefault.declaration?.type !== 'FunctionDeclaration') {
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
const body = exportDefault.declaration.body;
|
|
38
|
-
if (!body || (body.type !== 'FunctionBody' && body.type !== 'BlockStatement')) {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
return exportDefault.declaration;
|
|
42
|
-
}
|
|
43
|
-
/** Every reference to `name` (outside `ignoredSpans`) must sit inside the
|
|
44
|
-
* component body — otherwise moving the binding into the component would
|
|
45
|
-
* break module-level consumers (metadata, generateStaticParams, …). */
|
|
46
|
-
function referencesConfinedToFunction(program, name, fn, ignoredSpans) {
|
|
47
|
-
let confined = true;
|
|
48
|
-
walkAll(program, (node) => {
|
|
49
|
-
if (node.type !== 'Identifier' || node.name !== name) {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
const inIgnored = ignoredSpans.some((span) => node.start >= span.start && node.end <= span.end);
|
|
53
|
-
const inFn = node.start >= fn.body.start && node.end <= fn.body.end;
|
|
54
|
-
if (!inIgnored && !inFn) {
|
|
55
|
-
confined = false;
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
return confined;
|
|
59
|
-
}
|
|
60
|
-
function relativeImport(fromFile, toFile) {
|
|
61
|
-
const rel = path.relative(path.dirname(fromFile), toFile).split(path.sep).join('/');
|
|
62
|
-
return rel.startsWith('.') ? rel : `./${rel}`;
|
|
63
|
-
}
|
|
64
|
-
function pageFilesUnder(dir) {
|
|
65
|
-
const out = [];
|
|
66
|
-
const walk = (current) => {
|
|
67
|
-
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
68
|
-
if (entry.name === 'node_modules' || entry.name.startsWith('.')) {
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
const full = path.join(current, entry.name);
|
|
72
|
-
if (entry.isDirectory()) {
|
|
73
|
-
walk(full);
|
|
74
|
-
}
|
|
75
|
-
else if (/\.(tsx|jsx|js)$/.test(entry.name)) {
|
|
76
|
-
out.push(full);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
if (fs.existsSync(dir)) {
|
|
81
|
-
walk(dir);
|
|
82
|
-
}
|
|
83
|
-
return out;
|
|
84
|
-
}
|
|
85
|
-
export function rewireNextCollectionPages(cwd, detection, scan, drafts) {
|
|
86
|
-
const changes = [];
|
|
87
|
-
const warnings = [];
|
|
88
|
-
let rewired = 0;
|
|
89
|
-
if (!detection.appDir) {
|
|
90
|
-
return { changes, warnings: ['Collection pages not rewired: pages-router project.'], rewired };
|
|
91
|
-
}
|
|
92
|
-
const appDirAbs = path.join(cwd, detection.appDir);
|
|
93
|
-
const uidBySourceName = new Map(drafts.map((draft) => [draft.sourceName, draft.uid]));
|
|
94
|
-
const draftsBySourceFile = new Map();
|
|
95
|
-
for (const draft of drafts) {
|
|
96
|
-
const bucket = draftsBySourceFile.get(draft.source) ?? [];
|
|
97
|
-
bucket.push(draft.uid);
|
|
98
|
-
draftsBySourceFile.set(draft.source, bucket);
|
|
99
|
-
}
|
|
100
|
-
const bail = (page, reason) => {
|
|
101
|
-
warnings.push(`Page not rewired (${page}): ${reason} — wire it with wrapCollection per the prompt recipe.`);
|
|
102
|
-
};
|
|
103
|
-
// ── In-code array literals: extract to src/data/<name>.json + wire ──
|
|
104
|
-
for (const candidate of scan.collections) {
|
|
105
|
-
const pageAbs = path.join(cwd, candidate.file);
|
|
106
|
-
if (!pageAbs.startsWith(appDirAbs + path.sep)) {
|
|
107
|
-
bail(candidate.file, `in-code collection \`${candidate.name}\` sits outside the app directory`);
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
const uid = uidBySourceName.get(candidate.name);
|
|
111
|
-
if (!uid) {
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
const rows = candidate.typedRows;
|
|
115
|
-
if (!rows || rows.length !== candidate.count) {
|
|
116
|
-
bail(candidate.file, `\`${candidate.name}\` is larger than the extraction window`);
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
const code = fs.readFileSync(pageAbs, 'utf8');
|
|
120
|
-
if (code.includes("'use client'") || code.includes('"use client"')) {
|
|
121
|
-
bail(candidate.file, 'client component (no server fetch)');
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
let program;
|
|
125
|
-
try {
|
|
126
|
-
program = parseSync(pageAbs, code).program;
|
|
127
|
-
}
|
|
128
|
-
catch {
|
|
129
|
-
bail(candidate.file, 'could not parse');
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
// `const x = [...]`, `= [...] as const`, `= [...] satisfies T` all count.
|
|
133
|
-
const isArrayInit = (init) => {
|
|
134
|
-
const inner = init?.type === 'TSAsExpression' || init?.type === 'TSSatisfiesExpression' ? init.expression : init;
|
|
135
|
-
return inner?.type === 'ArrayExpression';
|
|
136
|
-
};
|
|
137
|
-
const declaration = (program.body ?? []).find((node) => node.type === 'VariableDeclaration' &&
|
|
138
|
-
node.declarations?.length === 1 &&
|
|
139
|
-
node.declarations[0]?.id?.name === candidate.name &&
|
|
140
|
-
isArrayInit(node.declarations[0]?.init));
|
|
141
|
-
if (!declaration) {
|
|
142
|
-
bail(candidate.file, `top-level \`const ${candidate.name} = [...]\` not found`);
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
const fn = defaultExportFunction(program);
|
|
146
|
-
if (!fn) {
|
|
147
|
-
bail(candidate.file, 'the default export is not a plain function declaration');
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
if (!referencesConfinedToFunction(program, candidate.name, fn, [declaration])) {
|
|
151
|
-
bail(candidate.file, `\`${candidate.name}\` is referenced outside the page component`);
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
const dataDir = path.join(cwd, 'src', 'data');
|
|
155
|
-
const dataFileAbs = path.join(dataDir, `${candidate.name}.json`);
|
|
156
|
-
if (fs.existsSync(dataFileAbs)) {
|
|
157
|
-
bail(candidate.file, `src/data/${candidate.name}.json already exists`);
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
fs.mkdirSync(dataDir, { recursive: true });
|
|
161
|
-
fs.writeFileSync(dataFileAbs, `${JSON.stringify(rows, null, 2)}\n`);
|
|
162
|
-
const edits = [{ start: declaration.start, end: declaration.end, replacement: '' }];
|
|
163
|
-
if (!fn.async) {
|
|
164
|
-
edits.push({ start: fn.start, end: fn.start, replacement: 'async ' });
|
|
165
|
-
}
|
|
166
|
-
edits.push({
|
|
167
|
-
start: fn.body.start + 1,
|
|
168
|
-
end: fn.body.start + 1,
|
|
169
|
-
replacement: `\n const ${candidate.name} = await wrapCollection('${uid}', ${candidate.name}Source);\n`,
|
|
170
|
-
});
|
|
171
|
-
let next = applyEdits(code, edits);
|
|
172
|
-
next = `import ${candidate.name}Source from '${relativeImport(pageAbs, dataFileAbs)}';\n${next}`;
|
|
173
|
-
next = ensureFimoImport(next, ['wrapCollection'], 'fimo/next/server');
|
|
174
|
-
fs.writeFileSync(pageAbs, next);
|
|
175
|
-
rewired += 1;
|
|
176
|
-
changes.push(`rewired ${candidate.file}: \`${candidate.name}\` extracted to src/data/${candidate.name}.json and read through wrapCollection('${uid}')`);
|
|
177
|
-
}
|
|
178
|
-
// ── Data-file default imports: wire the importing pages ──
|
|
179
|
-
const dataFileUids = new Map();
|
|
180
|
-
for (const dataFile of scan.dataFiles) {
|
|
181
|
-
const uids = draftsBySourceFile.get(dataFile.file);
|
|
182
|
-
if (uids && uids.length === 1 && /\.json$/i.test(dataFile.file)) {
|
|
183
|
-
dataFileUids.set(path.resolve(cwd, dataFile.file), uids[0]);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
if (dataFileUids.size > 0) {
|
|
187
|
-
for (const pageAbs of pageFilesUnder(appDirAbs)) {
|
|
188
|
-
const code = fs.readFileSync(pageAbs, 'utf8');
|
|
189
|
-
const rel = path.relative(cwd, pageAbs);
|
|
190
|
-
if (!code.includes('src/data/')) {
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
193
|
-
if (code.includes('wrapCollection(')) {
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
196
|
-
let program;
|
|
197
|
-
try {
|
|
198
|
-
program = parseSync(pageAbs, code).program;
|
|
199
|
-
}
|
|
200
|
-
catch {
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
const imports = (program.body ?? []).filter((node) => node.type === 'ImportDeclaration' && typeof node.source?.value === 'string');
|
|
204
|
-
const targets = [];
|
|
205
|
-
for (const declaration of imports) {
|
|
206
|
-
const specifier = (declaration.specifiers ?? []).find((entry) => entry.type === 'ImportDefaultSpecifier');
|
|
207
|
-
if (!specifier || !declaration.source.value.startsWith('.')) {
|
|
208
|
-
continue;
|
|
209
|
-
}
|
|
210
|
-
const resolved = path.resolve(path.dirname(pageAbs), declaration.source.value);
|
|
211
|
-
const uid = dataFileUids.get(resolved);
|
|
212
|
-
if (uid) {
|
|
213
|
-
targets.push({ node: declaration, local: specifier.local.name, uid });
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
if (targets.length === 0) {
|
|
217
|
-
continue;
|
|
218
|
-
}
|
|
219
|
-
if (code.includes("'use client'") || code.includes('"use client"')) {
|
|
220
|
-
bail(rel, 'client component (no server fetch)');
|
|
221
|
-
continue;
|
|
222
|
-
}
|
|
223
|
-
const fn = defaultExportFunction(program);
|
|
224
|
-
if (!fn) {
|
|
225
|
-
bail(rel, 'the default export is not a plain function declaration');
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
const confined = targets.every((target) => referencesConfinedToFunction(program, target.local, fn, [target.node]));
|
|
229
|
-
if (!confined) {
|
|
230
|
-
bail(rel, 'the imported collection is referenced outside the page component');
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
const edits = [];
|
|
234
|
-
if (!fn.async) {
|
|
235
|
-
edits.push({ start: fn.start, end: fn.start, replacement: 'async ' });
|
|
236
|
-
}
|
|
237
|
-
for (const target of targets) {
|
|
238
|
-
const specifier = (target.node.specifiers ?? []).find((entry) => entry.type === 'ImportDefaultSpecifier');
|
|
239
|
-
edits.push({
|
|
240
|
-
start: specifier.local.start,
|
|
241
|
-
end: specifier.local.end,
|
|
242
|
-
replacement: `${target.local}Source`,
|
|
243
|
-
});
|
|
244
|
-
edits.push({
|
|
245
|
-
start: fn.body.start + 1,
|
|
246
|
-
end: fn.body.start + 1,
|
|
247
|
-
replacement: `\n const ${target.local} = await wrapCollection('${target.uid}', ${target.local}Source);\n`,
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
let next = applyEdits(code, edits);
|
|
251
|
-
next = ensureFimoImport(next, ['wrapCollection'], 'fimo/next/server');
|
|
252
|
-
fs.writeFileSync(pageAbs, next);
|
|
253
|
-
rewired += 1;
|
|
254
|
-
changes.push(`rewired ${rel}: ${targets.map((target) => `wrapCollection('${target.uid}')`).join(', ')} over the imported source rows`);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
return { changes, warnings, rewired };
|
|
258
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export interface DiscoveredRoute {
|
|
2
|
-
id: string;
|
|
3
|
-
path: string;
|
|
4
|
-
label: string;
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* Discover page routes of a Next.js app-router directory (`app/` or
|
|
8
|
-
* `src/app/`). Returns display routes for `fimo-config.json#routes` so the
|
|
9
|
-
* Studio page switcher can enumerate a wrapped site's pages.
|
|
10
|
-
*/
|
|
11
|
-
export declare function discoverNextRoutes(appDir: string): DiscoveredRoute[];
|
|
12
|
-
/**
|
|
13
|
-
* Pages-router discovery (`pages/` or `src/pages/`): every module is a route
|
|
14
|
-
* except `_app`/`_document`/`_error` and `api/`. `[param]` segments map to
|
|
15
|
-
* `:param` (the parity capture skips dynamic routes, same as app router).
|
|
16
|
-
*/
|
|
17
|
-
export declare function discoverNextPagesRoutes(pagesDir: string): DiscoveredRoute[];
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import { readdirSync, statSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
const PAGE_FILES = new Set(['page.tsx', 'page.jsx', 'page.ts', 'page.js', 'page.mdx']);
|
|
4
|
-
const titleCase = (seg) => seg
|
|
5
|
-
.replace(/[_\-]+/g, ' ')
|
|
6
|
-
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
7
|
-
.split(/\s+/)
|
|
8
|
-
.filter(Boolean)
|
|
9
|
-
.map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
|
|
10
|
-
.join(' ');
|
|
11
|
-
function segmentToPath(segment) {
|
|
12
|
-
// Route groups `(marketing)` don't contribute URL segments. Parallel-route
|
|
13
|
-
// slots `@modal` and private folders `_lib` are not standalone pages.
|
|
14
|
-
if (segment.startsWith('(')) {
|
|
15
|
-
return '';
|
|
16
|
-
}
|
|
17
|
-
if (segment.startsWith('@') || segment.startsWith('_')) {
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
const catchAll = segment.match(/^\[\.\.\.(.+)\]$/) ?? segment.match(/^\[\[\.\.\.(.+)\]\]$/);
|
|
21
|
-
if (catchAll) {
|
|
22
|
-
return `:${catchAll[1]}*`;
|
|
23
|
-
}
|
|
24
|
-
const param = segment.match(/^\[(.+)\]$/);
|
|
25
|
-
if (param) {
|
|
26
|
-
return `:${param[1]}`;
|
|
27
|
-
}
|
|
28
|
-
return segment;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Discover page routes of a Next.js app-router directory (`app/` or
|
|
32
|
-
* `src/app/`). Returns display routes for `fimo-config.json#routes` so the
|
|
33
|
-
* Studio page switcher can enumerate a wrapped site's pages.
|
|
34
|
-
*/
|
|
35
|
-
export function discoverNextRoutes(appDir) {
|
|
36
|
-
const routes = [];
|
|
37
|
-
const walk = (dir, segments) => {
|
|
38
|
-
let entries;
|
|
39
|
-
try {
|
|
40
|
-
entries = readdirSync(dir);
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
if (entries.some((entry) => PAGE_FILES.has(entry))) {
|
|
46
|
-
const parts = segments.filter((s) => s.length > 0);
|
|
47
|
-
const routePath = `/${parts.join('/')}`.replace(/\/+/g, '/');
|
|
48
|
-
const lastStatic = [...parts].reverse().find((p) => !p.startsWith(':'));
|
|
49
|
-
routes.push({
|
|
50
|
-
id: routePath === '/' ? 'home' : parts.map((p) => p.replace(/^:/, '')).join('-'),
|
|
51
|
-
path: routePath,
|
|
52
|
-
label: routePath === '/' ? 'Home' : titleCase(lastStatic ?? parts[parts.length - 1] ?? routePath),
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
for (const entry of entries) {
|
|
56
|
-
const full = join(dir, entry);
|
|
57
|
-
let isDir = false;
|
|
58
|
-
try {
|
|
59
|
-
isDir = statSync(full).isDirectory();
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
if (!isDir) {
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
const mapped = segmentToPath(entry);
|
|
68
|
-
if (mapped === null) {
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
walk(full, [...segments, mapped]);
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
walk(appDir, []);
|
|
75
|
-
return routes.sort((a, b) => a.path.localeCompare(b.path));
|
|
76
|
-
}
|
|
77
|
-
const PAGES_EXTENSIONS = /\.(tsx|jsx|ts|js)$/;
|
|
78
|
-
const PAGES_SKIP = new Set(['api']);
|
|
79
|
-
/**
|
|
80
|
-
* Pages-router discovery (`pages/` or `src/pages/`): every module is a route
|
|
81
|
-
* except `_app`/`_document`/`_error` and `api/`. `[param]` segments map to
|
|
82
|
-
* `:param` (the parity capture skips dynamic routes, same as app router).
|
|
83
|
-
*/
|
|
84
|
-
export function discoverNextPagesRoutes(pagesDir) {
|
|
85
|
-
const routes = [];
|
|
86
|
-
const toSegment = (name) => name
|
|
87
|
-
.replace(/^\[\.\.\.(.+)\]$/, ':$1')
|
|
88
|
-
.replace(/^\[\[\.\.\.(.+)\]\]$/, ':$1')
|
|
89
|
-
.replace(/^\[(.+)\]$/, ':$1');
|
|
90
|
-
const walk = (dir, segments) => {
|
|
91
|
-
let entries;
|
|
92
|
-
try {
|
|
93
|
-
entries = readdirSync(dir);
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
for (const entry of entries) {
|
|
99
|
-
const full = join(dir, entry);
|
|
100
|
-
let isDir = false;
|
|
101
|
-
try {
|
|
102
|
-
isDir = statSync(full).isDirectory();
|
|
103
|
-
}
|
|
104
|
-
catch {
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
if (isDir) {
|
|
108
|
-
// api/ is only special at the pages root.
|
|
109
|
-
if (!(segments.length === 0 && PAGES_SKIP.has(entry))) {
|
|
110
|
-
walk(full, [...segments, toSegment(entry)]);
|
|
111
|
-
}
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
if (!PAGES_EXTENSIONS.test(entry)) {
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
const base = entry.replace(PAGES_EXTENSIONS, '');
|
|
118
|
-
if (base.startsWith('_')) {
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
const parts = base === 'index' ? segments : [...segments, toSegment(base)];
|
|
122
|
-
const routePath = `/${parts.join('/')}`.replace(/\/+/g, '/');
|
|
123
|
-
const lastStatic = [...parts].reverse().find((part) => !part.startsWith(':'));
|
|
124
|
-
routes.push({
|
|
125
|
-
id: routePath === '/' ? 'home' : parts.map((part) => part.replace(/^:/, '')).join('-'),
|
|
126
|
-
path: routePath,
|
|
127
|
-
label: routePath === '/' ? 'Home' : titleCase(lastStatic ?? parts[parts.length - 1] ?? routePath),
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
walk(pagesDir, []);
|
|
132
|
-
routes.sort((a, b) => a.path.localeCompare(b.path));
|
|
133
|
-
return routes;
|
|
134
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type { FrameworkDetection } from '../../detect-framework.js';
|
|
2
|
-
/**
|
|
3
|
-
* Next.js-side wrap wiring: everything the wrap migration must touch that is
|
|
4
|
-
* specific to this framework lives HERE, not in the generic flow. Mirror this
|
|
5
|
-
* module when adding a framework (see the fimo-framework-adapter skill).
|
|
6
|
-
*/
|
|
7
|
-
export declare function ensureNextWiring(cwd: string): {
|
|
8
|
-
changes: string[];
|
|
9
|
-
warnings: string[];
|
|
10
|
-
};
|
|
11
|
-
export interface RootWiringResult {
|
|
12
|
-
changes: string[];
|
|
13
|
-
warnings: string[];
|
|
14
|
-
/** True when the root wiring is in place (pre-existing or just applied). */
|
|
15
|
-
wired: boolean;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Candidate app-router root-layout files: the direct `<appDir>/layout.*` plus,
|
|
19
|
-
* for i18n apps that place the `<html>` layout under a dynamic segment
|
|
20
|
-
* (next-intl's `app/[locale]/layout.tsx`), any `<appDir>/[segment]/layout.*`.
|
|
21
|
-
* Existing files only, in resolution order.
|
|
22
|
-
*/
|
|
23
|
-
export declare function nextRootLayoutFiles(cwd: string, appDir: string): string[];
|
|
24
|
-
/**
|
|
25
|
-
* Wire the app-router root layout deterministically: imports, async default
|
|
26
|
-
* export, loader calls, `<FimoNextScripts />` in head, and `{children}`
|
|
27
|
-
* wrapped in `<FimoNextProviders>`. Only the provably safe shape is touched —
|
|
28
|
-
* one default-exported function whose JSX returns `<html>…<body>` with a
|
|
29
|
-
* `{children}` expression somewhere inside body (create-next-app and its
|
|
30
|
-
* variants). Anything else bails with the reason, and the agent prompt keeps
|
|
31
|
-
* the manual recipe.
|
|
32
|
-
*/
|
|
33
|
-
export declare function ensureNextLayoutWiring(cwd: string, detection: Pick<FrameworkDetection, 'appDir'>): RootWiringResult;
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { parseSync } from 'oxc-parser';
|
|
4
|
-
import { ensureFimoImport } from '../../apply-wrap.js';
|
|
5
|
-
/**
|
|
6
|
-
* Next.js-side wrap wiring: everything the wrap migration must touch that is
|
|
7
|
-
* specific to this framework lives HERE, not in the generic flow. Mirror this
|
|
8
|
-
* module when adding a framework (see the fimo-framework-adapter skill).
|
|
9
|
-
*/
|
|
10
|
-
export function ensureNextWiring(cwd) {
|
|
11
|
-
const changes = [];
|
|
12
|
-
const warnings = [];
|
|
13
|
-
const configFile = ['next.config.ts', 'next.config.mjs', 'next.config.js', 'next.config.cjs']
|
|
14
|
-
.map((candidate) => path.join(cwd, candidate))
|
|
15
|
-
.find((candidate) => fs.existsSync(candidate));
|
|
16
|
-
if (configFile) {
|
|
17
|
-
const source = fs.readFileSync(configFile, 'utf8');
|
|
18
|
-
if (!source.includes('allowedDevOrigins')) {
|
|
19
|
-
// The three mainstream config shapes: a named object binding, a direct
|
|
20
|
-
// `export default {` literal, and CJS `module.exports = {`.
|
|
21
|
-
let patched = source.replace(/(const\s+nextConfig[^=]*=\s*\{)/, `$1\n allowedDevOrigins: ['*.preview.fimo.ai'],`);
|
|
22
|
-
if (patched === source) {
|
|
23
|
-
patched = source.replace(/((?:export\s+default|module\.exports\s*=)\s*\{)/, `$1\n allowedDevOrigins: ['*.preview.fimo.ai'],\n`);
|
|
24
|
-
}
|
|
25
|
-
if (patched !== source) {
|
|
26
|
-
fs.writeFileSync(configFile, patched);
|
|
27
|
-
changes.push(`added allowedDevOrigins to ${path.basename(configFile)}`);
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
warnings.push(`Could not patch ${path.basename(configFile)} automatically — add: allowedDevOrigins: ['*.preview.fimo.ai']`);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
warnings.push("No next.config found — create one with: allowedDevOrigins: ['*.preview.fimo.ai']");
|
|
36
|
-
}
|
|
37
|
-
return { changes, warnings };
|
|
38
|
-
}
|
|
39
|
-
function findFirst(node, predicate) {
|
|
40
|
-
if (!node || typeof node !== 'object') {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
if (predicate(node)) {
|
|
44
|
-
return node;
|
|
45
|
-
}
|
|
46
|
-
for (const value of Object.values(node)) {
|
|
47
|
-
if (Array.isArray(value)) {
|
|
48
|
-
for (const entry of value) {
|
|
49
|
-
const found = findFirst(entry, predicate);
|
|
50
|
-
if (found) {
|
|
51
|
-
return found;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
else if (value && typeof value === 'object') {
|
|
56
|
-
const found = findFirst(value, predicate);
|
|
57
|
-
if (found) {
|
|
58
|
-
return found;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
const jsxName = (element) => element?.openingElement?.name?.name;
|
|
65
|
-
/**
|
|
66
|
-
* Candidate app-router root-layout files: the direct `<appDir>/layout.*` plus,
|
|
67
|
-
* for i18n apps that place the `<html>` layout under a dynamic segment
|
|
68
|
-
* (next-intl's `app/[locale]/layout.tsx`), any `<appDir>/[segment]/layout.*`.
|
|
69
|
-
* Existing files only, in resolution order.
|
|
70
|
-
*/
|
|
71
|
-
export function nextRootLayoutFiles(cwd, appDir) {
|
|
72
|
-
const names = ['layout.tsx', 'layout.jsx', 'layout.js'];
|
|
73
|
-
const dirs = [path.join(cwd, appDir)];
|
|
74
|
-
try {
|
|
75
|
-
for (const entry of fs.readdirSync(path.join(cwd, appDir), { withFileTypes: true })) {
|
|
76
|
-
if (entry.isDirectory() && entry.name.startsWith('[') && entry.name.endsWith(']')) {
|
|
77
|
-
dirs.push(path.join(cwd, appDir, entry.name));
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
catch {
|
|
82
|
-
// appDir missing — no candidates
|
|
83
|
-
}
|
|
84
|
-
return dirs.flatMap((dir) => names.map((name) => path.join(dir, name))).filter((file) => fs.existsSync(file));
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Wire the app-router root layout deterministically: imports, async default
|
|
88
|
-
* export, loader calls, `<FimoNextScripts />` in head, and `{children}`
|
|
89
|
-
* wrapped in `<FimoNextProviders>`. Only the provably safe shape is touched —
|
|
90
|
-
* one default-exported function whose JSX returns `<html>…<body>` with a
|
|
91
|
-
* `{children}` expression somewhere inside body (create-next-app and its
|
|
92
|
-
* variants). Anything else bails with the reason, and the agent prompt keeps
|
|
93
|
-
* the manual recipe.
|
|
94
|
-
*/
|
|
95
|
-
export function ensureNextLayoutWiring(cwd, detection) {
|
|
96
|
-
if (!detection.appDir) {
|
|
97
|
-
return {
|
|
98
|
-
changes: [],
|
|
99
|
-
warnings: ['Root wiring not applied: pages-router project (no app directory) — wire pages/_app manually.'],
|
|
100
|
-
wired: false,
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
const candidates = nextRootLayoutFiles(cwd, detection.appDir);
|
|
104
|
-
// Already wired anywhere — an agent may have mounted the [locale] layout by hand.
|
|
105
|
-
if (candidates.some((file) => fs.readFileSync(file, 'utf8').includes('FimoNextProviders'))) {
|
|
106
|
-
return { changes: [], warnings: [], wired: true };
|
|
107
|
-
}
|
|
108
|
-
// The real root layout is the one that returns <html> (next-intl puts it under
|
|
109
|
-
// app/[locale]/); fall back to the first candidate for the bail message.
|
|
110
|
-
const layoutFile = candidates.find((file) => fs.readFileSync(file, 'utf8').includes('<html')) ?? candidates[0];
|
|
111
|
-
const rel = layoutFile ? path.relative(cwd, layoutFile) : `${detection.appDir}/layout.tsx`;
|
|
112
|
-
const bail = (reason) => ({
|
|
113
|
-
changes: [],
|
|
114
|
-
warnings: [`Root wiring not applied: ${reason} — follow the layout recipe in the agent prompt (${rel}).`],
|
|
115
|
-
wired: false,
|
|
116
|
-
});
|
|
117
|
-
if (!layoutFile) {
|
|
118
|
-
return bail('no root layout file found');
|
|
119
|
-
}
|
|
120
|
-
const code = fs.readFileSync(layoutFile, 'utf8');
|
|
121
|
-
let program;
|
|
122
|
-
try {
|
|
123
|
-
program = parseSync(layoutFile, code).program;
|
|
124
|
-
}
|
|
125
|
-
catch {
|
|
126
|
-
return bail('root layout could not be parsed');
|
|
127
|
-
}
|
|
128
|
-
const exportDefault = (program.body ?? []).find((node) => node.type === 'ExportDefaultDeclaration');
|
|
129
|
-
if (!exportDefault || exportDefault.declaration?.type !== 'FunctionDeclaration') {
|
|
130
|
-
return bail('the default export is not a plain function declaration');
|
|
131
|
-
}
|
|
132
|
-
const layoutFn = exportDefault.declaration;
|
|
133
|
-
const body = layoutFn.body;
|
|
134
|
-
if (!body || (body.type !== 'FunctionBody' && body.type !== 'BlockStatement')) {
|
|
135
|
-
return bail('the layout function has no block body');
|
|
136
|
-
}
|
|
137
|
-
const html = findFirst(body, (node) => node?.type === 'JSXElement' && jsxName(node) === 'html');
|
|
138
|
-
if (!html) {
|
|
139
|
-
return bail('the layout does not return an <html> element');
|
|
140
|
-
}
|
|
141
|
-
const htmlBody = (html.children ?? []).find((child) => child?.type === 'JSXElement' && jsxName(child) === 'body');
|
|
142
|
-
if (!htmlBody) {
|
|
143
|
-
return bail('no <body> element found under <html>');
|
|
144
|
-
}
|
|
145
|
-
const childrenExpr = findFirst(htmlBody, (node) => node?.type === 'JSXExpressionContainer' &&
|
|
146
|
-
node.expression?.type === 'Identifier' &&
|
|
147
|
-
node.expression?.name === 'children');
|
|
148
|
-
if (!childrenExpr) {
|
|
149
|
-
return bail('no {children} expression found inside <body>');
|
|
150
|
-
}
|
|
151
|
-
const edits = [];
|
|
152
|
-
if (!layoutFn.async) {
|
|
153
|
-
edits.push({ start: layoutFn.start, end: layoutFn.start, replacement: 'async ' });
|
|
154
|
-
}
|
|
155
|
-
edits.push({
|
|
156
|
-
start: body.start + 1,
|
|
157
|
-
end: body.start + 1,
|
|
158
|
-
replacement: '\n const config = loadFimoConfig();\n const labels = await loadFimoLabels();\n',
|
|
159
|
-
});
|
|
160
|
-
edits.push({
|
|
161
|
-
start: childrenExpr.start,
|
|
162
|
-
end: childrenExpr.end,
|
|
163
|
-
replacement: `<FimoNextProviders config={config} labels={labels}>${code.slice(childrenExpr.start, childrenExpr.end)}</FimoNextProviders>`,
|
|
164
|
-
});
|
|
165
|
-
const head = (html.children ?? []).find((child) => child?.type === 'JSXElement' && jsxName(child) === 'head');
|
|
166
|
-
if (head) {
|
|
167
|
-
const at = head.openingElement.end;
|
|
168
|
-
edits.push({ start: at, end: at, replacement: '<FimoNextScripts />' });
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
const at = html.openingElement.end;
|
|
172
|
-
edits.push({
|
|
173
|
-
start: at,
|
|
174
|
-
end: at,
|
|
175
|
-
replacement: '\n <head>\n <FimoNextScripts />\n </head>',
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
let next = code;
|
|
179
|
-
for (const edit of edits.sort((a, b) => b.start - a.start)) {
|
|
180
|
-
next = next.slice(0, edit.start) + edit.replacement + next.slice(edit.end);
|
|
181
|
-
}
|
|
182
|
-
next = ensureFimoImport(next, ['FimoNextProviders', 'FimoNextScripts'], 'fimo/next');
|
|
183
|
-
next = ensureFimoImport(next, ['loadFimoConfig', 'loadFimoLabels'], 'fimo/next/server');
|
|
184
|
-
fs.writeFileSync(layoutFile, next);
|
|
185
|
-
return { changes: [`wired Fimo providers into ${rel}`], warnings: [], wired: true };
|
|
186
|
-
}
|