fimo 0.17.0 → 0.18.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/astro/components/DateTime.astro +22 -0
- package/assets/astro/components/index.d.ts +1 -0
- package/assets/astro/components/index.js +1 -0
- package/assets/content-templates/shared/types.eta +2 -0
- package/assets/skills/fimo-cli/SKILL.md +3 -2
- package/assets/skills/fimo-cli/references/content.md +2 -2
- package/assets/skills/fimo-cli/references/deploy.md +2 -0
- package/assets/skills/fimo-cli/references/init.md +11 -4
- package/assets/skills/fimo-cli/references/locales.md +38 -14
- package/assets/skills/fimo-cli/references/pages.md +70 -0
- package/assets/skills/fimo-cli/references/webhooks.md +1 -1
- package/assets/skills/fimo-code/SKILL.md +11 -0
- package/assets/skills/fimo-code/assets/react-router-locales/LocaleLayout.tsx +12 -0
- package/assets/skills/fimo-code/assets/react-router-locales/routes.ts +7 -6
- package/assets/skills/fimo-code/references/best-practices/astro.md +47 -0
- package/assets/skills/fimo-code/references/best-practices/next.md +42 -0
- package/assets/skills/fimo-code/references/best-practices/sveltekit.md +17 -0
- package/assets/skills/fimo-code/references/content/richtext.md +5 -0
- package/assets/skills/fimo-code/references/content/schema.md +4 -1
- package/assets/skills/fimo-code/references/frameworks/astro.md +5 -3
- package/assets/skills/fimo-code/references/frameworks/hosting.md +5 -2
- package/assets/skills/fimo-code/references/frameworks/next/app-router.md +6 -0
- package/assets/skills/fimo-code/references/frameworks/next/locales.md +12 -2
- package/assets/skills/fimo-code/references/frameworks/next.md +7 -0
- package/assets/skills/fimo-code/references/frameworks/react-router.md +15 -7
- package/assets/skills/fimo-code/references/frameworks/sveltekit.md +15 -8
- package/assets/skills/fimo-code/references/labels.md +9 -1
- package/assets/skills/fimo-code/references/locales.md +8 -2
- package/assets/skills/fimo-code/references/migrations/react-router-route-ownership.md +93 -0
- package/assets/skills/fimo-code/references/ui.md +21 -2
- package/assets/skills/fimo-migration/SKILL.md +276 -51
- package/assets/skills/fimo-migration/references/content.md +119 -35
- package/assets/skills/fimo-migration/references/decision.md +72 -0
- package/assets/skills/fimo-migration/references/forms.md +38 -6
- package/assets/skills/fimo-migration/references/frameworks/nextjs.md +179 -23
- package/assets/skills/fimo-migration/references/import-contract.md +71 -19
- package/assets/skills/fimo-migration/references/manifest.md +11 -161
- package/assets/skills/fimo-migration/references/media.md +33 -1
- package/assets/skills/fimo-migration/references/platforms.md +26 -2
- package/assets/skills/fimo-migration/references/qa.md +167 -13
- package/assets/skills/fimo-migration/references/workers/content-execution.md +39 -0
- package/assets/skills/fimo-migration/references/workers/content-planner.md +64 -0
- package/assets/skills/fimo-migration/references/workers/framework.md +37 -0
- package/assets/skills/fimo-migration/references/workers/validator.md +25 -0
- package/assets/skills/fimo-migration/references/workers.md +82 -0
- package/assets/skills/fimo-migration/references/workflow.md +140 -192
- package/assets/skills/fimo-migration/scripts/apply-decision.mjs +534 -0
- package/assets/skills/fimo-migration/scripts/build-canonical-manifest.mjs +7 -4
- package/assets/skills/fimo-migration/scripts/write-migration-report.mjs +191 -4
- package/assets/skills/fimo-studio/references/cms.md +25 -7
- package/assets/skills/fimo-studio/references/webhooks.md +1 -1
- package/assets/sveltekit/components/DateTime.svelte +35 -0
- package/assets/sveltekit/components/FimoProvider.svelte +2 -2
- package/assets/sveltekit/components/index.d.ts +1 -0
- package/assets/sveltekit/components/index.js +1 -0
- package/dist/build/next/config.d.ts +4 -1
- package/dist/build/next/config.js +21 -5
- package/dist/build/sveltekit/vite.d.ts +3 -4
- package/dist/build/sveltekit/vite.js +3 -4
- package/dist/build/vite/plugins/runtime-env.d.ts +2 -1
- package/dist/build/vite/plugins/runtime-env.js +14 -10
- package/dist/cli/bundle.json +2 -2
- package/dist/cli/index.js +76 -78
- package/dist/runtime/app/FimoProviders.js +25 -14
- package/dist/runtime/app/prefetch.d.ts +3 -1
- package/dist/runtime/app/prefetch.js +11 -55
- package/dist/runtime/astro/preview.js +7 -9
- package/dist/runtime/content/client/internal.js +1 -0
- package/dist/runtime/i18n/locale.d.ts +4 -0
- package/dist/runtime/i18n/locale.js +14 -14
- package/dist/runtime/next/client-runtime.js +5 -1
- package/dist/runtime/next/client.d.ts +3 -2
- package/dist/runtime/next/client.js +2 -1
- package/dist/runtime/next/labels.d.ts +2 -2
- package/dist/runtime/next/labels.js +2 -2
- package/dist/runtime/next/preview.d.ts +13 -0
- package/dist/runtime/next/preview.js +34 -0
- package/dist/runtime/next/primitives/server.d.ts +2 -0
- package/dist/runtime/next/primitives/server.js +8 -0
- package/dist/runtime/next/provider.d.ts +5 -0
- package/dist/runtime/next/provider.js +17 -3
- package/dist/runtime/next/server-runtime.js +4 -1
- package/dist/runtime/next/server.d.ts +1 -1
- package/dist/runtime/next/server.js +1 -1
- package/dist/runtime/paths/get-fimo-paths.d.ts +6 -5
- package/dist/runtime/paths/get-fimo-paths.js +43 -42
- package/dist/runtime/preview/location.d.ts +19 -2
- package/dist/runtime/preview/location.js +16 -2
- package/dist/runtime/primitives/index.d.ts +1 -0
- package/dist/runtime/primitives/index.js +1 -0
- package/dist/runtime/react/primitives/editable/DateTime.d.ts +4 -0
- package/dist/runtime/react/primitives/editable/DateTime.js +13 -0
- package/dist/runtime/react/primitives/renderers/DateRenderer.d.ts +7 -1
- package/dist/runtime/react/primitives/renderers/DateRenderer.js +11 -8
- package/dist/runtime/react-router/{routes.d.ts → legacy/routes.d.ts} +6 -1
- package/dist/runtime/react-router/{routes.js → legacy/routes.js} +22 -7
- package/dist/runtime/routes/contract.d.ts +116 -0
- package/dist/runtime/routes/contract.js +377 -0
- package/dist/runtime/routes/index.d.ts +3 -0
- package/dist/runtime/routes/index.js +3 -0
- package/dist/runtime/routing/AppRoutesProvider.js +4 -3
- package/dist/runtime/seo/resolver.d.ts +5 -0
- package/dist/runtime/seo/resolver.js +10 -42
- package/dist/runtime/shared/fimo-config.types.d.ts +34 -8
- package/dist/runtime/shared/preview-location.d.ts +1 -22
- package/dist/runtime/shared/preview-location.js +5 -93
- package/dist/runtime/sveltekit/preview.d.ts +12 -3
- package/dist/runtime/sveltekit/preview.js +13 -8
- package/dist/scripts/sync-forms.js +1 -1
- package/dist/scripts/sync-schemas.js +1 -4
- package/dist/scripts/validate-fimo-config.js +5 -1
- package/dist/scripts/validate-page-registry.d.ts +2 -0
- package/dist/scripts/validate-page-registry.js +167 -0
- package/dist/scripts/validate-route-metadata.js +132 -41
- package/package.json +7 -3
- package/templates/astro/.fimo/config.json +13 -5
- package/templates/astro/AGENTS.md +5 -1
- package/templates/astro/astro.config.mjs +7 -0
- package/templates/astro/pnpm-workspace.yaml +0 -3
- package/templates/astro/src/components/ProjectOverview.astro +9 -6
- package/templates/astro/src/layouts/Base.astro +1 -1
- package/templates/astro/src/locales.ts +4 -0
- package/templates/astro/src/pages/[locale]/index.astro +19 -0
- package/templates/astro/src/pages/[locale]/projects/[slug].astro +35 -0
- package/templates/astro/src/pages/[locale]/projects/index.astro +19 -0
- package/templates/astro/src/pages/index.astro +5 -2
- package/templates/astro/src/pages/projects/[slug].astro +4 -2
- package/templates/astro/src/pages/projects/index.astro +5 -2
- package/templates/nextjs/.fimo/config.json +2 -1
- package/templates/nextjs/AGENTS.md +5 -0
- package/templates/nextjs/src/app/(default)/layout.tsx +10 -0
- package/templates/nextjs/src/app/[locale]/layout.tsx +22 -0
- package/templates/nextjs/src/app/[locale]/page.tsx +1 -0
- package/templates/nextjs/src/components/Document.tsx +26 -0
- package/templates/nextjs/src/components/FimoPreviewBridge.tsx +13 -0
- package/templates/nextjs/src/locales.ts +13 -0
- package/templates/react-router/.fimo/config.json +3 -6
- package/templates/react-router/AGENTS.md +4 -0
- package/templates/react-router/package.json +1 -1
- package/templates/react-router/pnpm-workspace.yaml +1 -3
- package/templates/react-router/react-router.config.ts +4 -10
- package/templates/react-router/src/pages/LocaleLayout.tsx +12 -0
- package/templates/react-router/src/root.tsx +2 -4
- package/templates/react-router/src/routes.ts +5 -3
- package/templates/sveltekit/.fimo/config.json +2 -1
- package/templates/sveltekit/AGENTS.md +5 -0
- package/templates/sveltekit/src/app.html +1 -1
- package/templates/sveltekit/src/hooks.server.ts +11 -0
- package/templates/sveltekit/src/locales.ts +8 -0
- package/templates/sveltekit/src/params/locale.ts +5 -0
- package/templates/sveltekit/src/routes/+layout.server.ts +6 -1
- package/templates/sveltekit/src/routes/+layout.svelte +5 -0
- package/assets/skills/fimo-migration/references/inventory/coordinator.md +0 -40
- package/assets/skills/fimo-migration/references/inventory/locales/next-intl.md +0 -39
- package/assets/skills/fimo-migration/references/inventory/locales.md +0 -41
- package/assets/skills/fimo-migration/scripts/inventory/locales/next-intl.mjs +0 -459
- package/templates/nextjs/src/app/layout.tsx +0 -17
- /package/templates/nextjs/src/app/{page.tsx → (default)/page.tsx} +0 -0
- /package/templates/sveltekit/src/routes/{+page.svelte → [[locale=locale]]/+page.svelte} +0 -0
|
@@ -1,459 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync } from 'node:fs';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import process from 'node:process';
|
|
6
|
-
|
|
7
|
-
const IGNORED_DIRECTORIES = new Set([
|
|
8
|
-
'.git',
|
|
9
|
-
'.next',
|
|
10
|
-
'.output',
|
|
11
|
-
'.turbo',
|
|
12
|
-
'build',
|
|
13
|
-
'coverage',
|
|
14
|
-
'dist',
|
|
15
|
-
'node_modules',
|
|
16
|
-
'public',
|
|
17
|
-
]);
|
|
18
|
-
const SOURCE_EXTENSIONS = new Set(['.js', '.jsx', '.mjs', '.mts', '.ts', '.tsx']);
|
|
19
|
-
const MAX_SOURCE_FILES = 5_000;
|
|
20
|
-
const MAX_SOURCE_BYTES = 1_000_000;
|
|
21
|
-
const MAX_DICTIONARY_FILES = 10_000;
|
|
22
|
-
const CONVENTIONAL_PATTERN = 'messages/{locale}.json';
|
|
23
|
-
|
|
24
|
-
function fail(message) {
|
|
25
|
-
process.stderr.write(`${message}\n`);
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function parseArguments(argv) {
|
|
30
|
-
if (argv.length === 0) {
|
|
31
|
-
fail('Usage: next-intl.mjs <source-root> [--messages-pattern <relative-pattern>]');
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const sourceRoot = argv[0];
|
|
35
|
-
let messagesPattern;
|
|
36
|
-
|
|
37
|
-
for (let index = 1; index < argv.length; index += 1) {
|
|
38
|
-
if (argv[index] !== '--messages-pattern' || messagesPattern !== undefined || !argv[index + 1]) {
|
|
39
|
-
fail('Usage: next-intl.mjs <source-root> [--messages-pattern <relative-pattern>]');
|
|
40
|
-
}
|
|
41
|
-
messagesPattern = argv[index + 1];
|
|
42
|
-
index += 1;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return { sourceRoot, messagesPattern };
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function toPosix(value) {
|
|
49
|
-
return value.split(path.sep).join('/');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function lineOf(source, offset) {
|
|
53
|
-
return source.slice(0, offset).split('\n').length;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function validatePattern(patternValue, root) {
|
|
57
|
-
if (path.isAbsolute(patternValue)) {
|
|
58
|
-
fail('The messages pattern must be relative to the source root.');
|
|
59
|
-
}
|
|
60
|
-
if ((patternValue.match(/\{locale\}/g) ?? []).length !== 1) {
|
|
61
|
-
fail('The messages pattern must contain {locale} exactly once.');
|
|
62
|
-
}
|
|
63
|
-
if (!patternValue.endsWith('.json')) {
|
|
64
|
-
fail('The messages pattern must point to JSON files.');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const normalized = path.normalize(patternValue);
|
|
68
|
-
if ((normalized.match(/\{locale\}/g) ?? []).length !== 1) {
|
|
69
|
-
fail('The normalized messages pattern must retain {locale} exactly once.');
|
|
70
|
-
}
|
|
71
|
-
const resolved = path.resolve(root, normalized.replace('{locale}', '__locale__'));
|
|
72
|
-
if (resolved !== root && !resolved.startsWith(`${root}${path.sep}`)) {
|
|
73
|
-
fail('The messages pattern must stay inside the source root.');
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return toPosix(normalized);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function listSourceFiles(root) {
|
|
80
|
-
const files = [];
|
|
81
|
-
let truncated = false;
|
|
82
|
-
|
|
83
|
-
function visit(directory) {
|
|
84
|
-
for (const entry of readdirSync(directory, { withFileTypes: true }).sort((left, right) =>
|
|
85
|
-
left.name.localeCompare(right.name)
|
|
86
|
-
)) {
|
|
87
|
-
if (files.length >= MAX_SOURCE_FILES) {
|
|
88
|
-
truncated = true;
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
const absolute = path.join(directory, entry.name);
|
|
92
|
-
if (entry.isSymbolicLink()) {
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
if (entry.isDirectory()) {
|
|
96
|
-
if (!IGNORED_DIRECTORIES.has(entry.name)) {
|
|
97
|
-
visit(absolute);
|
|
98
|
-
}
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
if (
|
|
102
|
-
entry.isFile() &&
|
|
103
|
-
SOURCE_EXTENSIONS.has(path.extname(entry.name)) &&
|
|
104
|
-
lstatSync(absolute).size <= MAX_SOURCE_BYTES
|
|
105
|
-
) {
|
|
106
|
-
files.push(absolute);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
visit(root);
|
|
112
|
-
return { files, truncated };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function readPackageEvidence(root) {
|
|
116
|
-
const packagePath = path.join(root, 'package.json');
|
|
117
|
-
if (!existsSync(packagePath)) {
|
|
118
|
-
return { declared: false, evidence: [] };
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
let manifest;
|
|
122
|
-
try {
|
|
123
|
-
manifest = JSON.parse(readFileSync(packagePath, 'utf8'));
|
|
124
|
-
} catch {
|
|
125
|
-
return { declared: false, evidence: [] };
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const sections = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
|
|
129
|
-
for (const section of sections) {
|
|
130
|
-
if (typeof manifest[section]?.['next-intl'] === 'string') {
|
|
131
|
-
const source = readFileSync(packagePath, 'utf8');
|
|
132
|
-
const offset = source.indexOf('"next-intl"');
|
|
133
|
-
return {
|
|
134
|
-
declared: true,
|
|
135
|
-
evidence: [
|
|
136
|
-
{
|
|
137
|
-
claim: 'next-intl.declared',
|
|
138
|
-
path: 'package.json',
|
|
139
|
-
line: lineOf(source, Math.max(offset, 0)),
|
|
140
|
-
value: manifest[section]['next-intl'],
|
|
141
|
-
},
|
|
142
|
-
],
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return { declared: false, evidence: [] };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function deriveMessagePatterns(files, root) {
|
|
151
|
-
const findings = [];
|
|
152
|
-
const importPattern = /import\s*\(\s*`([^`]*\$\{[^}]+\}[^`]*\.json)`\s*\)/g;
|
|
153
|
-
|
|
154
|
-
for (const absolute of files) {
|
|
155
|
-
const source = readFileSync(absolute, 'utf8');
|
|
156
|
-
for (const match of source.matchAll(importPattern)) {
|
|
157
|
-
const template = match[1];
|
|
158
|
-
if ((template.match(/\$\{[^}]+\}/g) ?? []).length !== 1) {
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
const relativeTemplate = template.replace(/\$\{[^}]+\}/, '{locale}');
|
|
162
|
-
const resolvedTemplate = path.resolve(path.dirname(absolute), relativeTemplate.replace('{locale}', '__locale__'));
|
|
163
|
-
if (resolvedTemplate !== root && !resolvedTemplate.startsWith(`${root}${path.sep}`)) {
|
|
164
|
-
continue;
|
|
165
|
-
}
|
|
166
|
-
const rootRelative = toPosix(path.relative(root, resolvedTemplate)).replace('__locale__', '{locale}');
|
|
167
|
-
findings.push({
|
|
168
|
-
pattern: rootRelative,
|
|
169
|
-
source: 'request-config',
|
|
170
|
-
path: toPosix(path.relative(root, absolute)),
|
|
171
|
-
line: lineOf(source, match.index ?? 0),
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return findings;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function parseQuotedValues(value) {
|
|
180
|
-
return [...value.matchAll(/(['"])(.*?)\1/g)].map((match) => match[2]);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function readStaticBindings(source) {
|
|
184
|
-
const arrays = new Map();
|
|
185
|
-
const strings = new Map();
|
|
186
|
-
|
|
187
|
-
for (const match of source.matchAll(/(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*\[([^\]]*)\](?:\s+as\s+const)?/g)) {
|
|
188
|
-
const values = parseQuotedValues(match[2]);
|
|
189
|
-
if (values.length > 0) {
|
|
190
|
-
arrays.set(match[1], values);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
for (const match of source.matchAll(/(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(['"])(.*?)\2/g)) {
|
|
194
|
-
strings.set(match[1], match[3]);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return { arrays, strings };
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function readRoutingCandidates(files, root) {
|
|
201
|
-
const localeCandidates = [];
|
|
202
|
-
const defaultLocaleCandidates = [];
|
|
203
|
-
|
|
204
|
-
for (const absolute of files) {
|
|
205
|
-
const source = readFileSync(absolute, 'utf8');
|
|
206
|
-
if (!source.includes('defineRouting')) {
|
|
207
|
-
continue;
|
|
208
|
-
}
|
|
209
|
-
const bindings = readStaticBindings(source);
|
|
210
|
-
|
|
211
|
-
for (const match of source.matchAll(/\blocales\s*:\s*(\[[^\]]*\]|[A-Za-z_$][\w$]*)/g)) {
|
|
212
|
-
const expression = match[1];
|
|
213
|
-
const locales = expression.startsWith('[') ? parseQuotedValues(expression) : bindings.arrays.get(expression);
|
|
214
|
-
if (locales?.length) {
|
|
215
|
-
localeCandidates.push({
|
|
216
|
-
locales: [...new Set(locales)].sort(),
|
|
217
|
-
path: toPosix(path.relative(root, absolute)),
|
|
218
|
-
line: lineOf(source, match.index ?? 0),
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
for (const match of source.matchAll(/\bdefaultLocale\s*:\s*(?:(['"])(.*?)\1|([A-Za-z_$][\w$]*))/g)) {
|
|
224
|
-
const locale = match[2] ?? bindings.strings.get(match[3]);
|
|
225
|
-
if (locale) {
|
|
226
|
-
defaultLocaleCandidates.push({
|
|
227
|
-
locale,
|
|
228
|
-
path: toPosix(path.relative(root, absolute)),
|
|
229
|
-
line: lineOf(source, match.index ?? 0),
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
return { localeCandidates, defaultLocaleCandidates };
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function uniqueBy(items, keyOf) {
|
|
239
|
-
const seen = new Set();
|
|
240
|
-
return items.filter((item) => {
|
|
241
|
-
const key = keyOf(item);
|
|
242
|
-
if (seen.has(key)) {
|
|
243
|
-
return false;
|
|
244
|
-
}
|
|
245
|
-
seen.add(key);
|
|
246
|
-
return true;
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function selectConsistent(items, valueOf) {
|
|
251
|
-
const values = uniqueBy(items.map(valueOf), (value) => JSON.stringify(value));
|
|
252
|
-
return values.length === 1 ? values[0] : null;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function inspectDictionaries(root, patterns) {
|
|
256
|
-
const dictionaries = [];
|
|
257
|
-
let truncated = false;
|
|
258
|
-
|
|
259
|
-
for (const patternEntry of patterns) {
|
|
260
|
-
const escaped = patternEntry.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace('\\{locale\\}', '([^/]+)');
|
|
261
|
-
const matcher = new RegExp(`^${escaped}$`);
|
|
262
|
-
const prefix = patternEntry.pattern.slice(0, patternEntry.pattern.indexOf('{locale}'));
|
|
263
|
-
const scanRootRelative = prefix.includes('/') ? prefix.slice(0, prefix.lastIndexOf('/')) : '';
|
|
264
|
-
const scanRoot = path.resolve(root, scanRootRelative || '.');
|
|
265
|
-
if (!existsSync(scanRoot) || !lstatSync(scanRoot).isDirectory()) {
|
|
266
|
-
continue;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const candidates = [];
|
|
270
|
-
function visit(directory) {
|
|
271
|
-
for (const entry of readdirSync(directory, { withFileTypes: true }).sort((left, right) =>
|
|
272
|
-
left.name.localeCompare(right.name)
|
|
273
|
-
)) {
|
|
274
|
-
if (candidates.length >= MAX_DICTIONARY_FILES) {
|
|
275
|
-
truncated = true;
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
const absolute = path.join(directory, entry.name);
|
|
279
|
-
if (entry.isSymbolicLink()) {
|
|
280
|
-
continue;
|
|
281
|
-
}
|
|
282
|
-
if (entry.isDirectory()) {
|
|
283
|
-
if (!IGNORED_DIRECTORIES.has(entry.name)) {
|
|
284
|
-
visit(absolute);
|
|
285
|
-
}
|
|
286
|
-
} else if (entry.isFile() && entry.name.endsWith('.json')) {
|
|
287
|
-
candidates.push(absolute);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
visit(scanRoot);
|
|
292
|
-
|
|
293
|
-
for (const absolute of candidates) {
|
|
294
|
-
const relative = toPosix(path.relative(root, absolute));
|
|
295
|
-
const match = relative.match(matcher);
|
|
296
|
-
if (match) {
|
|
297
|
-
dictionaries.push({ locale: match[1], path: relative, pattern: patternEntry.pattern });
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
return {
|
|
303
|
-
dictionaries: uniqueBy(dictionaries, (item) => `${item.locale}\0${item.path}\0${item.pattern}`).sort(
|
|
304
|
-
(left, right) => left.locale.localeCompare(right.locale) || left.path.localeCompare(right.path)
|
|
305
|
-
),
|
|
306
|
-
truncated,
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
const { sourceRoot: sourceRootArgument, messagesPattern: suppliedPattern } = parseArguments(process.argv.slice(2));
|
|
311
|
-
const sourceRoot = path.resolve(sourceRootArgument);
|
|
312
|
-
if (!existsSync(sourceRoot) || !lstatSync(sourceRoot).isDirectory()) {
|
|
313
|
-
fail(`Source root is not a directory: ${sourceRootArgument}`);
|
|
314
|
-
}
|
|
315
|
-
const canonicalRoot = realpathSync(sourceRoot);
|
|
316
|
-
|
|
317
|
-
const sourceInspection = listSourceFiles(canonicalRoot);
|
|
318
|
-
const sourceFiles = sourceInspection.files;
|
|
319
|
-
const packageFinding = readPackageEvidence(canonicalRoot);
|
|
320
|
-
const derivedPatterns = deriveMessagePatterns(sourceFiles, canonicalRoot);
|
|
321
|
-
const patterns = [];
|
|
322
|
-
|
|
323
|
-
if (suppliedPattern) {
|
|
324
|
-
patterns.push({ pattern: validatePattern(suppliedPattern, canonicalRoot), source: 'agent-supplied' });
|
|
325
|
-
} else {
|
|
326
|
-
for (const finding of derivedPatterns) {
|
|
327
|
-
patterns.push({ pattern: validatePattern(finding.pattern, canonicalRoot), source: finding.source });
|
|
328
|
-
}
|
|
329
|
-
if (existsSync(path.join(canonicalRoot, 'messages'))) {
|
|
330
|
-
patterns.push({ pattern: CONVENTIONAL_PATTERN, source: 'conventional' });
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
const uniquePatterns = uniqueBy(patterns, (item) => item.pattern).sort((left, right) =>
|
|
335
|
-
left.pattern.localeCompare(right.pattern)
|
|
336
|
-
);
|
|
337
|
-
const dictionaryInspection = inspectDictionaries(canonicalRoot, uniquePatterns);
|
|
338
|
-
const dictionaries = dictionaryInspection.dictionaries;
|
|
339
|
-
const routing = readRoutingCandidates(sourceFiles, canonicalRoot);
|
|
340
|
-
const configuredLocales = selectConsistent(routing.localeCandidates, (item) => item.locales);
|
|
341
|
-
const defaultLocale = selectConsistent(routing.defaultLocaleCandidates, (item) => item.locale);
|
|
342
|
-
const dictionaryLocales = [...new Set(dictionaries.map((item) => item.locale))].sort();
|
|
343
|
-
const unresolved = [];
|
|
344
|
-
|
|
345
|
-
if (sourceInspection.truncated) {
|
|
346
|
-
unresolved.push({ code: 'source-scan-truncated', claim: `Source scanning stopped after ${MAX_SOURCE_FILES} files.` });
|
|
347
|
-
}
|
|
348
|
-
if (!packageFinding.declared) {
|
|
349
|
-
unresolved.push({
|
|
350
|
-
code: 'next-intl-not-declared',
|
|
351
|
-
claim: 'Whether this project uses next-intl is not proven by package.json.',
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
if (uniquePatterns.length === 0) {
|
|
355
|
-
unresolved.push({
|
|
356
|
-
code: 'messages-pattern-not-found',
|
|
357
|
-
claim: 'The location of next-intl message dictionaries is not proven.',
|
|
358
|
-
});
|
|
359
|
-
} else if (dictionaries.length === 0) {
|
|
360
|
-
unresolved.push({
|
|
361
|
-
code: 'message-dictionaries-not-found',
|
|
362
|
-
claim: 'No JSON message dictionaries matched the selected pattern.',
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
if (dictionaryInspection.truncated) {
|
|
366
|
-
unresolved.push({
|
|
367
|
-
code: 'dictionary-scan-truncated',
|
|
368
|
-
claim: `Dictionary scanning stopped after ${MAX_DICTIONARY_FILES} JSON files.`,
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
if (routing.localeCandidates.length === 0) {
|
|
372
|
-
unresolved.push({
|
|
373
|
-
code: 'configured-locales-not-found',
|
|
374
|
-
claim: 'The statically configured next-intl locales are not proven.',
|
|
375
|
-
});
|
|
376
|
-
} else if (configuredLocales === null) {
|
|
377
|
-
unresolved.push({
|
|
378
|
-
code: 'configured-locales-conflict',
|
|
379
|
-
claim: 'Static routing files declare different locale lists.',
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
|
-
if (routing.defaultLocaleCandidates.length === 0) {
|
|
383
|
-
unresolved.push({
|
|
384
|
-
code: 'default-locale-not-found',
|
|
385
|
-
claim: 'The statically configured next-intl default locale is not proven.',
|
|
386
|
-
});
|
|
387
|
-
} else if (defaultLocale === null) {
|
|
388
|
-
unresolved.push({
|
|
389
|
-
code: 'default-locale-conflict',
|
|
390
|
-
claim: 'Static routing files declare different default locales.',
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
if (configuredLocales) {
|
|
394
|
-
for (const locale of configuredLocales.filter((value) => !dictionaryLocales.includes(value))) {
|
|
395
|
-
unresolved.push({
|
|
396
|
-
code: 'configured-locale-without-dictionary',
|
|
397
|
-
claim: `Configured locale ${locale} has no matching message dictionary.`,
|
|
398
|
-
locale,
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
for (const locale of dictionaryLocales.filter((value) => !configuredLocales.includes(value))) {
|
|
402
|
-
unresolved.push({
|
|
403
|
-
code: 'dictionary-locale-not-configured',
|
|
404
|
-
claim: `Message dictionary locale ${locale} is not in the static routing configuration.`,
|
|
405
|
-
locale,
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
if (defaultLocale && configuredLocales && !configuredLocales.includes(defaultLocale)) {
|
|
410
|
-
unresolved.push({
|
|
411
|
-
code: 'default-locale-not-configured',
|
|
412
|
-
claim: `Default locale ${defaultLocale} is not in the static routing configuration.`,
|
|
413
|
-
locale: defaultLocale,
|
|
414
|
-
});
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
const evidence = [
|
|
418
|
-
...packageFinding.evidence,
|
|
419
|
-
...derivedPatterns
|
|
420
|
-
.filter((item) => uniquePatterns.some((patternEntry) => patternEntry.pattern === item.pattern))
|
|
421
|
-
.map((item) => ({ claim: 'messages.pattern', path: item.path, line: item.line, value: item.pattern })),
|
|
422
|
-
...dictionaries.map((item) => ({ claim: 'messages.dictionary', path: item.path, line: 1, value: item.locale })),
|
|
423
|
-
...routing.localeCandidates.map((item) => ({
|
|
424
|
-
claim: 'routing.locales',
|
|
425
|
-
path: item.path,
|
|
426
|
-
line: item.line,
|
|
427
|
-
value: item.locales,
|
|
428
|
-
})),
|
|
429
|
-
...routing.defaultLocaleCandidates.map((item) => ({
|
|
430
|
-
claim: 'routing.defaultLocale',
|
|
431
|
-
path: item.path,
|
|
432
|
-
line: item.line,
|
|
433
|
-
value: item.locale,
|
|
434
|
-
})),
|
|
435
|
-
].sort(
|
|
436
|
-
(left, right) =>
|
|
437
|
-
left.path.localeCompare(right.path) || left.line - right.line || left.claim.localeCompare(right.claim)
|
|
438
|
-
);
|
|
439
|
-
|
|
440
|
-
const result = {
|
|
441
|
-
version: 1,
|
|
442
|
-
area: 'locales',
|
|
443
|
-
facts: {
|
|
444
|
-
system: 'next-intl',
|
|
445
|
-
declared: packageFinding.declared,
|
|
446
|
-
messagesPatterns: uniquePatterns,
|
|
447
|
-
dictionaryCandidates: dictionaries,
|
|
448
|
-
configuredLocaleCandidates: routing.localeCandidates,
|
|
449
|
-
configuredLocales,
|
|
450
|
-
defaultLocaleCandidates: routing.defaultLocaleCandidates,
|
|
451
|
-
defaultLocale,
|
|
452
|
-
},
|
|
453
|
-
evidence,
|
|
454
|
-
unresolved: unresolved.sort(
|
|
455
|
-
(left, right) => left.code.localeCompare(right.code) || (left.locale ?? '').localeCompare(right.locale ?? '')
|
|
456
|
-
),
|
|
457
|
-
};
|
|
458
|
-
|
|
459
|
-
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { FimoProvider } from 'fimo/next/client';
|
|
2
|
-
import type { ReactNode } from 'react';
|
|
3
|
-
|
|
4
|
-
import './globals.css';
|
|
5
|
-
|
|
6
|
-
export default function RootLayout({ children }: Readonly<{ children: ReactNode }>) {
|
|
7
|
-
const previewScriptUrl = process.env.NEXT_PUBLIC_FIMO_PREVIEW_SCRIPT_URL;
|
|
8
|
-
|
|
9
|
-
return (
|
|
10
|
-
<html lang="en">
|
|
11
|
-
<body>
|
|
12
|
-
<FimoProvider>{children}</FimoProvider>
|
|
13
|
-
{previewScriptUrl ? <script src={previewScriptUrl} defer /> : null}
|
|
14
|
-
</body>
|
|
15
|
-
</html>
|
|
16
|
-
);
|
|
17
|
-
}
|
|
File without changes
|
|
File without changes
|