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

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,325 @@
1
+ import "node:module";
2
+ import node_fs from "node:fs";
3
+ import node_path from "node:path";
4
+ import { SourceSyntaxError, parseSource, traverseSource, unwrapExpression } from "./source-analysis.js";
5
+ import * as __rspack_external__babel_types_6e8337dd from "@babel/types";
6
+ const edge = '@modern-js/bff-effect/effect-edge';
7
+ const nodeRuntime = '@modern-js/bff-effect/effect';
8
+ const assembly = '@modern-js/bff-effect/assembly';
9
+ 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.';
10
+ const MAX_API_SOURCE_MODULES = 256;
11
+ function createEffectApiImportResolver(filename) {
12
+ const directory = node_fs.realpathSync(node_path.dirname(filename));
13
+ const owner = node_fs.realpathSync(node_path.resolve(directory, '..'));
14
+ const resolve = (from)=>(specifier)=>{
15
+ if (!specifier.startsWith('.')) return;
16
+ const target = node_path.resolve(node_path.dirname(from), specifier);
17
+ if (!target.startsWith(`${owner}${node_path.sep}`)) return;
18
+ for (const candidate of [
19
+ target,
20
+ `${target}.ts`,
21
+ `${target}.mts`
22
+ ])try {
23
+ const real = node_fs.realpathSync(candidate);
24
+ if (!real.startsWith(`${owner}${node_path.sep}`) || node_fs.statSync(real).size > 1000000) return;
25
+ return {
26
+ id: real,
27
+ source: node_fs.readFileSync(real, 'utf8'),
28
+ resolveImport: resolve(real)
29
+ };
30
+ } catch {}
31
+ };
32
+ return resolve(node_path.join(directory, node_path.basename(filename)));
33
+ }
34
+ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
35
+ const modules = new Map();
36
+ const owners = new Map();
37
+ const paths = new Map();
38
+ const mutated = new Set();
39
+ const load = (input)=>{
40
+ const existing = modules.get(input.id);
41
+ if (existing) return existing;
42
+ if (modules.size >= MAX_API_SOURCE_MODULES || input.source.length > 1000000) throw new SourceSyntaxError('API source budget exceeded');
43
+ const file = parseSource(input.source, input.id, 'Invalid API syntax');
44
+ const module = {
45
+ ...input,
46
+ file
47
+ };
48
+ modules.set(input.id, module);
49
+ traverseSource(file, {
50
+ enter (nodePath) {
51
+ paths.set(nodePath.node, nodePath);
52
+ owners.set(nodePath.node, module);
53
+ if (nodePath.isAssignmentExpression() || nodePath.isUpdateExpression() || nodePath.isUnaryExpression({
54
+ operator: 'delete'
55
+ })) {
56
+ let target = nodePath.isAssignmentExpression() ? nodePath.node.left : nodePath.node.argument;
57
+ while(__rspack_external__babel_types_6e8337dd.isMemberExpression(target))target = target.object;
58
+ if (__rspack_external__babel_types_6e8337dd.isIdentifier(target)) {
59
+ const binding = nodePath.scope.getBinding(target.name);
60
+ if (binding) mutated.add(binding);
61
+ }
62
+ }
63
+ if (nodePath.isTSModuleDeclaration() && __rspack_external__babel_types_6e8337dd.isIdentifier(nodePath.node.id)) {
64
+ const binding = nodePath.scope.getBinding(nodePath.node.id.name);
65
+ if (binding) mutated.add(binding);
66
+ }
67
+ }
68
+ });
69
+ return module;
70
+ };
71
+ try {
72
+ const entry = load({
73
+ id: '/entry.ts',
74
+ source,
75
+ resolveImport
76
+ });
77
+ const unwrap = (node)=>unwrapExpression(node, true);
78
+ const binding = (node)=>{
79
+ node = unwrap(node);
80
+ if (!__rspack_external__babel_types_6e8337dd.isIdentifier(node)) return;
81
+ const nodePath = paths.get(node);
82
+ if (!nodePath) throw new Error('Missing API lexical scope');
83
+ const value = nodePath.scope.getBinding(node.name);
84
+ return value?.constant && !mutated.has(value) ? value : void 0;
85
+ };
86
+ const declaration = (node)=>binding(node)?.path.node;
87
+ const imported = (node)=>{
88
+ const value = binding(node);
89
+ const decl = value?.path.node;
90
+ if (!decl || !__rspack_external__babel_types_6e8337dd.isImportSpecifier(decl) && !__rspack_external__babel_types_6e8337dd.isImportNamespaceSpecifier(decl) || __rspack_external__babel_types_6e8337dd.isImportSpecifier(decl) && 'type' === decl.importKind) return;
91
+ const statement = value.path.parent;
92
+ if (!__rspack_external__babel_types_6e8337dd.isImportDeclaration(statement) || 'type' === statement.importKind) return;
93
+ return {
94
+ namespace: __rspack_external__babel_types_6e8337dd.isImportNamespaceSpecifier(decl),
95
+ name: __rspack_external__babel_types_6e8337dd.isImportNamespaceSpecifier(decl) ? '*' : __rspack_external__babel_types_6e8337dd.isIdentifier(decl.imported) ? decl.imported.name : decl.imported.value,
96
+ specifier: statement.source.value
97
+ };
98
+ };
99
+ const namespaceSources = {
100
+ HttpApi: [
101
+ edge,
102
+ 'effect/unstable/httpapi'
103
+ ],
104
+ HttpApiBuilder: [
105
+ edge,
106
+ 'effect/unstable/httpapi'
107
+ ],
108
+ HttpRouter: [
109
+ edge,
110
+ 'effect/unstable/http'
111
+ ]
112
+ };
113
+ const native = (node, name, sources)=>{
114
+ const value = imported(node);
115
+ if (!value) return false;
116
+ const importedNameMatches = value.namespace ? value.specifier === `effect/${name}` : value.name === name && value.specifier !== `effect/${name}`;
117
+ return importedNameMatches && (sources ?? namespaceSources[name] ?? [
118
+ edge
119
+ ]).includes(value.specifier);
120
+ };
121
+ const method = (node, namespace, member, sources)=>{
122
+ node = unwrap(node);
123
+ return __rspack_external__babel_types_6e8337dd.isMemberExpression(node) && !node.computed && __rspack_external__babel_types_6e8337dd.isIdentifier(node.property, {
124
+ name: member
125
+ }) && native(node.object, namespace, sources);
126
+ };
127
+ const initialized = (node)=>{
128
+ const value = binding(node);
129
+ return value?.kind === 'const' && __rspack_external__babel_types_6e8337dd.isVariableDeclarator(value.path.node) ? value.path.node.init ?? void 0 : void 0;
130
+ };
131
+ const moduleFor = (node)=>{
132
+ const value = imported(node);
133
+ if (!value) return;
134
+ const input = owners.get(node)?.resolveImport?.(value.specifier);
135
+ return input ? {
136
+ module: load(input),
137
+ name: value.name
138
+ } : void 0;
139
+ };
140
+ const exported = (module, name)=>{
141
+ for (const statement of module.file.program.body)if (__rspack_external__babel_types_6e8337dd.isExportNamedDeclaration(statement) && 'type' !== statement.exportKind) {
142
+ if (__rspack_external__babel_types_6e8337dd.isVariableDeclaration(statement.declaration)) {
143
+ for (const decl of statement.declaration.declarations)if (__rspack_external__babel_types_6e8337dd.isIdentifier(decl.id, {
144
+ name
145
+ })) return decl.id;
146
+ }
147
+ if (!statement.source) {
148
+ const specifier = statement.specifiers.find((element)=>__rspack_external__babel_types_6e8337dd.isExportSpecifier(element) && 'type' !== element.exportKind && (__rspack_external__babel_types_6e8337dd.isIdentifier(element.exported) ? element.exported.name : element.exported.value) === name);
149
+ if (__rspack_external__babel_types_6e8337dd.isExportSpecifier(specifier)) return specifier.local;
150
+ }
151
+ }
152
+ };
153
+ const externalValue = (node)=>{
154
+ const value = moduleFor(node);
155
+ return value ? exported(value.module, value.name) : void 0;
156
+ };
157
+ const apiIdentity = (node, seen = new Set())=>{
158
+ node = unwrap(node);
159
+ if (seen.has(node)) return;
160
+ seen.add(node);
161
+ const value = imported(node);
162
+ if (value) {
163
+ if (!value.specifier.startsWith('.')) return;
164
+ const module = moduleFor(node);
165
+ const exportedValue = module && exported(module.module, module.name);
166
+ if (exportedValue) return apiIdentity(exportedValue, seen);
167
+ if (module && /(?:^|\/)shared\/(?:api|rpc)\.ts$/u.test(value.specifier)) return `${module.module.id}:import:${value.name}`;
168
+ if (!owners.get(node)?.resolveImport && /(?:^|\/)shared\/(?:api|rpc)\.ts$/u.test(value.specifier)) return `${value.specifier}:${value.name}`;
169
+ return;
170
+ }
171
+ const init = initialized(node);
172
+ if (init && __rspack_external__babel_types_6e8337dd.isIdentifier(unwrap(init))) return apiIdentity(init, seen);
173
+ const valueBinding = binding(node);
174
+ return valueBinding ? `${owners.get(valueBinding.path.node)?.id}:${valueBinding.identifier.start}` : void 0;
175
+ };
176
+ const properties = (node)=>{
177
+ node = unwrap(node);
178
+ if (!__rspack_external__babel_types_6e8337dd.isObjectExpression(node)) return;
179
+ const result = new Map();
180
+ for (const prop of node.properties){
181
+ if (!__rspack_external__babel_types_6e8337dd.isObjectProperty(prop) || prop.computed || !__rspack_external__babel_types_6e8337dd.isIdentifier(prop.key) || result.has(prop.key.name)) return;
182
+ result.set(prop.key.name, prop.value);
183
+ }
184
+ return result;
185
+ };
186
+ const active = new Set();
187
+ const guarded = (node, check)=>{
188
+ node = unwrap(node);
189
+ if (active.has(node) || active.size > 128) return false;
190
+ active.add(node);
191
+ try {
192
+ return check(node);
193
+ } finally{
194
+ active.delete(node);
195
+ }
196
+ };
197
+ const layerSources = [
198
+ edge,
199
+ 'effect',
200
+ 'effect/Layer'
201
+ ];
202
+ const returned = (body)=>{
203
+ if (!__rspack_external__babel_types_6e8337dd.isBlockStatement(body)) return body;
204
+ const statements = body.body;
205
+ const last = statements.at(-1);
206
+ return statements.slice(0, -1).every((statement)=>__rspack_external__babel_types_6e8337dd.isVariableDeclaration(statement) || __rspack_external__babel_types_6e8337dd.isTSTypeAliasDeclaration(statement) || __rspack_external__babel_types_6e8337dd.isTSInterfaceDeclaration(statement)) && __rspack_external__babel_types_6e8337dd.isReturnStatement(last) ? last.argument : void 0;
207
+ };
208
+ const pipe = (node, check)=>__rspack_external__babel_types_6e8337dd.isMemberExpression(node.callee) && !node.callee.computed && __rspack_external__babel_types_6e8337dd.isIdentifier(node.callee.property, {
209
+ name: 'pipe'
210
+ }) && check(node.callee.object) && node.arguments.every((argument)=>method(argument, 'Layer', 'orDie', layerSources) || __rspack_external__babel_types_6e8337dd.isCallExpression(argument) && method(argument.callee, 'Layer', 'provide', layerSources) && argument.arguments.length > 0);
211
+ const handled = (node)=>{
212
+ node = unwrap(node);
213
+ if (!__rspack_external__babel_types_6e8337dd.isArrowFunctionExpression(node) && !__rspack_external__babel_types_6e8337dd.isFunctionExpression(node)) return false;
214
+ const parameter = node.params[0];
215
+ if (!__rspack_external__babel_types_6e8337dd.isIdentifier(parameter)) return false;
216
+ const body = returned(node.body);
217
+ const chain = (value, count = 0)=>{
218
+ value = unwrap(value);
219
+ if (__rspack_external__babel_types_6e8337dd.isIdentifier(value)) return count > 0 && declaration(value) === parameter;
220
+ return __rspack_external__babel_types_6e8337dd.isCallExpression(value) && __rspack_external__babel_types_6e8337dd.isMemberExpression(value.callee) && !value.callee.computed && __rspack_external__babel_types_6e8337dd.isIdentifier(value.callee.property) && [
221
+ 'handle',
222
+ 'handleRaw'
223
+ ].includes(value.callee.property.name) && 2 === value.arguments.length && chain(value.callee.object, count + 1);
224
+ };
225
+ return !!body && chain(body);
226
+ };
227
+ const handlers = (node, api)=>guarded(node, (node)=>{
228
+ const init = initialized(node) ?? externalValue(node);
229
+ if (init) return handlers(init, api);
230
+ if (!__rspack_external__babel_types_6e8337dd.isCallExpression(node)) return false;
231
+ if (pipe(node, (value)=>handlers(value, api))) return true;
232
+ if (method(node.callee, 'Layer', 'mergeAll', layerSources)) return node.arguments.length > 0 && node.arguments.every((argument)=>handlers(argument, api));
233
+ return method(node.callee, 'HttpApiBuilder', 'group') && 3 === node.arguments.length && apiIdentity(node.arguments[0]) === api && handled(node.arguments[2]);
234
+ });
235
+ const transport = (node)=>guarded(node, (node)=>{
236
+ const init = initialized(node);
237
+ if (init) return transport(init);
238
+ if (!__rspack_external__babel_types_6e8337dd.isCallExpression(node)) return method(node, 'Layer', 'empty', layerSources);
239
+ return pipe(node, transport) || method(node.callee, 'HttpRouter', 'cors') || method(node.callee, 'Layer', 'mergeAll', layerSources) && node.arguments.length > 0 && node.arguments.every(transport);
240
+ });
241
+ const directLayer = (node, api)=>guarded(node, (node)=>{
242
+ const init = initialized(node);
243
+ if (init) return directLayer(init, api);
244
+ if (!__rspack_external__babel_types_6e8337dd.isCallExpression(node) || !__rspack_external__babel_types_6e8337dd.isMemberExpression(node.callee) || node.callee.computed || !__rspack_external__babel_types_6e8337dd.isIdentifier(node.callee.property, {
245
+ name: 'pipe'
246
+ })) return false;
247
+ const base = unwrap(node.callee.object);
248
+ return __rspack_external__babel_types_6e8337dd.isCallExpression(base) && method(base.callee, 'HttpApiBuilder', 'layer') && 1 === base.arguments.length && apiIdentity(base.arguments[0]) === api && node.arguments.length > 0 && node.arguments.every((argument)=>__rspack_external__babel_types_6e8337dd.isCallExpression(argument) && method(argument.callee, 'Layer', 'provide', layerSources) && 1 === argument.arguments.length && handlers(argument.arguments[0], api));
249
+ });
250
+ const rpc = (values)=>{
251
+ const api = values.get('api');
252
+ const apiLayer = values.get('layer');
253
+ const apiInit = api && (initialized(api) ?? unwrap(api));
254
+ if (!apiInit || !__rspack_external__babel_types_6e8337dd.isCallExpression(apiInit) || !method(apiInit.callee, 'HttpApi', 'make') || 1 !== apiInit.arguments.length || !apiLayer || !transport(apiLayer)) return false;
255
+ const config = values.get('rpc');
256
+ const fields = config && properties(config);
257
+ const route = fields?.get('path');
258
+ const serialization = fields?.get('serialization');
259
+ if (!__rspack_external__babel_types_6e8337dd.isStringLiteral(route, {
260
+ value: '/rpc'
261
+ }) || !__rspack_external__babel_types_6e8337dd.isStringLiteral(serialization, {
262
+ value: 'json'
263
+ })) return false;
264
+ const group = fields?.get('group');
265
+ const layer = fields?.get('layer');
266
+ const value = group && imported(group);
267
+ if (!group || !layer || value?.specifier !== '../shared/rpc.ts') return false;
268
+ const groupValue = externalValue(group);
269
+ const groupInit = groupValue && initialized(groupValue);
270
+ if (!groupInit || !__rspack_external__babel_types_6e8337dd.isCallExpression(groupInit) || !method(groupInit.callee, 'RpcGroup', 'make', [
271
+ 'effect/unstable/rpc',
272
+ '@modern-js/bff-effect/effect-client'
273
+ ])) return false;
274
+ const layerInit = initialized(layer) ?? unwrap(layer);
275
+ if (!__rspack_external__babel_types_6e8337dd.isCallExpression(layerInit) || !__rspack_external__babel_types_6e8337dd.isMemberExpression(layerInit.callee) || layerInit.callee.computed || !__rspack_external__babel_types_6e8337dd.isIdentifier(layerInit.callee.property, {
276
+ name: 'toLayer'
277
+ }) || apiIdentity(layerInit.callee.object) !== apiIdentity(group) || 1 !== layerInit.arguments.length) return false;
278
+ const implementation = unwrap(layerInit.arguments[0]);
279
+ if (!__rspack_external__babel_types_6e8337dd.isCallExpression(implementation) || !__rspack_external__babel_types_6e8337dd.isMemberExpression(implementation.callee) || implementation.callee.computed || !__rspack_external__babel_types_6e8337dd.isIdentifier(implementation.callee.property, {
280
+ name: 'of'
281
+ }) || apiIdentity(implementation.callee.object) !== apiIdentity(group) || 1 !== implementation.arguments.length) return false;
282
+ const entries = properties(implementation.arguments[0]);
283
+ return !!entries?.size && [
284
+ ...entries.values()
285
+ ].every((value)=>__rspack_external__babel_types_6e8337dd.isArrowFunctionExpression(value) || __rspack_external__babel_types_6e8337dd.isFunctionExpression(value));
286
+ };
287
+ const runtime = (node)=>guarded(node, (node)=>{
288
+ const init = initialized(node);
289
+ if (init) return runtime(init);
290
+ if (!__rspack_external__babel_types_6e8337dd.isCallExpression(node)) return false;
291
+ const value = imported(node.callee);
292
+ if (value?.name === 'defineEffectBff' && [
293
+ edge,
294
+ nodeRuntime
295
+ ].includes(value.specifier) || value?.name === 'assembleEffectBffRuntime' && value.specifier === assembly) {
296
+ if (1 !== node.arguments.length) return false;
297
+ const values = properties(node.arguments[0]);
298
+ if (!values) return false;
299
+ if ('defineEffectBff' === value.name && values.has('rpc')) return rpc(values);
300
+ const api = values.get('api');
301
+ const apiBinding = api && imported(api);
302
+ if (!api || apiBinding?.specifier !== '../shared/api.ts') return false;
303
+ const identity = apiIdentity(api);
304
+ if (!identity) return false;
305
+ const layer = values.get('defineEffectBff' === value.name ? 'layer' : 'handlers');
306
+ const extra = values.get('transport');
307
+ return void 0 !== layer && ('defineEffectBff' === value.name ? directLayer(layer, identity) : handlers(layer, identity)) && (!extra || transport(extra));
308
+ }
309
+ const decl = declaration(node.callee);
310
+ const factory = decl && __rspack_external__babel_types_6e8337dd.isFunctionDeclaration(decl) ? decl : initialized(node.callee);
311
+ if (!factory || !__rspack_external__babel_types_6e8337dd.isFunctionDeclaration(factory) && !__rspack_external__babel_types_6e8337dd.isArrowFunctionExpression(factory) && !__rspack_external__babel_types_6e8337dd.isFunctionExpression(factory) || !factory.body) return false;
312
+ const result = returned(factory.body);
313
+ return !!result && runtime(result);
314
+ });
315
+ const root = entry.file.program.body.find((statement)=>__rspack_external__babel_types_6e8337dd.isExportDefaultDeclaration(statement));
316
+ return root && runtime(root.declaration) ? void 0 : failure;
317
+ } catch (cause) {
318
+ if (cause instanceof SourceSyntaxError) return failure;
319
+ const message = 'object' == typeof cause && null !== cause && 'message' in cause && 'string' == typeof cause.message ? cause.message : String(cause);
320
+ throw new Error(`Strict Effect API analysis failed: ${message}`, {
321
+ cause
322
+ });
323
+ }
324
+ }
325
+ export { createEffectApiImportResolver, strictEffectRuntimeTopologyViolation };
@@ -0,0 +1 @@
1
+ export declare function runMicroVerticalApiCheckCli(args?: string[], filesOnly?: boolean): Promise<number>;
@@ -17,4 +17,4 @@ export type WorkspaceSourceCheckOptions = {
17
17
  readonly pluralCategories?: Readonly<Record<string, readonly string[]>>;
18
18
  };
