fimo 0.8.0 → 0.9.0-experimental.10
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/agent-templates/content-translator/scripts/write-locale-files.ts +66 -0
- package/assets/agents-starter/claude/settings.json +19 -0
- package/assets/content-templates/hooks-template.eta +4 -2
- package/assets/content-templates/types-template.eta +67 -1
- package/assets/skills/fimo/SKILL.md +1 -1
- package/assets/skills/fimo/references/content.md +86 -15
- package/assets/skills/fimo/references/translations.md +75 -0
- package/assets/skills/fimo-cli/SKILL.md +6 -2
- package/assets/skills/fimo-cli/references/content.md +61 -2
- package/assets/skills/fimo-cli/references/integrations/firecrawl.md +20 -4
- package/assets/skills/fimo-cli/references/integrations.md +5 -1
- package/assets/skills/fimo-cli/references/webhooks.md +162 -0
- package/assets/skills/fimo-migration/SKILL.md +24 -2
- package/assets/skills/fimo-migration/references/content.md +24 -0
- package/assets/skills/fimo-migration/references/manifest.md +46 -0
- package/assets/skills/fimo-migration/references/pages.md +234 -0
- package/assets/skills/fimo-migration/references/qa.md +24 -2
- package/assets/skills/fimo-migration/references/workflow.md +12 -1
- package/assets/skills/fimo-migration/references/wrap.md +662 -0
- package/assets/skills/fimo-studio/SKILL.md +5 -3
- package/assets/skills/fimo-studio/references/cms.md +9 -1
- package/assets/skills/fimo-studio/references/visual-editing.md +5 -3
- package/assets/skills/fimo-studio/references/webhooks.md +42 -0
- package/dist/build/vite/plugins/translations.d.ts +2 -8
- package/dist/build/vite/plugins/translations.js +31 -131
- package/dist/cli/bundle.json +1 -1
- package/dist/cli/index.js +1337 -189
- package/dist/runtime/app/FimoProviders.d.ts +1 -11
- package/dist/runtime/app/FimoProviders.js +31 -30
- package/dist/runtime/astro/index.d.ts +2 -0
- package/dist/runtime/astro/index.js +3 -0
- package/dist/runtime/astro/server.d.ts +3 -0
- package/dist/runtime/astro/server.js +6 -0
- package/dist/runtime/content/client/internal.js +1 -0
- package/dist/runtime/content/client/types.d.ts +16 -0
- package/dist/runtime/core/BuiltWithBadge.d.ts +2 -0
- package/dist/runtime/core/BuiltWithBadge.js +68 -0
- package/dist/runtime/i18n/FimoLocaleContext.js +1 -0
- package/dist/runtime/next/FimoNextProviders.d.ts +24 -0
- package/dist/runtime/next/FimoNextProviders.js +20 -0
- package/dist/runtime/next/FimoNextScripts.d.ts +10 -0
- package/dist/runtime/next/FimoNextScripts.js +19 -0
- package/dist/runtime/next/index.d.ts +14 -0
- package/dist/runtime/next/index.js +14 -0
- package/dist/runtime/next/server.d.ts +2 -0
- package/dist/runtime/next/server.js +4 -0
- package/dist/runtime/nuxt/composables.d.ts +9 -0
- package/dist/runtime/nuxt/composables.js +57 -0
- package/dist/runtime/nuxt/index.d.ts +3 -0
- package/dist/runtime/nuxt/index.js +6 -0
- package/dist/runtime/nuxt/server.d.ts +3 -0
- package/dist/runtime/nuxt/server.js +6 -0
- package/dist/runtime/primitives/components/RichText.d.ts +5 -1
- package/dist/runtime/primitives/components/RichText.js +2 -2
- package/dist/runtime/primitives/labels-hook.d.ts +33 -0
- package/dist/runtime/primitives/labels-hook.js +41 -0
- package/dist/runtime/primitives/labels-store.d.ts +12 -0
- package/dist/runtime/primitives/labels-store.js +60 -0
- package/dist/runtime/primitives/lib/useFimoLiveValue.d.ts +5 -3
- package/dist/runtime/primitives/lib/useFimoLiveValue.js +11 -4
- package/dist/runtime/primitives/translations.d.ts +1 -27
- package/dist/runtime/primitives/translations.js +7 -85
- package/dist/runtime/react/FimoSpaProviders.d.ts +36 -0
- package/dist/runtime/react/FimoSpaProviders.js +122 -0
- package/dist/runtime/react/index.d.ts +35 -0
- package/dist/runtime/react/index.js +40 -0
- package/dist/runtime/react/spa-content-cache.d.ts +38 -0
- package/dist/runtime/react/spa-content-cache.js +64 -0
- package/dist/runtime/remix/FimoRemixProviders.d.ts +16 -0
- package/dist/runtime/remix/FimoRemixProviders.js +12 -0
- package/dist/runtime/remix/FimoRemixScripts.d.ts +10 -0
- package/dist/runtime/remix/FimoRemixScripts.js +19 -0
- package/dist/runtime/remix/index.d.ts +12 -0
- package/dist/runtime/remix/index.js +11 -0
- package/dist/runtime/remix/server.d.ts +2 -0
- package/dist/runtime/remix/server.js +2 -0
- package/dist/runtime/seo/neutral.d.ts +22 -0
- package/dist/runtime/seo/neutral.js +17 -0
- package/dist/runtime/shared/display-routes.d.ts +14 -0
- package/dist/runtime/shared/display-routes.js +25 -0
- package/dist/runtime/shared/fimo-config.server.d.ts +0 -1
- package/dist/runtime/shared/fimo-config.server.js +1 -13
- package/dist/runtime/shared/fimo-config.types.d.ts +2 -0
- package/dist/runtime/shared/i18n-messages.d.ts +29 -0
- package/dist/runtime/shared/i18n-messages.js +79 -0
- package/dist/runtime/shared/labels-fetch.pure.d.ts +17 -0
- package/dist/runtime/shared/labels-fetch.pure.js +71 -0
- package/dist/runtime/shared/labels-fetch.server.d.ts +7 -0
- package/dist/runtime/shared/labels-fetch.server.js +97 -0
- package/dist/runtime/shared/labels-t.pure.d.ts +2 -0
- package/dist/runtime/shared/labels-t.pure.js +10 -0
- package/dist/runtime/strapi/client.d.ts +30 -0
- package/dist/runtime/strapi/client.js +132 -0
- package/dist/runtime/strapi/index.d.ts +5 -0
- package/dist/runtime/strapi/index.js +6 -0
- package/dist/runtime/strapi/reshape.d.ts +50 -0
- package/dist/runtime/strapi/reshape.js +121 -0
- package/dist/runtime/svelte/index.d.ts +3 -0
- package/dist/runtime/svelte/index.js +7 -0
- package/dist/runtime/svelte/server.d.ts +3 -0
- package/dist/runtime/svelte/server.js +6 -0
- package/dist/runtime/vue/index.d.ts +3 -0
- package/dist/runtime/vue/index.js +5 -0
- package/dist/runtime/vue/server.d.ts +3 -0
- package/dist/runtime/vue/server.js +6 -0
- package/dist/runtime/wrapped/FimoFileLink.d.ts +17 -0
- package/dist/runtime/wrapped/FimoFileLink.js +19 -0
- package/dist/runtime/wrapped/FimoMediaImage.d.ts +14 -0
- package/dist/runtime/wrapped/FimoMediaImage.js +20 -0
- package/dist/runtime/wrapped/FimoMediaVideo.d.ts +14 -0
- package/dist/runtime/wrapped/FimoMediaVideo.js +20 -0
- package/dist/runtime/wrapped/FimoRichTextBlock.d.ts +26 -0
- package/dist/runtime/wrapped/FimoRichTextBlock.js +22 -0
- package/dist/runtime/wrapped/FimoText.d.ts +22 -0
- package/dist/runtime/wrapped/FimoText.js +20 -0
- package/dist/runtime/wrapped/WrappedContentContext.d.ts +28 -0
- package/dist/runtime/wrapped/WrappedContentContext.js +30 -0
- package/dist/runtime/wrapped/WrappedProviders.d.ts +33 -0
- package/dist/runtime/wrapped/WrappedProviders.js +101 -0
- package/dist/runtime/wrapped/content-fetch.pure.d.ts +20 -0
- package/dist/runtime/wrapped/content-fetch.pure.js +106 -0
- package/dist/runtime/wrapped/dom.d.ts +4 -0
- package/dist/runtime/wrapped/dom.js +23 -0
- package/dist/runtime/wrapped/server.d.ts +80 -0
- package/dist/runtime/wrapped/server.js +207 -0
- package/dist/scripts/apply-wrap-templates.d.ts +61 -0
- package/dist/scripts/apply-wrap-templates.js +137 -0
- package/dist/scripts/apply-wrap.d.ts +73 -0
- package/dist/scripts/apply-wrap.js +340 -0
- package/dist/scripts/cms/discover.d.ts +2 -0
- package/dist/scripts/cms/discover.js +17 -0
- package/dist/scripts/cms/example-content.d.ts +33 -0
- package/dist/scripts/cms/example-content.js +165 -0
- package/dist/scripts/cms/strapi/detect.d.ts +2 -0
- package/dist/scripts/cms/strapi/detect.js +186 -0
- package/dist/scripts/cms/strapi/index.d.ts +3 -0
- package/dist/scripts/cms/strapi/index.js +25 -0
- package/dist/scripts/cms/strapi/list-source.d.ts +17 -0
- package/dist/scripts/cms/strapi/list-source.js +90 -0
- package/dist/scripts/cms/strapi/schema-map.d.ts +9 -0
- package/dist/scripts/cms/strapi/schema-map.js +289 -0
- package/dist/scripts/cms/strapi/shapes.d.ts +25 -0
- package/dist/scripts/cms/strapi/shapes.js +48 -0
- package/dist/scripts/cms/strapi/touchpoints.d.ts +31 -0
- package/dist/scripts/cms/strapi/touchpoints.js +145 -0
- package/dist/scripts/cms/strapi/wire-client.d.ts +15 -0
- package/dist/scripts/cms/strapi/wire-client.js +107 -0
- package/dist/scripts/cms/strapi/wiring.d.ts +17 -0
- package/dist/scripts/cms/strapi/wiring.js +46 -0
- package/dist/scripts/cms/types.d.ts +145 -0
- package/dist/scripts/cms/types.js +11 -0
- package/dist/scripts/detect-framework.d.ts +39 -0
- package/dist/scripts/detect-framework.js +139 -0
- package/dist/scripts/draft-rows.d.ts +19 -0
- package/dist/scripts/draft-rows.js +178 -0
- package/dist/scripts/draft-schemas.d.ts +27 -0
- package/dist/scripts/draft-schemas.js +247 -0
- package/dist/scripts/extract-translations.js +16 -11
- package/dist/scripts/frameworks/astro/routes.d.ts +3 -0
- package/dist/scripts/frameworks/astro/routes.js +44 -0
- package/dist/scripts/frameworks/astro/template-style.d.ts +7 -0
- package/dist/scripts/frameworks/astro/template-style.js +19 -0
- package/dist/scripts/frameworks/discover.d.ts +83 -0
- package/dist/scripts/frameworks/discover.js +288 -0
- package/dist/scripts/frameworks/discovered-route.d.ts +9 -0
- package/dist/scripts/frameworks/discovered-route.js +17 -0
- package/dist/scripts/frameworks/next/rewire.d.ts +43 -0
- package/dist/scripts/frameworks/next/rewire.js +282 -0
- package/dist/scripts/frameworks/next/routes.d.ts +30 -0
- package/dist/scripts/frameworks/next/routes.js +176 -0
- package/dist/scripts/frameworks/next/wiring.d.ts +33 -0
- package/dist/scripts/frameworks/next/wiring.js +186 -0
- package/dist/scripts/frameworks/nuxt/routes.d.ts +3 -0
- package/dist/scripts/frameworks/nuxt/routes.js +38 -0
- package/dist/scripts/frameworks/nuxt/template-style.d.ts +9 -0
- package/dist/scripts/frameworks/nuxt/template-style.js +27 -0
- package/dist/scripts/frameworks/remix/routes.d.ts +3 -0
- package/dist/scripts/frameworks/remix/routes.js +36 -0
- package/dist/scripts/frameworks/support.d.ts +75 -0
- package/dist/scripts/frameworks/support.js +165 -0
- package/dist/scripts/frameworks/sveltekit/routes.d.ts +3 -0
- package/dist/scripts/frameworks/sveltekit/routes.js +35 -0
- package/dist/scripts/frameworks/sveltekit/template-style.d.ts +9 -0
- package/dist/scripts/frameworks/sveltekit/template-style.js +54 -0
- package/dist/scripts/frameworks/vite/routes.d.ts +20 -0
- package/dist/scripts/frameworks/vite/routes.js +143 -0
- package/dist/scripts/frameworks/vite/wiring.d.ts +22 -0
- package/dist/scripts/frameworks/vite/wiring.js +144 -0
- package/dist/scripts/i18n-namespace.d.ts +27 -0
- package/dist/scripts/i18n-namespace.js +155 -0
- package/dist/scripts/lib/humanize.d.ts +14 -0
- package/dist/scripts/lib/humanize.js +54 -0
- package/dist/scripts/lib/validation-report.d.ts +1 -3
- package/dist/scripts/lib/validation-report.js +3 -9
- package/dist/scripts/render-parity/capture.d.ts +64 -0
- package/dist/scripts/render-parity/capture.js +436 -0
- package/dist/scripts/render-parity/compare.d.ts +59 -0
- package/dist/scripts/render-parity/compare.js +0 -0
- package/dist/scripts/render-parity/normalize-html.d.ts +13 -0
- package/dist/scripts/render-parity/normalize-html.js +223 -0
- package/dist/scripts/render-parity/types.d.ts +41 -0
- package/dist/scripts/render-parity/types.js +1 -0
- package/dist/scripts/routes/bindings.d.ts +93 -0
- package/dist/scripts/routes/bindings.js +117 -0
- package/dist/scripts/routes/label-prefixes.d.ts +49 -0
- package/dist/scripts/routes/label-prefixes.js +136 -0
- package/dist/scripts/routes/module-resolve.d.ts +20 -0
- package/dist/scripts/routes/module-resolve.js +78 -0
- package/dist/scripts/routes/page-structure.d.ts +72 -0
- package/dist/scripts/routes/page-structure.js +234 -0
- package/dist/scripts/scan-wrappable.d.ts +200 -0
- package/dist/scripts/scan-wrappable.js +1355 -0
- package/dist/scripts/validate-route-metadata.js +11 -0
- package/dist/scripts/validate-schemas.js +42 -1
- package/package.json +88 -4
- package/release.json +2 -2
- package/templates/react-router/package.json +1 -1
- package/templates/react-router/translations/en.json +1 -0
|
@@ -0,0 +1,186 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { pushRoute, toSegment } from '../discovered-route.js';
|
|
4
|
+
/** Nuxt file-based routes under pages/ (index.vue maps to the dir path). */
|
|
5
|
+
export function discoverNuxtRoutes(pagesDir) {
|
|
6
|
+
const routes = [];
|
|
7
|
+
const walk = (dir, segments) => {
|
|
8
|
+
let entries;
|
|
9
|
+
try {
|
|
10
|
+
entries = readdirSync(dir);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
const full = join(dir, entry);
|
|
17
|
+
let isDir = false;
|
|
18
|
+
try {
|
|
19
|
+
isDir = statSync(full).isDirectory();
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (isDir) {
|
|
25
|
+
walk(full, [...segments, toSegment(entry)]);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (!entry.endsWith('.vue')) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const base = entry.replace(/\.vue$/, '');
|
|
32
|
+
pushRoute(routes, base === 'index' ? segments : [...segments, toSegment(base)], full);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
walk(pagesDir, []);
|
|
36
|
+
routes.sort((a, b) => a.path.localeCompare(b.path));
|
|
37
|
+
return routes;
|
|
38
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TemplateWrapStyle } from '../../apply-wrap-templates.js';
|
|
2
|
+
/**
|
|
3
|
+
* Nuxt template wraps: the useFimoT composable is client-safe (fimo/nuxt
|
|
4
|
+
* never pulls server loaders into the bundle) and transfers the dictionary
|
|
5
|
+
* through the Nuxt payload, so a single `<script setup>` line wires a file.
|
|
6
|
+
* Options-API scripts have no safe injection point (top-level await needs
|
|
7
|
+
* setup) — those files stay agent work.
|
|
8
|
+
*/
|
|
9
|
+
export declare const nuxtTemplateWrapStyle: TemplateWrapStyle;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nuxt template wraps: the useFimoT composable is client-safe (fimo/nuxt
|
|
3
|
+
* never pulls server loaders into the bundle) and transfers the dictionary
|
|
4
|
+
* through the Nuxt payload, so a single `<script setup>` line wires a file.
|
|
5
|
+
* Options-API scripts have no safe injection point (top-level await needs
|
|
6
|
+
* setup) — those files stay agent work.
|
|
7
|
+
*/
|
|
8
|
+
export const nuxtTemplateWrapStyle = {
|
|
9
|
+
ext: '.vue',
|
|
10
|
+
head: "import { useFimoT } from 'fimo/nuxt'\nconst t = await useFimoT()",
|
|
11
|
+
headMarker: /useFimoT/,
|
|
12
|
+
open: '{{ t(',
|
|
13
|
+
close: ') }}',
|
|
14
|
+
injectHead: (code, head) => {
|
|
15
|
+
// An SFC may carry BOTH <script> and <script setup>: target the setup
|
|
16
|
+
// block specifically (top-level await is only legal there).
|
|
17
|
+
const setup = code.match(/<script[^>]*\bsetup\b[^>]*>\r?\n?/);
|
|
18
|
+
if (setup && setup.index !== undefined) {
|
|
19
|
+
const at = setup.index + setup[0].length;
|
|
20
|
+
return `${code.slice(0, at)}${head}\n${code.slice(at)}`;
|
|
21
|
+
}
|
|
22
|
+
if (/<script\b/.test(code)) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
return `<script setup>\n${head}\n</script>\n\n${code}`;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { readdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { pushRoute } from '../discovered-route.js';
|
|
4
|
+
const FILE_EXT = /\.(tsx|jsx|ts|js|mdx)$/;
|
|
5
|
+
/** Remix v2 flat routes under app/routes: dots are slashes, $param is dynamic. */
|
|
6
|
+
export function discoverRemixRoutes(routesDir) {
|
|
7
|
+
const routes = [];
|
|
8
|
+
let entries;
|
|
9
|
+
try {
|
|
10
|
+
entries = readdirSync(routesDir);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return routes;
|
|
14
|
+
}
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
if (!FILE_EXT.test(entry)) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const base = entry.replace(FILE_EXT, '');
|
|
20
|
+
if (base === '_index') {
|
|
21
|
+
pushRoute(routes, [], join(routesDir, entry));
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
// Pathless layout segments start with _ ; trailing ._index maps to the parent.
|
|
25
|
+
const parts = base
|
|
26
|
+
.replace(/\._index$/, '')
|
|
27
|
+
.split('.')
|
|
28
|
+
.filter((seg) => !seg.startsWith('_'))
|
|
29
|
+
.map((seg) => (seg.startsWith('$') ? `:${seg.slice(1) || 'splat'}` : seg));
|
|
30
|
+
if (parts.length > 0) {
|
|
31
|
+
pushRoute(routes, parts, join(routesDir, entry));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
routes.sort((a, b) => a.path.localeCompare(b.path));
|
|
35
|
+
return routes;
|
|
36
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { FrameworkDetection } from '../detect-framework.js';
|
|
2
|
+
/**
|
|
3
|
+
* A host dependency the wrap gates on, with the floor Fimo's runtime requires.
|
|
4
|
+
* The floor is honest to the API surface the in-tree runtime actually uses —
|
|
5
|
+
* React frameworks gate on `react` 18 (the runtime touches `useSyncExternalStore`),
|
|
6
|
+
* Vue/Svelte/Astro on their own framework floor. The version gate is what turns
|
|
7
|
+
* an unsupported host into one actionable sentence instead of an npm `ERESOLVE`
|
|
8
|
+
* or an install-then-crash.
|
|
9
|
+
*/
|
|
10
|
+
export interface SupportCheck {
|
|
11
|
+
pkg: string;
|
|
12
|
+
/** Floor, `X.Y.Z`. */
|
|
13
|
+
min: string;
|
|
14
|
+
label: string;
|
|
15
|
+
/** Why this floor exists — shown to the user. */
|
|
16
|
+
reason: string;
|
|
17
|
+
/** One actionable next step — shown as the error hint. */
|
|
18
|
+
remedy: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The runtime-floor checks for a detected framework. React-based frameworks all
|
|
22
|
+
* gate on `react`; the template frameworks gate on their own package. Frameworks
|
|
23
|
+
* with no runtime coupling (or none detected) return [] — nothing to gate.
|
|
24
|
+
*/
|
|
25
|
+
export declare function supportChecksFor(detection: Pick<FrameworkDetection, 'framework' | 'react'>): SupportCheck[];
|
|
26
|
+
interface ParsedVersion {
|
|
27
|
+
major: number;
|
|
28
|
+
minor: number;
|
|
29
|
+
patch: number;
|
|
30
|
+
/** The original spec, for display. */
|
|
31
|
+
raw: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The lowest concrete version a spec implies. Handles the shapes seen in real
|
|
35
|
+
* package.json / installed manifests: `^18.2.0`, `~3.4`, `>=2.0.0`, `18 || 19`,
|
|
36
|
+
* `18.3.1`, ` >= 14.0.0 `, bare `18`. Returns null for anything without a
|
|
37
|
+
* numeric floor (`latest`, `*`, `workspace:*`, `catalog:`, a git/file url) — we
|
|
38
|
+
* never gate on a version we cannot read.
|
|
39
|
+
*/
|
|
40
|
+
export declare function parseVersionFloor(spec: string): ParsedVersion | null;
|
|
41
|
+
/**
|
|
42
|
+
* The host's version of `pkg`. Prefers the INSTALLED manifest
|
|
43
|
+
* (`node_modules/<pkg>/package.json`) — the ground truth of what will run —
|
|
44
|
+
* and falls back to the declared range floor in the host package.json (a fresh
|
|
45
|
+
* clone before install). Returns null when neither is readable.
|
|
46
|
+
*/
|
|
47
|
+
export declare function readHostVersion(cwd: string, pkg: string): ParsedVersion | null;
|
|
48
|
+
export interface HostSupportViolation {
|
|
49
|
+
subject: string;
|
|
50
|
+
/** The version we found on the host. */
|
|
51
|
+
found: string;
|
|
52
|
+
/** The floor Fimo requires. */
|
|
53
|
+
min: string;
|
|
54
|
+
/** Headline sentence for the error message. */
|
|
55
|
+
headline: string;
|
|
56
|
+
/** Actionable next step for the error hint. */
|
|
57
|
+
remedy: string;
|
|
58
|
+
}
|
|
59
|
+
export interface HostSupportResult {
|
|
60
|
+
ok: boolean;
|
|
61
|
+
violations: HostSupportViolation[];
|
|
62
|
+
/** Versions read successfully — for the summary line ("React 18.3.1"). */
|
|
63
|
+
detected: Array<{
|
|
64
|
+
label: string;
|
|
65
|
+
version: string;
|
|
66
|
+
}>;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Gate the wrap on the host's framework/runtime versions. Silent for supported
|
|
70
|
+
* hosts (the common case); returns one violation per below-floor dependency so
|
|
71
|
+
* the caller can stop with a clear message. A version we cannot read is never a
|
|
72
|
+
* violation — the peer range and the install still catch a genuinely absent dep.
|
|
73
|
+
*/
|
|
74
|
+
export declare function checkHostSupport(cwd: string, detection: Pick<FrameworkDetection, 'framework' | 'react'>): HostSupportResult;
|
|
75
|
+
export {};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
const REACT_CHECK = {
|
|
4
|
+
pkg: 'react',
|
|
5
|
+
min: '18.0.0',
|
|
6
|
+
label: 'React',
|
|
7
|
+
reason: "Fimo's inline-editing runtime uses React 18 APIs (useSyncExternalStore).",
|
|
8
|
+
remedy: 'Upgrade React to 18 or newer. The content still migrates on older React; only inline editing needs 18+.',
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* The runtime-floor checks for a detected framework. React-based frameworks all
|
|
12
|
+
* gate on `react`; the template frameworks gate on their own package. Frameworks
|
|
13
|
+
* with no runtime coupling (or none detected) return [] — nothing to gate.
|
|
14
|
+
*/
|
|
15
|
+
export function supportChecksFor(detection) {
|
|
16
|
+
switch (detection.framework) {
|
|
17
|
+
case 'next':
|
|
18
|
+
case 'remix':
|
|
19
|
+
case 'react-router':
|
|
20
|
+
return [REACT_CHECK];
|
|
21
|
+
case 'vite':
|
|
22
|
+
return detection.react ? [REACT_CHECK] : [];
|
|
23
|
+
case 'nuxt':
|
|
24
|
+
return [
|
|
25
|
+
{
|
|
26
|
+
pkg: 'nuxt',
|
|
27
|
+
min: '3.0.0',
|
|
28
|
+
label: 'Nuxt',
|
|
29
|
+
reason: 'Fimo supports Nuxt 3+ (Vue 3). Nuxt 2 is end-of-life.',
|
|
30
|
+
remedy: 'Upgrade to Nuxt 3 or newer.',
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
case 'vue':
|
|
34
|
+
return [
|
|
35
|
+
{
|
|
36
|
+
pkg: 'vue',
|
|
37
|
+
min: '3.0.0',
|
|
38
|
+
label: 'Vue',
|
|
39
|
+
reason: 'Fimo supports Vue 3+. Vue 2 is end-of-life.',
|
|
40
|
+
remedy: 'Upgrade to Vue 3 or newer.',
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
case 'astro':
|
|
44
|
+
return [
|
|
45
|
+
{
|
|
46
|
+
pkg: 'astro',
|
|
47
|
+
min: '4.0.0',
|
|
48
|
+
label: 'Astro',
|
|
49
|
+
reason: 'Fimo supports Astro 4+.',
|
|
50
|
+
remedy: 'Upgrade Astro to 4 or newer.',
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
case 'sveltekit':
|
|
54
|
+
return [
|
|
55
|
+
{
|
|
56
|
+
pkg: '@sveltejs/kit',
|
|
57
|
+
min: '2.0.0',
|
|
58
|
+
label: 'SvelteKit',
|
|
59
|
+
reason: 'Fimo supports SvelteKit 2+.',
|
|
60
|
+
remedy: 'Upgrade @sveltejs/kit to 2 or newer.',
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
default:
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The lowest concrete version a spec implies. Handles the shapes seen in real
|
|
69
|
+
* package.json / installed manifests: `^18.2.0`, `~3.4`, `>=2.0.0`, `18 || 19`,
|
|
70
|
+
* `18.3.1`, ` >= 14.0.0 `, bare `18`. Returns null for anything without a
|
|
71
|
+
* numeric floor (`latest`, `*`, `workspace:*`, `catalog:`, a git/file url) — we
|
|
72
|
+
* never gate on a version we cannot read.
|
|
73
|
+
*/
|
|
74
|
+
export function parseVersionFloor(spec) {
|
|
75
|
+
if (!spec) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
// A `||` union — the floor is the lowest branch, i.e. the first token.
|
|
79
|
+
const first = spec.split('||')[0];
|
|
80
|
+
const match = first.match(/(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
|
|
81
|
+
if (!match) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
major: Number(match[1]),
|
|
86
|
+
minor: match[2] ? Number(match[2]) : 0,
|
|
87
|
+
patch: match[3] ? Number(match[3]) : 0,
|
|
88
|
+
raw: spec.trim(),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function isBelowFloor(found, min) {
|
|
92
|
+
const floor = parseVersionFloor(min);
|
|
93
|
+
if (!floor) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
if (found.major !== floor.major) {
|
|
97
|
+
return found.major < floor.major;
|
|
98
|
+
}
|
|
99
|
+
if (found.minor !== floor.minor) {
|
|
100
|
+
return found.minor < floor.minor;
|
|
101
|
+
}
|
|
102
|
+
return found.patch < floor.patch;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The host's version of `pkg`. Prefers the INSTALLED manifest
|
|
106
|
+
* (`node_modules/<pkg>/package.json`) — the ground truth of what will run —
|
|
107
|
+
* and falls back to the declared range floor in the host package.json (a fresh
|
|
108
|
+
* clone before install). Returns null when neither is readable.
|
|
109
|
+
*/
|
|
110
|
+
export function readHostVersion(cwd, pkg) {
|
|
111
|
+
const installed = join(cwd, 'node_modules', pkg, 'package.json');
|
|
112
|
+
if (existsSync(installed)) {
|
|
113
|
+
try {
|
|
114
|
+
const version = JSON.parse(readFileSync(installed, 'utf8')).version;
|
|
115
|
+
const parsed = parseVersionFloor(String(version));
|
|
116
|
+
if (parsed) {
|
|
117
|
+
return parsed;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// fall through to the declared range
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const rootPkg = join(cwd, 'package.json');
|
|
125
|
+
if (existsSync(rootPkg)) {
|
|
126
|
+
try {
|
|
127
|
+
const pkgJson = JSON.parse(readFileSync(rootPkg, 'utf8'));
|
|
128
|
+
const spec = { ...pkgJson.dependencies, ...pkgJson.devDependencies }[pkg];
|
|
129
|
+
if (spec) {
|
|
130
|
+
return parseVersionFloor(String(spec));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// unreadable — nothing to gate on
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Gate the wrap on the host's framework/runtime versions. Silent for supported
|
|
141
|
+
* hosts (the common case); returns one violation per below-floor dependency so
|
|
142
|
+
* the caller can stop with a clear message. A version we cannot read is never a
|
|
143
|
+
* violation — the peer range and the install still catch a genuinely absent dep.
|
|
144
|
+
*/
|
|
145
|
+
export function checkHostSupport(cwd, detection) {
|
|
146
|
+
const violations = [];
|
|
147
|
+
const detected = [];
|
|
148
|
+
for (const check of supportChecksFor(detection)) {
|
|
149
|
+
const found = readHostVersion(cwd, check.pkg);
|
|
150
|
+
if (!found) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
detected.push({ label: check.label, version: found.raw });
|
|
154
|
+
if (isBelowFloor(found, check.min)) {
|
|
155
|
+
violations.push({
|
|
156
|
+
subject: check.label,
|
|
157
|
+
found: found.raw,
|
|
158
|
+
min: check.min,
|
|
159
|
+
headline: `${check.label} ${found.raw} is below Fimo's floor of ${check.label} ${check.min}. ${check.reason}`,
|
|
160
|
+
remedy: check.remedy,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return { ok: violations.length === 0, violations, detected };
|
|
165
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { pushRoute, toSegment } from '../discovered-route.js';
|
|
4
|
+
/** SvelteKit routes: every directory under src/routes holding a +page file. */
|
|
5
|
+
export function discoverSvelteKitRoutes(routesDir) {
|
|
6
|
+
const routes = [];
|
|
7
|
+
const walk = (dir, segments) => {
|
|
8
|
+
let entries;
|
|
9
|
+
try {
|
|
10
|
+
entries = readdirSync(dir);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const pageFile = entries.find((entry) => /^\+page\.(svelte|md)$/.test(entry));
|
|
16
|
+
if (pageFile) {
|
|
17
|
+
// Route groups (parens) are layout-only segments.
|
|
18
|
+
pushRoute(routes, segments.filter((seg) => !seg.startsWith('(')), join(dir, pageFile));
|
|
19
|
+
}
|
|
20
|
+
for (const entry of entries) {
|
|
21
|
+
const full = join(dir, entry);
|
|
22
|
+
try {
|
|
23
|
+
if (statSync(full).isDirectory()) {
|
|
24
|
+
walk(full, [...segments, toSegment(entry)]);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
walk(routesDir, []);
|
|
33
|
+
routes.sort((a, b) => a.path.localeCompare(b.path));
|
|
34
|
+
return routes;
|
|
35
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type TemplateWrapStyle } from '../../apply-wrap-templates.js';
|
|
2
|
+
/**
|
|
3
|
+
* SvelteKit template wraps. The t binding is deliberately a mode-agnostic
|
|
4
|
+
* `const` — a `$derived` flips a legacy component into runes mode (breaking
|
|
5
|
+
* its own `$:` statements) and a `$:` reactive runs after init (arrays built
|
|
6
|
+
* at init see undefined). Labels only change with a reload in edit mode,
|
|
7
|
+
* which the wrap contract accepts.
|
|
8
|
+
*/
|
|
9
|
+
export declare const svelteTemplateWrapStyle: TemplateWrapStyle;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { existsSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { basename, dirname, join } from 'node:path';
|
|
3
|
+
import { injectIntoInstanceScript } from '../../apply-wrap-templates.js';
|
|
4
|
+
import { slugify } from '../../apply-wrap.js';
|
|
5
|
+
/**
|
|
6
|
+
* SvelteKit template wraps. The t binding is deliberately a mode-agnostic
|
|
7
|
+
* `const` — a `$derived` flips a legacy component into runes mode (breaking
|
|
8
|
+
* its own `$:` statements) and a `$:` reactive runs after init (arrays built
|
|
9
|
+
* at init see undefined). Labels only change with a reload in edit mode,
|
|
10
|
+
* which the wrap contract accepts.
|
|
11
|
+
*/
|
|
12
|
+
export const svelteTemplateWrapStyle = {
|
|
13
|
+
ext: '.svelte',
|
|
14
|
+
head: "import { page } from '$app/stores';\n\timport { makeFimoT } from 'fimo/svelte';\n\tconst t = makeFimoT($page.data.fimoLabels);",
|
|
15
|
+
headMarker: /makeFimoT/,
|
|
16
|
+
open: '{t(',
|
|
17
|
+
close: ')}',
|
|
18
|
+
injectHead: (code, head) => injectIntoInstanceScript(code, head, '<script>'),
|
|
19
|
+
// Route-convention filenames carry no meaning: scope +page/+layout keys by
|
|
20
|
+
// their route directory (src/routes/pricing/+page.svelte -> "pricing").
|
|
21
|
+
fileScope: (file) => {
|
|
22
|
+
const stem = basename(file).replace(/\.svelte$/, '');
|
|
23
|
+
if (!stem.startsWith('+')) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const dir = basename(dirname(file));
|
|
27
|
+
if (!dir || dir === 'routes' || dir === 'src') {
|
|
28
|
+
return stem === '+layout' ? 'layout' : 'home';
|
|
29
|
+
}
|
|
30
|
+
return slugify(dir) || 'home';
|
|
31
|
+
},
|
|
32
|
+
// The store form reads $page.data.fimoLabels: wire the root server load
|
|
33
|
+
// once when the project has none. An existing +layout.server.* is agent
|
|
34
|
+
// work (merging into a user load function is judgment, and until then the
|
|
35
|
+
// inline fallbacks keep rendering identical).
|
|
36
|
+
ensureWiring: (rootDir) => {
|
|
37
|
+
const routesDir = join(rootDir, 'src', 'routes');
|
|
38
|
+
const existing = ['+layout.server.js', '+layout.server.ts'].some((name) => existsSync(join(routesDir, name)));
|
|
39
|
+
if (existing || !existsSync(routesDir)) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
const layoutPath = join(routesDir, '+layout.server.js');
|
|
43
|
+
writeFileSync(layoutPath, [
|
|
44
|
+
"import { loadFimoLabels } from 'fimo/svelte/server';",
|
|
45
|
+
'',
|
|
46
|
+
'export const load = async () => {',
|
|
47
|
+
'\tconst labels = await loadFimoLabels();',
|
|
48
|
+
'\treturn { fimoLabels: labels.dictionaries[labels.defaultLocale] ?? {} };',
|
|
49
|
+
'};',
|
|
50
|
+
'',
|
|
51
|
+
].join('\n'));
|
|
52
|
+
return ['src/routes/+layout.server.js'];
|
|
53
|
+
},
|
|
54
|
+
};
|