@bleedingdev/modern-js-code-tools 3.9.0-ultramodern.9 → 3.9.3-ultramodern.2
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 +81 -244
- package/dist/cjs/microvertical-api-boundary.cjs +352 -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 +350 -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 +350 -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 +0 -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,66 +1,14 @@
|
|
|
1
1
|
import "node:module";
|
|
2
|
-
import node_fs from "node:fs";
|
|
3
|
-
import node_path from "node:path";
|
|
4
|
-
import { parse } from "@babel/parser";
|
|
5
|
-
import traverse, { Hub, NodePath } from "@babel/traverse";
|
|
6
2
|
import { baselinePublicIdentityIsExact } from "./microvertical-api-owner.js";
|
|
3
|
+
import { createModuleGraph, exportedConst, localConst, propertyName } from "./module-graph.js";
|
|
4
|
+
import { SourceSyntaxError, unwrapExpression } from "./source-analysis.js";
|
|
7
5
|
import * as __rspack_external__babel_types_6e8337dd from "@babel/types";
|
|
8
|
-
const consumerParserPlugins = (filePath)=>filePath.endsWith('.tsx') || filePath.endsWith('.jsx') ? [
|
|
9
|
-
"typescript",
|
|
10
|
-
'jsx'
|
|
11
|
-
] : [
|
|
12
|
-
"typescript"
|
|
13
|
-
];
|
|
14
|
-
class ConsumerSyntaxError extends Error {
|
|
15
|
-
}
|
|
16
|
-
class ConsumerValidationHub extends Hub {
|
|
17
|
-
buildError(_node, message) {
|
|
18
|
-
return new ConsumerSyntaxError(message);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
function parseConsumer(filePath) {
|
|
22
|
-
if (node_fs.statSync(filePath).size > 1000000) throw new Error(`${filePath}: consumer source exceeds 1 MB analysis budget`);
|
|
23
|
-
let file;
|
|
24
|
-
try {
|
|
25
|
-
file = parse(node_fs.readFileSync(filePath, 'utf8'), {
|
|
26
|
-
sourceType: 'module',
|
|
27
|
-
sourceFilename: filePath,
|
|
28
|
-
plugins: consumerParserPlugins(filePath)
|
|
29
|
-
});
|
|
30
|
-
} catch (error) {
|
|
31
|
-
if ('object' == typeof error && null !== error && 'code' in error && 'BABEL_PARSER_SYNTAX_ERROR' === error.code) throw new ConsumerSyntaxError(error instanceof Error ? error.message : String(error));
|
|
32
|
-
throw error;
|
|
33
|
-
}
|
|
34
|
-
const program = NodePath.get({
|
|
35
|
-
hub: new ConsumerValidationHub(),
|
|
36
|
-
parentPath: void 0,
|
|
37
|
-
parent: file,
|
|
38
|
-
container: file,
|
|
39
|
-
key: 'program'
|
|
40
|
-
});
|
|
41
|
-
program.setContext();
|
|
42
|
-
traverse(file, {
|
|
43
|
-
AssignmentExpression (p) {
|
|
44
|
-
throw new ConsumerSyntaxError(`contract bindings must be immutable at ${p.node.start}`);
|
|
45
|
-
},
|
|
46
|
-
UpdateExpression (p) {
|
|
47
|
-
throw new ConsumerSyntaxError(`contract bindings must be immutable at ${p.node.start}`);
|
|
48
|
-
},
|
|
49
|
-
TSModuleDeclaration () {
|
|
50
|
-
throw new ConsumerSyntaxError('contract bindings must not be merged with namespaces');
|
|
51
|
-
}
|
|
52
|
-
}, program.scope, void 0, program);
|
|
53
|
-
return file;
|
|
54
|
-
}
|
|
55
6
|
const camelCaseStem = (stem)=>stem.replaceAll(/-([a-z0-9])/gu, (_match, letter)=>letter.toUpperCase());
|
|
56
7
|
const pascalCaseStem = (stem)=>{
|
|
57
8
|
const camelStem = camelCaseStem(stem);
|
|
58
9
|
return `${camelStem.slice(0, 1).toUpperCase()}${camelStem.slice(1)}`;
|
|
59
10
|
};
|
|
60
11
|
const identifierName = (node)=>void 0 !== node && __rspack_external__babel_types_6e8337dd.isIdentifier(node) ? node.name : void 0;
|
|
61
|
-
const propertyName = (node)=>{
|
|
62
|
-
if (void 0 !== node && (__rspack_external__babel_types_6e8337dd.isIdentifier(node) || __rspack_external__babel_types_6e8337dd.isStringLiteral(node) || __rspack_external__babel_types_6e8337dd.isNumericLiteral(node))) return __rspack_external__babel_types_6e8337dd.isIdentifier(node) ? node.name : String(node.value);
|
|
63
|
-
};
|
|
64
12
|
const accessPath = (node)=>{
|
|
65
13
|
if (__rspack_external__babel_types_6e8337dd.isIdentifier(node)) return [
|
|
66
14
|
node.name
|
|
@@ -74,11 +22,6 @@ const accessPath = (node)=>{
|
|
|
74
22
|
}
|
|
75
23
|
};
|
|
76
24
|
const isAccessPath = (node, expected)=>accessPath(node)?.join('.') === expected.join('.');
|
|
77
|
-
const unwrapExpression = (expression)=>{
|
|
78
|
-
let current = expression;
|
|
79
|
-
while(__rspack_external__babel_types_6e8337dd.isTSAsExpression(current) || __rspack_external__babel_types_6e8337dd.isParenthesizedExpression(current) || __rspack_external__babel_types_6e8337dd.isTSSatisfiesExpression(current))current = current.expression;
|
|
80
|
-
return current;
|
|
81
|
-
};
|
|
82
25
|
const stringLiteral = (expression)=>{
|
|
83
26
|
if (null == expression) return;
|
|
84
27
|
const unwrapped = unwrapExpression(expression);
|
|
@@ -94,135 +37,6 @@ const callExpression = (expression, callee)=>{
|
|
|
94
37
|
const unwrapped = unwrapExpression(expression);
|
|
95
38
|
return __rspack_external__babel_types_6e8337dd.isCallExpression(unwrapped) && isAccessPath(unwrapped.callee, callee) ? unwrapped : void 0;
|
|
96
39
|
};
|
|
97
|
-
const localConst = (sourceFile, name)=>{
|
|
98
|
-
for (const item of sourceFile.program.body){
|
|
99
|
-
const statement = __rspack_external__babel_types_6e8337dd.isExportNamedDeclaration(item) ? item.declaration : item;
|
|
100
|
-
if (!__rspack_external__babel_types_6e8337dd.isVariableDeclaration(statement) || 'const' !== statement.kind) continue;
|
|
101
|
-
const matches = statement.declarations.filter((declaration)=>__rspack_external__babel_types_6e8337dd.isIdentifier(declaration.id, {
|
|
102
|
-
name
|
|
103
|
-
}));
|
|
104
|
-
if (1 === matches.length) return matches[0];
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
const exportedConst = (sourceFile, name)=>{
|
|
108
|
-
for (const statement of sourceFile.program.body)if (__rspack_external__babel_types_6e8337dd.isExportNamedDeclaration(statement) && 'type' !== statement.exportKind) {
|
|
109
|
-
if (__rspack_external__babel_types_6e8337dd.isVariableDeclaration(statement.declaration) && 'const' === statement.declaration.kind) {
|
|
110
|
-
const declaration = statement.declaration.declarations.find((value)=>__rspack_external__babel_types_6e8337dd.isIdentifier(value.id, {
|
|
111
|
-
name
|
|
112
|
-
}));
|
|
113
|
-
if (declaration) return declaration;
|
|
114
|
-
}
|
|
115
|
-
if (!statement.source && statement.specifiers.some((value)=>__rspack_external__babel_types_6e8337dd.isExportSpecifier(value) && 'type' !== value.exportKind && __rspack_external__babel_types_6e8337dd.isIdentifier(value.local, {
|
|
116
|
-
name
|
|
117
|
-
}) && __rspack_external__babel_types_6e8337dd.isIdentifier(value.exported, {
|
|
118
|
-
name
|
|
119
|
-
}))) return localConst(sourceFile, name);
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
const resolveRelativeModulePath = (fromPath, specifier)=>{
|
|
123
|
-
if (!/^\.\.?\//u.test(specifier)) return;
|
|
124
|
-
const resolved = node_path.resolve(node_path.dirname(fromPath), specifier);
|
|
125
|
-
const stem = resolved.replace(/\.(?:[cm]?[jt]sx?)$/u, '');
|
|
126
|
-
for (const candidate of [
|
|
127
|
-
`${stem}.ts`,
|
|
128
|
-
`${stem}.tsx`,
|
|
129
|
-
node_path.join(stem, 'index.ts'),
|
|
130
|
-
node_path.join(stem, 'index.tsx')
|
|
131
|
-
])if (node_fs.existsSync(candidate) && node_fs.statSync(candidate).isFile()) return candidate;
|
|
132
|
-
};
|
|
133
|
-
const createModuleReader = (root)=>{
|
|
134
|
-
const modules = new Map([
|
|
135
|
-
[
|
|
136
|
-
root.path,
|
|
137
|
-
root
|
|
138
|
-
]
|
|
139
|
-
]);
|
|
140
|
-
return (filePath)=>{
|
|
141
|
-
if (modules.has(filePath)) return modules.get(filePath);
|
|
142
|
-
if (modules.size >= 256) return;
|
|
143
|
-
let module;
|
|
144
|
-
try {
|
|
145
|
-
module = {
|
|
146
|
-
file: parseConsumer(filePath),
|
|
147
|
-
path: filePath
|
|
148
|
-
};
|
|
149
|
-
} catch {
|
|
150
|
-
module = void 0;
|
|
151
|
-
}
|
|
152
|
-
modules.set(filePath, module);
|
|
153
|
-
return module;
|
|
154
|
-
};
|
|
155
|
-
};
|
|
156
|
-
const importedBinding = (module, name)=>{
|
|
157
|
-
for (const statement of module.file.program.body)if (__rspack_external__babel_types_6e8337dd.isImportDeclaration(statement) && 'type' !== statement.importKind) {
|
|
158
|
-
for (const specifier of statement.specifiers)if (__rspack_external__babel_types_6e8337dd.isIdentifier(specifier.local, {
|
|
159
|
-
name
|
|
160
|
-
})) {
|
|
161
|
-
if (__rspack_external__babel_types_6e8337dd.isImportSpecifier(specifier) && 'type' !== specifier.importKind) return {
|
|
162
|
-
imported: propertyName(specifier.imported) ?? name,
|
|
163
|
-
specifier: statement.source.value
|
|
164
|
-
};
|
|
165
|
-
if (__rspack_external__babel_types_6e8337dd.isImportDefaultSpecifier(specifier)) return {
|
|
166
|
-
imported: 'default',
|
|
167
|
-
specifier: statement.source.value
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
const reexportedBinding = (module, name)=>{
|
|
173
|
-
for (const statement of module.file.program.body){
|
|
174
|
-
if (!__rspack_external__babel_types_6e8337dd.isExportNamedDeclaration(statement) || 'type' === statement.exportKind) continue;
|
|
175
|
-
const specifier = statement.specifiers.find((value)=>__rspack_external__babel_types_6e8337dd.isExportSpecifier(value) && 'type' !== value.exportKind && propertyName(value.exported) === name);
|
|
176
|
-
if (void 0 !== specifier && __rspack_external__babel_types_6e8337dd.isExportSpecifier(specifier)) return {
|
|
177
|
-
local: specifier.local.name,
|
|
178
|
-
...statement.source ? {
|
|
179
|
-
specifier: statement.source.value
|
|
180
|
-
} : {}
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
const defaultExport = (module)=>{
|
|
185
|
-
for (const statement of module.file.program.body){
|
|
186
|
-
if (!__rspack_external__babel_types_6e8337dd.isExportDefaultDeclaration(statement)) continue;
|
|
187
|
-
const declaration = statement.declaration;
|
|
188
|
-
if (__rspack_external__babel_types_6e8337dd.isIdentifier(declaration)) return declaration.name;
|
|
189
|
-
return __rspack_external__babel_types_6e8337dd.isExpression(declaration) ? declaration : void 0;
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
const resolveBinding = (module, name, scope, readModule, seen)=>{
|
|
193
|
-
const key = `${scope}:${module.path}#${name}`;
|
|
194
|
-
if (seen.has(key) || seen.size >= 512) return;
|
|
195
|
-
seen.add(key);
|
|
196
|
-
const declaration = 'export' === scope ? exportedConst(module.file, name) : localConst(module.file, name);
|
|
197
|
-
if (declaration?.init) return {
|
|
198
|
-
expression: declaration.init,
|
|
199
|
-
module
|
|
200
|
-
};
|
|
201
|
-
const throughModule = (specifier, imported)=>{
|
|
202
|
-
const resolvedPath = resolveRelativeModulePath(module.path, specifier);
|
|
203
|
-
const target = void 0 === resolvedPath ? void 0 : readModule(resolvedPath);
|
|
204
|
-
return void 0 === target ? void 0 : resolveBinding(target, imported, 'export', readModule, seen);
|
|
205
|
-
};
|
|
206
|
-
if ('local' === scope) {
|
|
207
|
-
const imported = importedBinding(module, name);
|
|
208
|
-
return void 0 === imported ? void 0 : throughModule(imported.specifier, imported.imported);
|
|
209
|
-
}
|
|
210
|
-
if ('default' === name) {
|
|
211
|
-
const exported = defaultExport(module);
|
|
212
|
-
if ('string' == typeof exported) return resolveBinding(module, exported, 'local', readModule, seen);
|
|
213
|
-
return void 0 === exported ? void 0 : {
|
|
214
|
-
expression: exported,
|
|
215
|
-
module
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
const reexported = reexportedBinding(module, name);
|
|
219
|
-
if (void 0 !== reexported) return void 0 === reexported.specifier ? resolveBinding(module, reexported.local, 'local', readModule, seen) : throughModule(reexported.specifier, reexported.local);
|
|
220
|
-
for (const statement of module.file.program.body){
|
|
221
|
-
if (!__rspack_external__babel_types_6e8337dd.isExportAllDeclaration(statement) || 'type' === statement.exportKind) continue;
|
|
222
|
-
const resolved = throughModule(statement.source.value, name);
|
|
223
|
-
if (void 0 !== resolved) return resolved;
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
40
|
const objectLiteral = (expression)=>{
|
|
227
41
|
if (null == expression) return;
|
|
228
42
|
const unwrapped = unwrapExpression(expression);
|
|
@@ -322,14 +136,21 @@ const singleAddedArgument = (expression, factory, names)=>{
|
|
|
322
136
|
if (1 !== chain.base.arguments.length || !names.includes(stringLiteral(chain.base.arguments[0]) ?? '') || 1 !== chain.methods.length || method?.name !== 'add' || 1 !== method.arguments.length) return;
|
|
323
137
|
return method.arguments[0];
|
|
324
138
|
};
|
|
325
|
-
const
|
|
326
|
-
|
|
139
|
+
const rootGroupExpression = (graph, expression)=>{
|
|
140
|
+
if (void 0 === expression) return;
|
|
141
|
+
const node = unwrapExpression(expression);
|
|
142
|
+
if (!__rspack_external__babel_types_6e8337dd.isIdentifier(node)) return node;
|
|
143
|
+
const resolved = graph.resolve(graph.root, node.name, 'local');
|
|
144
|
+
return resolved?.kind === 'declaration' && resolved.module === graph.root ? unwrapExpression(resolved.expression) : void 0;
|
|
145
|
+
};
|
|
146
|
+
const foundationGroup = (graph, declaration, stem, readinessSchemaName)=>{
|
|
147
|
+
const group = rootGroupExpression(graph, singleAddedArgument(declaration?.init, [
|
|
327
148
|
'HttpApi',
|
|
328
149
|
'make'
|
|
329
150
|
], [
|
|
330
151
|
`${pascalCaseStem(stem)}FoundationApi`,
|
|
331
152
|
`${pascalCaseStem(stem)}ApiFoundation`
|
|
332
|
-
]);
|
|
153
|
+
]));
|
|
333
154
|
const endpointExpression = singleAddedArgument(group, [
|
|
334
155
|
'HttpApiGroup',
|
|
335
156
|
'make'
|
|
@@ -342,17 +163,24 @@ const foundationIsExact = (declaration, stem, readinessSchemaName)=>{
|
|
|
342
163
|
], 3);
|
|
343
164
|
const endpointOptions = objectLiteral(endpoint?.arguments[2]);
|
|
344
165
|
const endpointProperties = void 0 === endpointOptions ? void 0 : propertyAssignments(endpointOptions.properties);
|
|
345
|
-
return void 0 !== endpoint && 'readiness' === stringLiteral(endpoint.arguments[0]) && stringLiteral(endpoint.arguments[1]) === `/${stem}/readiness` && endpointProperties?.size === 1 && identifierName(endpointProperties.get('success')?.value) === readinessSchemaName;
|
|
166
|
+
return void 0 !== endpoint && 'readiness' === stringLiteral(endpoint.arguments[0]) && stringLiteral(endpoint.arguments[1]) === `/${stem}/readiness` && endpointProperties?.size === 1 && identifierName(endpointProperties.get('success')?.value) === readinessSchemaName ? group : void 0;
|
|
346
167
|
};
|
|
347
|
-
const rootComposesFoundation = (
|
|
168
|
+
const rootComposesFoundation = (graph, declaration, stem, foundationName, foundation)=>{
|
|
169
|
+
const sourceFile = graph.root.file;
|
|
348
170
|
const chain = directCallChain(declaration?.init);
|
|
349
171
|
const first = chain?.methods[0];
|
|
172
|
+
const firstArgument = first?.arguments[0];
|
|
350
173
|
return void 0 !== chain && isAccessPath(chain.base.callee, [
|
|
351
174
|
'HttpApi',
|
|
352
175
|
'make'
|
|
353
|
-
]) && 1 === chain.base.arguments.length && stringLiteral(chain.base.arguments[0]) === `${pascalCaseStem(stem)}Api` && chain.methods.length > 0 && chain.methods.every((method, index)=>
|
|
176
|
+
]) && 1 === chain.base.arguments.length && stringLiteral(chain.base.arguments[0]) === `${pascalCaseStem(stem)}Api` && chain.methods.length > 0 && chain.methods.every((method, index)=>{
|
|
177
|
+
const combinatorArity = nonEndpointCombinators.api[method.name];
|
|
178
|
+
if (void 0 !== combinatorArity) return method.arguments.length === combinatorArity;
|
|
179
|
+
if ('add' === method.name) return method.arguments.length > 0;
|
|
180
|
+
return 1 === method.arguments.length && ('addHttpApi' === method.name || index === chain.methods.length - 1 && 'pipe' === method.name && 'identity' === identifierName(method.arguments[0]) && importsExactBindings(sourceFile, 'effect', [
|
|
354
181
|
'identity'
|
|
355
|
-
]))
|
|
182
|
+
]));
|
|
183
|
+
}) && void 0 !== firstArgument && (first?.name === 'addHttpApi' && identifierName(firstArgument) === foundationName || first?.name === 'add' && __rspack_external__babel_types_6e8337dd.isIdentifier(firstArgument) && rootGroupExpression(graph, firstArgument) === foundation);
|
|
356
184
|
};
|
|
357
185
|
const nonEndpointCombinators = {
|
|
358
186
|
api: {
|
|
@@ -368,8 +196,7 @@ const nonEndpointCombinators = {
|
|
|
368
196
|
middleware: 1
|
|
369
197
|
}
|
|
370
198
|
};
|
|
371
|
-
const reachableEndpoints = (
|
|
372
|
-
const readModule = createModuleReader(rootModule);
|
|
199
|
+
const reachableEndpoints = (graph, declaration)=>{
|
|
373
200
|
const active = new Set();
|
|
374
201
|
const endpoints = [];
|
|
375
202
|
const identities = new Set();
|
|
@@ -391,8 +218,8 @@ const reachableEndpoints = (rootModule, declaration)=>{
|
|
|
391
218
|
active.add(node);
|
|
392
219
|
try {
|
|
393
220
|
if (__rspack_external__babel_types_6e8337dd.isIdentifier(node)) {
|
|
394
|
-
const resolved =
|
|
395
|
-
return
|
|
221
|
+
const resolved = graph.resolve(module, node.name, 'local');
|
|
222
|
+
return resolved?.kind === 'declaration' && visit(resolved.expression, resolved.module, kind, group);
|
|
396
223
|
}
|
|
397
224
|
if ('endpoint' === kind) {
|
|
398
225
|
if (!__rspack_external__babel_types_6e8337dd.isCallExpression(node) || node.arguments.length < 2 || node.arguments.length > 3) return false;
|
|
@@ -436,18 +263,36 @@ const reachableEndpoints = (rootModule, declaration)=>{
|
|
|
436
263
|
}
|
|
437
264
|
const combinatorArity = combinators[method.name];
|
|
438
265
|
if (void 0 !== combinatorArity) return method.arguments.length === combinatorArity;
|
|
266
|
+
if ('add' === method.name) return method.arguments.length > 0 && method.arguments.every((argument)=>visitAdded(argument, module, 'api' === kind ? 'group' : 'endpoint', 'group' === kind ? name : ''));
|
|
439
267
|
if (1 !== method.arguments.length) return false;
|
|
440
268
|
if ('pipe' === method.name) return 'identity' === identifierName(method.arguments[0]) && importsExactBindings(module.file, 'effect', [
|
|
441
269
|
'identity'
|
|
442
270
|
]);
|
|
443
|
-
if ('add' === method.name) return visit(method.arguments[0], module, 'api' === kind ? 'group' : 'endpoint', 'group' === kind ? name : '');
|
|
444
271
|
return 'api' === kind && 'addHttpApi' === method.name && visit(method.arguments[0], module, 'api');
|
|
445
272
|
});
|
|
446
273
|
} finally{
|
|
447
274
|
active.delete(node);
|
|
448
275
|
}
|
|
449
276
|
};
|
|
450
|
-
|
|
277
|
+
const visitAdded = (argument, module, kind, group)=>{
|
|
278
|
+
if (!__rspack_external__babel_types_6e8337dd.isSpreadElement(argument)) return visit(argument, module, kind, group);
|
|
279
|
+
let node = unwrapExpression(argument.argument);
|
|
280
|
+
let owner = module;
|
|
281
|
+
if (__rspack_external__babel_types_6e8337dd.isIdentifier(node)) {
|
|
282
|
+
const resolved = graph.resolve(module, node.name, 'local');
|
|
283
|
+
if (resolved?.kind !== 'declaration') return false;
|
|
284
|
+
node = unwrapExpression(resolved.expression);
|
|
285
|
+
owner = resolved.module;
|
|
286
|
+
}
|
|
287
|
+
if (!__rspack_external__babel_types_6e8337dd.isArrayExpression(node) || active.has(node) || ++visits > 2048) return false;
|
|
288
|
+
active.add(node);
|
|
289
|
+
try {
|
|
290
|
+
return node.elements.every((element)=>null !== element && visitAdded(element, owner, kind, group));
|
|
291
|
+
} finally{
|
|
292
|
+
active.delete(node);
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
return visit(declaration?.init, graph.root, 'api') ? endpoints : void 0;
|
|
451
296
|
};
|
|
452
297
|
const operationContextFields = (property)=>{
|
|
453
298
|
const constructorCall = exactCall(property.value, [
|
|
@@ -578,8 +423,8 @@ const readinessSchemaIsShared = (declaration, markerSchemaName, ownerMarkerIsIde
|
|
|
578
423
|
return void 0 !== schema && (schema.identity && ownerMarkerIsIdentity || 1 === schema.assignments.size && identifierName(schema.assignments.get('marker')?.value) === markerSchemaName);
|
|
579
424
|
};
|
|
580
425
|
const declarationIsIdentifier = (declaration, identifier)=>declaration?.init != null && identifierName(unwrapExpression(declaration.init)) === identifier;
|
|
581
|
-
const validateParsedContract = (
|
|
582
|
-
const sourceFile =
|
|
426
|
+
const validateParsedContract = (graph, stem, expectation)=>{
|
|
427
|
+
const sourceFile = graph.root.file;
|
|
583
428
|
const exportStem = camelCaseStem(stem);
|
|
584
429
|
const foundationName = `${exportStem}FoundationApi`;
|
|
585
430
|
const markerSchemaName = `${exportStem}MarkerSchema`;
|
|
@@ -594,9 +439,10 @@ const validateParsedContract = (module, stem, expectation)=>{
|
|
|
594
439
|
if (!importsSharedBaselinePrimitives(sourceFile, expectation.baselinePackage)) return 'MicroVertical root contract must import exact baseline primitives from the framework baseline package';
|
|
595
440
|
if (!markerSchemaIsShared(sourceFile, markerDeclaration)) return 'MicroVertical root contract must consume the shared build marker schema without overriding shared fields';
|
|
596
441
|
if (!readinessSchemaIsShared(exportedConst(sourceFile, readinessSchemaName), markerSchemaName, declarationIsIdentifier(markerDeclaration, 'MicroVerticalBuildMarkerSchema'))) return 'MicroVertical readiness schema must consume the shared readiness schema without overriding shared fields';
|
|
597
|
-
|
|
598
|
-
if (
|
|
599
|
-
|
|
442
|
+
const foundation = foundationGroup(graph, exportedConst(sourceFile, foundationName), stem, readinessSchemaName);
|
|
443
|
+
if (void 0 === foundation) return 'MicroVertical readiness foundation API must directly compose its exact readiness endpoint and foundation identity';
|
|
444
|
+
if (!rootComposesFoundation(graph, exportedConst(sourceFile, `${exportStem}Api`), stem, foundationName, foundation)) return 'MicroVertical root API must explicitly compose its readiness foundation API';
|
|
445
|
+
const endpoints = reachableEndpoints(graph, exportedConst(sourceFile, `${exportStem}Api`));
|
|
600
446
|
if (!endpoints) return 'MicroVertical root API must compose bounded native endpoint declarations without ambiguous identities';
|
|
601
447
|
if (!operationMapIsConnected(exportedConst(sourceFile, `${exportStem}OperationContexts`), stem, expectation.operationPaths ?? {}, endpoints)) return 'MicroVertical operation map must construct every operation with the shared context constructor';
|
|
602
448
|
if (!metadataIsExact(exportedConst(sourceFile, `${exportStem}ApiContract`), expectation, endpoints)) return 'MicroVertical root contract must keep exact owner and API path metadata without forbidden fields';
|
|
@@ -608,15 +454,21 @@ const configuredMicroVerticalApiStem = (verticalPath, verticals)=>{
|
|
|
608
454
|
};
|
|
609
455
|
const microVerticalApiBaselineViolation = (stem, filePath, expectation)=>{
|
|
610
456
|
try {
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
457
|
+
const modules = createModuleGraph();
|
|
458
|
+
const graph = {
|
|
459
|
+
...modules,
|
|
460
|
+
root: modules.module(filePath)
|
|
461
|
+
};
|
|
462
|
+
const [mutation] = graph.root.mutations;
|
|
463
|
+
if (mutation) return `MicroVertical root contract must be valid TypeScript syntax (contract bindings must be immutable at ${mutation.start})`;
|
|
464
|
+
let identityMismatch;
|
|
465
|
+
if (!baselinePublicIdentityIsExact(filePath, expectation, (message)=>{
|
|
466
|
+
identityMismatch = message;
|
|
467
|
+
})) return `MicroVertical baseline imports must resolve the exact framework owner public export and schema identity${identityMismatch ? ` (${identityMismatch})` : ''}`;
|
|
468
|
+
return validateParsedContract(graph, stem, expectation);
|
|
617
469
|
} catch (error) {
|
|
618
|
-
if (error instanceof
|
|
470
|
+
if (error instanceof SourceSyntaxError) return `MicroVertical root contract must be valid TypeScript syntax (${error.message})`;
|
|
619
471
|
throw error;
|
|
620
472
|
}
|
|
621
473
|
};
|
|
622
|
-
export { configuredMicroVerticalApiStem,
|
|
474
|
+
export { configuredMicroVerticalApiStem, microVerticalApiBaselineViolation };
|