19
19
  export declare const runWorkspaceSourceCheck: ({ cwd, sourceRoots, locales, pluralCategories, }?: WorkspaceSourceCheckOptions) => number;
20
- export declare const main: () => void;
20
+ export declare const runWorkspaceSourceCheckCli: (args?: readonly string[]) => number;
@@ -0,0 +1,23 @@
1
+ export interface MicroVerticalApiBaselineExpectation {
2
+ /** Optional override for operation IDs outside reachable endpoint naming conventions. */
3
+ readonly operationPaths?: Readonly<Record<string, string>>;
4
+ readonly additionalPaths: Readonly<Record<string, string>>;
5
+ readonly apiPrefix: string;
6
+ readonly basePath: string;
7
+ readonly effectClientPackage: string;
8
+ readonly ownerId: string;
9
+ readonly readinessPath: string;
10
+ readonly baselinePackage: '@modern-js/bff-effect/microvertical-api';
11
+ readonly baselinePackageDirectory: string;
12
+ }
13
+ export interface MicroVerticalTopologyEntry {
14
+ readonly api?: {
15
+ readonly readiness?: {
16
+ readonly endpoint?: string;
17
+ };
18
+ };
19
+ readonly id: string;
20
+ readonly path?: string;
21
+ }
22
+ export declare const configuredMicroVerticalApiStem: (verticalPath: string, verticals: readonly MicroVerticalTopologyEntry[]) => string | undefined;
23
+ export declare const microVerticalApiBaselineViolation: (stem: string, filePath: string, expectation: MicroVerticalApiBaselineExpectation) => string | undefined;
@@ -0,0 +1,46 @@
1
+ import { type ModuleGraph } from './module-graph.js';
2
+ export type { MicroVerticalApiBaselineExpectation, MicroVerticalTopologyEntry, } from './microvertical-api-baseline.js';
3
+ export { configuredMicroVerticalApiStem, microVerticalApiBaselineViolation, } from './microvertical-api-baseline.js';
4
+ export type { ModuleGraph, ModuleGraphHop, ResolvedBinding, SourceModule, } from './module-graph.js';
5
+ export { createModuleGraph } from './module-graph.js';
6
+ export interface MicroVerticalConfiguredApp {
7
+ readonly path: string;
8
+ readonly id?: string;
9
+ readonly kind?: string;
10
+ readonly package?: string;
11
+ readonly surfaceProfile?: string;
12
+ readonly deliveryUnitKind?: string;
13
+ readonly api?: {
14
+ readonly stem?: string;
15
+ readonly prefix?: string;
16
+ readonly protocol?: 'rest' | 'rpc';
17
+ readonly operationPaths?: Readonly<Record<string, string>>;
18
+ readonly additionalPaths?: Readonly<Record<string, string>>;
19
+ };
20
+ }
21
+ export interface MicroVerticalApiContractRuleContext {
22
+ /** Workspace-relative app directory. */
23
+ readonly appPath: string;
24
+ readonly protocol: 'rest' | 'rpc';
25
+ /** Graph rooted at the app's shared contract (`graph.root`). */
26
+ readonly graph: ModuleGraph;
27
+ }
28
+ /** A consumer-owned contract rule; each returned message is a violation. */
29
+ export type MicroVerticalApiContractRule = (context: MicroVerticalApiContractRuleContext) => readonly string[];
30
+ export interface MicroVerticalApiCheckOptions {
31
+ readonly workspaceRoot: string;
32
+ readonly configuredApps?: readonly MicroVerticalConfiguredApp[];
33
+ /** Explicit expected installed owner, useful for isolated/non-hoisted installations. */
34
+ readonly baselinePackageDirectory?: string;
35
+ /** Rules run once per API app against its shared contract's module graph. */
36
+ readonly contractRules?: readonly MicroVerticalApiContractRule[];
37
+ }
38
+ export interface MicroVerticalApiCheckResult {
39
+ readonly diagnostics: readonly string[];
40
+ readonly toolErrors: readonly string[];
41
+ readonly topologyFilesAnalyzed: number;
42
+ }
43
+ /** Full standalone check: source runtime topology is analyzed once per entry. */
44
+ export declare const checkMicroVerticalApiBoundaries: (options: MicroVerticalApiCheckOptions) => MicroVerticalApiCheckResult;
45
+ /** Consumer files/export/owner checks; caller must separately run runtime topology analysis. */
46
+ export declare const checkMicroVerticalApiConsumerFiles: (options: MicroVerticalApiCheckOptions) => MicroVerticalApiCheckResult;
@@ -0,0 +1,6 @@
1
+ import type { MicroVerticalApiBaselineExpectation } from './microvertical-api-baseline.js';
2
+ export declare const baselinePackage = "@modern-js/bff-effect/microvertical-api";
3
+ /** Locate the installed package from this consumer's resolver, never this tool's installation. */
4
+ export declare function resolveBaselinePackageDirectory(fromFile: string): string;
5
+ /** Follow only public bindings and local barrels. Never examine schema implementation expressions. */
6
+ export declare function baselinePublicIdentityIsExact(fromFile: string, expectation: MicroVerticalApiBaselineExpectation): boolean;
@@ -0,0 +1,52 @@
1
+ import * as t from '@babel/types';
2
+ /** A parsed consumer module and the package directory it may traverse privately. */
3
+ export interface SourceModule {
4
+ readonly file: t.File;
5
+ /** Real absolute path. */
6
+ readonly path: string;
7
+ /** Real package directory; relative imports may not leave it. */
8
+ readonly boundary: string;
9
+ }
10
+ /** One `name` looked up in the module at `path`, in traversal order. */
11
+ export interface ModuleGraphHop {
12
+ readonly path: string;
13
+ readonly name: string;
14
+ }
15
+ /**
16
+ * Where a binding ends up. `declaration` is a workspace `const` initializer;
17
+ * `external` is a named import or re-export from a package outside the
18
+ * workspace source graph. `chain` starts at the requesting module and ends at
19
+ * the declaring module (or the last workspace module before the package).
20
+ */
21
+ export type ResolvedBinding = {
22
+ readonly kind: 'declaration';
23
+ readonly expression: t.Expression;
24
+ readonly module: SourceModule;
25
+ readonly chain: readonly ModuleGraphHop[];
26
+ } | {
27
+ readonly kind: 'external';
28
+ readonly specifier: string;
29
+ readonly name: string;
30
+ readonly chain: readonly ModuleGraphHop[];
31
+ };
32
+ export interface ModuleGraph {
33
+ readonly root: SourceModule;
34
+ /**
35
+ * Follow a local binding (`scope: 'local'`) or an export (`'export'`) of
36
+ * `module` through relative imports, re-exports and public workspace
37
+ * package exports. Unresolvable, cyclic or over-budget bindings return
38
+ * undefined so callers fail closed.
39
+ */
40
+ resolve(module: SourceModule, name: string, scope: 'export' | 'local'): ResolvedBinding | undefined;
41
+ }
42
+ /** Resolution follows `const` bindings, so a module that mutates them is unreadable. */
43
+ export declare function parseConsumer(filePath: string): t.File;
44
+ export declare const propertyName: (node: t.Node | undefined) => string | undefined;
45
+ export declare const localConst: (sourceFile: t.File, name: string) => t.VariableDeclarator | undefined;
46
+ export declare const exportedConst: (sourceFile: t.File, name: string) => t.VariableDeclarator | undefined;
47
+ /**
48
+ * The module graph rooted at `filePath`. Each module is parsed once per graph;
49
+ * an unreadable module stays unresolved instead of assumed valid. Throws
50
+ * `SourceSyntaxError` when the root itself is not a readable consumer module.
51
+ */
52
+ export declare function createModuleGraph(filePath: string): ModuleGraph;
@@ -0,0 +1,10 @@
1
+ import { type Visitor } from '@babel/traverse';
2
+ import * as t from '@babel/types';
3
+ /** Only JSX extensions enable JSX: generic arrows in .ts must remain valid. */
4
+ export declare const consumerParserPlugins: (filePath: string) => ('typescript' | 'jsx')[];
5
+ /** Only parser/binder diagnostics are policy violations; tool failures escape. */
6
+ export declare class SourceSyntaxError extends Error {
7
+ }
8
+ export declare function parseSource(source: string, filename: string, syntaxMessage?: string): t.File;
9
+ export declare function traverseSource(file: t.File, visitor: Visitor): void;
10
+ export declare function unwrapExpression(node: t.Node, includeAssertions?: boolean): t.Node;
@@ -0,0 +1,10 @@
1
+ /** A bounded, owner-local source resolver; never executes application modules. */
2
+ export interface EffectApiSource {
3
+ readonly id: string;
4
+ readonly source: string;
5
+ readonly resolveImport?: (specifier: string) => EffectApiSource | undefined;
6
+ }
7
+ export type EffectApiImportResolver = (specifier: string) => EffectApiSource | undefined;
8
+ export declare function createEffectApiImportResolver(filename: string): EffectApiImportResolver;
9
+ /** Proves binding identity and an exported composition, not the occurrence of names. */
10
+ export declare function strictEffectRuntimeTopologyViolation(source: string, resolveImport?: EffectApiImportResolver): string | undefined;
package/package.json CHANGED
@@ -23,7 +23,7 @@
23
23
  "engines": {
24
24
  "node": ">=20"
25
25
  },
