@bleedingdev/modern-js-code-tools 3.9.0-ultramodern.9 → 3.9.3-ultramodern.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/bin/modern-api-check-files.mjs +4 -1
- package/bin/modern-api-check.mjs +1 -1
- package/bin/modern-i18n-check.mjs +4 -0
- package/dist/cjs/cli/microvertical-api-check.cjs +36 -7
- package/dist/cjs/cli/workspace-source-check.cjs +90 -17
- package/dist/cjs/microvertical-api-baseline.cjs +89 -246
- package/dist/cjs/microvertical-api-boundary.cjs +507 -64
- package/dist/cjs/microvertical-api-owner.cjs +5 -2
- package/dist/cjs/module-graph.cjs +1862 -0
- package/dist/cjs/oxlint-plugin/rules/strict-effect-api-boundaries.cjs +1 -1
- package/dist/cjs/source-analysis.cjs +108 -0
- package/dist/cjs/strict-effect-runtime.cjs +75 -57
- package/dist/esm/cli/microvertical-api-check.js +26 -7
- package/dist/esm/cli/workspace-source-check.js +86 -13
- package/dist/esm/microvertical-api-baseline.js +66 -214
- package/dist/esm/microvertical-api-boundary.js +505 -65
- package/dist/esm/microvertical-api-owner.js +5 -2
- package/dist/esm/module-graph.js +1804 -0
- package/dist/esm/oxlint-plugin/rules/strict-effect-api-boundaries.js +1 -1
- package/dist/esm/source-analysis.js +48 -0
- package/dist/esm/strict-effect-runtime.js +75 -56
- package/dist/esm-node/cli/microvertical-api-check.js +26 -7
- package/dist/esm-node/cli/workspace-source-check.js +86 -13
- package/dist/esm-node/microvertical-api-baseline.js +66 -214
- package/dist/esm-node/microvertical-api-boundary.js +505 -65
- package/dist/esm-node/microvertical-api-owner.js +5 -2
- package/dist/esm-node/module-graph.js +1805 -0
- package/dist/esm-node/oxlint-plugin/rules/strict-effect-api-boundaries.js +1 -1
- package/dist/esm-node/source-analysis.js +49 -0
- package/dist/esm-node/strict-effect-runtime.js +75 -56
- package/dist/types/cli/microvertical-api-check.d.ts +1 -1
- package/dist/types/cli/workspace-source-check.d.ts +1 -1
- package/dist/types/microvertical-api-baseline.d.ts +4 -5
- package/dist/types/microvertical-api-boundary.d.ts +17 -2
- package/dist/types/microvertical-api-owner.d.ts +1 -1
- package/dist/types/module-graph.d.ts +100 -0
- package/dist/types/source-analysis.d.ts +13 -0
- package/package.json +17 -13
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { runMicroVerticalApiCheckCli } from '../dist/esm-node/cli/microvertical-api-check.js';
|
|
3
3
|
|
|
4
|
-
process.exitCode = runMicroVerticalApiCheckCli(
|
|
4
|
+
process.exitCode = await runMicroVerticalApiCheckCli(
|
|
5
|
+
process.argv.slice(2),
|
|
6
|
+
true,
|
|
7
|
+
);
|
package/bin/modern-api-check.mjs
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __webpack_require__ = {};
|
|
3
|
+
(()=>{
|
|
4
|
+
__webpack_require__.n = (module)=>{
|
|
5
|
+
var getter = module && module.__esModule ? ()=>module['default'] : ()=>module;
|
|
6
|
+
__webpack_require__.d(getter, {
|
|
7
|
+
a: getter
|
|
8
|
+
});
|
|
9
|
+
return getter;
|
|
10
|
+
};
|
|
11
|
+
})();
|
|
3
12
|
(()=>{
|
|
4
13
|
__webpack_require__.d = (exports1, getters, values)=>{
|
|
5
14
|
var define = (defs, kind)=>{
|
|
@@ -30,30 +39,50 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
30
39
|
__webpack_require__.d(__webpack_exports__, {
|
|
31
40
|
runMicroVerticalApiCheckCli: ()=>runMicroVerticalApiCheckCli
|
|
32
41
|
});
|
|
42
|
+
const external_node_path_namespaceObject = require("node:path");
|
|
43
|
+
var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
|
|
44
|
+
const external_node_url_namespaceObject = require("node:url");
|
|
33
45
|
const external_microvertical_api_boundary_cjs_namespaceObject = require("../microvertical-api-boundary.cjs");
|
|
34
|
-
|
|
46
|
+
const flags = [
|
|
47
|
+
'--workspace-root',
|
|
48
|
+
'--baseline-package-directory',
|
|
49
|
+
'--rules'
|
|
50
|
+
];
|
|
51
|
+
async function loadSourceRules(file) {
|
|
52
|
+
const rules = (await import((0, external_node_url_namespaceObject.pathToFileURL)(external_node_path_default().resolve(file)).href)).default;
|
|
53
|
+
if (!Array.isArray(rules) || !rules.every((rule)=>'function' == typeof rule)) throw new Error(`${file}: default export must be an array of source rule functions, e.g. \`export default [myRule]\``);
|
|
54
|
+
return rules;
|
|
55
|
+
}
|
|
56
|
+
async function runMicroVerticalApiCheckCli(args = process.argv.slice(2), filesOnly = false) {
|
|
35
57
|
let workspaceRoot = process.env.ULTRAMODERN_WORKSPACE_ROOT ?? process.cwd();
|
|
36
58
|
let baselinePackageDirectory;
|
|
59
|
+
const ruleFiles = [];
|
|
37
60
|
for(let index = 0; index < args.length; index += 1){
|
|
38
61
|
const argument = args[index];
|
|
39
62
|
if ('--help' === argument || '-h' === argument) {
|
|
40
|
-
console.log(`${filesOnly ? 'modern-api-check-files' : 'modern-api-check'} [--workspace-root <path>] [--baseline-package-directory <path>]
|
|
63
|
+
console.log(`${filesOnly ? 'modern-api-check-files' : 'modern-api-check'} [--workspace-root <path>] [--baseline-package-directory <path>] [--rules <module>]...\n${filesOnly ? 'Checks consumer files, contracts, package exports and owner identity. Does not analyze runtime topology; run the strict Effect source phase separately.' : 'Checks consumer files, contracts, package exports, owner identity and runtime topology once per API entry.'}\nThe baseline owner defaults to each contract's native package resolver. --baseline-package-directory requires that exact installed owner for every contract.\n--rules loads a module whose default export is an array of source rules; each rule runs once per workspace source file with the shared module graph.\nWorkspace defaults to ULTRAMODERN_WORKSPACE_ROOT then cwd. Exit codes: 0 valid, 1 consumer violation, 2 tool/configuration failure.`);
|
|
41
64
|
return 0;
|
|
42
65
|
}
|
|
43
|
-
if (![
|
|
44
|
-
'--workspace-root',
|
|
45
|
-
'--baseline-package-directory'
|
|
46
|
-
].includes(argument ?? '') || !args[index + 1] || args[index + 1]?.startsWith('--')) {
|
|
66
|
+
if (!flags.includes(argument ?? '') || !args[index + 1] || args[index + 1]?.startsWith('--')) {
|
|
47
67
|
console.error(`Invalid argument: ${argument ?? ''}. Use --help.`);
|
|
48
68
|
return 2;
|
|
49
69
|
}
|
|
50
70
|
const value = args[++index];
|
|
51
71
|
if ('--workspace-root' === argument) workspaceRoot = value;
|
|
72
|
+
else if ('--rules' === argument) ruleFiles.push(value);
|
|
52
73
|
else baselinePackageDirectory = value;
|
|
53
74
|
}
|
|
75
|
+
const sourceRules = [];
|
|
76
|
+
for (const file of ruleFiles)try {
|
|
77
|
+
sourceRules.push(...await loadSourceRules(file));
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error(`API tool error: --rules ${error instanceof Error ? error.message : String(error)}`);
|
|
80
|
+
return 2;
|
|
81
|
+
}
|
|
54
82
|
const result = (filesOnly ? external_microvertical_api_boundary_cjs_namespaceObject.checkMicroVerticalApiConsumerFiles : external_microvertical_api_boundary_cjs_namespaceObject.checkMicroVerticalApiBoundaries)({
|
|
55
83
|
workspaceRoot,
|
|
56
|
-
baselinePackageDirectory
|
|
84
|
+
baselinePackageDirectory,
|
|
85
|
+
sourceRules
|
|
57
86
|
});
|
|
58
87
|
for (const diagnostic of result.diagnostics)console.error(`API violation: ${diagnostic}`);
|
|
59
88
|
for (const error of result.toolErrors)console.error(`API tool error: ${error}`);
|
|
@@ -37,8 +37,8 @@ var __webpack_require__ = {};
|
|
|
37
37
|
var __webpack_exports__ = {};
|
|
38
38
|
__webpack_require__.r(__webpack_exports__);
|
|
39
39
|
__webpack_require__.d(__webpack_exports__, {
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
runWorkspaceSourceCheck: ()=>runWorkspaceSourceCheck,
|
|
41
|
+
runWorkspaceSourceCheckCli: ()=>runWorkspaceSourceCheckCli
|
|
42
42
|
});
|
|
43
43
|
const external_node_fs_namespaceObject = require("node:fs");
|
|
44
44
|
var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_namespaceObject);
|
|
@@ -50,6 +50,11 @@ const DEFAULT_LOCALES = [
|
|
|
50
50
|
'en',
|
|
51
51
|
'cs'
|
|
52
52
|
];
|
|
53
|
+
const DEFAULT_SOURCE_ROOTS = [
|
|
54
|
+
'apps',
|
|
55
|
+
'verticals',
|
|
56
|
+
'packages'
|
|
57
|
+
];
|
|
53
58
|
const ignoredDirectories = new Set([
|
|
54
59
|
'.modern',
|
|
55
60
|
'.modernjs',
|
|
@@ -83,6 +88,8 @@ const createLocaleJsonMatcher = (locales)=>{
|
|
|
83
88
|
const resolvePluralCategories = (locale, overrides)=>overrides?.[locale] ?? new Intl.PluralRules(locale).resolvedOptions().pluralCategories;
|
|
84
89
|
const readText = (filePath)=>external_node_fs_default().readFileSync(filePath, 'utf-8');
|
|
85
90
|
const localeImportPattern = (locale)=>new RegExp(`import\\s+(?:\\*\\s+as\\s+)?[A-Za-z_$][\\w$]*\\s+from\\s+['"]\\.\\./locales/${escapeRegExp(locale)}/[^'"]+\\.json['"]`, 'u');
|
|
91
|
+
class SourceViolation extends Error {
|
|
92
|
+
}
|
|
86
93
|
const checkRuntimeResources = (root, filePath, text, locales)=>{
|
|
87
94
|
const relative = relativePath(root, filePath);
|
|
88
95
|
if (!relative.endsWith('/src/modern.runtime.ts')) return;
|
|
@@ -90,7 +97,7 @@ const checkRuntimeResources = (root, filePath, text, locales)=>{
|
|
|
90
97
|
const registersResources = /initOptions\s*:\s*\{[\s\S]*?\bresources\s*[,:}]/u.test(text);
|
|
91
98
|
if (missingLocales.length > 0 || !registersResources) {
|
|
92
99
|
const detail = missingLocales.length > 0 ? `missing locale JSON imports for: ${missingLocales.join(', ')}` : 'initOptions does not register a `resources` entry';
|
|
93
|
-
throw new
|
|
100
|
+
throw new SourceViolation(`${relative} must register locale JSON resources in modern.runtime.ts so Worker SSR and hydration use the same first-render translations (${detail}).`);
|
|
94
101
|
}
|
|
95
102
|
};
|
|
96
103
|
const visitLocaleKeys = (value, visitor, pathParts = [])=>{
|
|
@@ -110,7 +117,7 @@ const checkPluralResources = (root, filePath, json, requiredSuffixes, pluralSuff
|
|
|
110
117
|
visitLocaleKeys(json, (key, value, pathParts)=>{
|
|
111
118
|
if ('string' != typeof value || !value.includes('{{count}}')) return;
|
|
112
119
|
const suffixMatch = key.match(pluralSuffixPattern);
|
|
113
|
-
if (!suffixMatch) throw new
|
|
120
|
+
if (!suffixMatch) throw new SourceViolation(`${relative} key ${pathParts.join('.')} contains {{count}} but is not plural-suffixed.`);
|
|
114
121
|
const [, base = '', suffix = ''] = suffixMatch;
|
|
115
122
|
const parentPath = pathParts.slice(0, -1).join('.');
|
|
116
123
|
const groupKey = `${parentPath}.${base}`;
|
|
@@ -118,7 +125,7 @@ const checkPluralResources = (root, filePath, json, requiredSuffixes, pluralSuff
|
|
|
118
125
|
existing.add(suffix);
|
|
119
126
|
groups.set(groupKey, existing);
|
|
120
127
|
});
|
|
121
|
-
for (const [group, suffixes] of groups)for (const suffix of requiredSuffixes)if (!suffixes.has(suffix)) throw new
|
|
128
|
+
for (const [group, suffixes] of groups)for (const suffix of requiredSuffixes)if (!suffixes.has(suffix)) throw new SourceViolation(`${relative} plural group ${group} is missing _${suffix}.`);
|
|
122
129
|
};
|
|
123
130
|
const runRuntimeAndLocaleResourceChecks = (root, sourceRoots, locales, pluralCategories)=>{
|
|
124
131
|
if (0 === locales.length) return;
|
|
@@ -140,11 +147,7 @@ const runRuntimeAndLocaleResourceChecks = (root, sourceRoots, locales, pluralCat
|
|
|
140
147
|
checkPluralResources(root, filePath, JSON.parse(readText(filePath)), localeCategories.get(language) ?? [], pluralSuffixPattern);
|
|
141
148
|
}
|
|
142
149
|
};
|
|
143
|
-
const runWorkspaceSourceCheck = ({ cwd = process.cwd(), sourceRoots =
|
|
144
|
-
'apps',
|
|
145
|
-
'verticals',
|
|
146
|
-
'packages'
|
|
147
|
-
], locales = DEFAULT_LOCALES, pluralCategories } = {})=>{
|
|
150
|
+
const runWorkspaceSourceCheck = ({ cwd = process.cwd(), sourceRoots = DEFAULT_SOURCE_ROOTS, locales = DEFAULT_LOCALES, pluralCategories } = {})=>{
|
|
148
151
|
const oxlintResult = (0, external_oxlint_cjs_namespaceObject.runOxlintRules)({
|
|
149
152
|
cwd,
|
|
150
153
|
targets: sourceRoots,
|
|
@@ -177,20 +180,90 @@ const runWorkspaceSourceCheck = ({ cwd = process.cwd(), sourceRoots = [
|
|
|
177
180
|
try {
|
|
178
181
|
runRuntimeAndLocaleResourceChecks(cwd, sourceRoots, locales, pluralCategories);
|
|
179
182
|
} catch (error) {
|
|
180
|
-
console.error(error instanceof Error ? error.message :
|
|
181
|
-
return 1;
|
|
183
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
184
|
+
return error instanceof SourceViolation ? 1 : 2;
|
|
182
185
|
}
|
|
183
186
|
console.log(WORKSPACE_SOURCE_SUCCESS);
|
|
184
187
|
return 0;
|
|
185
188
|
};
|
|
186
|
-
const
|
|
187
|
-
|
|
189
|
+
const USAGE = `modern-i18n-check [--workspace-root <path>]
|
|
190
|
+
Runs the UltraModern i18n and boundary source checks for a workspace.
|
|
191
|
+
Workspace defaults to ULTRAMODERN_WORKSPACE_ROOT then cwd. Options come from
|
|
192
|
+
package.json "modernjs.i18nCheck": { sourceRoots, locales, pluralCategories }.
|
|
193
|
+
Exit codes: 0 valid, 1 source violation, 2 tool/configuration failure.`;
|
|
194
|
+
const isStringArray = (value)=>Array.isArray(value) && value.every((entry)=>'string' == typeof entry && entry.length > 0);
|
|
195
|
+
const isLocaleArray = (value)=>{
|
|
196
|
+
if (!isStringArray(value)) return false;
|
|
197
|
+
try {
|
|
198
|
+
Intl.getCanonicalLocales(value);
|
|
199
|
+
return true;
|
|
200
|
+
} catch {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
const isRecord = (value)=>'object' == typeof value && null !== value && !Array.isArray(value);
|
|
205
|
+
const isWorkspaceDirectory = (root, entry)=>{
|
|
206
|
+
if (external_node_path_default().isAbsolute(entry)) return false;
|
|
207
|
+
const target = external_node_path_default().resolve(root, entry);
|
|
208
|
+
if (!external_node_fs_default().existsSync(target) || !external_node_fs_default().statSync(target).isDirectory()) return false;
|
|
209
|
+
const relative = external_node_path_default().relative(external_node_fs_default().realpathSync(root), external_node_fs_default().realpathSync(target));
|
|
210
|
+
return '' !== relative && '..' !== relative && !relative.startsWith(`..${external_node_path_default().sep}`) && !external_node_path_default().isAbsolute(relative);
|
|
211
|
+
};
|
|
212
|
+
const CLDR_PLURAL_CATEGORIES = new Set([
|
|
213
|
+
'zero',
|
|
214
|
+
'one',
|
|
215
|
+
'two',
|
|
216
|
+
'few',
|
|
217
|
+
'many',
|
|
218
|
+
'other'
|
|
219
|
+
]);
|
|
220
|
+
const isPluralCategories = (value)=>isRecord(value) && Object.values(value).every((categories)=>isStringArray(categories) && categories.includes('other') && categories.every((category)=>CLDR_PLURAL_CATEGORIES.has(category)));
|
|
221
|
+
const readWorkspaceCheckOptions = (root)=>{
|
|
222
|
+
const manifestPath = external_node_path_default().join(root, 'package.json');
|
|
223
|
+
const modernjs = JSON.parse(readText(manifestPath)).modernjs;
|
|
224
|
+
const config = isRecord(modernjs) && Object.hasOwn(modernjs, 'i18nCheck') ? modernjs.i18nCheck : {};
|
|
225
|
+
const invalid = (field, expected)=>new Error(`${manifestPath} "modernjs.i18nCheck${field}" must be ${expected}.`);
|
|
226
|
+
if (!isRecord(config)) throw invalid('', 'an object');
|
|
227
|
+
const { locales, pluralCategories } = config;
|
|
228
|
+
if (void 0 !== locales && !isLocaleArray(locales)) throw invalid('.locales', 'an array of BCP 47 locale codes');
|
|
229
|
+
if (void 0 !== pluralCategories && !isPluralCategories(pluralCategories)) throw invalid('.pluralCategories', 'an object of locale -> CLDR plural categories including "other"');
|
|
230
|
+
const sourceRoots = Object.hasOwn(config, 'sourceRoots') ? config.sourceRoots : DEFAULT_SOURCE_ROOTS.filter((entry)=>isWorkspaceDirectory(root, entry));
|
|
231
|
+
if (!isStringArray(sourceRoots) || 0 === sourceRoots.length || !sourceRoots.every((entry)=>isWorkspaceDirectory(root, entry))) throw invalid('.sourceRoots', `a non-empty array of existing workspace-relative directories (default: whichever of ${DEFAULT_SOURCE_ROOTS.join(', ')} exist)`);
|
|
232
|
+
return {
|
|
233
|
+
cwd: root,
|
|
234
|
+
sourceRoots,
|
|
235
|
+
locales,
|
|
236
|
+
pluralCategories
|
|
237
|
+
};
|
|
238
|
+
};
|
|
239
|
+
const runWorkspaceSourceCheckCli = (args = process.argv.slice(2))=>{
|
|
240
|
+
let workspaceRoot = process.env.ULTRAMODERN_WORKSPACE_ROOT ?? process.cwd();
|
|
241
|
+
for(let index = 0; index < args.length; index += 1){
|
|
242
|
+
const argument = args[index];
|
|
243
|
+
if ('--help' === argument || '-h' === argument) {
|
|
244
|
+
console.log(USAGE);
|
|
245
|
+
return 0;
|
|
246
|
+
}
|
|
247
|
+
const value = args[index + 1];
|
|
248
|
+
if ('--workspace-root' !== argument || !value || value.startsWith('--')) {
|
|
249
|
+
console.error(`Invalid argument: ${argument ?? ''}. Use --help.`);
|
|
250
|
+
return 2;
|
|
251
|
+
}
|
|
252
|
+
workspaceRoot = value;
|
|
253
|
+
index += 1;
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
return runWorkspaceSourceCheck(readWorkspaceCheckOptions(external_node_path_default().resolve(workspaceRoot)));
|
|
257
|
+
} catch (error) {
|
|
258
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
259
|
+
return 2;
|
|
260
|
+
}
|
|
188
261
|
};
|
|
189
|
-
exports.main = __webpack_exports__.main;
|
|
190
262
|
exports.runWorkspaceSourceCheck = __webpack_exports__.runWorkspaceSourceCheck;
|
|
263
|
+
exports.runWorkspaceSourceCheckCli = __webpack_exports__.runWorkspaceSourceCheckCli;
|
|
191
264
|
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
192
|
-
"
|
|
193
|
-
"
|
|
265
|
+
"runWorkspaceSourceCheck",
|
|
266
|
+
"runWorkspaceSourceCheckCli"
|
|
194
267
|
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
195
268
|
Object.defineProperty(exports, '__esModule', {
|
|
196
269
|
value: true
|