@bleedingdev/modern-js-code-tools 3.9.0-ultramodern.12 → 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 +90 -19
- package/dist/cjs/microvertical-api-boundary.cjs +88 -35
- package/dist/cjs/oxlint-plugin/rules/strict-effect-api-boundaries.cjs +1 -1
- package/dist/cjs/strict-effect-runtime.cjs +6 -5
- package/dist/esm/microvertical-api-baseline.js +90 -19
- package/dist/esm/microvertical-api-boundary.js +88 -35
- package/dist/esm/oxlint-plugin/rules/strict-effect-api-boundaries.js +1 -1
- package/dist/esm/strict-effect-runtime.js +6 -5
- package/dist/esm-node/microvertical-api-baseline.js +90 -19
- package/dist/esm-node/microvertical-api-boundary.js +88 -35
- package/dist/esm-node/oxlint-plugin/rules/strict-effect-api-boundaries.js +1 -1
- package/dist/esm-node/strict-effect-runtime.js +6 -5
- package/package.json +8 -7
|
@@ -20,13 +20,42 @@ const normalize = (file)=>file.split(node_path.sep).join('/').replace(/^\.\//u,
|
|
|
20
20
|
const errorMessage = (error)=>error instanceof Error ? error.message : String(error);
|
|
21
21
|
const apiApp = (app)=>'vertical' === app.kind && 'ui-only' !== app.surfaceProfile && 'horizontal-remote' !== app.deliveryUnitKind;
|
|
22
22
|
function configuredApps(root, supplied) {
|
|
23
|
-
const
|
|
24
|
-
if (!Array.isArray(
|
|
23
|
+
const topology = supplied ? void 0 : JSON.parse(node_fs.readFileSync(node_path.join(root, 'topology/reference-topology.json'), 'utf8'));
|
|
24
|
+
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');
|
|
25
|
+
const entries = topology?.shell ? [
|
|
26
|
+
topology.shell,
|
|
27
|
+
...topology.shells ?? [],
|
|
28
|
+
...topology.verticals ?? []
|
|
29
|
+
] : void 0;
|
|
30
|
+
const source = supplied ?? entries?.map((entry)=>{
|
|
31
|
+
if (!entry || 'string' != typeof entry.path || !entry.path || node_path.isAbsolute(entry.path) || entry.path.split(/[\\/]/u).includes('..')) throw new Error('topology/reference-topology.json: each app must have an explicit workspace path');
|
|
32
|
+
const packageFile = node_path.join(root, entry.path, 'package.json');
|
|
33
|
+
const manifest = JSON.parse(node_fs.readFileSync(packageFile, 'utf8'));
|
|
34
|
+
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`);
|
|
35
|
+
const api = entry.api;
|
|
36
|
+
const stem = api?.stem ?? api?.readiness?.endpoint?.match(/^\/([a-z0-9]+(?:-[a-z0-9]+)*)\/readiness$/u)?.[1];
|
|
37
|
+
return {
|
|
38
|
+
id: entry.id,
|
|
39
|
+
kind: entry.kind,
|
|
40
|
+
path: entry.path,
|
|
41
|
+
package: manifest.name,
|
|
42
|
+
surfaceProfile: entry.surfaceProfile,
|
|
43
|
+
deliveryUnitKind: entry.deliveryUnitKind,
|
|
44
|
+
...void 0 === api ? {} : {
|
|
45
|
+
api: {
|
|
46
|
+
stem,
|
|
47
|
+
prefix: api.bff?.prefix,
|
|
48
|
+
protocol: api.protocol
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
if (!Array.isArray(source)) throw new Error('topology/reference-topology.json: shell and verticals must describe apps');
|
|
25
54
|
const paths = new Set();
|
|
26
55
|
for (const app of source){
|
|
27
|
-
if (!app || 'object' != typeof app || 'string' != typeof app.path || !app.path || node_path.isAbsolute(app.path) || app.path.split(/[\\/]/u).includes('..')) throw new Error('
|
|
56
|
+
if (!app || 'object' != typeof app || 'string' != typeof app.path || !app.path || node_path.isAbsolute(app.path) || app.path.split(/[\\/]/u).includes('..')) throw new Error('topology/reference-topology.json: each app must have a relative workspace path');
|
|
28
57
|
const key = normalize(app.path);
|
|
29
|
-
if (paths.has(key)) throw new Error(
|
|
58
|
+
if (paths.has(key)) throw new Error(`topology/reference-topology.json: duplicate app path ${key}`);
|
|
30
59
|
paths.add(key);
|
|
31
60
|
for (const name of [
|
|
32
61
|
'id',
|
|
@@ -35,7 +64,7 @@ function configuredApps(root, supplied) {
|
|
|
35
64
|
'surfaceProfile',
|
|
36
65
|
'deliveryUnitKind'
|
|
37
66
|
])if (void 0 !== app[name] && 'string' != typeof app[name]) throw new Error(`${key}: ${name} must be a string`);
|
|
38
|
-
|
|
67
|
+
const allowedFields = {
|
|
39
68
|
kind: [
|
|
40
69
|
'shell',
|
|
41
70
|
'vertical'
|
|
@@ -49,7 +78,11 @@ function configuredApps(root, supplied) {
|
|
|
49
78
|
'microvertical',
|
|
50
79
|
'horizontal-remote'
|
|
51
80
|
]
|
|
52
|
-
}
|
|
81
|
+
};
|
|
82
|
+
for (const field of Object.keys(allowedFields)){
|
|
83
|
+
const value = app[field];
|
|
84
|
+
if (void 0 !== value && !allowedFields[field].includes(value)) throw new Error(`${key}: invalid ${field}`);
|
|
85
|
+
}
|
|
53
86
|
if (void 0 !== app.api) {
|
|
54
87
|
if (!app.api || 'object' != typeof app.api || Array.isArray(app.api)) throw new Error(`${key}: api must be an object`);
|
|
55
88
|
if (void 0 !== app.api.protocol && ![
|
|
@@ -137,12 +170,27 @@ function check(options, topology) {
|
|
|
137
170
|
`${base}/index.tsx`
|
|
138
171
|
].find((candidate)=>exists(candidate));
|
|
139
172
|
};
|
|
140
|
-
const
|
|
173
|
+
const clientExportFile = (appPath, exported)=>{
|
|
174
|
+
if ('string' != typeof exported || !exported.startsWith('./') || exported.split(/[\\/]/u).includes('..') || !/\.[cm]?[jt]sx?$/u.test(exported)) return;
|
|
175
|
+
const appDirectory = absolute(appPath);
|
|
176
|
+
const candidate = node_path.resolve(appDirectory, exported);
|
|
177
|
+
try {
|
|
178
|
+
const workspace = node_fs.realpathSync(root);
|
|
179
|
+
const owner = node_fs.realpathSync(appDirectory);
|
|
180
|
+
const real = node_fs.realpathSync(candidate);
|
|
181
|
+
if (!owner.startsWith(`${workspace}${node_path.sep}`) || !real.startsWith(`${owner}${node_path.sep}`) || !node_fs.statSync(real).isFile()) return;
|
|
182
|
+
} catch {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
return normalize(node_path.relative(root, candidate));
|
|
186
|
+
};
|
|
187
|
+
const composedModules = (file, appPath)=>{
|
|
141
188
|
const order = [];
|
|
142
189
|
const seen = new Set();
|
|
143
190
|
const queue = [
|
|
144
191
|
file
|
|
145
192
|
];
|
|
193
|
+
const owner = node_fs.realpathSync(absolute(appPath));
|
|
146
194
|
while(queue.length > 0 && order.length < 128){
|
|
147
195
|
const current = queue.shift();
|
|
148
196
|
if (seen.has(current)) continue;
|
|
@@ -153,16 +201,19 @@ function check(options, topology) {
|
|
|
153
201
|
for (const statement of ast.program.body){
|
|
154
202
|
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
203
|
const resolved = void 0 === source ? void 0 : resolveRelative(current, source);
|
|
156
|
-
if (void 0 !== resolved && !seen.has(resolved))
|
|
204
|
+
if (void 0 !== resolved && !seen.has(resolved)) {
|
|
205
|
+
const real = node_fs.realpathSync(absolute(resolved));
|
|
206
|
+
if (real.startsWith(`${owner}${node_path.sep}`)) queue.push(resolved);
|
|
207
|
+
}
|
|
157
208
|
}
|
|
158
209
|
}
|
|
159
210
|
return order;
|
|
160
211
|
};
|
|
161
|
-
const moduleShape = (file, imports, calls, patterns = [], defaultExport,
|
|
212
|
+
const moduleShape = (file, imports, calls, patterns = [], defaultExport, composedAppPath)=>{
|
|
162
213
|
if (!exists(file)) return;
|
|
163
214
|
const ast = parseFile(file);
|
|
164
215
|
if (!ast) return;
|
|
165
|
-
const shapeFiles =
|
|
216
|
+
const shapeFiles = composedAppPath ? composedModules(file, composedAppPath) : [
|
|
166
217
|
file
|
|
167
218
|
];
|
|
168
219
|
const body = shapeFiles.flatMap((shapeFile)=>parseFile(shapeFile)?.program.body ?? []);
|
|
@@ -272,19 +323,25 @@ function check(options, topology) {
|
|
|
272
323
|
}
|
|
273
324
|
return;
|
|
274
325
|
}
|
|
275
|
-
if (app?.kind === 'vertical') assert(void 0 !== app.api, `${appPath}: vertical must declare its Effect API in
|
|
326
|
+
if (app?.kind === 'vertical') assert(void 0 !== app.api, `${appPath}: vertical must declare its Effect API in topology/reference-topology.json`);
|
|
276
327
|
const stem = app?.api?.stem ?? node_path.posix.basename(appPath);
|
|
277
328
|
const rpc = app?.api?.protocol === 'rpc';
|
|
278
329
|
const contract = `${appPath}/shared/${rpc ? 'rpc' : 'api'}.ts`;
|
|
279
|
-
const client = `${appPath}/src/api/${stem}-${rpc ? 'rpc-client' : 'client'}.ts`;
|
|
280
330
|
const entry = `${appPath}/api/index.ts`;
|
|
281
331
|
for (const file of [
|
|
282
332
|
entry,
|
|
283
|
-
contract
|
|
284
|
-
client
|
|
333
|
+
contract
|
|
285
334
|
])assert(exists(file), `${file}: required API surface is missing`);
|
|
335
|
+
const packageFile = `${appPath}/package.json`;
|
|
336
|
+
assert(exists(packageFile), `${packageFile}: required API package is missing`);
|
|
337
|
+
if (!exists(packageFile)) return;
|
|
338
|
+
const manifest = JSON.parse(read(packageFile));
|
|
339
|
+
assert(manifest.exports?.['./api'] === `./shared/${rpc ? 'rpc' : 'api'}.ts`, `${packageFile}: invalid ./api export`);
|
|
340
|
+
const clientKey = `./api/${rpc ? 'rpc-client' : 'client'}`;
|
|
341
|
+
const client = clientExportFile(appPath, manifest.exports?.[clientKey]);
|
|
342
|
+
assert(void 0 !== client, `${packageFile}: invalid API client export`);
|
|
343
|
+
assert(manifest.exports?.[`./api/${rpc ? 'client' : 'rpc-client'}`] === void 0, `${packageFile}: forbidden opposite protocol client export`);
|
|
286
344
|
noPath(`${appPath}/shared/${rpc ? 'api' : 'rpc'}.ts`, `must not emit a ${rpc ? 'REST' : 'RPC'} contract`);
|
|
287
|
-
noPath(`${appPath}/src/api/${stem}-${rpc ? 'client' : 'rpc-client'}.ts`, `must not emit a ${rpc ? 'REST' : 'RPC'} client`);
|
|
288
345
|
if (rpc) {
|
|
289
346
|
moduleShape(entry, [
|
|
290
347
|
[
|
|
@@ -350,21 +407,24 @@ function check(options, topology) {
|
|
|
350
407
|
const violation = microVerticalApiBaselineViolation(stem, absolute(contract), expectation);
|
|
351
408
|
if (violation) diagnostics.push(`${contract}: ${violation}`);
|
|
352
409
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
410
|
+
if (client) {
|
|
411
|
+
const relativeContract = node_path.posix.relative(node_path.posix.dirname(client), contract);
|
|
412
|
+
moduleShape(client, [
|
|
413
|
+
[
|
|
414
|
+
effect,
|
|
415
|
+
[
|
|
416
|
+
'Effect',
|
|
417
|
+
rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
|
|
418
|
+
]
|
|
419
|
+
],
|
|
356
420
|
[
|
|
357
|
-
'
|
|
358
|
-
|
|
421
|
+
relativeContract.startsWith('.') ? relativeContract : `./${relativeContract}`,
|
|
422
|
+
[]
|
|
359
423
|
]
|
|
360
|
-
],
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
]
|
|
365
|
-
], [
|
|
366
|
-
rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
|
|
367
|
-
], [], void 0, true);
|
|
424
|
+
], [
|
|
425
|
+
rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
|
|
426
|
+
], [], void 0, appPath);
|
|
427
|
+
}
|
|
368
428
|
moduleShape(`${appPath}/api/effect-api.ts`, [], [], [
|
|
369
429
|
[
|
|
370
430
|
/\bbackendFederationContract\b/u,
|
|
@@ -405,13 +465,6 @@ function check(options, topology) {
|
|
|
405
465
|
'must enable strictEffectApproach'
|
|
406
466
|
]
|
|
407
467
|
]);
|
|
408
|
-
const packageFile = `${appPath}/package.json`;
|
|
409
|
-
if (exists(packageFile)) {
|
|
410
|
-
const manifest = JSON.parse(read(packageFile));
|
|
411
|
-
assert(manifest.exports?.['./api'] === `./shared/${rpc ? 'rpc' : 'api'}.ts`, `${packageFile}: invalid ./api export`);
|
|
412
|
-
assert(manifest.exports?.[`./api/${rpc ? 'rpc-client' : 'client'}`] === `./src/api/${stem}-${rpc ? 'rpc-client' : 'client'}.ts`, `${packageFile}: invalid API client export`);
|
|
413
|
-
assert(manifest.exports?.[`./api/${rpc ? 'client' : 'rpc-client'}`] === void 0, `${packageFile}: forbidden opposite protocol client export`);
|
|
414
|
-
}
|
|
415
468
|
});
|
|
416
469
|
const shell = 'apps/shell-super-app';
|
|
417
470
|
const verticals = apps.filter((app)=>app.path.startsWith('verticals/') && apiApp(app));
|
|
@@ -20,7 +20,7 @@ const createStrictEffectApiBoundariesRule = ()=>({
|
|
|
20
20
|
});
|
|
21
21
|
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.');
|
|
22
22
|
reportProgramPattern(context, node, source, /\bruntimeFramework\s*(?::|=)\s*['"]hono['"]/u, 'UltraModern API apps must use bff.runtimeFramework: "effect".');
|
|
23
|
-
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.');
|
|
23
|
+
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.');
|
|
24
24
|
reportProgramPattern(context, node, source, /\bstrictEffectApproach\s*(?::|=)\s*false\b/u, 'UltraModern API apps must keep strictEffectApproach enabled.');
|
|
25
25
|
if (isApiEntryFile(filename)) {
|
|
26
26
|
const violation = strictEffectRuntimeTopologyViolation(source, createEffectApiImportResolver(filename));
|
|
@@ -4,8 +4,9 @@ import { SourceSyntaxError, parseSource, traverseSource, unwrapExpression } from
|
|
|
4
4
|
import * as __rspack_external__babel_types_6e8337dd from "@babel/types";
|
|
5
5
|
const edge = '@modern-js/bff-effect/effect-edge';
|
|
6
6
|
const nodeRuntime = '@modern-js/bff-effect/effect';
|
|
7
|
-
const
|
|
8
|
-
const failure = 'Generated API entries must export defineEffectBff(...) or the
|
|
7
|
+
const assembly = '@modern-js/bff-effect/assembly';
|
|
8
|
+
const failure = 'Generated API entries must export defineEffectBff(...) or the native Effect BFF assembly helper with an explicitly composed handler Layer and an unshadowed executable root; entries must implement handlers through HttpApiBuilder.group.';
|
|
9
|
+
const MAX_API_SOURCE_MODULES = 256;
|
|
9
10
|
function createEffectApiImportResolver(filename) {
|
|
10
11
|
const directory = node_fs.realpathSync(node_path.dirname(filename));
|
|
11
12
|
const owner = node_fs.realpathSync(node_path.resolve(directory, '..'));
|
|
@@ -37,7 +38,7 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
|
|
|
37
38
|
const load = (input)=>{
|
|
38
39
|
const existing = modules.get(input.id);
|
|
39
40
|
if (existing) return existing;
|
|
40
|
-
if (modules.size >=
|
|
41
|
+
if (modules.size >= MAX_API_SOURCE_MODULES || input.source.length > 1000000) throw new SourceSyntaxError('API source budget exceeded');
|
|
41
42
|
const file = parseSource(input.source, input.id, 'Invalid API syntax');
|
|
42
43
|
const module = {
|
|
43
44
|
...input,
|
|
@@ -201,7 +202,7 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
|
|
|
201
202
|
if (!__rspack_external__babel_types_6e8337dd.isBlockStatement(body)) return body;
|
|
202
203
|
const statements = body.body;
|
|
203
204
|
const last = statements.at(-1);
|
|
204
|
-
return statements.slice(0, -1).every((statement)=>__rspack_external__babel_types_6e8337dd.isVariableDeclaration(statement)) && __rspack_external__babel_types_6e8337dd.isReturnStatement(last) ? last.argument : void 0;
|
|
205
|
+
return statements.slice(0, -1).every((statement)=>__rspack_external__babel_types_6e8337dd.isVariableDeclaration(statement) || __rspack_external__babel_types_6e8337dd.isTSTypeAliasDeclaration(statement) || __rspack_external__babel_types_6e8337dd.isTSInterfaceDeclaration(statement)) && __rspack_external__babel_types_6e8337dd.isReturnStatement(last) ? last.argument : void 0;
|
|
205
206
|
};
|
|
206
207
|
const pipe = (node, check)=>__rspack_external__babel_types_6e8337dd.isMemberExpression(node.callee) && !node.callee.computed && __rspack_external__babel_types_6e8337dd.isIdentifier(node.callee.property, {
|
|
207
208
|
name: 'pipe'
|
|
@@ -290,7 +291,7 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
|
|
|
290
291
|
if (value?.name === 'defineEffectBff' && [
|
|
291
292
|
edge,
|
|
292
293
|
nodeRuntime
|
|
293
|
-
].includes(value.specifier) || value?.name === 'assembleEffectBffRuntime' &&
|
|
294
|
+
].includes(value.specifier) || value?.name === 'assembleEffectBffRuntime' && value.specifier === assembly) {
|
|
294
295
|
if (1 !== node.arguments.length) return false;
|
|
295
296
|
const values = properties(node.arguments[0]);
|
|
296
297
|
if (!values) return false;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import node_fs from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
3
4
|
import node_path from "node:path";
|
|
5
|
+
import { exports as external_resolve_exports_exports } from "resolve.exports";
|
|
4
6
|
import { baselinePublicIdentityIsExact } from "./microvertical-api-owner.js";
|
|
5
7
|
import { SourceSyntaxError, parseSource, traverseSource, unwrapExpression } from "./source-analysis.js";
|
|
6
8
|
import * as __rspack_external__babel_types_6e8337dd from "@babel/types";
|
|
@@ -82,16 +84,81 @@ const exportedConst = (sourceFile, name)=>{
|
|
|
82
84
|
}))) return localConst(sourceFile, name);
|
|
83
85
|
}
|
|
84
86
|
};
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
const inside = (file, boundary)=>file === boundary || file.startsWith(`${boundary}${node_path.sep}`);
|
|
88
|
+
const containingWorkspace = (file)=>{
|
|
89
|
+
for(let directory = node_path.dirname(file);; directory = node_path.dirname(directory)){
|
|
90
|
+
if (node_fs.existsSync(node_path.join(directory, 'pnpm-workspace.yaml'))) return node_fs.realpathSync(directory);
|
|
91
|
+
if (directory === node_path.dirname(directory)) return;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const sourceBoundary = (file, workspace)=>{
|
|
95
|
+
for(let directory = node_path.dirname(file);; directory = node_path.dirname(directory)){
|
|
96
|
+
if (node_fs.existsSync(node_path.join(directory, 'package.json')) && directory !== workspace) return node_fs.realpathSync(directory);
|
|
97
|
+
if (directory === workspace || directory === node_path.dirname(directory)) return node_fs.realpathSync(node_path.dirname(file));
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const sourceFileAt = (target, boundary)=>{
|
|
101
|
+
const stem = target.replace(/\.(?:[cm]?[jt]sx?)$/u, '');
|
|
89
102
|
for (const candidate of [
|
|
90
103
|
`${stem}.ts`,
|
|
91
104
|
`${stem}.tsx`,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
105
|
+
`${stem}.mts`,
|
|
106
|
+
`${stem}.cts`,
|
|
107
|
+
target,
|
|
108
|
+
node_path.join(target, 'index.ts'),
|
|
109
|
+
node_path.join(target, 'index.tsx')
|
|
110
|
+
])try {
|
|
111
|
+
const real = node_fs.realpathSync(candidate);
|
|
112
|
+
if (inside(real, boundary) && node_fs.statSync(real).isFile()) return real;
|
|
113
|
+
} catch {}
|
|
114
|
+
};
|
|
115
|
+
const packageEntry = (specifier)=>{
|
|
116
|
+
if (specifier.startsWith('.') || specifier.startsWith('/') || specifier.startsWith('#')) return;
|
|
117
|
+
const parts = specifier.split('/');
|
|
118
|
+
const count = specifier.startsWith('@') ? 2 : 1;
|
|
119
|
+
if (parts.length < count || parts.slice(0, count).some((part)=>!part)) return;
|
|
120
|
+
return {
|
|
121
|
+
name: parts.slice(0, count).join('/'),
|
|
122
|
+
exportKey: parts.length === count ? '.' : `./${parts.slice(count).join('/')}`
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
const resolveModulePath = (module, specifier, workspace)=>{
|
|
126
|
+
if (/^\.\.?\//u.test(specifier)) {
|
|
127
|
+
const file = sourceFileAt(node_path.resolve(node_path.dirname(module.path), specifier), module.boundary);
|
|
128
|
+
return void 0 === file ? void 0 : {
|
|
129
|
+
path: file,
|
|
130
|
+
boundary: module.boundary
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const entry = packageEntry(specifier);
|
|
134
|
+
if (!entry || !workspace) return;
|
|
135
|
+
const paths = createRequire(module.path).resolve.paths(entry.name) ?? [];
|
|
136
|
+
for (const lookup of paths){
|
|
137
|
+
const directory = node_path.join(lookup, entry.name);
|
|
138
|
+
const manifest = node_path.join(directory, 'package.json');
|
|
139
|
+
if (!node_fs.existsSync(manifest)) continue;
|
|
140
|
+
const boundary = node_fs.realpathSync(directory);
|
|
141
|
+
if (!inside(boundary, workspace) || boundary.split(node_path.sep).includes('node_modules')) break;
|
|
142
|
+
const pkg = JSON.parse(node_fs.readFileSync(manifest, 'utf8'));
|
|
143
|
+
if (pkg.name !== entry.name || void 0 === pkg.exports) break;
|
|
144
|
+
let target;
|
|
145
|
+
try {
|
|
146
|
+
const targets = external_resolve_exports_exports(pkg, entry.exportKey, {
|
|
147
|
+
conditions: [
|
|
148
|
+
'modern:source'
|
|
149
|
+
]
|
|
150
|
+
});
|
|
151
|
+
target = Array.isArray(targets) ? targets[0] : void 0;
|
|
152
|
+
} catch {
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
if (!target || !target.startsWith('./')) break;
|
|
156
|
+
const file = sourceFileAt(node_path.resolve(boundary, target), boundary);
|
|
157
|
+
return void 0 === file ? void 0 : {
|
|
158
|
+
path: file,
|
|
159
|
+
boundary
|
|
160
|
+
};
|
|
161
|
+
}
|
|
95
162
|
};
|
|
96
163
|
const createModuleReader = (root)=>{
|
|
97
164
|
const modules = new Map([
|
|
@@ -100,14 +167,15 @@ const createModuleReader = (root)=>{
|
|
|
100
167
|
root
|
|
101
168
|
]
|
|
102
169
|
]);
|
|
103
|
-
return (filePath)=>{
|
|
170
|
+
return (filePath, boundary)=>{
|
|
104
171
|
if (modules.has(filePath)) return modules.get(filePath);
|
|
105
172
|
if (modules.size >= 256) return;
|
|
106
173
|
let module;
|
|
107
174
|
try {
|
|
108
175
|
module = {
|
|
109
176
|
file: parseConsumer(filePath),
|
|
110
|
-
path: filePath
|
|
177
|
+
path: filePath,
|
|
178
|
+
boundary
|
|
111
179
|
};
|
|
112
180
|
} catch {
|
|
113
181
|
module = void 0;
|
|
@@ -137,7 +205,7 @@ const reexportedBinding = (module, name)=>{
|
|
|
137
205
|
if (!__rspack_external__babel_types_6e8337dd.isExportNamedDeclaration(statement) || 'type' === statement.exportKind) continue;
|
|
138
206
|
const specifier = statement.specifiers.find((value)=>__rspack_external__babel_types_6e8337dd.isExportSpecifier(value) && 'type' !== value.exportKind && propertyName(value.exported) === name);
|
|
139
207
|
if (void 0 !== specifier && __rspack_external__babel_types_6e8337dd.isExportSpecifier(specifier)) return {
|
|
140
|
-
local: specifier.local.name,
|
|
208
|
+
local: __rspack_external__babel_types_6e8337dd.isIdentifier(specifier.local) ? specifier.local.name : specifier.local.value,
|
|
141
209
|
...statement.source ? {
|
|
142
210
|
specifier: statement.source.value
|
|
143
211
|
} : {}
|
|
@@ -152,7 +220,7 @@ const defaultExport = (module)=>{
|
|
|
152
220
|
return __rspack_external__babel_types_6e8337dd.isExpression(declaration) ? declaration : void 0;
|
|
153
221
|
}
|
|
154
222
|
};
|
|
155
|
-
const resolveBinding = (module, name, scope, readModule, seen)=>{
|
|
223
|
+
const resolveBinding = (module, name, scope, readModule, workspace, seen)=>{
|
|
156
224
|
const key = `${scope}:${module.path}#${name}`;
|
|
157
225
|
if (seen.has(key) || seen.size >= 512) return;
|
|
158
226
|
seen.add(key);
|
|
@@ -162,9 +230,9 @@ const resolveBinding = (module, name, scope, readModule, seen)=>{
|
|
|
162
230
|
module
|
|
163
231
|
};
|
|
164
232
|
const throughModule = (specifier, imported)=>{
|
|
165
|
-
const
|
|
166
|
-
const target =
|
|
167
|
-
return void 0 === target ? void 0 : resolveBinding(target, imported, 'export', readModule, seen);
|
|
233
|
+
const resolved = resolveModulePath(module, specifier, workspace);
|
|
234
|
+
const target = resolved && readModule(resolved.path, resolved.boundary);
|
|
235
|
+
return void 0 === target ? void 0 : resolveBinding(target, imported, 'export', readModule, workspace, seen);
|
|
168
236
|
};
|
|
169
237
|
if ('local' === scope) {
|
|
170
238
|
const imported = importedBinding(module, name);
|
|
@@ -172,14 +240,14 @@ const resolveBinding = (module, name, scope, readModule, seen)=>{
|
|
|
172
240
|
}
|
|
173
241
|
if ('default' === name) {
|
|
174
242
|
const exported = defaultExport(module);
|
|
175
|
-
if ('string' == typeof exported) return resolveBinding(module, exported, 'local', readModule, seen);
|
|
243
|
+
if ('string' == typeof exported) return resolveBinding(module, exported, 'local', readModule, workspace, seen);
|
|
176
244
|
return void 0 === exported ? void 0 : {
|
|
177
245
|
expression: exported,
|
|
178
246
|
module
|
|
179
247
|
};
|
|
180
248
|
}
|
|
181
249
|
const reexported = reexportedBinding(module, name);
|
|
182
|
-
if (void 0 !== reexported) return void 0 === reexported.specifier ? resolveBinding(module, reexported.local, 'local', readModule, seen) : throughModule(reexported.specifier, reexported.local);
|
|
250
|
+
if (void 0 !== reexported) return void 0 === reexported.specifier ? resolveBinding(module, reexported.local, 'local', readModule, workspace, seen) : throughModule(reexported.specifier, reexported.local);
|
|
183
251
|
for (const statement of module.file.program.body){
|
|
184
252
|
if (!__rspack_external__babel_types_6e8337dd.isExportAllDeclaration(statement) || 'type' === statement.exportKind) continue;
|
|
185
253
|
const resolved = throughModule(statement.source.value, name);
|
|
@@ -333,6 +401,7 @@ const nonEndpointCombinators = {
|
|
|
333
401
|
};
|
|
334
402
|
const reachableEndpoints = (rootModule, declaration)=>{
|
|
335
403
|
const readModule = createModuleReader(rootModule);
|
|
404
|
+
const workspace = containingWorkspace(rootModule.path);
|
|
336
405
|
const active = new Set();
|
|
337
406
|
const endpoints = [];
|
|
338
407
|
const identities = new Set();
|
|
@@ -354,7 +423,7 @@ const reachableEndpoints = (rootModule, declaration)=>{
|
|
|
354
423
|
active.add(node);
|
|
355
424
|
try {
|
|
356
425
|
if (__rspack_external__babel_types_6e8337dd.isIdentifier(node)) {
|
|
357
|
-
const resolved = resolveBinding(module, node.name, 'local', readModule, new Set());
|
|
426
|
+
const resolved = resolveBinding(module, node.name, 'local', readModule, workspace, new Set());
|
|
358
427
|
return void 0 !== resolved && visit(resolved.expression, resolved.module, kind, group);
|
|
359
428
|
}
|
|
360
429
|
if ('endpoint' === kind) {
|
|
@@ -571,11 +640,13 @@ const configuredMicroVerticalApiStem = (verticalPath, verticals)=>{
|
|
|
571
640
|
};
|
|
572
641
|
const microVerticalApiBaselineViolation = (stem, filePath, expectation)=>{
|
|
573
642
|
try {
|
|
574
|
-
const
|
|
643
|
+
const realPath = node_fs.realpathSync(filePath);
|
|
644
|
+
const sourceFile = parseConsumer(realPath);
|
|
575
645
|
if (!baselinePublicIdentityIsExact(filePath, expectation)) return 'MicroVertical baseline imports must resolve the exact framework owner public export and schema identity';
|
|
576
646
|
return validateParsedContract({
|
|
577
647
|
file: sourceFile,
|
|
578
|
-
path:
|
|
648
|
+
path: realPath,
|
|
649
|
+
boundary: sourceBoundary(realPath, containingWorkspace(realPath))
|
|
579
650
|
}, stem, expectation);
|
|
580
651
|
} catch (error) {
|
|
581
652
|
if (error instanceof SourceSyntaxError) return `MicroVertical root contract must be valid TypeScript syntax (${error.message})`;
|