@bleedingdev/modern-js-code-tools 3.9.0-ultramodern.11 → 3.9.0-ultramodern.13
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 +108 -78
- package/dist/cjs/microvertical-api-boundary.cjs +90 -36
- package/dist/cjs/oxlint-plugin/rules/strict-effect-api-boundaries.cjs +1 -1
- package/dist/cjs/source-analysis.cjs +104 -0
- package/dist/cjs/strict-effect-runtime.cjs +12 -47
- package/dist/esm/microvertical-api-baseline.js +99 -65
- package/dist/esm/microvertical-api-boundary.js +90 -36
- package/dist/esm/oxlint-plugin/rules/strict-effect-api-boundaries.js +1 -1
- package/dist/esm/source-analysis.js +44 -0
- package/dist/esm/strict-effect-runtime.js +12 -46
- package/dist/esm-node/microvertical-api-baseline.js +99 -65
- package/dist/esm-node/microvertical-api-boundary.js +90 -36
- package/dist/esm-node/oxlint-plugin/rules/strict-effect-api-boundaries.js +1 -1
- package/dist/esm-node/source-analysis.js +45 -0
- package/dist/esm-node/strict-effect-runtime.js +12 -46
- package/dist/types/microvertical-api-baseline.d.ts +0 -5
- package/dist/types/source-analysis.d.ts +10 -0
- package/package.json +8 -7
|
@@ -38,63 +38,31 @@ var __webpack_exports__ = {};
|
|
|
38
38
|
__webpack_require__.r(__webpack_exports__);
|
|
39
39
|
__webpack_require__.d(__webpack_exports__, {
|
|
40
40
|
configuredMicroVerticalApiStem: ()=>configuredMicroVerticalApiStem,
|
|
41
|
-
consumerParserPlugins: ()=>consumerParserPlugins,
|
|
42
41
|
microVerticalApiBaselineViolation: ()=>microVerticalApiBaselineViolation
|
|
43
42
|
});
|
|
44
43
|
const external_node_fs_namespaceObject = require("node:fs");
|
|
45
44
|
var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_namespaceObject);
|
|
45
|
+
const external_node_module_namespaceObject = require("node:module");
|
|
46
46
|
const external_node_path_namespaceObject = require("node:path");
|
|
47
47
|
var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
|
|
48
|
-
const parser_namespaceObject = require("@babel/parser");
|
|
49
|
-
const traverse_namespaceObject = require("@babel/traverse");
|
|
50
|
-
var traverse_default = /*#__PURE__*/ __webpack_require__.n(traverse_namespaceObject);
|
|
51
48
|
const types_namespaceObject = require("@babel/types");
|
|
49
|
+
const external_resolve_exports_namespaceObject = require("resolve.exports");
|
|
52
50
|
const external_microvertical_api_owner_cjs_namespaceObject = require("./microvertical-api-owner.cjs");
|
|
53
|
-
const
|
|
54
|
-
"typescript",
|
|
55
|
-
'jsx'
|
|
56
|
-
] : [
|
|
57
|
-
"typescript"
|
|
58
|
-
];
|
|
59
|
-
class ConsumerSyntaxError extends Error {
|
|
60
|
-
}
|
|
61
|
-
class ConsumerValidationHub extends traverse_namespaceObject.Hub {
|
|
62
|
-
buildError(_node, message) {
|
|
63
|
-
return new ConsumerSyntaxError(message);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
51
|
+
const external_source_analysis_cjs_namespaceObject = require("./source-analysis.cjs");
|
|
66
52
|
function parseConsumer(filePath) {
|
|
67
53
|
if (external_node_fs_default().statSync(filePath).size > 1000000) throw new Error(`${filePath}: consumer source exceeds 1 MB analysis budget`);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
file = (0, parser_namespaceObject.parse)(external_node_fs_default().readFileSync(filePath, 'utf8'), {
|
|
71
|
-
sourceType: 'module',
|
|
72
|
-
sourceFilename: filePath,
|
|
73
|
-
plugins: consumerParserPlugins(filePath)
|
|
74
|
-
});
|
|
75
|
-
} catch (error) {
|
|
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));
|
|
77
|
-
throw error;
|
|
78
|
-
}
|
|
79
|
-
const program = traverse_namespaceObject.NodePath.get({
|
|
80
|
-
hub: new ConsumerValidationHub(),
|
|
81
|
-
parentPath: void 0,
|
|
82
|
-
parent: file,
|
|
83
|
-
container: file,
|
|
84
|
-
key: 'program'
|
|
85
|
-
});
|
|
86
|
-
program.setContext();
|
|
87
|
-
traverse_default()(file, {
|
|
54
|
+
const file = (0, external_source_analysis_cjs_namespaceObject.parseSource)(external_node_fs_default().readFileSync(filePath, 'utf8'), filePath);
|
|
55
|
+
(0, external_source_analysis_cjs_namespaceObject.traverseSource)(file, {
|
|
88
56
|
AssignmentExpression (p) {
|
|
89
|
-
throw new
|
|
57
|
+
throw new external_source_analysis_cjs_namespaceObject.SourceSyntaxError(`contract bindings must be immutable at ${p.node.start}`);
|
|
90
58
|
},
|
|
91
59
|
UpdateExpression (p) {
|
|
92
|
-
throw new
|
|
60
|
+
throw new external_source_analysis_cjs_namespaceObject.SourceSyntaxError(`contract bindings must be immutable at ${p.node.start}`);
|
|
93
61
|
},
|
|
94
62
|
TSModuleDeclaration () {
|
|
95
|
-
throw new
|
|
63
|
+
throw new external_source_analysis_cjs_namespaceObject.SourceSyntaxError('contract bindings must not be merged with namespaces');
|
|
96
64
|
}
|
|
97
|
-
}
|
|
65
|
+
});
|
|
98
66
|
return file;
|
|
99
67
|
}
|
|
100
68
|
const camelCaseStem = (stem)=>stem.replaceAll(/-([a-z0-9])/gu, (_match, letter)=>letter.toUpperCase());
|
|
@@ -119,24 +87,19 @@ const accessPath = (node)=>{
|
|
|
119
87
|
}
|
|
120
88
|
};
|
|
121
89
|
const isAccessPath = (node, expected)=>accessPath(node)?.join('.') === expected.join('.');
|
|
122
|
-
const unwrapExpression = (expression)=>{
|
|
123
|
-
let current = expression;
|
|
124
|
-
while(types_namespaceObject.isTSAsExpression(current) || types_namespaceObject.isParenthesizedExpression(current) || types_namespaceObject.isTSSatisfiesExpression(current))current = current.expression;
|
|
125
|
-
return current;
|
|
126
|
-
};
|
|
127
90
|
const stringLiteral = (expression)=>{
|
|
128
91
|
if (null == expression) return;
|
|
129
|
-
const unwrapped = unwrapExpression(expression);
|
|
92
|
+
const unwrapped = (0, external_source_analysis_cjs_namespaceObject.unwrapExpression)(expression);
|
|
130
93
|
return types_namespaceObject.isStringLiteral(unwrapped) ? unwrapped.value : void 0;
|
|
131
94
|
};
|
|
132
95
|
const numericLiteral = (expression)=>{
|
|
133
96
|
if (null == expression) return;
|
|
134
|
-
const unwrapped = unwrapExpression(expression);
|
|
97
|
+
const unwrapped = (0, external_source_analysis_cjs_namespaceObject.unwrapExpression)(expression);
|
|
135
98
|
return types_namespaceObject.isNumericLiteral(unwrapped) ? Number(unwrapped.value) : void 0;
|
|
136
99
|
};
|
|
137
100
|
const callExpression = (expression, callee)=>{
|
|
138
101
|
if (null == expression) return;
|
|
139
|
-
const unwrapped = unwrapExpression(expression);
|
|
102
|
+
const unwrapped = (0, external_source_analysis_cjs_namespaceObject.unwrapExpression)(expression);
|
|
140
103
|
return types_namespaceObject.isCallExpression(unwrapped) && isAccessPath(unwrapped.callee, callee) ? unwrapped : void 0;
|
|
141
104
|
};
|
|
142
105
|
const localConst = (sourceFile, name)=>{
|
|
@@ -164,16 +127,81 @@ const exportedConst = (sourceFile, name)=>{
|
|
|
164
127
|
}))) return localConst(sourceFile, name);
|
|
165
128
|
}
|
|
166
129
|
};
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
130
|
+
const inside = (file, boundary)=>file === boundary || file.startsWith(`${boundary}${external_node_path_default().sep}`);
|
|
131
|
+
const containingWorkspace = (file)=>{
|
|
132
|
+
for(let directory = external_node_path_default().dirname(file);; directory = external_node_path_default().dirname(directory)){
|
|
133
|
+
if (external_node_fs_default().existsSync(external_node_path_default().join(directory, 'pnpm-workspace.yaml'))) return external_node_fs_default().realpathSync(directory);
|
|
134
|
+
if (directory === external_node_path_default().dirname(directory)) return;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
const sourceBoundary = (file, workspace)=>{
|
|
138
|
+
for(let directory = external_node_path_default().dirname(file);; directory = external_node_path_default().dirname(directory)){
|
|
139
|
+
if (external_node_fs_default().existsSync(external_node_path_default().join(directory, 'package.json')) && directory !== workspace) return external_node_fs_default().realpathSync(directory);
|
|
140
|
+
if (directory === workspace || directory === external_node_path_default().dirname(directory)) return external_node_fs_default().realpathSync(external_node_path_default().dirname(file));
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
const sourceFileAt = (target, boundary)=>{
|
|
144
|
+
const stem = target.replace(/\.(?:[cm]?[jt]sx?)$/u, '');
|
|
171
145
|
for (const candidate of [
|
|
172
146
|
`${stem}.ts`,
|
|
173
147
|
`${stem}.tsx`,
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
148
|
+
`${stem}.mts`,
|
|
149
|
+
`${stem}.cts`,
|
|
150
|
+
target,
|
|
151
|
+
external_node_path_default().join(target, 'index.ts'),
|
|
152
|
+
external_node_path_default().join(target, 'index.tsx')
|
|
153
|
+
])try {
|
|
154
|
+
const real = external_node_fs_default().realpathSync(candidate);
|
|
155
|
+
if (inside(real, boundary) && external_node_fs_default().statSync(real).isFile()) return real;
|
|
156
|
+
} catch {}
|
|
157
|
+
};
|
|
158
|
+
const packageEntry = (specifier)=>{
|
|
159
|
+
if (specifier.startsWith('.') || specifier.startsWith('/') || specifier.startsWith('#')) return;
|
|
160
|
+
const parts = specifier.split('/');
|
|
161
|
+
const count = specifier.startsWith('@') ? 2 : 1;
|
|
162
|
+
if (parts.length < count || parts.slice(0, count).some((part)=>!part)) return;
|
|
163
|
+
return {
|
|
164
|
+
name: parts.slice(0, count).join('/'),
|
|
165
|
+
exportKey: parts.length === count ? '.' : `./${parts.slice(count).join('/')}`
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
const resolveModulePath = (module, specifier, workspace)=>{
|
|
169
|
+
if (/^\.\.?\//u.test(specifier)) {
|
|
170
|
+
const file = sourceFileAt(external_node_path_default().resolve(external_node_path_default().dirname(module.path), specifier), module.boundary);
|
|
171
|
+
return void 0 === file ? void 0 : {
|
|
172
|
+
path: file,
|
|
173
|
+
boundary: module.boundary
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
const entry = packageEntry(specifier);
|
|
177
|
+
if (!entry || !workspace) return;
|
|
178
|
+
const paths = (0, external_node_module_namespaceObject.createRequire)(module.path).resolve.paths(entry.name) ?? [];
|
|
179
|
+
for (const lookup of paths){
|
|
180
|
+
const directory = external_node_path_default().join(lookup, entry.name);
|
|
181
|
+
const manifest = external_node_path_default().join(directory, 'package.json');
|
|
182
|
+
if (!external_node_fs_default().existsSync(manifest)) continue;
|
|
183
|
+
const boundary = external_node_fs_default().realpathSync(directory);
|
|
184
|
+
if (!inside(boundary, workspace) || boundary.split(external_node_path_default().sep).includes('node_modules')) break;
|
|
185
|
+
const pkg = JSON.parse(external_node_fs_default().readFileSync(manifest, 'utf8'));
|
|
186
|
+
if (pkg.name !== entry.name || void 0 === pkg.exports) break;
|
|
187
|
+
let target;
|
|
188
|
+
try {
|
|
189
|
+
const targets = (0, external_resolve_exports_namespaceObject.exports)(pkg, entry.exportKey, {
|
|
190
|
+
conditions: [
|
|
191
|
+
'modern:source'
|
|
192
|
+
]
|
|
193
|
+
});
|
|
194
|
+
target = Array.isArray(targets) ? targets[0] : void 0;
|
|
195
|
+
} catch {
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
if (!target || !target.startsWith('./')) break;
|
|
199
|
+
const file = sourceFileAt(external_node_path_default().resolve(boundary, target), boundary);
|
|
200
|
+
return void 0 === file ? void 0 : {
|
|
201
|
+
path: file,
|
|
202
|
+
boundary
|
|
203
|
+
};
|
|
204
|
+
}
|
|
177
205
|
};
|
|
178
206
|
const createModuleReader = (root)=>{
|
|
179
207
|
const modules = new Map([
|
|
@@ -182,14 +210,15 @@ const createModuleReader = (root)=>{
|
|
|
182
210
|
root
|
|
183
211
|
]
|
|
184
212
|
]);
|
|
185
|
-
return (filePath)=>{
|
|
213
|
+
return (filePath, boundary)=>{
|
|
186
214
|
if (modules.has(filePath)) return modules.get(filePath);
|
|
187
215
|
if (modules.size >= 256) return;
|
|
188
216
|
let module;
|
|
189
217
|
try {
|
|
190
218
|
module = {
|
|
191
219
|
file: parseConsumer(filePath),
|
|
192
|
-
path: filePath
|
|
220
|
+
path: filePath,
|
|
221
|
+
boundary
|
|
193
222
|
};
|
|
194
223
|
} catch {
|
|
195
224
|
module = void 0;
|
|
@@ -219,7 +248,7 @@ const reexportedBinding = (module, name)=>{
|
|
|
219
248
|
if (!types_namespaceObject.isExportNamedDeclaration(statement) || 'type' === statement.exportKind) continue;
|
|
220
249
|
const specifier = statement.specifiers.find((value)=>types_namespaceObject.isExportSpecifier(value) && 'type' !== value.exportKind && propertyName(value.exported) === name);
|
|
221
250
|
if (void 0 !== specifier && types_namespaceObject.isExportSpecifier(specifier)) return {
|
|
222
|
-
local: specifier.local.name,
|
|
251
|
+
local: types_namespaceObject.isIdentifier(specifier.local) ? specifier.local.name : specifier.local.value,
|
|
223
252
|
...statement.source ? {
|
|
224
253
|
specifier: statement.source.value
|
|
225
254
|
} : {}
|
|
@@ -234,7 +263,7 @@ const defaultExport = (module)=>{
|
|
|
234
263
|
return types_namespaceObject.isExpression(declaration) ? declaration : void 0;
|
|
235
264
|
}
|
|
236
265
|
};
|
|
237
|
-
const resolveBinding = (module, name, scope, readModule, seen)=>{
|
|
266
|
+
const resolveBinding = (module, name, scope, readModule, workspace, seen)=>{
|
|
238
267
|
const key = `${scope}:${module.path}#${name}`;
|
|
239
268
|
if (seen.has(key) || seen.size >= 512) return;
|
|
240
269
|
seen.add(key);
|
|
@@ -244,9 +273,9 @@ const resolveBinding = (module, name, scope, readModule, seen)=>{
|
|
|
244
273
|
module
|
|
245
274
|
};
|
|
246
275
|
const throughModule = (specifier, imported)=>{
|
|
247
|
-
const
|
|
248
|
-
const target =
|
|
249
|
-
return void 0 === target ? void 0 : resolveBinding(target, imported, 'export', readModule, seen);
|
|
276
|
+
const resolved = resolveModulePath(module, specifier, workspace);
|
|
277
|
+
const target = resolved && readModule(resolved.path, resolved.boundary);
|
|
278
|
+
return void 0 === target ? void 0 : resolveBinding(target, imported, 'export', readModule, workspace, seen);
|
|
250
279
|
};
|
|
251
280
|
if ('local' === scope) {
|
|
252
281
|
const imported = importedBinding(module, name);
|
|
@@ -254,14 +283,14 @@ const resolveBinding = (module, name, scope, readModule, seen)=>{
|
|
|
254
283
|
}
|
|
255
284
|
if ('default' === name) {
|
|
256
285
|
const exported = defaultExport(module);
|
|
257
|
-
if ('string' == typeof exported) return resolveBinding(module, exported, 'local', readModule, seen);
|
|
286
|
+
if ('string' == typeof exported) return resolveBinding(module, exported, 'local', readModule, workspace, seen);
|
|
258
287
|
return void 0 === exported ? void 0 : {
|
|
259
288
|
expression: exported,
|
|
260
289
|
module
|
|
261
290
|
};
|
|
262
291
|
}
|
|
263
292
|
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);
|
|
293
|
+
if (void 0 !== reexported) return void 0 === reexported.specifier ? resolveBinding(module, reexported.local, 'local', readModule, workspace, seen) : throughModule(reexported.specifier, reexported.local);
|
|
265
294
|
for (const statement of module.file.program.body){
|
|
266
295
|
if (!types_namespaceObject.isExportAllDeclaration(statement) || 'type' === statement.exportKind) continue;
|
|
267
296
|
const resolved = throughModule(statement.source.value, name);
|
|
@@ -270,7 +299,7 @@ const resolveBinding = (module, name, scope, readModule, seen)=>{
|
|
|
270
299
|
};
|
|
271
300
|
const objectLiteral = (expression)=>{
|
|
272
301
|
if (null == expression) return;
|
|
273
|
-
const unwrapped = unwrapExpression(expression);
|
|
302
|
+
const unwrapped = (0, external_source_analysis_cjs_namespaceObject.unwrapExpression)(expression);
|
|
274
303
|
return types_namespaceObject.isObjectExpression(unwrapped) ? unwrapped : void 0;
|
|
275
304
|
};
|
|
276
305
|
const propertyAssignments = (properties)=>{
|
|
@@ -289,7 +318,7 @@ const exactCall = (expression, callee, argumentCount)=>{
|
|
|
289
318
|
};
|
|
290
319
|
const sharedSchemaObject = (declaration, sharedSchemaName, protectedFields)=>{
|
|
291
320
|
if (declaration?.init == null) return;
|
|
292
|
-
const initializer = unwrapExpression(declaration.init);
|
|
321
|
+
const initializer = (0, external_source_analysis_cjs_namespaceObject.unwrapExpression)(declaration.init);
|
|
293
322
|
if (types_namespaceObject.isIdentifier(initializer) && initializer.name === sharedSchemaName) return {
|
|
294
323
|
assignments: new Map(),
|
|
295
324
|
identity: true
|
|
@@ -313,14 +342,14 @@ const sharedSchemaObject = (declaration, sharedSchemaName, protectedFields)=>{
|
|
|
313
342
|
};
|
|
314
343
|
const directCallChain = (expression)=>{
|
|
315
344
|
if (null == expression) return;
|
|
316
|
-
let current = unwrapExpression(expression);
|
|
345
|
+
let current = (0, external_source_analysis_cjs_namespaceObject.unwrapExpression)(expression);
|
|
317
346
|
const methods = [];
|
|
318
347
|
while(types_namespaceObject.isCallExpression(current) && types_namespaceObject.isMemberExpression(current.callee) && !current.callee.computed && types_namespaceObject.isIdentifier(current.callee.property) && !types_namespaceObject.isIdentifier(current.callee.object)){
|
|
319
348
|
methods.unshift({
|
|
320
349
|
arguments: current.arguments,
|
|
321
350
|
name: current.callee.property.name
|
|
322
351
|
});
|
|
323
|
-
current = unwrapExpression(current.callee.object);
|
|
352
|
+
current = (0, external_source_analysis_cjs_namespaceObject.unwrapExpression)(current.callee.object);
|
|
324
353
|
}
|
|
325
354
|
if (!types_namespaceObject.isCallExpression(current)) return;
|
|
326
355
|
return {
|
|
@@ -331,7 +360,7 @@ const directCallChain = (expression)=>{
|
|
|
331
360
|
const brandedStringSchemaIsExact = (declaration, brand)=>{
|
|
332
361
|
const initializer = declaration?.init;
|
|
333
362
|
if (null == initializer) return false;
|
|
334
|
-
const pipeCall = unwrapExpression(initializer);
|
|
363
|
+
const pipeCall = (0, external_source_analysis_cjs_namespaceObject.unwrapExpression)(initializer);
|
|
335
364
|
if (!types_namespaceObject.isCallExpression(pipeCall) || !types_namespaceObject.isMemberExpression(pipeCall.callee) || pipeCall.callee.computed || !types_namespaceObject.isIdentifier(pipeCall.callee.property, {
|
|
336
365
|
name: 'pipe'
|
|
337
366
|
}) || !isAccessPath(pipeCall.callee.object, [
|
|
@@ -415,6 +444,7 @@ const nonEndpointCombinators = {
|
|
|
415
444
|
};
|
|
416
445
|
const reachableEndpoints = (rootModule, declaration)=>{
|
|
417
446
|
const readModule = createModuleReader(rootModule);
|
|
447
|
+
const workspace = containingWorkspace(rootModule.path);
|
|
418
448
|
const active = new Set();
|
|
419
449
|
const endpoints = [];
|
|
420
450
|
const identities = new Set();
|
|
@@ -431,12 +461,12 @@ const reachableEndpoints = (rootModule, declaration)=>{
|
|
|
431
461
|
let visits = 0;
|
|
432
462
|
const visit = (expression, module, kind, group = '')=>{
|
|
433
463
|
if (!expression) return false;
|
|
434
|
-
const node = unwrapExpression(expression);
|
|
464
|
+
const node = (0, external_source_analysis_cjs_namespaceObject.unwrapExpression)(expression);
|
|
435
465
|
if (active.has(node) || active.size >= 128 || ++visits > 2048) return false;
|
|
436
466
|
active.add(node);
|
|
437
467
|
try {
|
|
438
468
|
if (types_namespaceObject.isIdentifier(node)) {
|
|
439
|
-
const resolved = resolveBinding(module, node.name, 'local', readModule, new Set());
|
|
469
|
+
const resolved = resolveBinding(module, node.name, 'local', readModule, workspace, new Set());
|
|
440
470
|
return void 0 !== resolved && visit(resolved.expression, resolved.module, kind, group);
|
|
441
471
|
}
|
|
442
472
|
if ('endpoint' === kind) {
|
|
@@ -622,7 +652,7 @@ const readinessSchemaIsShared = (declaration, markerSchemaName, ownerMarkerIsIde
|
|
|
622
652
|
]);
|
|
623
653
|
return void 0 !== schema && (schema.identity && ownerMarkerIsIdentity || 1 === schema.assignments.size && identifierName(schema.assignments.get('marker')?.value) === markerSchemaName);
|
|
624
654
|
};
|
|
625
|
-
const declarationIsIdentifier = (declaration, identifier)=>declaration?.init != null && identifierName(unwrapExpression(declaration.init)) === identifier;
|
|
655
|
+
const declarationIsIdentifier = (declaration, identifier)=>declaration?.init != null && identifierName((0, external_source_analysis_cjs_namespaceObject.unwrapExpression)(declaration.init)) === identifier;
|
|
626
656
|
const validateParsedContract = (module, stem, expectation)=>{
|
|
627
657
|
const sourceFile = module.file;
|
|
628
658
|
const exportStem = camelCaseStem(stem);
|
|
@@ -653,23 +683,23 @@ const configuredMicroVerticalApiStem = (verticalPath, verticals)=>{
|
|
|
653
683
|
};
|
|
654
684
|
const microVerticalApiBaselineViolation = (stem, filePath, expectation)=>{
|
|
655
685
|
try {
|
|
656
|
-
const
|
|
686
|
+
const realPath = external_node_fs_default().realpathSync(filePath);
|
|
687
|
+
const sourceFile = parseConsumer(realPath);
|
|
657
688
|
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';
|
|
658
689
|
return validateParsedContract({
|
|
659
690
|
file: sourceFile,
|
|
660
|
-
path:
|
|
691
|
+
path: realPath,
|
|
692
|
+
boundary: sourceBoundary(realPath, containingWorkspace(realPath))
|
|
661
693
|
}, stem, expectation);
|
|
662
694
|
} catch (error) {
|
|
663
|
-
if (error instanceof
|
|
695
|
+
if (error instanceof external_source_analysis_cjs_namespaceObject.SourceSyntaxError) return `MicroVertical root contract must be valid TypeScript syntax (${error.message})`;
|
|
664
696
|
throw error;
|
|
665
697
|
}
|
|
666
698
|
};
|
|
667
699
|
exports.configuredMicroVerticalApiStem = __webpack_exports__.configuredMicroVerticalApiStem;
|
|
668
|
-
exports.consumerParserPlugins = __webpack_exports__.consumerParserPlugins;
|
|
669
700
|
exports.microVerticalApiBaselineViolation = __webpack_exports__.microVerticalApiBaselineViolation;
|
|
670
701
|
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
671
702
|
"configuredMicroVerticalApiStem",
|
|
672
|
-
"consumerParserPlugins",
|
|
673
703
|
"microVerticalApiBaselineViolation"
|
|
674
704
|
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
675
705
|
Object.defineProperty(exports, '__esModule', {
|
|
@@ -50,6 +50,7 @@ const parser_namespaceObject = require("@babel/parser");
|
|
|
50
50
|
const types_namespaceObject = require("@babel/types");
|
|
51
51
|
const external_microvertical_api_baseline_cjs_namespaceObject = require("./microvertical-api-baseline.cjs");
|
|
52
52
|
const external_microvertical_api_owner_cjs_namespaceObject = require("./microvertical-api-owner.cjs");
|
|
53
|
+
const external_source_analysis_cjs_namespaceObject = require("./source-analysis.cjs");
|
|
53
54
|
const external_strict_effect_runtime_cjs_namespaceObject = require("./strict-effect-runtime.cjs");
|
|
54
55
|
const ignored = new Set([
|
|
55
56
|
'.git',
|
|
@@ -65,13 +66,42 @@ const normalize = (file)=>file.split(external_node_path_default().sep).join('/')
|
|
|
65
66
|
const errorMessage = (error)=>error instanceof Error ? error.message : String(error);
|
|
66
67
|
const apiApp = (app)=>'vertical' === app.kind && 'ui-only' !== app.surfaceProfile && 'horizontal-remote' !== app.deliveryUnitKind;
|
|
67
68
|
function configuredApps(root, supplied) {
|
|
68
|
-
const
|
|
69
|
-
if (!Array.isArray(
|
|
69
|
+
const topology = supplied ? void 0 : JSON.parse(external_node_fs_default().readFileSync(external_node_path_default().join(root, 'topology/reference-topology.json'), 'utf8'));
|
|
70
|
+
if (topology && (!topology.shell || !Array.isArray(topology.verticals) || void 0 !== topology.shells && !Array.isArray(topology.shells))) throw new Error('topology/reference-topology.json: shell and verticals must be present');
|
|
71
|
+
const entries = topology?.shell ? [
|
|
72
|
+
topology.shell,
|
|
73
|
+
...topology.shells ?? [],
|
|
74
|
+
...topology.verticals ?? []
|
|
75
|
+
] : void 0;
|
|
76
|
+
const source = supplied ?? entries?.map((entry)=>{
|
|
77
|
+
if (!entry || 'string' != typeof entry.path || !entry.path || external_node_path_default().isAbsolute(entry.path) || entry.path.split(/[\\/]/u).includes('..')) throw new Error('topology/reference-topology.json: each app must have an explicit workspace path');
|
|
78
|
+
const packageFile = external_node_path_default().join(root, entry.path, 'package.json');
|
|
79
|
+
const manifest = JSON.parse(external_node_fs_default().readFileSync(packageFile, 'utf8'));
|
|
80
|
+
if ('string' != typeof manifest.name || !manifest.name || void 0 !== entry.package && entry.package !== manifest.name) throw new Error(`${entry.path}/package.json: package name must match topology`);
|
|
81
|
+
const api = entry.api;
|
|
82
|
+
const stem = api?.stem ?? api?.readiness?.endpoint?.match(/^\/([a-z0-9]+(?:-[a-z0-9]+)*)\/readiness$/u)?.[1];
|
|
83
|
+
return {
|
|
84
|
+
id: entry.id,
|
|
85
|
+
kind: entry.kind,
|
|
86
|
+
path: entry.path,
|
|
87
|
+
package: manifest.name,
|
|
88
|
+
surfaceProfile: entry.surfaceProfile,
|
|
89
|
+
deliveryUnitKind: entry.deliveryUnitKind,
|
|
90
|
+
...void 0 === api ? {} : {
|
|
91
|
+
api: {
|
|
92
|
+
stem,
|
|
93
|
+
prefix: api.bff?.prefix,
|
|
94
|
+
protocol: api.protocol
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
if (!Array.isArray(source)) throw new Error('topology/reference-topology.json: shell and verticals must describe apps');
|
|
70
100
|
const paths = new Set();
|
|
71
101
|
for (const app of source){
|
|
72
|
-
if (!app || 'object' != typeof app || 'string' != typeof app.path || !app.path || external_node_path_default().isAbsolute(app.path) || app.path.split(/[\\/]/u).includes('..')) throw new Error('
|
|
102
|
+
if (!app || 'object' != typeof app || 'string' != typeof app.path || !app.path || external_node_path_default().isAbsolute(app.path) || app.path.split(/[\\/]/u).includes('..')) throw new Error('topology/reference-topology.json: each app must have a relative workspace path');
|
|
73
103
|
const key = normalize(app.path);
|
|
74
|
-
if (paths.has(key)) throw new Error(
|
|
104
|
+
if (paths.has(key)) throw new Error(`topology/reference-topology.json: duplicate app path ${key}`);
|
|
75
105
|
paths.add(key);
|
|
76
106
|
for (const name of [
|
|
77
107
|
'id',
|
|
@@ -80,7 +110,7 @@ function configuredApps(root, supplied) {
|
|
|
80
110
|
'surfaceProfile',
|
|
81
111
|
'deliveryUnitKind'
|
|
82
112
|
])if (void 0 !== app[name] && 'string' != typeof app[name]) throw new Error(`${key}: ${name} must be a string`);
|
|
83
|
-
|
|
113
|
+
const allowedFields = {
|
|
84
114
|
kind: [
|
|
85
115
|
'shell',
|
|
86
116
|
'vertical'
|
|
@@ -94,7 +124,11 @@ function configuredApps(root, supplied) {
|
|
|
94
124
|
'microvertical',
|
|
95
125
|
'horizontal-remote'
|
|
96
126
|
]
|
|
97
|
-
}
|
|
127
|
+
};
|
|
128
|
+
for (const field of Object.keys(allowedFields)){
|
|
129
|
+
const value = app[field];
|
|
130
|
+
if (void 0 !== value && !allowedFields[field].includes(value)) throw new Error(`${key}: invalid ${field}`);
|
|
131
|
+
}
|
|
98
132
|
if (void 0 !== app.api) {
|
|
99
133
|
if (!app.api || 'object' != typeof app.api || Array.isArray(app.api)) throw new Error(`${key}: api must be an object`);
|
|
100
134
|
if (void 0 !== app.api.protocol && ![
|
|
@@ -159,7 +193,7 @@ function check(options, topology) {
|
|
|
159
193
|
const ast = (0, parser_namespaceObject.parse)(read(file), {
|
|
160
194
|
sourceType: 'module',
|
|
161
195
|
sourceFilename: file,
|
|
162
|
-
plugins: (0,
|
|
196
|
+
plugins: (0, external_source_analysis_cjs_namespaceObject.consumerParserPlugins)(file)
|
|
163
197
|
});
|
|
164
198
|
parsed.set(file, ast);
|
|
165
199
|
return ast;
|
|
@@ -182,12 +216,27 @@ function check(options, topology) {
|
|
|
182
216
|
`${base}/index.tsx`
|
|
183
217
|
].find((candidate)=>exists(candidate));
|
|
184
218
|
};
|
|
185
|
-
const
|
|
219
|
+
const clientExportFile = (appPath, exported)=>{
|
|
220
|
+
if ('string' != typeof exported || !exported.startsWith('./') || exported.split(/[\\/]/u).includes('..') || !/\.[cm]?[jt]sx?$/u.test(exported)) return;
|
|
221
|
+
const appDirectory = absolute(appPath);
|
|
222
|
+
const candidate = external_node_path_default().resolve(appDirectory, exported);
|
|
223
|
+
try {
|
|
224
|
+
const workspace = external_node_fs_default().realpathSync(root);
|
|
225
|
+
const owner = external_node_fs_default().realpathSync(appDirectory);
|
|
226
|
+
const real = external_node_fs_default().realpathSync(candidate);
|
|
227
|
+
if (!owner.startsWith(`${workspace}${external_node_path_default().sep}`) || !real.startsWith(`${owner}${external_node_path_default().sep}`) || !external_node_fs_default().statSync(real).isFile()) return;
|
|
228
|
+
} catch {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
return normalize(external_node_path_default().relative(root, candidate));
|
|
232
|
+
};
|
|
233
|
+
const composedModules = (file, appPath)=>{
|
|
186
234
|
const order = [];
|
|
187
235
|
const seen = new Set();
|
|
188
236
|
const queue = [
|
|
189
237
|
file
|
|
190
238
|
];
|
|
239
|
+
const owner = external_node_fs_default().realpathSync(absolute(appPath));
|
|
191
240
|
while(queue.length > 0 && order.length < 128){
|
|
192
241
|
const current = queue.shift();
|
|
193
242
|
if (seen.has(current)) continue;
|
|
@@ -198,16 +247,19 @@ function check(options, topology) {
|
|
|
198
247
|
for (const statement of ast.program.body){
|
|
199
248
|
const source = (types_namespaceObject.isImportDeclaration(statement) || types_namespaceObject.isExportNamedDeclaration(statement) || types_namespaceObject.isExportAllDeclaration(statement)) && statement.source ? statement.source.value : void 0;
|
|
200
249
|
const resolved = void 0 === source ? void 0 : resolveRelative(current, source);
|
|
201
|
-
if (void 0 !== resolved && !seen.has(resolved))
|
|
250
|
+
if (void 0 !== resolved && !seen.has(resolved)) {
|
|
251
|
+
const real = external_node_fs_default().realpathSync(absolute(resolved));
|
|
252
|
+
if (real.startsWith(`${owner}${external_node_path_default().sep}`)) queue.push(resolved);
|
|
253
|
+
}
|
|
202
254
|
}
|
|
203
255
|
}
|
|
204
256
|
return order;
|
|
205
257
|
};
|
|
206
|
-
const moduleShape = (file, imports, calls, patterns = [], defaultExport,
|
|
258
|
+
const moduleShape = (file, imports, calls, patterns = [], defaultExport, composedAppPath)=>{
|
|
207
259
|
if (!exists(file)) return;
|
|
208
260
|
const ast = parseFile(file);
|
|
209
261
|
if (!ast) return;
|
|
210
|
-
const shapeFiles =
|
|
262
|
+
const shapeFiles = composedAppPath ? composedModules(file, composedAppPath) : [
|
|
211
263
|
file
|
|
212
264
|
];
|
|
213
265
|
const body = shapeFiles.flatMap((shapeFile)=>parseFile(shapeFile)?.program.body ?? []);
|
|
@@ -317,19 +369,25 @@ function check(options, topology) {
|
|
|
317
369
|
}
|
|
318
370
|
return;
|
|
319
371
|
}
|
|
320
|
-
if (app?.kind === 'vertical') assert(void 0 !== app.api, `${appPath}: vertical must declare its Effect API in
|
|
372
|
+
if (app?.kind === 'vertical') assert(void 0 !== app.api, `${appPath}: vertical must declare its Effect API in topology/reference-topology.json`);
|
|
321
373
|
const stem = app?.api?.stem ?? external_node_path_default().posix.basename(appPath);
|
|
322
374
|
const rpc = app?.api?.protocol === 'rpc';
|
|
323
375
|
const contract = `${appPath}/shared/${rpc ? 'rpc' : 'api'}.ts`;
|
|
324
|
-
const client = `${appPath}/src/api/${stem}-${rpc ? 'rpc-client' : 'client'}.ts`;
|
|
325
376
|
const entry = `${appPath}/api/index.ts`;
|
|
326
377
|
for (const file of [
|
|
327
378
|
entry,
|
|
328
|
-
contract
|
|
329
|
-
client
|
|
379
|
+
contract
|
|
330
380
|
])assert(exists(file), `${file}: required API surface is missing`);
|
|
381
|
+
const packageFile = `${appPath}/package.json`;
|
|
382
|
+
assert(exists(packageFile), `${packageFile}: required API package is missing`);
|
|
383
|
+
if (!exists(packageFile)) return;
|
|
384
|
+
const manifest = JSON.parse(read(packageFile));
|
|
385
|
+
assert(manifest.exports?.['./api'] === `./shared/${rpc ? 'rpc' : 'api'}.ts`, `${packageFile}: invalid ./api export`);
|
|
386
|
+
const clientKey = `./api/${rpc ? 'rpc-client' : 'client'}`;
|
|
387
|
+
const client = clientExportFile(appPath, manifest.exports?.[clientKey]);
|
|
388
|
+
assert(void 0 !== client, `${packageFile}: invalid API client export`);
|
|
389
|
+
assert(manifest.exports?.[`./api/${rpc ? 'client' : 'rpc-client'}`] === void 0, `${packageFile}: forbidden opposite protocol client export`);
|
|
331
390
|
noPath(`${appPath}/shared/${rpc ? 'api' : 'rpc'}.ts`, `must not emit a ${rpc ? 'REST' : 'RPC'} contract`);
|
|
332
|
-
noPath(`${appPath}/src/api/${stem}-${rpc ? 'client' : 'rpc-client'}.ts`, `must not emit a ${rpc ? 'REST' : 'RPC'} client`);
|
|
333
391
|
if (rpc) {
|
|
334
392
|
moduleShape(entry, [
|
|
335
393
|
[
|
|
@@ -395,21 +453,24 @@ function check(options, topology) {
|
|
|
395
453
|
const violation = (0, external_microvertical_api_baseline_cjs_namespaceObject.microVerticalApiBaselineViolation)(stem, absolute(contract), expectation);
|
|
396
454
|
if (violation) diagnostics.push(`${contract}: ${violation}`);
|
|
397
455
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
456
|
+
if (client) {
|
|
457
|
+
const relativeContract = external_node_path_default().posix.relative(external_node_path_default().posix.dirname(client), contract);
|
|
458
|
+
moduleShape(client, [
|
|
459
|
+
[
|
|
460
|
+
effect,
|
|
461
|
+
[
|
|
462
|
+
'Effect',
|
|
463
|
+
rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
|
|
464
|
+
]
|
|
465
|
+
],
|
|
401
466
|
[
|
|
402
|
-
'
|
|
403
|
-
|
|
467
|
+
relativeContract.startsWith('.') ? relativeContract : `./${relativeContract}`,
|
|
468
|
+
[]
|
|
404
469
|
]
|
|
405
|
-
],
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
]
|
|
410
|
-
], [
|
|
411
|
-
rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
|
|
412
|
-
], [], void 0, true);
|
|
470
|
+
], [
|
|
471
|
+
rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
|
|
472
|
+
], [], void 0, appPath);
|
|
473
|
+
}
|
|
413
474
|
moduleShape(`${appPath}/api/effect-api.ts`, [], [], [
|
|
414
475
|
[
|
|
415
476
|
/\bbackendFederationContract\b/u,
|
|
@@ -450,13 +511,6 @@ function check(options, topology) {
|
|
|
450
511
|
'must enable strictEffectApproach'
|
|
451
512
|
]
|
|
452
513
|
]);
|
|
453
|
-
const packageFile = `${appPath}/package.json`;
|
|
454
|
-
if (exists(packageFile)) {
|
|
455
|
-
const manifest = JSON.parse(read(packageFile));
|
|
456
|
-
assert(manifest.exports?.['./api'] === `./shared/${rpc ? 'rpc' : 'api'}.ts`, `${packageFile}: invalid ./api export`);
|
|
457
|
-
assert(manifest.exports?.[`./api/${rpc ? 'rpc-client' : 'client'}`] === `./src/api/${stem}-${rpc ? 'rpc-client' : 'client'}.ts`, `${packageFile}: invalid API client export`);
|
|
458
|
-
assert(manifest.exports?.[`./api/${rpc ? 'client' : 'rpc-client'}`] === void 0, `${packageFile}: forbidden opposite protocol client export`);
|
|
459
|
-
}
|
|
460
514
|
});
|
|
461
515
|
const shell = 'apps/shell-super-app';
|
|
462
516
|
const verticals = apps.filter((app)=>app.path.startsWith('verticals/') && apiApp(app));
|
|
@@ -52,7 +52,7 @@ const createStrictEffectApiBoundariesRule = ()=>({
|
|
|
52
52
|
});
|
|
53
53
|
(0, external_ast_cjs_namespaceObject.reportProgramPattern)(context, node, source, /@modern-js\/plugin-bff\/(?:server|hono-server)(?![\w-])/u, 'UltraModern API workspaces must not import Hono server helpers; use @modern-js/bff-effect/effect-edge and HttpApi.');
|
|
54
54
|
(0, external_ast_cjs_namespaceObject.reportProgramPattern)(context, node, source, /\bruntimeFramework\s*(?::|=)\s*['"]hono['"]/u, 'UltraModern API apps must use bff.runtimeFramework: "effect".');
|
|
55
|
-
(0, external_ast_cjs_namespaceObject.reportProgramPattern)(context, node, source, /(?:from|import)\s*['"][^'"]*(?:api\/effect|shared\/effect)[^'"]*['"]|shared-effect-api/u, 'Import API code from direct api/index.ts, shared/api.ts or src/api/* paths, not api/effect, shared/effect or shared Effect API packages.');
|
|
55
|
+
(0, external_ast_cjs_namespaceObject.reportProgramPattern)(context, node, source, /(?:from|import)\s*['"][^'"]*(?:api\/effect|shared\/effect)(?=\/|\.[cm]?[jt]sx?['"]|['"])[^'"]*['"]|shared-effect-api/u, 'Import API code from direct api/index.ts, shared/api.ts or src/api/* paths, not api/effect, shared/effect or shared Effect API packages.');
|
|
56
56
|
(0, external_ast_cjs_namespaceObject.reportProgramPattern)(context, node, source, /\bstrictEffectApproach\s*(?::|=)\s*false\b/u, 'UltraModern API apps must keep strictEffectApproach enabled.');
|
|
57
57
|
if ((0, external_ast_cjs_namespaceObject.isApiEntryFile)(filename)) {
|
|
58
58
|
const violation = (0, external_strict_effect_runtime_cjs_namespaceObject.strictEffectRuntimeTopologyViolation)(source, (0, external_strict_effect_runtime_cjs_namespaceObject.createEffectApiImportResolver)(filename));
|