@bleedingdev/modern-js-code-tools 3.9.0-ultramodern.2 → 3.9.0-ultramodern.20

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.
Files changed (42) hide show
  1. package/bin/modern-api-check-files.mjs +7 -0
  2. package/bin/modern-api-check.mjs +4 -0
  3. package/bin/modern-i18n-check.mjs +4 -0
  4. package/dist/cjs/cli/microvertical-api-check.cjs +100 -0
  5. package/dist/cjs/cli/oxlint.cjs +9 -5
  6. package/dist/cjs/cli/workspace-source-check.cjs +90 -17
  7. package/dist/cjs/microvertical-api-baseline.cjs +505 -0
  8. package/dist/cjs/microvertical-api-boundary.cjs +586 -0
  9. package/dist/cjs/microvertical-api-owner.cjs +217 -0
  10. package/dist/cjs/module-graph.cjs +321 -0
  11. package/dist/cjs/oxlint-plugin/rules/strict-effect-api-boundaries.cjs +8 -6
  12. package/dist/cjs/source-analysis.cjs +104 -0
  13. package/dist/cjs/strict-effect-runtime.cjs +376 -0
  14. package/dist/esm/cli/microvertical-api-check.js +52 -0
  15. package/dist/esm/cli/oxlint.js +10 -6
  16. package/dist/esm/cli/workspace-source-check.js +86 -13
  17. package/dist/esm/microvertical-api-baseline.js +464 -0
  18. package/dist/esm/microvertical-api-boundary.js +525 -0
  19. package/dist/esm/microvertical-api-owner.js +162 -0
  20. package/dist/esm/module-graph.js +260 -0
  21. package/dist/esm/oxlint-plugin/rules/strict-effect-api-boundaries.js +8 -6
  22. package/dist/esm/source-analysis.js +44 -0
  23. package/dist/esm/strict-effect-runtime.js +324 -0
  24. package/dist/esm-node/cli/microvertical-api-check.js +53 -0
  25. package/dist/esm-node/cli/oxlint.js +10 -6
  26. package/dist/esm-node/cli/workspace-source-check.js +86 -13
  27. package/dist/esm-node/microvertical-api-baseline.js +465 -0
  28. package/dist/esm-node/microvertical-api-boundary.js +526 -0
  29. package/dist/esm-node/microvertical-api-owner.js +163 -0
  30. package/dist/esm-node/module-graph.js +261 -0
  31. package/dist/esm-node/oxlint-plugin/rules/strict-effect-api-boundaries.js +8 -6
  32. package/dist/esm-node/source-analysis.js +45 -0
  33. package/dist/esm-node/strict-effect-runtime.js +325 -0
  34. package/dist/types/cli/microvertical-api-check.d.ts +1 -0
  35. package/dist/types/cli/workspace-source-check.d.ts +1 -1
  36. package/dist/types/microvertical-api-baseline.d.ts +23 -0
  37. package/dist/types/microvertical-api-boundary.d.ts +46 -0
  38. package/dist/types/microvertical-api-owner.d.ts +6 -0
  39. package/dist/types/module-graph.d.ts +52 -0
  40. package/dist/types/source-analysis.d.ts +10 -0
  41. package/dist/types/strict-effect-runtime.d.ts +10 -0
  42. package/package.json +29 -6
