@bleedingdev/modern-js-code-tools 3.2.0-ultramodern.119 → 3.2.0-ultramodern.121
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.
|
@@ -47,6 +47,10 @@ const external_node_path_namespaceObject = require("node:path");
|
|
|
47
47
|
var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
|
|
48
48
|
const external_oxlint_cjs_namespaceObject = require("./oxlint.cjs");
|
|
49
49
|
const WORKSPACE_SOURCE_SUCCESS = 'UltraModern i18n and boundary guardrails validated';
|
|
50
|
+
const DEFAULT_LOCALES = [
|
|
51
|
+
'en',
|
|
52
|
+
'cs'
|
|
53
|
+
];
|
|
50
54
|
const ignoredDirectories = new Set([
|
|
51
55
|
'.modern',
|
|
52
56
|
'.modernjs',
|
|
@@ -54,6 +58,7 @@ const ignoredDirectories = new Set([
|
|
|
54
58
|
'dist',
|
|
55
59
|
'node_modules'
|
|
56
60
|
]);
|
|
61
|
+
const escapeRegExp = (value)=>value.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
57
62
|
const normalizePath = (filePath)=>filePath.replaceAll('\\', '/');
|
|
58
63
|
const relativePath = (root, filePath)=>normalizePath(external_node_path_default().relative(root, filePath));
|
|
59
64
|
const walk = (directory, files = [])=>{
|
|
@@ -72,13 +77,22 @@ const walk = (directory, files = [])=>{
|
|
|
72
77
|
return files;
|
|
73
78
|
};
|
|
74
79
|
const isSourceFile = (filePath)=>/\.(?:ts|tsx|js|jsx)$/u.test(filePath);
|
|
75
|
-
const
|
|
80
|
+
const createLocaleJsonMatcher = (locales)=>{
|
|
81
|
+
const pattern = new RegExp(`/locales/(?:${locales.map(escapeRegExp).join('|')})/[^/]+\\.json$`, 'u');
|
|
82
|
+
return (root, filePath)=>pattern.test(`/${relativePath(root, filePath)}`);
|
|
83
|
+
};
|
|
84
|
+
const resolvePluralCategories = (locale, overrides)=>overrides?.[locale] ?? new Intl.PluralRules(locale).resolvedOptions().pluralCategories;
|
|
76
85
|
const readText = (filePath)=>external_node_fs_default().readFileSync(filePath, 'utf-8');
|
|
77
|
-
const
|
|
86
|
+
const localeImportPattern = (locale)=>new RegExp(`import\\s+(?:\\*\\s+as\\s+)?[A-Za-z_$][\\w$]*\\s+from\\s+['"]\\.\\./locales/${escapeRegExp(locale)}/[^'"]+\\.json['"]`, 'u');
|
|
87
|
+
const checkRuntimeResources = (root, filePath, text, locales)=>{
|
|
78
88
|
const relative = relativePath(root, filePath);
|
|
79
89
|
if (!relative.endsWith('/src/modern.runtime.ts')) return;
|
|
80
|
-
const
|
|
81
|
-
|
|
90
|
+
const missingLocales = locales.filter((locale)=>!localeImportPattern(locale).test(text));
|
|
91
|
+
const registersResources = /initOptions\s*:\s*\{[\s\S]*?\bresources\s*[,:}]/u.test(text);
|
|
92
|
+
if (missingLocales.length > 0 || !registersResources) {
|
|
93
|
+
const detail = missingLocales.length > 0 ? `missing locale JSON imports for: ${missingLocales.join(', ')}` : 'initOptions does not register a `resources` entry';
|
|
94
|
+
throw new Error(`${relative} must register locale JSON resources in modern.runtime.ts so Worker SSR and hydration use the same first-render translations (${detail}).`);
|
|
95
|
+
}
|
|
82
96
|
};
|
|
83
97
|
const visitLocaleKeys = (value, visitor, pathParts = [])=>{
|
|
84
98
|
if (!value || 'object' != typeof value || Array.isArray(value)) return;
|
|
@@ -91,22 +105,12 @@ const visitLocaleKeys = (value, visitor, pathParts = [])=>{
|
|
|
91
105
|
visitLocaleKeys(child, visitor, nextPath);
|
|
92
106
|
}
|
|
93
107
|
};
|
|
94
|
-
const checkPluralResources = (root, filePath, json)=>{
|
|
108
|
+
const checkPluralResources = (root, filePath, json, requiredSuffixes, pluralSuffixPattern)=>{
|
|
95
109
|
const relative = relativePath(root, filePath);
|
|
96
|
-
const language = relative.split('/locales/')[1]?.split('/')[0];
|
|
97
|
-
const requiredSuffixes = 'cs' === language ? [
|
|
98
|
-
'one',
|
|
99
|
-
'few',
|
|
100
|
-
'many',
|
|
101
|
-
'other'
|
|
102
|
-
] : [
|
|
103
|
-
'one',
|
|
104
|
-
'other'
|
|
105
|
-
];
|
|
106
110
|
const groups = new Map();
|
|
107
111
|
visitLocaleKeys(json, (key, value, pathParts)=>{
|
|
108
112
|
if ('string' != typeof value || !value.includes('{{count}}')) return;
|
|
109
|
-
const suffixMatch = key.match(
|
|
113
|
+
const suffixMatch = key.match(pluralSuffixPattern);
|
|
110
114
|
if (!suffixMatch) throw new Error(`${relative} key ${pathParts.join('.')} contains {{count}} but is not plural-suffixed.`);
|
|
111
115
|
const [, base = '', suffix = ''] = suffixMatch;
|
|
112
116
|
const parentPath = pathParts.slice(0, -1).join('.');
|
|
@@ -117,15 +121,30 @@ const checkPluralResources = (root, filePath, json)=>{
|
|
|
117
121
|
});
|
|
118
122
|
for (const [group, suffixes] of groups)for (const suffix of requiredSuffixes)if (!suffixes.has(suffix)) throw new Error(`${relative} plural group ${group} is missing _${suffix}.`);
|
|
119
123
|
};
|
|
120
|
-
const runRuntimeAndLocaleResourceChecks = (root, sourceRoots)=>{
|
|
124
|
+
const runRuntimeAndLocaleResourceChecks = (root, sourceRoots, locales, pluralCategories)=>{
|
|
125
|
+
if (0 === locales.length) return;
|
|
126
|
+
const isLocaleJson = createLocaleJsonMatcher(locales);
|
|
127
|
+
const localeCategories = new Map(locales.map((locale)=>[
|
|
128
|
+
locale,
|
|
129
|
+
resolvePluralCategories(locale, pluralCategories)
|
|
130
|
+
]));
|
|
131
|
+
const pluralSuffixPattern = new RegExp(`^(.*)_(${[
|
|
132
|
+
...new Set([
|
|
133
|
+
...localeCategories.values()
|
|
134
|
+
].flat())
|
|
135
|
+
].map(escapeRegExp).join('|')})$`, 'u');
|
|
121
136
|
const files = sourceRoots.flatMap((sourceRoot)=>walk(external_node_path_default().join(root, sourceRoot)));
|
|
122
|
-
for (const filePath of files.filter(isSourceFile))checkRuntimeResources(root, filePath, readText(filePath));
|
|
123
|
-
for (const filePath of files.filter((filePath)=>isLocaleJson(root, filePath)))
|
|
137
|
+
for (const filePath of files.filter(isSourceFile))checkRuntimeResources(root, filePath, readText(filePath), locales);
|
|
138
|
+
for (const filePath of files.filter((filePath)=>isLocaleJson(root, filePath))){
|
|
139
|
+
const relative = relativePath(root, filePath);
|
|
140
|
+
const language = relative.split('/locales/')[1]?.split('/')[0] ?? '';
|
|
141
|
+
checkPluralResources(root, filePath, JSON.parse(readText(filePath)), localeCategories.get(language) ?? [], pluralSuffixPattern);
|
|
142
|
+
}
|
|
124
143
|
};
|
|
125
144
|
const runWorkspaceSourceCheck = ({ cwd = process.cwd(), sourceRoots = [
|
|
126
145
|
'apps',
|
|
127
146
|
'verticals'
|
|
128
|
-
] } = {})=>{
|
|
147
|
+
], locales = DEFAULT_LOCALES, pluralCategories } = {})=>{
|
|
129
148
|
const oxlintResult = (0, external_oxlint_cjs_namespaceObject.runOxlintRules)({
|
|
130
149
|
cwd,
|
|
131
150
|
targets: sourceRoots,
|
|
@@ -155,7 +174,7 @@ const runWorkspaceSourceCheck = ({ cwd = process.cwd(), sourceRoots = [
|
|
|
155
174
|
return oxlintResult.exitCode;
|
|
156
175
|
}
|
|
157
176
|
try {
|
|
158
|
-
runRuntimeAndLocaleResourceChecks(cwd, sourceRoots);
|
|
177
|
+
runRuntimeAndLocaleResourceChecks(cwd, sourceRoots, locales, pluralCategories);
|
|
159
178
|
} catch (error) {
|
|
160
179
|
console.error(error instanceof Error ? error.message : 'UltraModern workspace source checks failed.');
|
|
161
180
|
return 1;
|
|
@@ -2,6 +2,10 @@ import node_fs from "node:fs";
|
|
|
2
2
|
import node_path from "node:path";
|
|
3
3
|
import { printOxlintOutput, runOxlintRules } from "./oxlint.js";
|
|
4
4
|
const WORKSPACE_SOURCE_SUCCESS = 'UltraModern i18n and boundary guardrails validated';
|
|
5
|
+
const DEFAULT_LOCALES = [
|
|
6
|
+
'en',
|
|
7
|
+
'cs'
|
|
8
|
+
];
|
|
5
9
|
const ignoredDirectories = new Set([
|
|
6
10
|
'.modern',
|
|
7
11
|
'.modernjs',
|
|
@@ -9,6 +13,7 @@ const ignoredDirectories = new Set([
|
|
|
9
13
|
'dist',
|
|
10
14
|
'node_modules'
|
|
11
15
|
]);
|
|
16
|
+
const escapeRegExp = (value)=>value.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
12
17
|
const normalizePath = (filePath)=>filePath.replaceAll('\\', '/');
|
|
13
18
|
const relativePath = (root, filePath)=>normalizePath(node_path.relative(root, filePath));
|
|
14
19
|
const walk = (directory, files = [])=>{
|
|
@@ -27,13 +32,22 @@ const walk = (directory, files = [])=>{
|
|
|
27
32
|
return files;
|
|
28
33
|
};
|
|
29
34
|
const isSourceFile = (filePath)=>/\.(?:ts|tsx|js|jsx)$/u.test(filePath);
|
|
30
|
-
const
|
|
35
|
+
const createLocaleJsonMatcher = (locales)=>{
|
|
36
|
+
const pattern = new RegExp(`/locales/(?:${locales.map(escapeRegExp).join('|')})/[^/]+\\.json$`, 'u');
|
|
37
|
+
return (root, filePath)=>pattern.test(`/${relativePath(root, filePath)}`);
|
|
38
|
+
};
|
|
39
|
+
const resolvePluralCategories = (locale, overrides)=>overrides?.[locale] ?? new Intl.PluralRules(locale).resolvedOptions().pluralCategories;
|
|
31
40
|
const readText = (filePath)=>node_fs.readFileSync(filePath, 'utf-8');
|
|
32
|
-
const
|
|
41
|
+
const localeImportPattern = (locale)=>new RegExp(`import\\s+(?:\\*\\s+as\\s+)?[A-Za-z_$][\\w$]*\\s+from\\s+['"]\\.\\./locales/${escapeRegExp(locale)}/[^'"]+\\.json['"]`, 'u');
|
|
42
|
+
const checkRuntimeResources = (root, filePath, text, locales)=>{
|
|
33
43
|
const relative = relativePath(root, filePath);
|
|
34
44
|
if (!relative.endsWith('/src/modern.runtime.ts')) return;
|
|
35
|
-
const
|
|
36
|
-
|
|
45
|
+
const missingLocales = locales.filter((locale)=>!localeImportPattern(locale).test(text));
|
|
46
|
+
const registersResources = /initOptions\s*:\s*\{[\s\S]*?\bresources\s*[,:}]/u.test(text);
|
|
47
|
+
if (missingLocales.length > 0 || !registersResources) {
|
|
48
|
+
const detail = missingLocales.length > 0 ? `missing locale JSON imports for: ${missingLocales.join(', ')}` : 'initOptions does not register a `resources` entry';
|
|
49
|
+
throw new Error(`${relative} must register locale JSON resources in modern.runtime.ts so Worker SSR and hydration use the same first-render translations (${detail}).`);
|
|
50
|
+
}
|
|
37
51
|
};
|
|
38
52
|
const visitLocaleKeys = (value, visitor, pathParts = [])=>{
|
|
39
53
|
if (!value || 'object' != typeof value || Array.isArray(value)) return;
|
|
@@ -46,22 +60,12 @@ const visitLocaleKeys = (value, visitor, pathParts = [])=>{
|
|
|
46
60
|
visitLocaleKeys(child, visitor, nextPath);
|
|
47
61
|
}
|
|
48
62
|
};
|
|
49
|
-
const checkPluralResources = (root, filePath, json)=>{
|
|
63
|
+
const checkPluralResources = (root, filePath, json, requiredSuffixes, pluralSuffixPattern)=>{
|
|
50
64
|
const relative = relativePath(root, filePath);
|
|
51
|
-
const language = relative.split('/locales/')[1]?.split('/')[0];
|
|
52
|
-
const requiredSuffixes = 'cs' === language ? [
|
|
53
|
-
'one',
|
|
54
|
-
'few',
|
|
55
|
-
'many',
|
|
56
|
-
'other'
|
|
57
|
-
] : [
|
|
58
|
-
'one',
|
|
59
|
-
'other'
|
|
60
|
-
];
|
|
61
65
|
const groups = new Map();
|
|
62
66
|
visitLocaleKeys(json, (key, value, pathParts)=>{
|
|
63
67
|
if ('string' != typeof value || !value.includes('{{count}}')) return;
|
|
64
|
-
const suffixMatch = key.match(
|
|
68
|
+
const suffixMatch = key.match(pluralSuffixPattern);
|
|
65
69
|
if (!suffixMatch) throw new Error(`${relative} key ${pathParts.join('.')} contains {{count}} but is not plural-suffixed.`);
|
|
66
70
|
const [, base = '', suffix = ''] = suffixMatch;
|
|
67
71
|
const parentPath = pathParts.slice(0, -1).join('.');
|
|
@@ -72,15 +76,30 @@ const checkPluralResources = (root, filePath, json)=>{
|
|
|
72
76
|
});
|
|
73
77
|
for (const [group, suffixes] of groups)for (const suffix of requiredSuffixes)if (!suffixes.has(suffix)) throw new Error(`${relative} plural group ${group} is missing _${suffix}.`);
|
|
74
78
|
};
|
|
75
|
-
const runRuntimeAndLocaleResourceChecks = (root, sourceRoots)=>{
|
|
79
|
+
const runRuntimeAndLocaleResourceChecks = (root, sourceRoots, locales, pluralCategories)=>{
|
|
80
|
+
if (0 === locales.length) return;
|
|
81
|
+
const isLocaleJson = createLocaleJsonMatcher(locales);
|
|
82
|
+
const localeCategories = new Map(locales.map((locale)=>[
|
|
83
|
+
locale,
|
|
84
|
+
resolvePluralCategories(locale, pluralCategories)
|
|
85
|
+
]));
|
|
86
|
+
const pluralSuffixPattern = new RegExp(`^(.*)_(${[
|
|
87
|
+
...new Set([
|
|
88
|
+
...localeCategories.values()
|
|
89
|
+
].flat())
|
|
90
|
+
].map(escapeRegExp).join('|')})$`, 'u');
|
|
76
91
|
const files = sourceRoots.flatMap((sourceRoot)=>walk(node_path.join(root, sourceRoot)));
|
|
77
|
-
for (const filePath of files.filter(isSourceFile))checkRuntimeResources(root, filePath, readText(filePath));
|
|
78
|
-
for (const filePath of files.filter((filePath)=>isLocaleJson(root, filePath)))
|
|
92
|
+
for (const filePath of files.filter(isSourceFile))checkRuntimeResources(root, filePath, readText(filePath), locales);
|
|
93
|
+
for (const filePath of files.filter((filePath)=>isLocaleJson(root, filePath))){
|
|
94
|
+
const relative = relativePath(root, filePath);
|
|
95
|
+
const language = relative.split('/locales/')[1]?.split('/')[0] ?? '';
|
|
96
|
+
checkPluralResources(root, filePath, JSON.parse(readText(filePath)), localeCategories.get(language) ?? [], pluralSuffixPattern);
|
|
97
|
+
}
|
|
79
98
|
};
|
|
80
99
|
const runWorkspaceSourceCheck = ({ cwd = process.cwd(), sourceRoots = [
|
|
81
100
|
'apps',
|
|
82
101
|
'verticals'
|
|
83
|
-
] } = {})=>{
|
|
102
|
+
], locales = DEFAULT_LOCALES, pluralCategories } = {})=>{
|
|
84
103
|
const oxlintResult = runOxlintRules({
|
|
85
104
|
cwd,
|
|
86
105
|
targets: sourceRoots,
|
|
@@ -110,7 +129,7 @@ const runWorkspaceSourceCheck = ({ cwd = process.cwd(), sourceRoots = [
|
|
|
110
129
|
return oxlintResult.exitCode;
|
|
111
130
|
}
|
|
112
131
|
try {
|
|
113
|
-
runRuntimeAndLocaleResourceChecks(cwd, sourceRoots);
|
|
132
|
+
runRuntimeAndLocaleResourceChecks(cwd, sourceRoots, locales, pluralCategories);
|
|
114
133
|
} catch (error) {
|
|
115
134
|
console.error(error instanceof Error ? error.message : 'UltraModern workspace source checks failed.');
|
|
116
135
|
return 1;
|
|
@@ -3,6 +3,10 @@ import node_fs from "node:fs";
|
|
|
3
3
|
import node_path from "node:path";
|
|
4
4
|
import { printOxlintOutput, runOxlintRules } from "./oxlint.js";
|
|
5
5
|
const WORKSPACE_SOURCE_SUCCESS = 'UltraModern i18n and boundary guardrails validated';
|
|
6
|
+
const DEFAULT_LOCALES = [
|
|
7
|
+
'en',
|
|
8
|
+
'cs'
|
|
9
|
+
];
|
|
6
10
|
const ignoredDirectories = new Set([
|
|
7
11
|
'.modern',
|
|
8
12
|
'.modernjs',
|
|
@@ -10,6 +14,7 @@ const ignoredDirectories = new Set([
|
|
|
10
14
|
'dist',
|
|
11
15
|
'node_modules'
|
|
12
16
|
]);
|
|
17
|
+
const escapeRegExp = (value)=>value.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
13
18
|
const normalizePath = (filePath)=>filePath.replaceAll('\\', '/');
|
|
14
19
|
const relativePath = (root, filePath)=>normalizePath(node_path.relative(root, filePath));
|
|
15
20
|
const walk = (directory, files = [])=>{
|
|
@@ -28,13 +33,22 @@ const walk = (directory, files = [])=>{
|
|
|
28
33
|
return files;
|
|
29
34
|
};
|
|
30
35
|
const isSourceFile = (filePath)=>/\.(?:ts|tsx|js|jsx)$/u.test(filePath);
|
|
31
|
-
const
|
|
36
|
+
const createLocaleJsonMatcher = (locales)=>{
|
|
37
|
+
const pattern = new RegExp(`/locales/(?:${locales.map(escapeRegExp).join('|')})/[^/]+\\.json$`, 'u');
|
|
38
|
+
return (root, filePath)=>pattern.test(`/${relativePath(root, filePath)}`);
|
|
39
|
+
};
|
|
40
|
+
const resolvePluralCategories = (locale, overrides)=>overrides?.[locale] ?? new Intl.PluralRules(locale).resolvedOptions().pluralCategories;
|
|
32
41
|
const readText = (filePath)=>node_fs.readFileSync(filePath, 'utf-8');
|
|
33
|
-
const
|
|
42
|
+
const localeImportPattern = (locale)=>new RegExp(`import\\s+(?:\\*\\s+as\\s+)?[A-Za-z_$][\\w$]*\\s+from\\s+['"]\\.\\./locales/${escapeRegExp(locale)}/[^'"]+\\.json['"]`, 'u');
|
|
43
|
+
const checkRuntimeResources = (root, filePath, text, locales)=>{
|
|
34
44
|
const relative = relativePath(root, filePath);
|
|
35
45
|
if (!relative.endsWith('/src/modern.runtime.ts')) return;
|
|
36
|
-
const
|
|
37
|
-
|
|
46
|
+
const missingLocales = locales.filter((locale)=>!localeImportPattern(locale).test(text));
|
|
47
|
+
const registersResources = /initOptions\s*:\s*\{[\s\S]*?\bresources\s*[,:}]/u.test(text);
|
|
48
|
+
if (missingLocales.length > 0 || !registersResources) {
|
|
49
|
+
const detail = missingLocales.length > 0 ? `missing locale JSON imports for: ${missingLocales.join(', ')}` : 'initOptions does not register a `resources` entry';
|
|
50
|
+
throw new Error(`${relative} must register locale JSON resources in modern.runtime.ts so Worker SSR and hydration use the same first-render translations (${detail}).`);
|
|
51
|
+
}
|
|
38
52
|
};
|
|
39
53
|
const visitLocaleKeys = (value, visitor, pathParts = [])=>{
|
|
40
54
|
if (!value || 'object' != typeof value || Array.isArray(value)) return;
|
|
@@ -47,22 +61,12 @@ const visitLocaleKeys = (value, visitor, pathParts = [])=>{
|
|
|
47
61
|
visitLocaleKeys(child, visitor, nextPath);
|
|
48
62
|
}
|
|
49
63
|
};
|
|
50
|
-
const checkPluralResources = (root, filePath, json)=>{
|
|
64
|
+
const checkPluralResources = (root, filePath, json, requiredSuffixes, pluralSuffixPattern)=>{
|
|
51
65
|
const relative = relativePath(root, filePath);
|
|
52
|
-
const language = relative.split('/locales/')[1]?.split('/')[0];
|
|
53
|
-
const requiredSuffixes = 'cs' === language ? [
|
|
54
|
-
'one',
|
|
55
|
-
'few',
|
|
56
|
-
'many',
|
|
57
|
-
'other'
|
|
58
|
-
] : [
|
|
59
|
-
'one',
|
|
60
|
-
'other'
|
|
61
|
-
];
|
|
62
66
|
const groups = new Map();
|
|
63
67
|
visitLocaleKeys(json, (key, value, pathParts)=>{
|
|
64
68
|
if ('string' != typeof value || !value.includes('{{count}}')) return;
|
|
65
|
-
const suffixMatch = key.match(
|
|
69
|
+
const suffixMatch = key.match(pluralSuffixPattern);
|
|
66
70
|
if (!suffixMatch) throw new Error(`${relative} key ${pathParts.join('.')} contains {{count}} but is not plural-suffixed.`);
|
|
67
71
|
const [, base = '', suffix = ''] = suffixMatch;
|
|
68
72
|
const parentPath = pathParts.slice(0, -1).join('.');
|
|
@@ -73,15 +77,30 @@ const checkPluralResources = (root, filePath, json)=>{
|
|
|
73
77
|
});
|
|
74
78
|
for (const [group, suffixes] of groups)for (const suffix of requiredSuffixes)if (!suffixes.has(suffix)) throw new Error(`${relative} plural group ${group} is missing _${suffix}.`);
|
|
75
79
|
};
|
|
76
|
-
const runRuntimeAndLocaleResourceChecks = (root, sourceRoots)=>{
|
|
80
|
+
const runRuntimeAndLocaleResourceChecks = (root, sourceRoots, locales, pluralCategories)=>{
|
|
81
|
+
if (0 === locales.length) return;
|
|
82
|
+
const isLocaleJson = createLocaleJsonMatcher(locales);
|
|
83
|
+
const localeCategories = new Map(locales.map((locale)=>[
|
|
84
|
+
locale,
|
|
85
|
+
resolvePluralCategories(locale, pluralCategories)
|
|
86
|
+
]));
|
|
87
|
+
const pluralSuffixPattern = new RegExp(`^(.*)_(${[
|
|
88
|
+
...new Set([
|
|
89
|
+
...localeCategories.values()
|
|
90
|
+
].flat())
|
|
91
|
+
].map(escapeRegExp).join('|')})$`, 'u');
|
|
77
92
|
const files = sourceRoots.flatMap((sourceRoot)=>walk(node_path.join(root, sourceRoot)));
|
|
78
|
-
for (const filePath of files.filter(isSourceFile))checkRuntimeResources(root, filePath, readText(filePath));
|
|
79
|
-
for (const filePath of files.filter((filePath)=>isLocaleJson(root, filePath)))
|
|
93
|
+
for (const filePath of files.filter(isSourceFile))checkRuntimeResources(root, filePath, readText(filePath), locales);
|
|
94
|
+
for (const filePath of files.filter((filePath)=>isLocaleJson(root, filePath))){
|
|
95
|
+
const relative = relativePath(root, filePath);
|
|
96
|
+
const language = relative.split('/locales/')[1]?.split('/')[0] ?? '';
|
|
97
|
+
checkPluralResources(root, filePath, JSON.parse(readText(filePath)), localeCategories.get(language) ?? [], pluralSuffixPattern);
|
|
98
|
+
}
|
|
80
99
|
};
|
|
81
100
|
const runWorkspaceSourceCheck = ({ cwd = process.cwd(), sourceRoots = [
|
|
82
101
|
'apps',
|
|
83
102
|
'verticals'
|
|
84
|
-
] } = {})=>{
|
|
103
|
+
], locales = DEFAULT_LOCALES, pluralCategories } = {})=>{
|
|
85
104
|
const oxlintResult = runOxlintRules({
|
|
86
105
|
cwd,
|
|
87
106
|
targets: sourceRoots,
|
|
@@ -111,7 +130,7 @@ const runWorkspaceSourceCheck = ({ cwd = process.cwd(), sourceRoots = [
|
|
|
111
130
|
return oxlintResult.exitCode;
|
|
112
131
|
}
|
|
113
132
|
try {
|
|
114
|
-
runRuntimeAndLocaleResourceChecks(cwd, sourceRoots);
|
|
133
|
+
runRuntimeAndLocaleResourceChecks(cwd, sourceRoots, locales, pluralCategories);
|
|
115
134
|
} catch (error) {
|
|
116
135
|
console.error(error instanceof Error ? error.message : 'UltraModern workspace source checks failed.');
|
|
117
136
|
return 1;
|
|
@@ -1,8 +1,21 @@
|
|
|
1
|
-
type WorkspaceSourceCheckOptions = {
|
|
1
|
+
export type WorkspaceSourceCheckOptions = {
|
|
2
2
|
readonly cwd?: string;
|
|
3
3
|
readonly sourceRoots?: readonly string[];
|
|
4
|
+
/**
|
|
5
|
+
* Locale codes whose `locales/<locale>/*.json` resources are plural-checked
|
|
6
|
+
* and must be registered in each app's `src/modern.runtime.ts`.
|
|
7
|
+
* Defaults to `['en', 'cs']` (the UltraModern workspace convention).
|
|
8
|
+
* Pass an empty array to opt out of the runtime/locale resource checks.
|
|
9
|
+
*/
|
|
10
|
+
readonly locales?: readonly string[];
|
|
11
|
+
/**
|
|
12
|
+
* Per-locale plural-category overrides. Locales absent from this map
|
|
13
|
+
* resolve their categories through `Intl.PluralRules(locale)` (CLDR
|
|
14
|
+
* cardinal rules), e.g. `['one', 'other']` for `en` and
|
|
15
|
+
* `['one', 'few', 'many', 'other']` for `cs`.
|
|
16
|
+
*/
|
|
17
|
+
readonly pluralCategories?: Readonly<Record<string, readonly string[]>>;
|
|
4
18
|
};
|
|
5
19
|
export declare const WORKSPACE_SOURCE_SUCCESS = "UltraModern i18n and boundary guardrails validated";
|
|
6
|
-
export declare const runWorkspaceSourceCheck: ({ cwd, sourceRoots, }?: WorkspaceSourceCheckOptions) => number;
|
|
20
|
+
export declare const runWorkspaceSourceCheck: ({ cwd, sourceRoots, locales, pluralCategories, }?: WorkspaceSourceCheckOptions) => number;
|
|
7
21
|
export declare const main: () => void;
|
|
8
|
-
export {};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { runSingleAppI18nCheck } from './cli/i18n-check';
|
|
2
|
-
export { runWorkspaceSourceCheck } from './cli/workspace-source-check';
|
|
2
|
+
export { runWorkspaceSourceCheck, type WorkspaceSourceCheckOptions, } from './cli/workspace-source-check';
|
|
3
3
|
export { default as oxlintPlugin } from './oxlint-plugin';
|
package/package.json
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"engines": {
|
|
24
24
|
"node": ">=20"
|
|
25
25
|
},
|
|
26
|
-
"version": "3.2.0-ultramodern.
|
|
26
|
+
"version": "3.2.0-ultramodern.121",
|
|
27
27
|
"types": "./dist/types/index.d.ts",
|
|
28
28
|
"main": "./dist/esm-node/index.js",
|
|
29
29
|
"typesVersions": {
|
|
@@ -58,12 +58,12 @@
|
|
|
58
58
|
"dist"
|
|
59
59
|
],
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"oxlint": "1.
|
|
61
|
+
"oxlint": "1.69.0"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
|
-
"@rslib/core": "0.
|
|
65
|
-
"@types/node": "^25.9.
|
|
66
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
64
|
+
"@rslib/core": "0.22.0",
|
|
65
|
+
"@types/node": "^25.9.3",
|
|
66
|
+
"@typescript/native-preview": "7.0.0-dev.20260610.1",
|
|
67
67
|
"@scripts/rstest-config": "2.66.0"
|
|
68
68
|
},
|
|
69
69
|
"sideEffects": false,
|