26
- "version": "3.9.0-ultramodern.2",
26
+ "version": "3.9.0-ultramodern.21",
27
27
  "types": "./dist/types/index.d.ts",
28
28
  "main": "./dist/esm-node/index.js",
29
29
  "typesVersions": {
@@ -33,6 +33,9 @@
33
33
  ],
34
34
  "oxlint-plugin": [
35
35
  "./dist/types/oxlint-plugin.d.ts"
36
+ ],
37
+ "microvertical-api-boundary": [
38
+ "./dist/types/microvertical-api-boundary.d.ts"
36
39
  ]
37
40
  }
38
41
  },
@@ -45,6 +48,11 @@
45
48
  },
46
49
  "default": "./dist/esm-node/index.js"
47
50
  },
51
+ "./strict-effect-runtime": {
52
+ "types": "./dist/types/strict-effect-runtime.d.ts",
53
+ "import": "./dist/esm-node/strict-effect-runtime.js",
54
+ "require": "./dist/cjs/strict-effect-runtime.cjs"
55
+ },
48
56
  "./oxlint-plugin": {
49
57
  "types": "./dist/types/oxlint-plugin.d.ts",
50
58
  "node": {
@@ -52,18 +60,28 @@
52
60
  "require": "./dist/cjs/oxlint-plugin.cjs"
53
61
  },
54
62
  "default": "./dist/esm-node/oxlint-plugin.js"
63
+ },
64
+ "./microvertical-api-boundary": {
65
+ "types": "./dist/types/microvertical-api-boundary.d.ts",
66
+ "import": "./dist/esm-node/microvertical-api-boundary.js",
67
+ "require": "./dist/cjs/microvertical-api-boundary.cjs"
55
68
  }