@@ -0,0 +1,376 @@
1
+ "use strict";
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
+ })();
12
+ (()=>{
13
+ __webpack_require__.d = (exports1, getters, values)=>{
14
+ var define = (defs, kind)=>{
15
+ for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
16
+ enumerable: true,
17
+ [kind]: defs[key]
18
+ });
19
+ };
20
+ define(getters, "get");
21
+ define(values, "value");
22
+ };
23
+ })();
24
+ (()=>{
25
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
26
+ })();
27
+ (()=>{
28
+ __webpack_require__.r = (exports1)=>{
29
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
30
+ value: 'Module'
31
+ });
32
+ Object.defineProperty(exports1, '__esModule', {
33
+ value: true
34
+ });
35
+ };
36
+ })();
37
+ var __webpack_exports__ = {};
38
+ __webpack_require__.r(__webpack_exports__);
39
+ __webpack_require__.d(__webpack_exports__, {
40
+ createEffectApiImportResolver: ()=>createEffectApiImportResolver,
41
+ strictEffectRuntimeTopologyViolation: ()=>strictEffectRuntimeTopologyViolation
42
+ });
43
+ const external_node_fs_namespaceObject = require("node:fs");
44
+ var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_namespaceObject);
45
+ const external_node_path_namespaceObject = require("node:path");
46
+ var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
47
+ const types_namespaceObject = require("@babel/types");
48
+ const external_source_analysis_cjs_namespaceObject = require("./source-analysis.cjs");
49
+ const edge = '@modern-js/bff-effect/effect-edge';
50
+ const nodeRuntime = '@modern-js/bff-effect/effect';
51
+ const assembly = '@modern-js/bff-effect/assembly';
52
+ const failure = 'Generated API entries must export defineEffectBff(...) or the native Effect BFF assembly helper with an explicitly composed handler Layer and an unshadowed executable root; entries must implement handlers through HttpApiBuilder.group.';
53
+ const MAX_API_SOURCE_MODULES = 256;
54
+ function createEffectApiImportResolver(filename) {
55
+ const directory = external_node_fs_default().realpathSync(external_node_path_default().dirname(filename));
56
+ const owner = external_node_fs_default().realpathSync(external_node_path_default().resolve(directory, '..'));
57
+ const resolve = (from)=>(specifier)=>{
58
+ if (!specifier.startsWith('.')) return;
59
+ const target = external_node_path_default().resolve(external_node_path_default().dirname(from), specifier);
60
+ if (!target.startsWith(`${owner}${external_node_path_default().sep}`)) return;
61
+ for (const candidate of [
62
+ target,
63
+ `${target}.ts`,
64
+ `${target}.mts`
65
+ ])try {
66
+ const real = external_node_fs_default().realpathSync(candidate);
67
+ if (!real.startsWith(`${owner}${external_node_path_default().sep}`) || external_node_fs_default().statSync(real).size > 1000000) return;
68
+ return {
69
+ id: real,
70
+ source: external_node_fs_default().readFileSync(real, 'utf8'),
71
+ resolveImport: resolve(real)
72
+ };
73
+ } catch {}
74
+ };
75
+ return resolve(external_node_path_default().join(directory, external_node_path_default().basename(filename)));
76
+ }
77
+ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
78
+ const modules = new Map();
79
+ const owners = new Map();
80
+ const paths = new Map();
81
+ const mutated = new Set();
82
+ const load = (input)=>{
83
+ const existing = modules.get(input.id);
84
+ if (existing) return existing;
85
+ if (modules.size >= MAX_API_SOURCE_MODULES || input.source.length > 1000000) throw new external_source_analysis_cjs_namespaceObject.SourceSyntaxError('API source budget exceeded');
86
+ const file = (0, external_source_analysis_cjs_namespaceObject.parseSource)(input.source, input.id, 'Invalid API syntax');
87
+ const module = {
88
+ ...input,
89
+ file
90
+ };
91
+ modules.set(input.id, module);
92
+ (0, external_source_analysis_cjs_namespaceObject.traverseSource)(file, {
93
+ enter (nodePath) {
94
+ paths.set(nodePath.node, nodePath);
95
+ owners.set(nodePath.node, module);
96
+ if (nodePath.isAssignmentExpression() || nodePath.isUpdateExpression() || nodePath.isUnaryExpression({
97
+ operator: 'delete'
98
+ })) {
99
+ let target = nodePath.isAssignmentExpression() ? nodePath.node.left : nodePath.node.argument;
100
+ while(types_namespaceObject.isMemberExpression(target))target = target.object;
101
+ if (types_namespaceObject.isIdentifier(target)) {
102
+ const binding = nodePath.scope.getBinding(target.name);
103
+ if (binding) mutated.add(binding);
104
+ }
105
+ }
106
+ if (nodePath.isTSModuleDeclaration() && types_namespaceObject.isIdentifier(nodePath.node.id)) {
107
+ const binding = nodePath.scope.getBinding(nodePath.node.id.name);
108
+ if (binding) mutated.add(binding);
109
+ }
110
+ }
111
+ });
112
+ return module;
113
+ };
114
+ try {
115
+ const entry = load({
116
+ id: '/entry.ts',
117
+ source,
118
+ resolveImport
119
+ });
120
+ const unwrap = (node)=>(0, external_source_analysis_cjs_namespaceObject.unwrapExpression)(node, true);
121
+ const binding = (node)=>{
122
+ node = unwrap(node);
123
+ if (!types_namespaceObject.isIdentifier(node)) return;
124
+ const nodePath = paths.get(node);
125
+ if (!nodePath) throw new Error('Missing API lexical scope');
126
+ const value = nodePath.scope.getBinding(node.name);
127
+ return value?.constant && !mutated.has(value) ? value : void 0;
128
+ };
129
+ const declaration = (node)=>binding(node)?.path.node;
130
+ const imported = (node)=>{
131
+ const value = binding(node);
132
+ const decl = value?.path.node;
133
+ if (!decl || !types_namespaceObject.isImportSpecifier(decl) && !types_namespaceObject.isImportNamespaceSpecifier(decl) || types_namespaceObject.isImportSpecifier(decl) && 'type' === decl.importKind) return;
134
+ const statement = value.path.parent;
135
+ if (!types_namespaceObject.isImportDeclaration(statement) || 'type' === statement.importKind) return;
136
+ return {
137
+ namespace: types_namespaceObject.isImportNamespaceSpecifier(decl),
138
+ name: types_namespaceObject.isImportNamespaceSpecifier(decl) ? '*' : types_namespaceObject.isIdentifier(decl.imported) ? decl.imported.name : decl.imported.value,
139
+ specifier: statement.source.value
140
+ };
141
+ };
142
+ const namespaceSources = {
143
+ HttpApi: [
144
+ edge,
145
+ 'effect/unstable/httpapi'
146
+ ],
147
+ HttpApiBuilder: [
148
+ edge,
149
+ 'effect/unstable/httpapi'
150
+ ],
151
+ HttpRouter: [
152
+ edge,
153
+ 'effect/unstable/http'
154
+ ]
155
+ };
156
+ const native = (node, name, sources)=>{
157
+ const value = imported(node);
158
+ if (!value) return false;
159
+ const importedNameMatches = value.namespace ? value.specifier === `effect/${name}` : value.name === name && value.specifier !== `effect/${name}`;
160
+ return importedNameMatches && (sources ?? namespaceSources[name] ?? [
161
+ edge
162
+ ]).includes(value.specifier);
163
+ };
164
+ const method = (node, namespace, member, sources)=>{
165
+ node = unwrap(node);
166
+ return types_namespaceObject.isMemberExpression(node) && !node.computed && types_namespaceObject.isIdentifier(node.property, {
167
+ name: member
168
+ }) && native(node.object, namespace, sources);
169
+ };
170
+ const initialized = (node)=>{
171
+ const value = binding(node);
172
+ return value?.kind === 'const' && types_namespaceObject.isVariableDeclarator(value.path.node) ? value.path.node.init ?? void 0 : void 0;
173
+ };
174
+ const moduleFor = (node)=>{
175
+ const value = imported(node);
176
+ if (!value) return;
177
+ const input = owners.get(node)?.resolveImport?.(value.specifier);
178
+ return input ? {
179
+ module: load(input),
180
+ name: value.name
181
+ } : void 0;
182
+ };
183
+ const exported = (module, name)=>{
184
+ for (const statement of module.file.program.body)if (types_namespaceObject.isExportNamedDeclaration(statement) && 'type' !== statement.exportKind) {
185
+ if (types_namespaceObject.isVariableDeclaration(statement.declaration)) {
186
+ for (const decl of statement.declaration.declarations)if (types_namespaceObject.isIdentifier(decl.id, {
187
+ name
188
+ })) return decl.id;
189
+ }
190
+ if (!statement.source) {
191
+ const specifier = statement.specifiers.find((element)=>types_namespaceObject.isExportSpecifier(element) && 'type' !== element.exportKind && (types_namespaceObject.isIdentifier(element.exported) ? element.exported.name : element.exported.value) === name);
192
+ if (types_namespaceObject.isExportSpecifier(specifier)) return specifier.local;
193
+ }
194
+ }
195
+ };
196
+ const externalValue = (node)=>{
197
+ const value = moduleFor(node);
198
+ return value ? exported(value.module, value.name) : void 0;
199
+ };
200
+ const apiIdentity = (node, seen = new Set())=>{
201
+ node = unwrap(node);
202
+ if (seen.has(node)) return;
203
+ seen.add(node);
204
+ const value = imported(node);
205
+ if (value) {
206
+ if (!value.specifier.startsWith('.')) return;
207
+ const module = moduleFor(node);
208
+ const exportedValue = module && exported(module.module, module.name);
209
+ if (exportedValue) return apiIdentity(exportedValue, seen);
210
+ if (module && /(?:^|\/)shared\/(?:api|rpc)\.ts$/u.test(value.specifier)) return `${module.module.id}:import:${value.name}`;
211
+ if (!owners.get(node)?.resolveImport && /(?:^|\/)shared\/(?:api|rpc)\.ts$/u.test(value.specifier)) return `${value.specifier}:${value.name}`;
212
+ return;
213
+ }
214
+ const init = initialized(node);
215
+ if (init && types_namespaceObject.isIdentifier(unwrap(init))) return apiIdentity(init, seen);
216
+ const valueBinding = binding(node);
217
+ return valueBinding ? `${owners.get(valueBinding.path.node)?.id}:${valueBinding.identifier.start}` : void 0;
218
+ };
219
+ const properties = (node)=>{
220
+ node = unwrap(node);
221
+ if (!types_namespaceObject.isObjectExpression(node)) return;
222
+ const result = new Map();
223
+ for (const prop of node.properties){
224
+ if (!types_namespaceObject.isObjectProperty(prop) || prop.computed || !types_namespaceObject.isIdentifier(prop.key) || result.has(prop.key.name)) return;
225
+ result.set(prop.key.name, prop.value);
226
+ }
227
+ return result;
228
+ };
229
+ const active = new Set();
230
+ const guarded = (node, check)=>{
231
+ node = unwrap(node);
232
+ if (active.has(node) || active.size > 128) return false;
233
+ active.add(node);
234
+ try {
235
+ return check(node);
236
+ } finally{
237
+ active.delete(node);
238
+ }
239
+ };
240
+ const layerSources = [
241
+ edge,
242
+ 'effect',
243
+ 'effect/Layer'
244
+ ];
245
+ const returned = (body)=>{
246
+ if (!types_namespaceObject.isBlockStatement(body)) return body;
247
+ const statements = body.body;
248
+ const last = statements.at(-1);
249
+ return statements.slice(0, -1).every((statement)=>types_namespaceObject.isVariableDeclaration(statement) || types_namespaceObject.isTSTypeAliasDeclaration(statement) || types_namespaceObject.isTSInterfaceDeclaration(statement)) && types_namespaceObject.isReturnStatement(last) ? last.argument : void 0;
250
+ };
251
+ const pipe = (node, check)=>types_namespaceObject.isMemberExpression(node.callee) && !node.callee.computed && types_namespaceObject.isIdentifier(node.callee.property, {
252
+ name: 'pipe'
253
+ }) && check(node.callee.object) && node.arguments.every((argument)=>method(argument, 'Layer', 'orDie', layerSources) || types_namespaceObject.isCallExpression(argument) && method(argument.callee, 'Layer', 'provide', layerSources) && argument.arguments.length > 0);
254
+ const handled = (node)=>{
255
+ node = unwrap(node);
256
+ if (!types_namespaceObject.isArrowFunctionExpression(node) && !types_namespaceObject.isFunctionExpression(node)) return false;
257
+ const parameter = node.params[0];
258
+ if (!types_namespaceObject.isIdentifier(parameter)) return false;
259
+ const body = returned(node.body);
260
+ const chain = (value, count = 0)=>{
261
+ value = unwrap(value);
262
+ if (types_namespaceObject.isIdentifier(value)) return count > 0 && declaration(value) === parameter;
263
+ return types_namespaceObject.isCallExpression(value) && types_namespaceObject.isMemberExpression(value.callee) && !value.callee.computed && types_namespaceObject.isIdentifier(value.callee.property) && [
264
+ 'handle',
265
+ 'handleRaw'
266
+ ].includes(value.callee.property.name) && 2 === value.arguments.length && chain(value.callee.object, count + 1);
267
+ };
268
+ return !!body && chain(body);
269
+ };
270
+ const handlers = (node, api)=>guarded(node, (node)=>{
271
+ const init = initialized(node) ?? externalValue(node);
272
+ if (init) return handlers(init, api);
273
+ if (!types_namespaceObject.isCallExpression(node)) return false;
274
+ if (pipe(node, (value)=>handlers(value, api))) return true;
275
+ if (method(node.callee, 'Layer', 'mergeAll', layerSources)) return node.arguments.length > 0 && node.arguments.every((argument)=>handlers(argument, api));
276
+ return method(node.callee, 'HttpApiBuilder', 'group') && 3 === node.arguments.length && apiIdentity(node.arguments[0]) === api && handled(node.arguments[2]);
277
+ });
278
+ const transport = (node)=>guarded(node, (node)=>{
279
+ const init = initialized(node);
280
+ if (init) return transport(init);
281
+ if (!types_namespaceObject.isCallExpression(node)) return method(node, 'Layer', 'empty', layerSources);
282
+ return pipe(node, transport) || method(node.callee, 'HttpRouter', 'cors') || method(node.callee, 'Layer', 'mergeAll', layerSources) && node.arguments.length > 0 && node.arguments.every(transport);
283
+ });
284
+ const directLayer = (node, api)=>guarded(node, (node)=>{
285
+ const init = initialized(node);
286
+ if (init) return directLayer(init, api);
287
+ if (!types_namespaceObject.isCallExpression(node) || !types_namespaceObject.isMemberExpression(node.callee) || node.callee.computed || !types_namespaceObject.isIdentifier(node.callee.property, {
288
+ name: 'pipe'
289
+ })) return false;
290
+ const base = unwrap(node.callee.object);
291
+ return types_namespaceObject.isCallExpression(base) && method(base.callee, 'HttpApiBuilder', 'layer') && 1 === base.arguments.length && apiIdentity(base.arguments[0]) === api && node.arguments.length > 0 && node.arguments.every((argument)=>types_namespaceObject.isCallExpression(argument) && method(argument.callee, 'Layer', 'provide', layerSources) && 1 === argument.arguments.length && handlers(argument.arguments[0], api));
292
+ });
293
+ const rpc = (values)=>{
294
+ const api = values.get('api');
295
+ const apiLayer = values.get('layer');
296
+ const apiInit = api && (initialized(api) ?? unwrap(api));
297
+ if (!apiInit || !types_namespaceObject.isCallExpression(apiInit) || !method(apiInit.callee, 'HttpApi', 'make') || 1 !== apiInit.arguments.length || !apiLayer || !transport(apiLayer)) return false;
298
+ const config = values.get('rpc');
299
+ const fields = config && properties(config);
300
+ const route = fields?.get('path');
301
+ const serialization = fields?.get('serialization');
302
+ if (!types_namespaceObject.isStringLiteral(route, {
303
+ value: '/rpc'
304
+ }) || !types_namespaceObject.isStringLiteral(serialization, {
305
+ value: 'json'
306
+ })) return false;
307
+ const group = fields?.get('group');
308
+ const layer = fields?.get('layer');
309
+ const value = group && imported(group);
310
+ if (!group || !layer || value?.specifier !== '../shared/rpc.ts') return false;
311
+ const groupValue = externalValue(group);
312
+ const groupInit = groupValue && initialized(groupValue);
313
+ if (!groupInit || !types_namespaceObject.isCallExpression(groupInit) || !method(groupInit.callee, 'RpcGroup', 'make', [
314
+ 'effect/unstable/rpc',
315
+ '@modern-js/bff-effect/effect-client'
316
+ ])) return false;
317
+ const layerInit = initialized(layer) ?? unwrap(layer);
318
+ if (!types_namespaceObject.isCallExpression(layerInit) || !types_namespaceObject.isMemberExpression(layerInit.callee) || layerInit.callee.computed || !types_namespaceObject.isIdentifier(layerInit.callee.property, {
319
+ name: 'toLayer'
320
+ }) || apiIdentity(layerInit.callee.object) !== apiIdentity(group) || 1 !== layerInit.arguments.length) return false;
321
+ const implementation = unwrap(layerInit.arguments[0]);
322
+ if (!types_namespaceObject.isCallExpression(implementation) || !types_namespaceObject.isMemberExpression(implementation.callee) || implementation.callee.computed || !types_namespaceObject.isIdentifier(implementation.callee.property, {
323
+ name: 'of'
324
+ }) || apiIdentity(implementation.callee.object) !== apiIdentity(group) || 1 !== implementation.arguments.length) return false;
325
+ const entries = properties(implementation.arguments[0]);
326
+ return !!entries?.size && [
327
+ ...entries.values()
328
+ ].every((value)=>types_namespaceObject.isArrowFunctionExpression(value) || types_namespaceObject.isFunctionExpression(value));
329
+ };
330
+ const runtime = (node)=>guarded(node, (node)=>{
331
+ const init = initialized(node);
332
+ if (init) return runtime(init);
333
+ if (!types_namespaceObject.isCallExpression(node)) return false;
334
+ const value = imported(node.callee);
335
+ if (value?.name === 'defineEffectBff' && [
336
+ edge,
337
+ nodeRuntime
338
+ ].includes(value.specifier) || value?.name === 'assembleEffectBffRuntime' && value.specifier === assembly) {
339
+ if (1 !== node.arguments.length) return false;
340
+ const values = properties(node.arguments[0]);
341
+ if (!values) return false;
342
+ if ('defineEffectBff' === value.name && values.has('rpc')) return rpc(values);
343
+ const api = values.get('api');
344
+ const apiBinding = api && imported(api);
345
+ if (!api || apiBinding?.specifier !== '../shared/api.ts') return false;
346
+ const identity = apiIdentity(api);
347
+ if (!identity) return false;
348
+ const layer = values.get('defineEffectBff' === value.name ? 'layer' : 'handlers');
349
+ const extra = values.get('transport');
350
+ return void 0 !== layer && ('defineEffectBff' === value.name ? directLayer(layer, identity) : handlers(layer, identity)) && (!extra || transport(extra));
351
+ }
352
+ const decl = declaration(node.callee);
353
+ const factory = decl && types_namespaceObject.isFunctionDeclaration(decl) ? decl : initialized(node.callee);
354
+ if (!factory || !types_namespaceObject.isFunctionDeclaration(factory) && !types_namespaceObject.isArrowFunctionExpression(factory) && !types_namespaceObject.isFunctionExpression(factory) || !factory.body) return false;
355
+ const result = returned(factory.body);
356
+ return !!result && runtime(result);
357
+ });
358
+ const root = entry.file.program.body.find((statement)=>types_namespaceObject.isExportDefaultDeclaration(statement));
359
+ return root && runtime(root.declaration) ? void 0 : failure;
360
+ } catch (cause) {
361
+ if (cause instanceof external_source_analysis_cjs_namespaceObject.SourceSyntaxError) return failure;
362
+ const message = 'object' == typeof cause && null !== cause && 'message' in cause && 'string' == typeof cause.message ? cause.message : String(cause);
363
+ throw new Error(`Strict Effect API analysis failed: ${message}`, {
364
+ cause
365
+ });
366
+ }
367
+ }
368
+ exports.createEffectApiImportResolver = __webpack_exports__.createEffectApiImportResolver;
369
+ exports.strictEffectRuntimeTopologyViolation = __webpack_exports__.strictEffectRuntimeTopologyViolation;
370
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
371
+ "createEffectApiImportResolver",
372
+ "strictEffectRuntimeTopologyViolation"
373
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
374
+ Object.defineProperty(exports, '__esModule', {
375
+ value: true
376
+ });
@@ -0,0 +1,52 @@
1
+ import node_path from "node:path";
2
+ import { pathToFileURL } from "node:url";
3
+ import { checkMicroVerticalApiBoundaries, checkMicroVerticalApiConsumerFiles } from "../microvertical-api-boundary.js";
4
+ const flags = [
5
+ '--workspace-root',
6
+ '--baseline-package-directory',
7
+ '--rules'
8
+ ];
9
+ async function loadContractRules(file) {
10
+ const rules = (await import(pathToFileURL(node_path.resolve(file)).href)).default;
11
+ if (!Array.isArray(rules) || !rules.every((rule)=>'function' == typeof rule)) throw new Error(`${file}: default export must be an array of contract rule functions, e.g. \`export default [myRule]\``);
12
+ return rules;
13
+ }
14
+ async function runMicroVerticalApiCheckCli(args = process.argv.slice(2), filesOnly = false) {
15
+ let workspaceRoot = process.env.ULTRAMODERN_WORKSPACE_ROOT ?? process.cwd();
16
+ let baselinePackageDirectory;
17
+ const ruleFiles = [];
18
+ for(let index = 0; index < args.length; index += 1){
19
+ const argument = args[index];
20
+ if ('--help' === argument || '-h' === argument) {
21
+ 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.'}\n--rules loads a module whose default export is an array of contract rules; each rule receives the module graph of every API contract.\nWorkspace defaults to ULTRAMODERN_WORKSPACE_ROOT then cwd. Exit codes: 0 valid, 1 consumer violation, 2 tool/configuration failure.`);
22
+ return 0;
23
+ }
24
+ if (!flags.includes(argument ?? '') || !args[index + 1] || args[index + 1]?.startsWith('--')) {
25
+ console.error(`Invalid argument: ${argument ?? ''}. Use --help.`);
26
+ return 2;
27
+ }
28
+ const value = args[++index];
29
+ if ('--workspace-root' === argument) workspaceRoot = value;
30
+ else if ('--rules' === argument) ruleFiles.push(value);
31
+ else baselinePackageDirectory = value;
32
+ }
33
+ const contractRules = [];
34
+ for (const file of ruleFiles)try {
35
+ contractRules.push(...await loadContractRules(file));
36
+ } catch (error) {
37
+ console.error(`API tool error: --rules ${error instanceof Error ? error.message : String(error)}`);
38
+ return 2;
39
+ }
40
+ const result = (filesOnly ? checkMicroVerticalApiConsumerFiles : checkMicroVerticalApiBoundaries)({
41
+ workspaceRoot,
42
+ baselinePackageDirectory,
43
+ contractRules
44
+ });
45
+ for (const diagnostic of result.diagnostics)console.error(`API violation: ${diagnostic}`);
46
+ for (const error of result.toolErrors)console.error(`API tool error: ${error}`);
47
+ if (result.toolErrors.length) return 2;
48
+ if (result.diagnostics.length) return 1;
49
+ console.log(`UltraModern API ${filesOnly ? 'consumer files' : 'boundary'} check passed.`);
50
+ return 0;
51
+ }
52
+ export { runMicroVerticalApiCheckCli };
@@ -3,7 +3,7 @@ import node_fs from "node:fs";
3
3
  import { createRequire } from "node:module";
