@bleedingdev/modern-js-code-tools 3.9.0-ultramodern.7 → 3.9.0-ultramodern.8
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/dist/cjs/microvertical-api-baseline.cjs +164 -13
- package/dist/cjs/microvertical-api-boundary.cjs +47 -10
- package/dist/esm/microvertical-api-baseline.js +161 -14
- package/dist/esm/microvertical-api-boundary.js +48 -11
- package/dist/esm-node/microvertical-api-baseline.js +161 -14
- package/dist/esm-node/microvertical-api-boundary.js +48 -11
- package/dist/types/microvertical-api-baseline.d.ts +5 -0
- package/package.json +1 -1
|
@@ -38,15 +38,24 @@ var __webpack_exports__ = {};
|
|
|
38
38
|
__webpack_require__.r(__webpack_exports__);
|
|
39
39
|
__webpack_require__.d(__webpack_exports__, {
|
|
40
40
|
configuredMicroVerticalApiStem: ()=>configuredMicroVerticalApiStem,
|
|
41
|
+
consumerParserPlugins: ()=>consumerParserPlugins,
|
|
41
42
|
microVerticalApiBaselineViolation: ()=>microVerticalApiBaselineViolation
|
|
42
43
|
});
|
|
43
44
|
const external_node_fs_namespaceObject = require("node:fs");
|
|
44
45
|
var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_namespaceObject);
|
|
46
|
+
const external_node_path_namespaceObject = require("node:path");
|
|
47
|
+
var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
|
|
45
48
|
const parser_namespaceObject = require("@babel/parser");
|
|
46
49
|
const traverse_namespaceObject = require("@babel/traverse");
|
|
47
50
|
var traverse_default = /*#__PURE__*/ __webpack_require__.n(traverse_namespaceObject);
|
|
48
51
|
const types_namespaceObject = require("@babel/types");
|
|
49
52
|
const external_microvertical_api_owner_cjs_namespaceObject = require("./microvertical-api-owner.cjs");
|
|
53
|
+
const consumerParserPlugins = (filePath)=>filePath.endsWith('.tsx') || filePath.endsWith('.jsx') ? [
|
|
54
|
+
"typescript",
|
|
55
|
+
'jsx'
|
|
56
|
+
] : [
|
|
57
|
+
"typescript"
|
|
58
|
+
];
|
|
50
59
|
class ConsumerSyntaxError extends Error {
|
|
51
60
|
}
|
|
52
61
|
class ConsumerValidationHub extends traverse_namespaceObject.Hub {
|
|
@@ -61,9 +70,7 @@ function parseConsumer(filePath) {
|
|
|
61
70
|
file = (0, parser_namespaceObject.parse)(external_node_fs_default().readFileSync(filePath, 'utf8'), {
|
|
62
71
|
sourceType: 'module',
|
|
63
72
|
sourceFilename: filePath,
|
|
64
|
-
plugins:
|
|
65
|
-
"typescript"
|
|
66
|
-
]
|
|
73
|
+
plugins: consumerParserPlugins(filePath)
|
|
67
74
|
});
|
|
68
75
|
} catch (error) {
|
|
69
76
|
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));
|
|
@@ -157,6 +164,110 @@ const exportedConst = (sourceFile, name)=>{
|
|
|
157
164
|
}))) return localConst(sourceFile, name);
|
|
158
165
|
}
|
|
159
166
|
};
|
|
167
|
+
const resolveRelativeModulePath = (fromPath, specifier)=>{
|
|
168
|
+
if (!/^\.\.?\//u.test(specifier)) return;
|
|
169
|
+
const resolved = external_node_path_default().resolve(external_node_path_default().dirname(fromPath), specifier);
|
|
170
|
+
const stem = resolved.replace(/\.(?:[cm]?[jt]sx?)$/u, '');
|
|
171
|
+
for (const candidate of [
|
|
172
|
+
`${stem}.ts`,
|
|
173
|
+
`${stem}.tsx`,
|
|
174
|
+
external_node_path_default().join(stem, 'index.ts'),
|
|
175
|
+
external_node_path_default().join(stem, 'index.tsx')
|
|
176
|
+
])if (external_node_fs_default().existsSync(candidate) && external_node_fs_default().statSync(candidate).isFile()) return candidate;
|
|
177
|
+
};
|
|
178
|
+
const createModuleReader = (root)=>{
|
|
179
|
+
const modules = new Map([
|
|
180
|
+
[
|
|
181
|
+
root.path,
|
|
182
|
+
root
|
|
183
|
+
]
|
|
184
|
+
]);
|
|
185
|
+
return (filePath)=>{
|
|
186
|
+
if (modules.has(filePath)) return modules.get(filePath);
|
|
187
|
+
if (modules.size >= 256) return;
|
|
188
|
+
let module;
|
|
189
|
+
try {
|
|
190
|
+
module = {
|
|
191
|
+
file: parseConsumer(filePath),
|
|
192
|
+
path: filePath
|
|
193
|
+
};
|
|
194
|
+
} catch {
|
|
195
|
+
module = void 0;
|
|
196
|
+
}
|
|
197
|
+
modules.set(filePath, module);
|
|
198
|
+
return module;
|
|
199
|
+
};
|
|
200
|
+
};
|
|
201
|
+
const importedBinding = (module, name)=>{
|
|
202
|
+
for (const statement of module.file.program.body)if (types_namespaceObject.isImportDeclaration(statement) && 'type' !== statement.importKind) {
|
|
203
|
+
for (const specifier of statement.specifiers)if (types_namespaceObject.isIdentifier(specifier.local, {
|
|
204
|
+
name
|
|
205
|
+
})) {
|
|
206
|
+
if (types_namespaceObject.isImportSpecifier(specifier) && 'type' !== specifier.importKind) return {
|
|
207
|
+
imported: propertyName(specifier.imported) ?? name,
|
|
208
|
+
specifier: statement.source.value
|
|
209
|
+
};
|
|
210
|
+
if (types_namespaceObject.isImportDefaultSpecifier(specifier)) return {
|
|
211
|
+
imported: 'default',
|
|
212
|
+
specifier: statement.source.value
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
const reexportedBinding = (module, name)=>{
|
|
218
|
+
for (const statement of module.file.program.body){
|
|
219
|
+
if (!types_namespaceObject.isExportNamedDeclaration(statement) || 'type' === statement.exportKind) continue;
|
|
220
|
+
const specifier = statement.specifiers.find((value)=>types_namespaceObject.isExportSpecifier(value) && 'type' !== value.exportKind && propertyName(value.exported) === name);
|
|
221
|
+
if (void 0 !== specifier && types_namespaceObject.isExportSpecifier(specifier)) return {
|
|
222
|
+
local: specifier.local.name,
|
|
223
|
+
...statement.source ? {
|
|
224
|
+
specifier: statement.source.value
|
|
225
|
+
} : {}
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
const defaultExport = (module)=>{
|
|
230
|
+
for (const statement of module.file.program.body){
|
|
231
|
+
if (!types_namespaceObject.isExportDefaultDeclaration(statement)) continue;
|
|
232
|
+
const declaration = statement.declaration;
|
|
233
|
+
if (types_namespaceObject.isIdentifier(declaration)) return declaration.name;
|
|
234
|
+
return types_namespaceObject.isExpression(declaration) ? declaration : void 0;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
const resolveBinding = (module, name, scope, readModule, seen)=>{
|
|
238
|
+
const key = `${scope}:${module.path}#${name}`;
|
|
239
|
+
if (seen.has(key) || seen.size >= 512) return;
|
|
240
|
+
seen.add(key);
|
|
241
|
+
const declaration = 'export' === scope ? exportedConst(module.file, name) : localConst(module.file, name);
|
|
242
|
+
if (declaration?.init) return {
|
|
243
|
+
expression: declaration.init,
|
|
244
|
+
module
|
|
245
|
+
};
|
|
246
|
+
const throughModule = (specifier, imported)=>{
|
|
247
|
+
const resolvedPath = resolveRelativeModulePath(module.path, specifier);
|
|
248
|
+
const target = void 0 === resolvedPath ? void 0 : readModule(resolvedPath);
|
|
249
|
+
return void 0 === target ? void 0 : resolveBinding(target, imported, 'export', readModule, seen);
|
|
250
|
+
};
|
|
251
|
+
if ('local' === scope) {
|
|
252
|
+
const imported = importedBinding(module, name);
|
|
253
|
+
return void 0 === imported ? void 0 : throughModule(imported.specifier, imported.imported);
|
|
254
|
+
}
|
|
255
|
+
if ('default' === name) {
|
|
256
|
+
const exported = defaultExport(module);
|
|
257
|
+
if ('string' == typeof exported) return resolveBinding(module, exported, 'local', readModule, seen);
|
|
258
|
+
return void 0 === exported ? void 0 : {
|
|
259
|
+
expression: exported,
|
|
260
|
+
module
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
const reexported = reexportedBinding(module, name);
|
|
264
|
+
if (void 0 !== reexported) return void 0 === reexported.specifier ? resolveBinding(module, reexported.local, 'local', readModule, seen) : throughModule(reexported.specifier, reexported.local);
|
|
265
|
+
for (const statement of module.file.program.body){
|
|
266
|
+
if (!types_namespaceObject.isExportAllDeclaration(statement) || 'type' === statement.exportKind) continue;
|
|
267
|
+
const resolved = throughModule(statement.source.value, name);
|
|
268
|
+
if (void 0 !== resolved) return resolved;
|
|
269
|
+
}
|
|
270
|
+
};
|
|
160
271
|
const objectLiteral = (expression)=>{
|
|
161
272
|
if (null == expression) return;
|
|
162
273
|
const unwrapped = unwrapExpression(expression);
|
|
@@ -288,7 +399,22 @@ const rootComposesFoundation = (sourceFile, declaration, stem, foundationName)=>
|
|
|
288
399
|
'identity'
|
|
289
400
|
]))) && first?.name === 'addHttpApi' && identifierName(first.arguments[0]) === foundationName;
|
|
290
401
|
};
|
|
291
|
-
const
|
|
402
|
+
const nonEndpointCombinators = {
|
|
403
|
+
api: {
|
|
404
|
+
annotate: 2,
|
|
405
|
+
annotateMerge: 1,
|
|
406
|
+
middleware: 1
|
|
407
|
+
},
|
|
408
|
+
group: {
|
|
409
|
+
annotate: 2,
|
|
410
|
+
annotateEndpoints: 2,
|
|
411
|
+
annotateEndpointsMerge: 1,
|
|
412
|
+
annotateMerge: 1,
|
|
413
|
+
middleware: 1
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
const reachableEndpoints = (rootModule, declaration)=>{
|
|
417
|
+
const readModule = createModuleReader(rootModule);
|
|
292
418
|
const active = new Set();
|
|
293
419
|
const endpoints = [];
|
|
294
420
|
const identities = new Set();
|
|
@@ -303,13 +429,16 @@ const reachableEndpoints = (sourceFile, declaration)=>{
|
|
|
303
429
|
options: 'OPTIONS'
|
|
304
430
|
};
|
|
305
431
|
let visits = 0;
|
|
306
|
-
const visit = (expression, kind, group = '')=>{
|
|
432
|
+
const visit = (expression, module, kind, group = '')=>{
|
|
307
433
|
if (!expression) return false;
|
|
308
434
|
const node = unwrapExpression(expression);
|
|
309
435
|
if (active.has(node) || active.size >= 128 || ++visits > 2048) return false;
|
|
310
436
|
active.add(node);
|
|
311
437
|
try {
|
|
312
|
-
if (types_namespaceObject.isIdentifier(node))
|
|
438
|
+
if (types_namespaceObject.isIdentifier(node)) {
|
|
439
|
+
const resolved = resolveBinding(module, node.name, 'local', readModule, new Set());
|
|
440
|
+
return void 0 !== resolved && visit(resolved.expression, resolved.module, kind, group);
|
|
441
|
+
}
|
|
313
442
|
if ('endpoint' === kind) {
|
|
314
443
|
if (!types_namespaceObject.isCallExpression(node) || node.arguments.length < 2 || node.arguments.length > 3) return false;
|
|
315
444
|
const callee = accessPath(node.callee);
|
|
@@ -335,19 +464,35 @@ const reachableEndpoints = (sourceFile, declaration)=>{
|
|
|
335
464
|
]) || 1 !== chain.base.arguments.length) return false;
|
|
336
465
|
const name = stringLiteral(chain.base.arguments[0]);
|
|
337
466
|
if (!name) return false;
|
|
467
|
+
const chainStart = endpoints.length;
|
|
468
|
+
const combinators = nonEndpointCombinators[kind];
|
|
338
469
|
return chain.methods.every((method)=>{
|
|
470
|
+
if ('prefix' === method.name) {
|
|
471
|
+
const prefix = stringLiteral(method.arguments[0]);
|
|
472
|
+
if (1 !== method.arguments.length || !prefix?.startsWith('/')) return false;
|
|
473
|
+
for(let index = chainStart; index < endpoints.length; index += 1){
|
|
474
|
+
const endpoint = endpoints[index];
|
|
475
|
+
endpoints[index] = {
|
|
476
|
+
...endpoint,
|
|
477
|
+
routePath: `${prefix}${endpoint.routePath}`
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
return true;
|
|
481
|
+
}
|
|
482
|
+
const combinatorArity = combinators[method.name];
|
|
483
|
+
if (void 0 !== combinatorArity) return method.arguments.length === combinatorArity;
|
|
339
484
|
if (1 !== method.arguments.length) return false;
|
|
340
|
-
if ('pipe' === method.name) return 'identity' === identifierName(method.arguments[0]) && importsExactBindings(
|
|
485
|
+
if ('pipe' === method.name) return 'identity' === identifierName(method.arguments[0]) && importsExactBindings(module.file, 'effect', [
|
|
341
486
|
'identity'
|
|
342
487
|
]);
|
|
343
|
-
if ('add' === method.name) return visit(method.arguments[0], 'api' === kind ? 'group' : 'endpoint', 'group' === kind ? name : '');
|
|
344
|
-
return 'api' === kind && 'addHttpApi' === method.name && visit(method.arguments[0], 'api');
|
|
488
|
+
if ('add' === method.name) return visit(method.arguments[0], module, 'api' === kind ? 'group' : 'endpoint', 'group' === kind ? name : '');
|
|
489
|
+
return 'api' === kind && 'addHttpApi' === method.name && visit(method.arguments[0], module, 'api');
|
|
345
490
|
});
|
|
346
491
|
} finally{
|
|
347
492
|
active.delete(node);
|
|
348
493
|
}
|
|
349
494
|
};
|
|
350
|
-
return visit(declaration?.init, 'api') ? endpoints : void 0;
|
|
495
|
+
return visit(declaration?.init, rootModule, 'api') ? endpoints : void 0;
|
|
351
496
|
};
|
|
352
497
|
const operationContextFields = (property)=>{
|
|
353
498
|
const constructorCall = exactCall(property.value, [
|
|
@@ -478,7 +623,8 @@ const readinessSchemaIsShared = (declaration, markerSchemaName, ownerMarkerIsIde
|
|
|
478
623
|
return void 0 !== schema && (schema.identity && ownerMarkerIsIdentity || 1 === schema.assignments.size && identifierName(schema.assignments.get('marker')?.value) === markerSchemaName);
|
|
479
624
|
};
|
|
480
625
|
const declarationIsIdentifier = (declaration, identifier)=>declaration?.init != null && identifierName(unwrapExpression(declaration.init)) === identifier;
|
|
481
|
-
const validateParsedContract = (
|
|
626
|
+
const validateParsedContract = (module, stem, expectation)=>{
|
|
627
|
+
const sourceFile = module.file;
|
|
482
628
|
const exportStem = camelCaseStem(stem);
|
|
483
629
|
const foundationName = `${exportStem}FoundationApi`;
|
|
484
630
|
const markerSchemaName = `${exportStem}MarkerSchema`;
|
|
@@ -495,7 +641,7 @@ const validateParsedContract = (sourceFile, stem, expectation)=>{
|
|
|
495
641
|
if (!readinessSchemaIsShared(exportedConst(sourceFile, readinessSchemaName), markerSchemaName, declarationIsIdentifier(markerDeclaration, 'MicroVerticalBuildMarkerSchema'))) return 'MicroVertical readiness schema must consume the shared readiness schema without overriding shared fields';
|
|
496
642
|
if (!foundationIsExact(exportedConst(sourceFile, foundationName), stem, readinessSchemaName)) return 'MicroVertical readiness foundation API must directly compose its exact readiness endpoint and foundation identity';
|
|
497
643
|
if (!rootComposesFoundation(sourceFile, exportedConst(sourceFile, `${exportStem}Api`), stem, foundationName)) return 'MicroVertical root API must explicitly compose its readiness foundation API';
|
|
498
|
-
const endpoints = reachableEndpoints(
|
|
644
|
+
const endpoints = reachableEndpoints(module, exportedConst(sourceFile, `${exportStem}Api`));
|
|
499
645
|
if (!endpoints) return 'MicroVertical root API must compose bounded native endpoint declarations without ambiguous identities';
|
|
500
646
|
if (!operationMapIsConnected(exportedConst(sourceFile, `${exportStem}OperationContexts`), stem, expectation.operationPaths ?? {}, endpoints)) return 'MicroVertical operation map must construct every operation with the shared context constructor';
|
|
501
647
|
if (!metadataIsExact(exportedConst(sourceFile, `${exportStem}ApiContract`), expectation, endpoints)) return 'MicroVertical root contract must keep exact owner and API path metadata without forbidden fields';
|
|
@@ -509,16 +655,21 @@ const microVerticalApiBaselineViolation = (stem, filePath, expectation)=>{
|
|
|
509
655
|
try {
|
|
510
656
|
const sourceFile = parseConsumer(filePath);
|
|
511
657
|
if (!(0, external_microvertical_api_owner_cjs_namespaceObject.baselinePublicIdentityIsExact)(filePath, expectation)) return 'MicroVertical baseline imports must resolve the exact framework owner public export and schema identity';
|
|
512
|
-
return validateParsedContract(
|
|
658
|
+
return validateParsedContract({
|
|
659
|
+
file: sourceFile,
|
|
660
|
+
path: filePath
|
|
661
|
+
}, stem, expectation);
|
|
513
662
|
} catch (error) {
|
|
514
663
|
if (error instanceof ConsumerSyntaxError) return `MicroVertical root contract must be valid TypeScript syntax (${error.message})`;
|
|
515
664
|
throw error;
|
|
516
665
|
}
|
|
517
666
|
};
|
|
518
667
|
exports.configuredMicroVerticalApiStem = __webpack_exports__.configuredMicroVerticalApiStem;
|
|
668
|
+
exports.consumerParserPlugins = __webpack_exports__.consumerParserPlugins;
|
|
519
669
|
exports.microVerticalApiBaselineViolation = __webpack_exports__.microVerticalApiBaselineViolation;
|
|
520
670
|
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
521
671
|
"configuredMicroVerticalApiStem",
|
|
672
|
+
"consumerParserPlugins",
|
|
522
673
|
"microVerticalApiBaselineViolation"
|
|
523
674
|
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
524
675
|
Object.defineProperty(exports, '__esModule', {
|
|
@@ -159,10 +159,7 @@ function check(options, topology) {
|
|
|
159
159
|
const ast = (0, parser_namespaceObject.parse)(read(file), {
|
|
160
160
|
sourceType: 'module',
|
|
161
161
|
sourceFilename: file,
|
|
162
|
-
plugins:
|
|
163
|
-
"typescript",
|
|
164
|
-
'jsx'
|
|
165
|
-
]
|
|
162
|
+
plugins: (0, external_microvertical_api_baseline_cjs_namespaceObject.consumerParserPlugins)(file)
|
|
166
163
|
});
|
|
167
164
|
parsed.set(file, ast);
|
|
168
165
|
return ast;
|
|
@@ -175,13 +172,49 @@ function check(options, topology) {
|
|
|
175
172
|
throw error;
|
|
176
173
|
}
|
|
177
174
|
};
|
|
178
|
-
const
|
|
175
|
+
const resolveRelative = (fromFile, specifier)=>{
|
|
176
|
+
if (!/^\.\.?\//u.test(specifier)) return;
|
|
177
|
+
const base = external_node_path_default().posix.join(external_node_path_default().posix.dirname(fromFile), specifier).replace(/\.(?:[cm]?[jt]sx?)$/u, '');
|
|
178
|
+
return [
|
|
179
|
+
`${base}.ts`,
|
|
180
|
+
`${base}.tsx`,
|
|
181
|
+
`${base}/index.ts`,
|
|
182
|
+
`${base}/index.tsx`
|
|
183
|
+
].find((candidate)=>exists(candidate));
|
|
184
|
+
};
|
|
185
|
+
const composedModules = (file)=>{
|
|
186
|
+
const order = [];
|
|
187
|
+
const seen = new Set();
|
|
188
|
+
const queue = [
|
|
189
|
+
file
|
|
190
|
+
];
|
|
191
|
+
while(queue.length > 0 && order.length < 128){
|
|
192
|
+
const current = queue.shift();
|
|
193
|
+
if (seen.has(current)) continue;
|
|
194
|
+
seen.add(current);
|
|
195
|
+
order.push(current);
|
|
196
|
+
const ast = parseFile(current);
|
|
197
|
+
if (!ast) continue;
|
|
198
|
+
for (const statement of ast.program.body){
|
|
199
|
+
const source = (types_namespaceObject.isImportDeclaration(statement) || types_namespaceObject.isExportNamedDeclaration(statement) || types_namespaceObject.isExportAllDeclaration(statement)) && statement.source ? statement.source.value : void 0;
|
|
200
|
+
const resolved = void 0 === source ? void 0 : resolveRelative(current, source);
|
|
201
|
+
if (void 0 !== resolved && !seen.has(resolved)) queue.push(resolved);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return order;
|
|
205
|
+
};
|
|
206
|
+
const moduleShape = (file, imports, calls, patterns = [], defaultExport, composed = false)=>{
|
|
179
207
|
if (!exists(file)) return;
|
|
180
208
|
const ast = parseFile(file);
|
|
181
209
|
if (!ast) return;
|
|
182
|
-
const
|
|
210
|
+
const shapeFiles = composed ? composedModules(file) : [
|
|
211
|
+
file
|
|
212
|
+
];
|
|
213
|
+
const body = shapeFiles.flatMap((shapeFile)=>parseFile(shapeFile)?.program.body ?? []);
|
|
183
214
|
for (const [specifier, names] of imports){
|
|
184
|
-
const
|
|
215
|
+
const target = resolveRelative(file, specifier);
|
|
216
|
+
const matchesSpecifier = (item, from)=>void 0 === target ? item.source.value === specifier : resolveRelative(from, item.source.value) === target;
|
|
217
|
+
const imported = shapeFiles.flatMap((shapeFile)=>(parseFile(shapeFile)?.program.body ?? []).filter((item)=>types_namespaceObject.isImportDeclaration(item) && 'type' !== item.importKind && matchesSpecifier(item, shapeFile)));
|
|
185
218
|
assert(imported.length, `${file}: must import from ${specifier}`);
|
|
186
219
|
for (const name of names)assert(imported.some((item)=>types_namespaceObject.isImportDeclaration(item) && item.specifiers.some((value)=>types_namespaceObject.isImportSpecifier(value) && 'type' !== value.importKind && types_namespaceObject.isIdentifier(value.imported, {
|
|
187
220
|
name
|
|
@@ -190,11 +223,15 @@ function check(options, topology) {
|
|
|
190
223
|
}))), `${file}: must import ${name} from ${specifier}`);
|
|
191
224
|
}
|
|
192
225
|
const found = new Set();
|
|
193
|
-
|
|
226
|
+
const collectCalls = (node)=>{
|
|
194
227
|
if (!types_namespaceObject.isCallExpression(node)) return;
|
|
195
228
|
if (types_namespaceObject.isIdentifier(node.callee)) found.add(node.callee.name);
|
|
196
229
|
if (types_namespaceObject.isMemberExpression(node.callee) && !node.callee.computed && types_namespaceObject.isIdentifier(node.callee.object) && types_namespaceObject.isIdentifier(node.callee.property)) found.add(`${node.callee.object.name}.${node.callee.property.name}`);
|
|
197
|
-
}
|
|
230
|
+
};
|
|
231
|
+
for (const shapeFile of shapeFiles){
|
|
232
|
+
const shapeAst = parseFile(shapeFile);
|
|
233
|
+
if (shapeAst) types_namespaceObject.traverseFast(shapeAst, collectCalls);
|
|
234
|
+
}
|
|
198
235
|
for (const call of calls)assert(found.has(call), `${file}: must call ${call}(...)`);
|
|
199
236
|
const source = read(file).replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/gu, '');
|
|
200
237
|
for (const [pattern, reason] of patterns)assert(pattern.test(source), `${file}: ${reason}`);
|
|
@@ -372,7 +409,7 @@ function check(options, topology) {
|
|
|
372
409
|
]
|
|
373
410
|
], [
|
|
374
411
|
rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
|
|
375
|
-
]);
|
|
412
|
+
], [], void 0, true);
|
|
376
413
|
moduleShape(`${appPath}/api/effect-api.ts`, [], [], [
|
|
377
414
|
[
|
|
378
415
|
/\bbackendFederationContract\b/u,
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import node_fs from "node:fs";
|
|
2
|
+
import node_path from "node:path";
|
|
2
3
|
import { parse } from "@babel/parser";
|
|
3
4
|
import traverse, { Hub, NodePath } from "@babel/traverse";
|
|
4
5
|
import { baselinePublicIdentityIsExact } from "./microvertical-api-owner.js";
|
|
5
6
|
import * as __rspack_external__babel_types_6e8337dd from "@babel/types";
|
|
7
|
+
const consumerParserPlugins = (filePath)=>filePath.endsWith('.tsx') || filePath.endsWith('.jsx') ? [
|
|
8
|
+
"typescript",
|
|
9
|
+
'jsx'
|
|
10
|
+
] : [
|
|
11
|
+
"typescript"
|
|
12
|
+
];
|
|
6
13
|
class ConsumerSyntaxError extends Error {
|
|
7
14
|
}
|
|
8
15
|
class ConsumerValidationHub extends Hub {
|
|
@@ -17,9 +24,7 @@ function parseConsumer(filePath) {
|
|
|
17
24
|
file = parse(node_fs.readFileSync(filePath, 'utf8'), {
|
|
18
25
|
sourceType: 'module',
|
|
19
26
|
sourceFilename: filePath,
|
|
20
|
-
plugins:
|
|
21
|
-
"typescript"
|
|
22
|
-
]
|
|
27
|
+
plugins: consumerParserPlugins(filePath)
|
|
23
28
|
});
|
|
24
29
|
} catch (error) {
|
|
25
30
|
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));
|
|
@@ -113,6 +118,110 @@ const exportedConst = (sourceFile, name)=>{
|
|
|
113
118
|
}))) return localConst(sourceFile, name);
|
|
114
119
|
}
|
|
115
120
|
};
|
|
121
|
+
const resolveRelativeModulePath = (fromPath, specifier)=>{
|
|
122
|
+
if (!/^\.\.?\//u.test(specifier)) return;
|
|
123
|
+
const resolved = node_path.resolve(node_path.dirname(fromPath), specifier);
|
|
124
|
+
const stem = resolved.replace(/\.(?:[cm]?[jt]sx?)$/u, '');
|
|
125
|
+
for (const candidate of [
|
|
126
|
+
`${stem}.ts`,
|
|
127
|
+
`${stem}.tsx`,
|
|
128
|
+
node_path.join(stem, 'index.ts'),
|
|
129
|
+
node_path.join(stem, 'index.tsx')
|
|
130
|
+
])if (node_fs.existsSync(candidate) && node_fs.statSync(candidate).isFile()) return candidate;
|
|
131
|
+
};
|
|
132
|
+
const createModuleReader = (root)=>{
|
|
133
|
+
const modules = new Map([
|
|
134
|
+
[
|
|
135
|
+
root.path,
|
|
136
|
+
root
|
|
137
|
+
]
|
|
138
|
+
]);
|
|
139
|
+
return (filePath)=>{
|
|
140
|
+
if (modules.has(filePath)) return modules.get(filePath);
|
|
141
|
+
if (modules.size >= 256) return;
|
|
142
|
+
let module;
|
|
143
|
+
try {
|
|
144
|
+
module = {
|
|
145
|
+
file: parseConsumer(filePath),
|
|
146
|
+
path: filePath
|
|
147
|
+
};
|
|
148
|
+
} catch {
|
|
149
|
+
module = void 0;
|
|
150
|
+
}
|
|
151
|
+
modules.set(filePath, module);
|
|
152
|
+
return module;
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
const importedBinding = (module, name)=>{
|
|
156
|
+
for (const statement of module.file.program.body)if (__rspack_external__babel_types_6e8337dd.isImportDeclaration(statement) && 'type' !== statement.importKind) {
|
|
157
|
+
for (const specifier of statement.specifiers)if (__rspack_external__babel_types_6e8337dd.isIdentifier(specifier.local, {
|
|
158
|
+
name
|
|
159
|
+
})) {
|
|
160
|
+
if (__rspack_external__babel_types_6e8337dd.isImportSpecifier(specifier) && 'type' !== specifier.importKind) return {
|
|
161
|
+
imported: propertyName(specifier.imported) ?? name,
|
|
162
|
+
specifier: statement.source.value
|
|
163
|
+
};
|
|
164
|
+
if (__rspack_external__babel_types_6e8337dd.isImportDefaultSpecifier(specifier)) return {
|
|
165
|
+
imported: 'default',
|
|
166
|
+
specifier: statement.source.value
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
const reexportedBinding = (module, name)=>{
|
|
172
|
+
for (const statement of module.file.program.body){
|
|
173
|
+
if (!__rspack_external__babel_types_6e8337dd.isExportNamedDeclaration(statement) || 'type' === statement.exportKind) continue;
|
|
174
|
+
const specifier = statement.specifiers.find((value)=>__rspack_external__babel_types_6e8337dd.isExportSpecifier(value) && 'type' !== value.exportKind && propertyName(value.exported) === name);
|
|
175
|
+
if (void 0 !== specifier && __rspack_external__babel_types_6e8337dd.isExportSpecifier(specifier)) return {
|
|
176
|
+
local: specifier.local.name,
|
|
177
|
+
...statement.source ? {
|
|
178
|
+
specifier: statement.source.value
|
|
179
|
+
} : {}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
const defaultExport = (module)=>{
|
|
184
|
+
for (const statement of module.file.program.body){
|
|
185
|
+
if (!__rspack_external__babel_types_6e8337dd.isExportDefaultDeclaration(statement)) continue;
|
|
186
|
+
const declaration = statement.declaration;
|
|
187
|
+
if (__rspack_external__babel_types_6e8337dd.isIdentifier(declaration)) return declaration.name;
|
|
188
|
+
return __rspack_external__babel_types_6e8337dd.isExpression(declaration) ? declaration : void 0;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
const resolveBinding = (module, name, scope, readModule, seen)=>{
|
|
192
|
+
const key = `${scope}:${module.path}#${name}`;
|
|
193
|
+
if (seen.has(key) || seen.size >= 512) return;
|
|
194
|
+
seen.add(key);
|
|
195
|
+
const declaration = 'export' === scope ? exportedConst(module.file, name) : localConst(module.file, name);
|
|
196
|
+
if (declaration?.init) return {
|
|
197
|
+
expression: declaration.init,
|
|
198
|
+
module
|
|
199
|
+
};
|
|
200
|
+
const throughModule = (specifier, imported)=>{
|
|
201
|
+
const resolvedPath = resolveRelativeModulePath(module.path, specifier);
|
|
202
|
+
const target = void 0 === resolvedPath ? void 0 : readModule(resolvedPath);
|
|
203
|
+
return void 0 === target ? void 0 : resolveBinding(target, imported, 'export', readModule, seen);
|
|
204
|
+
};
|
|
205
|
+
if ('local' === scope) {
|
|
206
|
+
const imported = importedBinding(module, name);
|
|
207
|
+
return void 0 === imported ? void 0 : throughModule(imported.specifier, imported.imported);
|
|
208
|
+
}
|
|
209
|
+
if ('default' === name) {
|
|
210
|
+
const exported = defaultExport(module);
|
|
211
|
+
if ('string' == typeof exported) return resolveBinding(module, exported, 'local', readModule, seen);
|
|
212
|
+
return void 0 === exported ? void 0 : {
|
|
213
|
+
expression: exported,
|
|
214
|
+
module
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const reexported = reexportedBinding(module, name);
|
|
218
|
+
if (void 0 !== reexported) return void 0 === reexported.specifier ? resolveBinding(module, reexported.local, 'local', readModule, seen) : throughModule(reexported.specifier, reexported.local);
|
|
219
|
+
for (const statement of module.file.program.body){
|
|
220
|
+
if (!__rspack_external__babel_types_6e8337dd.isExportAllDeclaration(statement) || 'type' === statement.exportKind) continue;
|
|
221
|
+
const resolved = throughModule(statement.source.value, name);
|
|
222
|
+
if (void 0 !== resolved) return resolved;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
116
225
|
const objectLiteral = (expression)=>{
|
|
117
226
|
if (null == expression) return;
|
|
118
227
|
const unwrapped = unwrapExpression(expression);
|
|
@@ -244,7 +353,22 @@ const rootComposesFoundation = (sourceFile, declaration, stem, foundationName)=>
|
|
|
244
353
|
'identity'
|
|
245
354
|
]))) && first?.name === 'addHttpApi' && identifierName(first.arguments[0]) === foundationName;
|
|
246
355
|
};
|
|
247
|
-
const
|
|
356
|
+
const nonEndpointCombinators = {
|
|
357
|
+
api: {
|
|
358
|
+
annotate: 2,
|
|
359
|
+
annotateMerge: 1,
|
|
360
|
+
middleware: 1
|
|
361
|
+
},
|
|
362
|
+
group: {
|
|
363
|
+
annotate: 2,
|
|
364
|
+
annotateEndpoints: 2,
|
|
365
|
+
annotateEndpointsMerge: 1,
|
|
366
|
+
annotateMerge: 1,
|
|
367
|
+
middleware: 1
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
const reachableEndpoints = (rootModule, declaration)=>{
|
|
371
|
+
const readModule = createModuleReader(rootModule);
|
|
248
372
|
const active = new Set();
|
|
249
373
|
const endpoints = [];
|
|
250
374
|
const identities = new Set();
|
|
@@ -259,13 +383,16 @@ const reachableEndpoints = (sourceFile, declaration)=>{
|
|
|
259
383
|
options: 'OPTIONS'
|
|
260
384
|
};
|
|
261
385
|
let visits = 0;
|
|
262
|
-
const visit = (expression, kind, group = '')=>{
|
|
386
|
+
const visit = (expression, module, kind, group = '')=>{
|
|
263
387
|
if (!expression) return false;
|
|
264
388
|
const node = unwrapExpression(expression);
|
|
265
389
|
if (active.has(node) || active.size >= 128 || ++visits > 2048) return false;
|
|
266
390
|
active.add(node);
|
|
267
391
|
try {
|
|
268
|
-
if (__rspack_external__babel_types_6e8337dd.isIdentifier(node))
|
|
392
|
+
if (__rspack_external__babel_types_6e8337dd.isIdentifier(node)) {
|
|
393
|
+
const resolved = resolveBinding(module, node.name, 'local', readModule, new Set());
|
|
394
|
+
return void 0 !== resolved && visit(resolved.expression, resolved.module, kind, group);
|
|
395
|
+
}
|
|
269
396
|
if ('endpoint' === kind) {
|
|
270
397
|
if (!__rspack_external__babel_types_6e8337dd.isCallExpression(node) || node.arguments.length < 2 || node.arguments.length > 3) return false;
|
|
271
398
|
const callee = accessPath(node.callee);
|
|
@@ -291,19 +418,35 @@ const reachableEndpoints = (sourceFile, declaration)=>{
|
|
|
291
418
|
]) || 1 !== chain.base.arguments.length) return false;
|
|
292
419
|
const name = stringLiteral(chain.base.arguments[0]);
|
|
293
420
|
if (!name) return false;
|
|
421
|
+
const chainStart = endpoints.length;
|
|
422
|
+
const combinators = nonEndpointCombinators[kind];
|
|
294
423
|
return chain.methods.every((method)=>{
|
|
424
|
+
if ('prefix' === method.name) {
|
|
425
|
+
const prefix = stringLiteral(method.arguments[0]);
|
|
426
|
+
if (1 !== method.arguments.length || !prefix?.startsWith('/')) return false;
|
|
427
|
+
for(let index = chainStart; index < endpoints.length; index += 1){
|
|
428
|
+
const endpoint = endpoints[index];
|
|
429
|
+
endpoints[index] = {
|
|
430
|
+
...endpoint,
|
|
431
|
+
routePath: `${prefix}${endpoint.routePath}`
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
return true;
|
|
435
|
+
}
|
|
436
|
+
const combinatorArity = combinators[method.name];
|
|
437
|
+
if (void 0 !== combinatorArity) return method.arguments.length === combinatorArity;
|
|
295
438
|
if (1 !== method.arguments.length) return false;
|
|
296
|
-
if ('pipe' === method.name) return 'identity' === identifierName(method.arguments[0]) && importsExactBindings(
|
|
439
|
+
if ('pipe' === method.name) return 'identity' === identifierName(method.arguments[0]) && importsExactBindings(module.file, 'effect', [
|
|
297
440
|
'identity'
|
|
298
441
|
]);
|
|
299
|
-
if ('add' === method.name) return visit(method.arguments[0], 'api' === kind ? 'group' : 'endpoint', 'group' === kind ? name : '');
|
|
300
|
-
return 'api' === kind && 'addHttpApi' === method.name && visit(method.arguments[0], 'api');
|
|
442
|
+
if ('add' === method.name) return visit(method.arguments[0], module, 'api' === kind ? 'group' : 'endpoint', 'group' === kind ? name : '');
|
|
443
|
+
return 'api' === kind && 'addHttpApi' === method.name && visit(method.arguments[0], module, 'api');
|
|
301
444
|
});
|
|
302
445
|
} finally{
|
|
303
446
|
active.delete(node);
|
|
304
447
|
}
|
|
305
448
|
};
|
|
306
|
-
return visit(declaration?.init, 'api') ? endpoints : void 0;
|
|
449
|
+
return visit(declaration?.init, rootModule, 'api') ? endpoints : void 0;
|
|
307
450
|
};
|
|
308
451
|
const operationContextFields = (property)=>{
|
|
309
452
|
const constructorCall = exactCall(property.value, [
|
|
@@ -434,7 +577,8 @@ const readinessSchemaIsShared = (declaration, markerSchemaName, ownerMarkerIsIde
|
|
|
434
577
|
return void 0 !== schema && (schema.identity && ownerMarkerIsIdentity || 1 === schema.assignments.size && identifierName(schema.assignments.get('marker')?.value) === markerSchemaName);
|
|
435
578
|
};
|
|
436
579
|
const declarationIsIdentifier = (declaration, identifier)=>declaration?.init != null && identifierName(unwrapExpression(declaration.init)) === identifier;
|
|
437
|
-
const validateParsedContract = (
|
|
580
|
+
const validateParsedContract = (module, stem, expectation)=>{
|
|
581
|
+
const sourceFile = module.file;
|
|
438
582
|
const exportStem = camelCaseStem(stem);
|
|
439
583
|
const foundationName = `${exportStem}FoundationApi`;
|
|
440
584
|
const markerSchemaName = `${exportStem}MarkerSchema`;
|
|
@@ -451,7 +595,7 @@ const validateParsedContract = (sourceFile, stem, expectation)=>{
|
|
|
451
595
|
if (!readinessSchemaIsShared(exportedConst(sourceFile, readinessSchemaName), markerSchemaName, declarationIsIdentifier(markerDeclaration, 'MicroVerticalBuildMarkerSchema'))) return 'MicroVertical readiness schema must consume the shared readiness schema without overriding shared fields';
|
|
452
596
|
if (!foundationIsExact(exportedConst(sourceFile, foundationName), stem, readinessSchemaName)) return 'MicroVertical readiness foundation API must directly compose its exact readiness endpoint and foundation identity';
|
|
453
597
|
if (!rootComposesFoundation(sourceFile, exportedConst(sourceFile, `${exportStem}Api`), stem, foundationName)) return 'MicroVertical root API must explicitly compose its readiness foundation API';
|
|
454
|
-
const endpoints = reachableEndpoints(
|
|
598
|
+
const endpoints = reachableEndpoints(module, exportedConst(sourceFile, `${exportStem}Api`));
|
|
455
599
|
if (!endpoints) return 'MicroVertical root API must compose bounded native endpoint declarations without ambiguous identities';
|
|
456
600
|
if (!operationMapIsConnected(exportedConst(sourceFile, `${exportStem}OperationContexts`), stem, expectation.operationPaths ?? {}, endpoints)) return 'MicroVertical operation map must construct every operation with the shared context constructor';
|
|
457
601
|
if (!metadataIsExact(exportedConst(sourceFile, `${exportStem}ApiContract`), expectation, endpoints)) return 'MicroVertical root contract must keep exact owner and API path metadata without forbidden fields';
|
|
@@ -465,10 +609,13 @@ const microVerticalApiBaselineViolation = (stem, filePath, expectation)=>{
|
|
|
465
609
|
try {
|
|
466
610
|
const sourceFile = parseConsumer(filePath);
|
|
467
611
|
if (!baselinePublicIdentityIsExact(filePath, expectation)) return 'MicroVertical baseline imports must resolve the exact framework owner public export and schema identity';
|
|
468
|
-
return validateParsedContract(
|
|
612
|
+
return validateParsedContract({
|
|
613
|
+
file: sourceFile,
|
|
614
|
+
path: filePath
|
|
615
|
+
}, stem, expectation);
|
|
469
616
|
} catch (error) {
|
|
470
617
|
if (error instanceof ConsumerSyntaxError) return `MicroVertical root contract must be valid TypeScript syntax (${error.message})`;
|
|
471
618
|
throw error;
|
|
472
619
|
}
|
|
473
620
|
};
|
|
474
|
-
export { configuredMicroVerticalApiStem, microVerticalApiBaselineViolation };
|
|
621
|
+
export { configuredMicroVerticalApiStem, consumerParserPlugins, microVerticalApiBaselineViolation };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import node_fs from "node:fs";
|
|
2
2
|
import node_path from "node:path";
|
|
3
3
|
import { parse } from "@babel/parser";
|
|
4
|
-
import { configuredMicroVerticalApiStem, microVerticalApiBaselineViolation } from "./microvertical-api-baseline.js";
|
|
4
|
+
import { configuredMicroVerticalApiStem, consumerParserPlugins, microVerticalApiBaselineViolation } from "./microvertical-api-baseline.js";
|
|
5
5
|
import { baselinePackage, resolveBaselinePackageDirectory } from "./microvertical-api-owner.js";
|
|
6
6
|
import { createEffectApiImportResolver, strictEffectRuntimeTopologyViolation } from "./strict-effect-runtime.js";
|
|
7
7
|
import * as __rspack_external__babel_types_6e8337dd from "@babel/types";
|
|
@@ -113,10 +113,7 @@ function check(options, topology) {
|
|
|
113
113
|
const ast = parse(read(file), {
|
|
114
114
|
sourceType: 'module',
|
|
115
115
|
sourceFilename: file,
|
|
116
|
-
plugins:
|
|
117
|
-
"typescript",
|
|
118
|
-
'jsx'
|
|
119
|
-
]
|
|
116
|
+
plugins: consumerParserPlugins(file)
|
|
120
117
|
});
|
|
121
118
|
parsed.set(file, ast);
|
|
122
119
|
return ast;
|
|
@@ -129,13 +126,49 @@ function check(options, topology) {
|
|
|
129
126
|
throw error;
|
|
130
127
|
}
|
|
131
128
|
};
|
|
132
|
-
const
|
|
129
|
+
const resolveRelative = (fromFile, specifier)=>{
|
|
130
|
+
if (!/^\.\.?\//u.test(specifier)) return;
|
|
131
|
+
const base = node_path.posix.join(node_path.posix.dirname(fromFile), specifier).replace(/\.(?:[cm]?[jt]sx?)$/u, '');
|
|
132
|
+
return [
|
|
133
|
+
`${base}.ts`,
|
|
134
|
+
`${base}.tsx`,
|
|
135
|
+
`${base}/index.ts`,
|
|
136
|
+
`${base}/index.tsx`
|
|
137
|
+
].find((candidate)=>exists(candidate));
|
|
138
|
+
};
|
|
139
|
+
const composedModules = (file)=>{
|
|
140
|
+
const order = [];
|
|
141
|
+
const seen = new Set();
|
|
142
|
+
const queue = [
|
|
143
|
+
file
|
|
144
|
+
];
|
|
145
|
+
while(queue.length > 0 && order.length < 128){
|
|
146
|
+
const current = queue.shift();
|
|
147
|
+
if (seen.has(current)) continue;
|
|
148
|
+
seen.add(current);
|
|
149
|
+
order.push(current);
|
|
150
|
+
const ast = parseFile(current);
|
|
151
|
+
if (!ast) continue;
|
|
152
|
+
for (const statement of ast.program.body){
|
|
153
|
+
const source = (__rspack_external__babel_types_6e8337dd.isImportDeclaration(statement) || __rspack_external__babel_types_6e8337dd.isExportNamedDeclaration(statement) || __rspack_external__babel_types_6e8337dd.isExportAllDeclaration(statement)) && statement.source ? statement.source.value : void 0;
|
|
154
|
+
const resolved = void 0 === source ? void 0 : resolveRelative(current, source);
|
|
155
|
+
if (void 0 !== resolved && !seen.has(resolved)) queue.push(resolved);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return order;
|
|
159
|
+
};
|
|
160
|
+
const moduleShape = (file, imports, calls, patterns = [], defaultExport, composed = false)=>{
|
|
133
161
|
if (!exists(file)) return;
|
|
134
162
|
const ast = parseFile(file);
|
|
135
163
|
if (!ast) return;
|
|
136
|
-
const
|
|
164
|
+
const shapeFiles = composed ? composedModules(file) : [
|
|
165
|
+
file
|
|
166
|
+
];
|
|
167
|
+
const body = shapeFiles.flatMap((shapeFile)=>parseFile(shapeFile)?.program.body ?? []);
|
|
137
168
|
for (const [specifier, names] of imports){
|
|
138
|
-
const
|
|
169
|
+
const target = resolveRelative(file, specifier);
|
|
170
|
+
const matchesSpecifier = (item, from)=>void 0 === target ? item.source.value === specifier : resolveRelative(from, item.source.value) === target;
|
|
171
|
+
const imported = shapeFiles.flatMap((shapeFile)=>(parseFile(shapeFile)?.program.body ?? []).filter((item)=>__rspack_external__babel_types_6e8337dd.isImportDeclaration(item) && 'type' !== item.importKind && matchesSpecifier(item, shapeFile)));
|
|
139
172
|
assert(imported.length, `${file}: must import from ${specifier}`);
|
|
140
173
|
for (const name of names)assert(imported.some((item)=>__rspack_external__babel_types_6e8337dd.isImportDeclaration(item) && item.specifiers.some((value)=>__rspack_external__babel_types_6e8337dd.isImportSpecifier(value) && 'type' !== value.importKind && __rspack_external__babel_types_6e8337dd.isIdentifier(value.imported, {
|
|
141
174
|
name
|
|
@@ -144,11 +177,15 @@ function check(options, topology) {
|
|
|
144
177
|
}))), `${file}: must import ${name} from ${specifier}`);
|
|
145
178
|
}
|
|
146
179
|
const found = new Set();
|
|
147
|
-
|
|
180
|
+
const collectCalls = (node)=>{
|
|
148
181
|
if (!__rspack_external__babel_types_6e8337dd.isCallExpression(node)) return;
|
|
149
182
|
if (__rspack_external__babel_types_6e8337dd.isIdentifier(node.callee)) found.add(node.callee.name);
|
|
150
183
|
if (__rspack_external__babel_types_6e8337dd.isMemberExpression(node.callee) && !node.callee.computed && __rspack_external__babel_types_6e8337dd.isIdentifier(node.callee.object) && __rspack_external__babel_types_6e8337dd.isIdentifier(node.callee.property)) found.add(`${node.callee.object.name}.${node.callee.property.name}`);
|
|
151
|
-
}
|
|
184
|
+
};
|
|
185
|
+
for (const shapeFile of shapeFiles){
|
|
186
|
+
const shapeAst = parseFile(shapeFile);
|
|
187
|
+
if (shapeAst) __rspack_external__babel_types_6e8337dd.traverseFast(shapeAst, collectCalls);
|
|
188
|
+
}
|
|
152
189
|
for (const call of calls)assert(found.has(call), `${file}: must call ${call}(...)`);
|
|
153
190
|
const source = read(file).replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/gu, '');
|
|
154
191
|
for (const [pattern, reason] of patterns)assert(pattern.test(source), `${file}: ${reason}`);
|
|
@@ -326,7 +363,7 @@ function check(options, topology) {
|
|
|
326
363
|
]
|
|
327
364
|
], [
|
|
328
365
|
rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
|
|
329
|
-
]);
|
|
366
|
+
], [], void 0, true);
|
|
330
367
|
moduleShape(`${appPath}/api/effect-api.ts`, [], [], [
|
|
331
368
|
[
|
|
332
369
|
/\bbackendFederationContract\b/u,
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import node_fs from "node:fs";
|
|
3
|
+
import node_path from "node:path";
|
|
3
4
|
import { parse } from "@babel/parser";
|
|
4
5
|
import traverse, { Hub, NodePath } from "@babel/traverse";
|
|
5
6
|
import { baselinePublicIdentityIsExact } from "./microvertical-api-owner.js";
|
|
6
7
|
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
|
+
];
|
|
7
14
|
class ConsumerSyntaxError extends Error {
|
|
8
15
|
}
|
|
9
16
|
class ConsumerValidationHub extends Hub {
|
|
@@ -18,9 +25,7 @@ function parseConsumer(filePath) {
|
|
|
18
25
|
file = parse(node_fs.readFileSync(filePath, 'utf8'), {
|
|
19
26
|
sourceType: 'module',
|
|
20
27
|
sourceFilename: filePath,
|
|
21
|
-
plugins:
|
|
22
|
-
"typescript"
|
|
23
|
-
]
|
|
28
|
+
plugins: consumerParserPlugins(filePath)
|
|
24
29
|
});
|
|
25
30
|
} catch (error) {
|
|
26
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));
|
|
@@ -114,6 +119,110 @@ const exportedConst = (sourceFile, name)=>{
|
|
|
114
119
|
}))) return localConst(sourceFile, name);
|
|
115
120
|
}
|
|
116
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
|
+
};
|
|
117
226
|
const objectLiteral = (expression)=>{
|
|
118
227
|
if (null == expression) return;
|
|
119
228
|
const unwrapped = unwrapExpression(expression);
|
|
@@ -245,7 +354,22 @@ const rootComposesFoundation = (sourceFile, declaration, stem, foundationName)=>
|
|
|
245
354
|
'identity'
|
|
246
355
|
]))) && first?.name === 'addHttpApi' && identifierName(first.arguments[0]) === foundationName;
|
|
247
356
|
};
|
|
248
|
-
const
|
|
357
|
+
const nonEndpointCombinators = {
|
|
358
|
+
api: {
|
|
359
|
+
annotate: 2,
|
|
360
|
+
annotateMerge: 1,
|
|
361
|
+
middleware: 1
|
|
362
|
+
},
|
|
363
|
+
group: {
|
|
364
|
+
annotate: 2,
|
|
365
|
+
annotateEndpoints: 2,
|
|
366
|
+
annotateEndpointsMerge: 1,
|
|
367
|
+
annotateMerge: 1,
|
|
368
|
+
middleware: 1
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
const reachableEndpoints = (rootModule, declaration)=>{
|
|
372
|
+
const readModule = createModuleReader(rootModule);
|
|
249
373
|
const active = new Set();
|
|
250
374
|
const endpoints = [];
|
|
251
375
|
const identities = new Set();
|
|
@@ -260,13 +384,16 @@ const reachableEndpoints = (sourceFile, declaration)=>{
|
|
|
260
384
|
options: 'OPTIONS'
|
|
261
385
|
};
|
|
262
386
|
let visits = 0;
|
|
263
|
-
const visit = (expression, kind, group = '')=>{
|
|
387
|
+
const visit = (expression, module, kind, group = '')=>{
|
|
264
388
|
if (!expression) return false;
|
|
265
389
|
const node = unwrapExpression(expression);
|
|
266
390
|
if (active.has(node) || active.size >= 128 || ++visits > 2048) return false;
|
|
267
391
|
active.add(node);
|
|
268
392
|
try {
|
|
269
|
-
if (__rspack_external__babel_types_6e8337dd.isIdentifier(node))
|
|
393
|
+
if (__rspack_external__babel_types_6e8337dd.isIdentifier(node)) {
|
|
394
|
+
const resolved = resolveBinding(module, node.name, 'local', readModule, new Set());
|
|
395
|
+
return void 0 !== resolved && visit(resolved.expression, resolved.module, kind, group);
|
|
396
|
+
}
|
|
270
397
|
if ('endpoint' === kind) {
|
|
271
398
|
if (!__rspack_external__babel_types_6e8337dd.isCallExpression(node) || node.arguments.length < 2 || node.arguments.length > 3) return false;
|
|
272
399
|
const callee = accessPath(node.callee);
|
|
@@ -292,19 +419,35 @@ const reachableEndpoints = (sourceFile, declaration)=>{
|
|
|
292
419
|
]) || 1 !== chain.base.arguments.length) return false;
|
|
293
420
|
const name = stringLiteral(chain.base.arguments[0]);
|
|
294
421
|
if (!name) return false;
|
|
422
|
+
const chainStart = endpoints.length;
|
|
423
|
+
const combinators = nonEndpointCombinators[kind];
|
|
295
424
|
return chain.methods.every((method)=>{
|
|
425
|
+
if ('prefix' === method.name) {
|
|
426
|
+
const prefix = stringLiteral(method.arguments[0]);
|
|
427
|
+
if (1 !== method.arguments.length || !prefix?.startsWith('/')) return false;
|
|
428
|
+
for(let index = chainStart; index < endpoints.length; index += 1){
|
|
429
|
+
const endpoint = endpoints[index];
|
|
430
|
+
endpoints[index] = {
|
|
431
|
+
...endpoint,
|
|
432
|
+
routePath: `${prefix}${endpoint.routePath}`
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
return true;
|
|
436
|
+
}
|
|
437
|
+
const combinatorArity = combinators[method.name];
|
|
438
|
+
if (void 0 !== combinatorArity) return method.arguments.length === combinatorArity;
|
|
296
439
|
if (1 !== method.arguments.length) return false;
|
|
297
|
-
if ('pipe' === method.name) return 'identity' === identifierName(method.arguments[0]) && importsExactBindings(
|
|
440
|
+
if ('pipe' === method.name) return 'identity' === identifierName(method.arguments[0]) && importsExactBindings(module.file, 'effect', [
|
|
298
441
|
'identity'
|
|
299
442
|
]);
|
|
300
|
-
if ('add' === method.name) return visit(method.arguments[0], 'api' === kind ? 'group' : 'endpoint', 'group' === kind ? name : '');
|
|
301
|
-
return 'api' === kind && 'addHttpApi' === method.name && visit(method.arguments[0], 'api');
|
|
443
|
+
if ('add' === method.name) return visit(method.arguments[0], module, 'api' === kind ? 'group' : 'endpoint', 'group' === kind ? name : '');
|
|
444
|
+
return 'api' === kind && 'addHttpApi' === method.name && visit(method.arguments[0], module, 'api');
|
|
302
445
|
});
|
|
303
446
|
} finally{
|
|
304
447
|
active.delete(node);
|
|
305
448
|
}
|
|
306
449
|
};
|
|
307
|
-
return visit(declaration?.init, 'api') ? endpoints : void 0;
|
|
450
|
+
return visit(declaration?.init, rootModule, 'api') ? endpoints : void 0;
|
|
308
451
|
};
|
|
309
452
|
const operationContextFields = (property)=>{
|
|
310
453
|
const constructorCall = exactCall(property.value, [
|
|
@@ -435,7 +578,8 @@ const readinessSchemaIsShared = (declaration, markerSchemaName, ownerMarkerIsIde
|
|
|
435
578
|
return void 0 !== schema && (schema.identity && ownerMarkerIsIdentity || 1 === schema.assignments.size && identifierName(schema.assignments.get('marker')?.value) === markerSchemaName);
|
|
436
579
|
};
|
|
437
580
|
const declarationIsIdentifier = (declaration, identifier)=>declaration?.init != null && identifierName(unwrapExpression(declaration.init)) === identifier;
|
|
438
|
-
const validateParsedContract = (
|
|
581
|
+
const validateParsedContract = (module, stem, expectation)=>{
|
|
582
|
+
const sourceFile = module.file;
|
|
439
583
|
const exportStem = camelCaseStem(stem);
|
|
440
584
|
const foundationName = `${exportStem}FoundationApi`;
|
|
441
585
|
const markerSchemaName = `${exportStem}MarkerSchema`;
|
|
@@ -452,7 +596,7 @@ const validateParsedContract = (sourceFile, stem, expectation)=>{
|
|
|
452
596
|
if (!readinessSchemaIsShared(exportedConst(sourceFile, readinessSchemaName), markerSchemaName, declarationIsIdentifier(markerDeclaration, 'MicroVerticalBuildMarkerSchema'))) return 'MicroVertical readiness schema must consume the shared readiness schema without overriding shared fields';
|
|
453
597
|
if (!foundationIsExact(exportedConst(sourceFile, foundationName), stem, readinessSchemaName)) return 'MicroVertical readiness foundation API must directly compose its exact readiness endpoint and foundation identity';
|
|
454
598
|
if (!rootComposesFoundation(sourceFile, exportedConst(sourceFile, `${exportStem}Api`), stem, foundationName)) return 'MicroVertical root API must explicitly compose its readiness foundation API';
|
|
455
|
-
const endpoints = reachableEndpoints(
|
|
599
|
+
const endpoints = reachableEndpoints(module, exportedConst(sourceFile, `${exportStem}Api`));
|
|
456
600
|
if (!endpoints) return 'MicroVertical root API must compose bounded native endpoint declarations without ambiguous identities';
|
|
457
601
|
if (!operationMapIsConnected(exportedConst(sourceFile, `${exportStem}OperationContexts`), stem, expectation.operationPaths ?? {}, endpoints)) return 'MicroVertical operation map must construct every operation with the shared context constructor';
|
|
458
602
|
if (!metadataIsExact(exportedConst(sourceFile, `${exportStem}ApiContract`), expectation, endpoints)) return 'MicroVertical root contract must keep exact owner and API path metadata without forbidden fields';
|
|
@@ -466,10 +610,13 @@ const microVerticalApiBaselineViolation = (stem, filePath, expectation)=>{
|
|
|
466
610
|
try {
|
|
467
611
|
const sourceFile = parseConsumer(filePath);
|
|
468
612
|
if (!baselinePublicIdentityIsExact(filePath, expectation)) return 'MicroVertical baseline imports must resolve the exact framework owner public export and schema identity';
|
|
469
|
-
return validateParsedContract(
|
|
613
|
+
return validateParsedContract({
|
|
614
|
+
file: sourceFile,
|
|
615
|
+
path: filePath
|
|
616
|
+
}, stem, expectation);
|
|
470
617
|
} catch (error) {
|
|
471
618
|
if (error instanceof ConsumerSyntaxError) return `MicroVertical root contract must be valid TypeScript syntax (${error.message})`;
|
|
472
619
|
throw error;
|
|
473
620
|
}
|
|
474
621
|
};
|
|
475
|
-
export { configuredMicroVerticalApiStem, microVerticalApiBaselineViolation };
|
|
622
|
+
export { configuredMicroVerticalApiStem, consumerParserPlugins, microVerticalApiBaselineViolation };
|
|
@@ -2,7 +2,7 @@ import "node:module";
|
|
|
2
2
|
import node_fs from "node:fs";
|
|
3
3
|
import node_path from "node:path";
|
|
4
4
|
import { parse } from "@babel/parser";
|
|
5
|
-
import { configuredMicroVerticalApiStem, microVerticalApiBaselineViolation } from "./microvertical-api-baseline.js";
|
|
5
|
+
import { configuredMicroVerticalApiStem, consumerParserPlugins, microVerticalApiBaselineViolation } from "./microvertical-api-baseline.js";
|
|
6
6
|
import { baselinePackage, resolveBaselinePackageDirectory } from "./microvertical-api-owner.js";
|
|
7
7
|
import { createEffectApiImportResolver, strictEffectRuntimeTopologyViolation } from "./strict-effect-runtime.js";
|
|
8
8
|
import * as __rspack_external__babel_types_6e8337dd from "@babel/types";
|
|
@@ -114,10 +114,7 @@ function check(options, topology) {
|
|
|
114
114
|
const ast = parse(read(file), {
|
|
115
115
|
sourceType: 'module',
|
|
116
116
|
sourceFilename: file,
|
|
117
|
-
plugins:
|
|
118
|
-
"typescript",
|
|
119
|
-
'jsx'
|
|
120
|
-
]
|
|
117
|
+
plugins: consumerParserPlugins(file)
|
|
121
118
|
});
|
|
122
119
|
parsed.set(file, ast);
|
|
123
120
|
return ast;
|
|
@@ -130,13 +127,49 @@ function check(options, topology) {
|
|
|
130
127
|
throw error;
|
|
131
128
|
}
|
|
132
129
|
};
|
|
133
|
-
const
|
|
130
|
+
const resolveRelative = (fromFile, specifier)=>{
|
|
131
|
+
if (!/^\.\.?\//u.test(specifier)) return;
|
|
132
|
+
const base = node_path.posix.join(node_path.posix.dirname(fromFile), specifier).replace(/\.(?:[cm]?[jt]sx?)$/u, '');
|
|
133
|
+
return [
|
|
134
|
+
`${base}.ts`,
|
|
135
|
+
`${base}.tsx`,
|
|
136
|
+
`${base}/index.ts`,
|
|
137
|
+
`${base}/index.tsx`
|
|
138
|
+
].find((candidate)=>exists(candidate));
|
|
139
|
+
};
|
|
140
|
+
const composedModules = (file)=>{
|
|
141
|
+
const order = [];
|
|
142
|
+
const seen = new Set();
|
|
143
|
+
const queue = [
|
|
144
|
+
file
|
|
145
|
+
];
|
|
146
|
+
while(queue.length > 0 && order.length < 128){
|
|
147
|
+
const current = queue.shift();
|
|
148
|
+
if (seen.has(current)) continue;
|
|
149
|
+
seen.add(current);
|
|
150
|
+
order.push(current);
|
|
151
|
+
const ast = parseFile(current);
|
|
152
|
+
if (!ast) continue;
|
|
153
|
+
for (const statement of ast.program.body){
|
|
154
|
+
const source = (__rspack_external__babel_types_6e8337dd.isImportDeclaration(statement) || __rspack_external__babel_types_6e8337dd.isExportNamedDeclaration(statement) || __rspack_external__babel_types_6e8337dd.isExportAllDeclaration(statement)) && statement.source ? statement.source.value : void 0;
|
|
155
|
+
const resolved = void 0 === source ? void 0 : resolveRelative(current, source);
|
|
156
|
+
if (void 0 !== resolved && !seen.has(resolved)) queue.push(resolved);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return order;
|
|
160
|
+
};
|
|
161
|
+
const moduleShape = (file, imports, calls, patterns = [], defaultExport, composed = false)=>{
|
|
134
162
|
if (!exists(file)) return;
|
|
135
163
|
const ast = parseFile(file);
|
|
136
164
|
if (!ast) return;
|
|
137
|
-
const
|
|
165
|
+
const shapeFiles = composed ? composedModules(file) : [
|
|
166
|
+
file
|
|
167
|
+
];
|
|
168
|
+
const body = shapeFiles.flatMap((shapeFile)=>parseFile(shapeFile)?.program.body ?? []);
|
|
138
169
|
for (const [specifier, names] of imports){
|
|
139
|
-
const
|
|
170
|
+
const target = resolveRelative(file, specifier);
|
|
171
|
+
const matchesSpecifier = (item, from)=>void 0 === target ? item.source.value === specifier : resolveRelative(from, item.source.value) === target;
|
|
172
|
+
const imported = shapeFiles.flatMap((shapeFile)=>(parseFile(shapeFile)?.program.body ?? []).filter((item)=>__rspack_external__babel_types_6e8337dd.isImportDeclaration(item) && 'type' !== item.importKind && matchesSpecifier(item, shapeFile)));
|
|
140
173
|
assert(imported.length, `${file}: must import from ${specifier}`);
|
|
141
174
|
for (const name of names)assert(imported.some((item)=>__rspack_external__babel_types_6e8337dd.isImportDeclaration(item) && item.specifiers.some((value)=>__rspack_external__babel_types_6e8337dd.isImportSpecifier(value) && 'type' !== value.importKind && __rspack_external__babel_types_6e8337dd.isIdentifier(value.imported, {
|
|
142
175
|
name
|
|
@@ -145,11 +178,15 @@ function check(options, topology) {
|
|
|
145
178
|
}))), `${file}: must import ${name} from ${specifier}`);
|
|
146
179
|
}
|
|
147
180
|
const found = new Set();
|
|
148
|
-
|
|
181
|
+
const collectCalls = (node)=>{
|
|
149
182
|
if (!__rspack_external__babel_types_6e8337dd.isCallExpression(node)) return;
|
|
150
183
|
if (__rspack_external__babel_types_6e8337dd.isIdentifier(node.callee)) found.add(node.callee.name);
|
|
151
184
|
if (__rspack_external__babel_types_6e8337dd.isMemberExpression(node.callee) && !node.callee.computed && __rspack_external__babel_types_6e8337dd.isIdentifier(node.callee.object) && __rspack_external__babel_types_6e8337dd.isIdentifier(node.callee.property)) found.add(`${node.callee.object.name}.${node.callee.property.name}`);
|
|
152
|
-
}
|
|
185
|
+
};
|
|
186
|
+
for (const shapeFile of shapeFiles){
|
|
187
|
+
const shapeAst = parseFile(shapeFile);
|
|
188
|
+
if (shapeAst) __rspack_external__babel_types_6e8337dd.traverseFast(shapeAst, collectCalls);
|
|
189
|
+
}
|
|
153
190
|
for (const call of calls)assert(found.has(call), `${file}: must call ${call}(...)`);
|
|
154
191
|
const source = read(file).replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/gu, '');
|
|
155
192
|
for (const [pattern, reason] of patterns)assert(pattern.test(source), `${file}: ${reason}`);
|
|
@@ -327,7 +364,7 @@ function check(options, topology) {
|
|
|
327
364
|
]
|
|
328
365
|
], [
|
|
329
366
|
rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
|
|
330
|
-
]);
|
|
367
|
+
], [], void 0, true);
|
|
331
368
|
moduleShape(`${appPath}/api/effect-api.ts`, [], [], [
|
|
332
369
|
[
|
|
333
370
|
/\bbackendFederationContract\b/u,
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Only `.tsx` is JSX. Enabling JSX for a `.ts` file misreads a generic arrow
|
|
3
|
+
* such as `const f = <T>(value: T) => value` as an unclosed JSX element.
|
|
4
|
+
*/
|
|
5
|
+
export declare const consumerParserPlugins: (filePath: string) => ('typescript' | 'jsx')[];
|
|
1
6
|
export interface MicroVerticalApiBaselineExpectation {
|
|
2
7
|
/** Optional override for operation IDs outside reachable endpoint naming conventions. */
|
|
3
8
|
readonly operationPaths?: Readonly<Record<string, string>>;
|