56
69
  },
57
70
  "files": [
58
- "dist"
71
+ "dist",
72
+ "bin"
59
73
  ],
60
74
  "dependencies": {
61
- "oxlint": "1.81.0"
75
+ "@babel/parser": "^8.0.6",
76
+ "@babel/traverse": "^8.0.6",
77
+ "@babel/types": "^8.0.6",
78
+ "oxlint": "1.85.0",
79
+ "resolve.exports": "2.0.3"
62
80
  },
63
81
  "devDependencies": {
64
- "@rslib/core": "1.0.0",
82
+ "@rslib/core": "1.0.2",
65
83
  "@scripts/rstest-config": "2.66.0",
66
- "@types/node": "^26.4.1",
84
+ "@types/node": "^26.6.2",
67
85
  "@typescript/native-preview": "7.0.0-dev.20260707.2"
68
86
  },
69
87
  "sideEffects": false,
@@ -71,9 +89,14 @@
71
89
  "access": "public"
72
90
  },
73
91
  "modern:source": "./src/index.ts",
92
+ "bin": {
93
+ "modern-api-check": "bin/modern-api-check.mjs",
94
+ "modern-api-check-files": "bin/modern-api-check-files.mjs",
95
+ "modern-i18n-check": "bin/modern-i18n-check.mjs"
96
+ },
74
97
  "scripts": {
75
98
  "build": "rm -rf dist && rslib build -c rslib.config.mts",
76
99
  "dev": "rslib build -c rslib.config.mts -w",
77
- "test": "rstest --passWithNoTests"
100
+ "test": "rstest"
78
101
  }
79
102
  }