4
4
  import node_os from "node:os";
5
5
  import node_path from "node:path";
6
- import { fileURLToPath } from "node:url";
6
+ import { fileURLToPath, pathToFileURL } from "node:url";
7
7
  const oxlint_require = createRequire(import.meta.url);
8
8
  const ignoredDirectories = new Set([
9
9
  '.modern',
@@ -73,22 +73,26 @@ const runOxlintRules = ({ cwd, targets, rules })=>{
73
73
  stdout: '',
74
74
  stderr: ''
75
75
  };
76
+ const pluginPath = resolvePluginPath();
76
77
  const tempDir = node_fs.mkdtempSync(node_path.join(node_os.tmpdir(), 'modern-code-tools-oxlint-'));
77
78
  const configPath = node_path.join(tempDir, 'oxlint.config.mjs');
78
- const pluginPath = resolvePluginPath();
79
- node_fs.writeFileSync(configPath, `export default {
80
- jsPlugins: [${JSON.stringify(pluginPath)}],
79
+ const adapterPath = node_path.join(tempDir, 'oxlint-plugin.mjs');
80
+ try {
81
+ node_fs.writeFileSync(adapterPath, `import plugin from ${JSON.stringify(pathToFileURL(pluginPath).href)};
82
+ export default plugin.rules ? plugin : plugin.default;
83
+ `, 'utf-8');
84
+ node_fs.writeFileSync(configPath, `export default {
85
+ jsPlugins: [${JSON.stringify(adapterPath)}],
81
86
  rules: ${JSON.stringify(rules, null, 2)}
82
87
  };
83
88
  `, 'utf-8');
84
- try {
85
89
  const result = spawnSync(process.execPath, [
86
90
  resolveOxlintBin(),
87
91
  ...resolvedTargets,
88
92
  '--config',
89
93
  configPath,
90
94
  '--format',
91
- 'unix',
95
+ 'default',
92
96
  '--quiet'
93
97
  ], {
94
98
  cwd,
@@ -6,6 +6,11 @@ const DEFAULT_LOCALES = [
6
6
  'en',
7
7
  'cs'
8
8
  ];
9
+ const DEFAULT_SOURCE_ROOTS = [
10
+ 'apps',
11
+ 'verticals',
12
+ 'packages'
13
+ ];
9
14
  const ignoredDirectories = new Set([
10
15
  '.modern',
11
16
  '.modernjs',
@@ -39,6 +44,8 @@ const createLocaleJsonMatcher = (locales)=>{
39
44
  const resolvePluralCategories = (locale, overrides)=>overrides?.[locale] ?? new Intl.PluralRules(locale).resolvedOptions().pluralCategories;
40
45
  const readText = (filePath)=>node_fs.readFileSync(filePath, 'utf-8');
41
46
  const localeImportPattern = (locale)=>new RegExp(`import\\s+(?:\\*\\s+as\\s+)?[A-Za-z_$][\\w$]*\\s+from\\s+['"]\\.\\./locales/${escapeRegExp(locale)}/[^'"]+\\.json['"]`, 'u');
47
+ class SourceViolation extends Error {
48
+ }
42
49
  const checkRuntimeResources = (root, filePath, text, locales)=>{
43
50
  const relative = relativePath(root, filePath);
44
51
  if (!relative.endsWith('/src/modern.runtime.ts')) return;
@@ -46,7 +53,7 @@ const checkRuntimeResources = (root, filePath, text, locales)=>{
46
53
  const registersResources = /initOptions\s*:\s*\{[\s\S]*?\bresources\s*[,:}]/u.test(text);
47
54
  if (missingLocales.length > 0 || !registersResources) {
48
55
  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}).`);
56
+ 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}).`);
50
57
  }
51
58
  };
52
59
  const visitLocaleKeys = (value, visitor, pathParts = [])=>{
@@ -66,7 +73,7 @@ const checkPluralResources = (root, filePath, json, requiredSuffixes, pluralSuff
66
73
  visitLocaleKeys(json, (key, value, pathParts)=>{
67
74
  if ('string' != typeof value || !value.includes('{{count}}')) return;
68
75
  const suffixMatch = key.match(pluralSuffixPattern);
69
- if (!suffixMatch) throw new Error(`${relative} key ${pathParts.join('.')} contains {{count}} but is not plural-suffixed.`);
76
+ if (!suffixMatch) throw new SourceViolation(`${relative} key ${pathParts.join('.')} contains {{count}} but is not plural-suffixed.`);
70
77
  const [, base = '', suffix = ''] = suffixMatch;
71
78
  const parentPath = pathParts.slice(0, -1).join('.');
72
79
  const groupKey = `${parentPath}.${base}`;
@@ -74,7 +81,7 @@ const checkPluralResources = (root, filePath, json, requiredSuffixes, pluralSuff
74
81
  existing.add(suffix);
75
82
  groups.set(groupKey, existing);
76
83
  });
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}.`);
84
+ 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}.`);
78
85
  };
79
86
  const runRuntimeAndLocaleResourceChecks = (root, sourceRoots, locales, pluralCategories)=>{
80
87
  if (0 === locales.length) return;
@@ -96,11 +103,7 @@ const runRuntimeAndLocaleResourceChecks = (root, sourceRoots, locales, pluralCat
96
103
  checkPluralResources(root, filePath, JSON.parse(readText(filePath)), localeCategories.get(language) ?? [], pluralSuffixPattern);
97
104
  }
98
105
  };
99
- const runWorkspaceSourceCheck = ({ cwd = process.cwd(), sourceRoots = [
100
- 'apps',
101
- 'verticals',
102
- 'packages'
103
- ], locales = DEFAULT_LOCALES, pluralCategories } = {})=>{
106
+ const runWorkspaceSourceCheck = ({ cwd = process.cwd(), sourceRoots = DEFAULT_SOURCE_ROOTS, locales = DEFAULT_LOCALES, pluralCategories } = {})=>{
104
107
  const oxlintResult = runOxlintRules({
105
108
  cwd,
106
109
  targets: sourceRoots,
@@ -133,13 +136,83 @@ const runWorkspaceSourceCheck = ({ cwd = process.cwd(), sourceRoots = [
133
136
  try {
134
137
  runRuntimeAndLocaleResourceChecks(cwd, sourceRoots, locales, pluralCategories);
135
138
  } catch (error) {
136
- console.error(error instanceof Error ? error.message : 'UltraModern workspace source checks failed.');
137
- return 1;
139
+ console.error(error instanceof Error ? error.message : String(error));
140
+ return error instanceof SourceViolation ? 1 : 2;
138
141
  }
139
142
  console.log(WORKSPACE_SOURCE_SUCCESS);
140
143
  return 0;
141
144
  };
142
- const main = ()=>{
143
- process.exitCode = runWorkspaceSourceCheck();
145
+ const USAGE = `modern-i18n-check [--workspace-root <path>]
146
+ Runs the UltraModern i18n and boundary source checks for a workspace.
147
+ Workspace defaults to ULTRAMODERN_WORKSPACE_ROOT then cwd. Options come from
148
+ package.json "modernjs.i18nCheck": { sourceRoots, locales, pluralCategories }.
149
+ Exit codes: 0 valid, 1 source violation, 2 tool/configuration failure.`;
150
+ const isStringArray = (value)=>Array.isArray(value) && value.every((entry)=>'string' == typeof entry && entry.length > 0);
151
+ const isLocaleArray = (value)=>{
152
+ if (!isStringArray(value)) return false;
153
+ try {
154
+ Intl.getCanonicalLocales(value);
155
+ return true;
156
+ } catch {
157
+ return false;
158
+ }
159
+ };
160
+ const isRecord = (value)=>'object' == typeof value && null !== value && !Array.isArray(value);
161
+ const isWorkspaceDirectory = (root, entry)=>{
162
+ if (node_path.isAbsolute(entry)) return false;
163
+ const target = node_path.resolve(root, entry);
164
+ if (!node_fs.existsSync(target) || !node_fs.statSync(target).isDirectory()) return false;
165
+ const relative = node_path.relative(node_fs.realpathSync(root), node_fs.realpathSync(target));
166
+ return '' !== relative && '..' !== relative && !relative.startsWith(`..${node_path.sep}`) && !node_path.isAbsolute(relative);
167
+ };
168
+ const CLDR_PLURAL_CATEGORIES = new Set([
169
+ 'zero',
170
+ 'one',
171
+ 'two',
172
+ 'few',
173
+ 'many',
174
+ 'other'
175
+ ]);
176
+ const isPluralCategories = (value)=>isRecord(value) && Object.values(value).every((categories)=>isStringArray(categories) && categories.includes('other') && categories.every((category)=>CLDR_PLURAL_CATEGORIES.has(category)));
177
+ const readWorkspaceCheckOptions = (root)=>{
178
+ const manifestPath = node_path.join(root, 'package.json');
179
+ const modernjs = JSON.parse(readText(manifestPath)).modernjs;
180
+ const config = isRecord(modernjs) && Object.hasOwn(modernjs, 'i18nCheck') ? modernjs.i18nCheck : {};
181
+ const invalid = (field, expected)=>new Error(`${manifestPath} "modernjs.i18nCheck${field}" must be ${expected}.`);
182
+ if (!isRecord(config)) throw invalid('', 'an object');
183
+ const { locales, pluralCategories } = config;
184
+ if (void 0 !== locales && !isLocaleArray(locales)) throw invalid('.locales', 'an array of BCP 47 locale codes');
185
+ if (void 0 !== pluralCategories && !isPluralCategories(pluralCategories)) throw invalid('.pluralCategories', 'an object of locale -> CLDR plural categories including "other"');
186
+ const sourceRoots = Object.hasOwn(config, 'sourceRoots') ? config.sourceRoots : DEFAULT_SOURCE_ROOTS.filter((entry)=>isWorkspaceDirectory(root, entry));
187
+ 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)`);
188
+ return {
189
+ cwd: root,
190
+ sourceRoots,
191
+ locales,
192
+ pluralCategories
193
+ };
194
+ };
195
+ const runWorkspaceSourceCheckCli = (args = process.argv.slice(2))=>{
196
+ let workspaceRoot = process.env.ULTRAMODERN_WORKSPACE_ROOT ?? process.cwd();
197
+ for(let index = 0; index < args.length; index += 1){
198
+ const argument = args[index];
199
+ if ('--help' === argument || '-h' === argument) {
200
+ console.log(USAGE);
201
+ return 0;
202
+ }
203
+ const value = args[index + 1];
204
+ if ('--workspace-root' !== argument || !value || value.startsWith('--')) {
205
+ console.error(`Invalid argument: ${argument ?? ''}. Use --help.`);
206
+ return 2;
207
+ }
208
+ workspaceRoot = value;
209
+ index += 1;
210
+ }
211
+ try {
212
+ return runWorkspaceSourceCheck(readWorkspaceCheckOptions(node_path.resolve(workspaceRoot)));
213
+ } catch (error) {
214
+ console.error(error instanceof Error ? error.message : String(error));
215
+ return 2;
216
+ }
144
217
  };
145
- export { main, runWorkspaceSourceCheck };
218
+ export { runWorkspaceSourceCheck, runWorkspaceSourceCheckCli };