@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.
@@ -1,8 +1,9 @@
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, consumerParserPlugins, microVerticalApiBaselineViolation } from "./microvertical-api-baseline.js";
4
+ import { configuredMicroVerticalApiStem, microVerticalApiBaselineViolation } from "./microvertical-api-baseline.js";
5
5
  import { baselinePackage, resolveBaselinePackageDirectory } from "./microvertical-api-owner.js";
6
+ import { consumerParserPlugins } from "./source-analysis.js";
6
7
  import { createEffectApiImportResolver, strictEffectRuntimeTopologyViolation } from "./strict-effect-runtime.js";
7
8
  import * as __rspack_external__babel_types_6e8337dd from "@babel/types";
8
9
  const ignored = new Set([
@@ -19,13 +20,42 @@ const normalize = (file)=>file.split(node_path.sep).join('/').replace(/^\.\//u,
19
20
  const errorMessage = (error)=>error instanceof Error ? error.message : String(error);
20
21
  const apiApp = (app)=>'vertical' === app.kind && 'ui-only' !== app.surfaceProfile && 'horizontal-remote' !== app.deliveryUnitKind;
21
22
  function configuredApps(root, supplied) {
22
- const source = supplied ?? JSON.parse(node_fs.readFileSync(node_path.join(root, '.modernjs/ultramodern.json'), 'utf8')).topology?.apps;
23
- if (!Array.isArray(source)) throw new Error('.modernjs/ultramodern.json: topology.apps must be an array');
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');
24
54
  const paths = new Set();
25
55
  for (const app of source){
26
- if (!app || 'object' != typeof app || 'string' != typeof app.path || !app.path || node_path.isAbsolute(app.path) || app.path.split(/[\\/]/u).includes('..')) throw new Error('.modernjs/ultramodern.json: each app must have a relative workspace path');
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');
27
57
  const key = normalize(app.path);
28
- if (paths.has(key)) throw new Error(`.modernjs/ultramodern.json: duplicate app path ${key}`);
58
+ if (paths.has(key)) throw new Error(`topology/reference-topology.json: duplicate app path ${key}`);
29
59
  paths.add(key);
30
60
  for (const name of [
31
61
  'id',
@@ -34,7 +64,7 @@ function configuredApps(root, supplied) {
34
64
  'surfaceProfile',
35
65
  'deliveryUnitKind'
36
66
  ])if (void 0 !== app[name] && 'string' != typeof app[name]) throw new Error(`${key}: ${name} must be a string`);
37
- for (const [field, allowed] of Object.entries({
67
+ const allowedFields = {
38
68
  kind: [
39
69
  'shell',
40
70
  'vertical'
@@ -48,7 +78,11 @@ function configuredApps(root, supplied) {
48
78
  'microvertical',
49
79
  'horizontal-remote'
50
80
  ]
51
- }))if (void 0 !== app[field] && !allowed.includes(app[field])) throw new Error(`${key}: invalid ${field}`);
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
+ }
52
86
  if (void 0 !== app.api) {
53
87
  if (!app.api || 'object' != typeof app.api || Array.isArray(app.api)) throw new Error(`${key}: api must be an object`);
54
88
  if (void 0 !== app.api.protocol && ![
@@ -136,12 +170,27 @@ function check(options, topology) {
136
170
  `${base}/index.tsx`
137
171
  ].find((candidate)=>exists(candidate));
138
172
  };
139
- const composedModules = (file)=>{
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)=>{
140
188
  const order = [];
141
189
  const seen = new Set();
142
190
  const queue = [
143
191
  file
144
192
  ];
193
+ const owner = node_fs.realpathSync(absolute(appPath));
145
194
  while(queue.length > 0 && order.length < 128){
146
195
  const current = queue.shift();
147
196
  if (seen.has(current)) continue;
@@ -152,16 +201,19 @@ function check(options, topology) {
152
201
  for (const statement of ast.program.body){
153
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;
154
203
  const resolved = void 0 === source ? void 0 : resolveRelative(current, source);
155
- if (void 0 !== resolved && !seen.has(resolved)) queue.push(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
+ }
156
208
  }
157
209
  }
158
210
  return order;
159
211
  };
160
- const moduleShape = (file, imports, calls, patterns = [], defaultExport, composed = false)=>{
212
+ const moduleShape = (file, imports, calls, patterns = [], defaultExport, composedAppPath)=>{
161
213
  if (!exists(file)) return;
162
214
  const ast = parseFile(file);
163
215
  if (!ast) return;
164
- const shapeFiles = composed ? composedModules(file) : [
216
+ const shapeFiles = composedAppPath ? composedModules(file, composedAppPath) : [
165
217
  file
166
218
  ];
167
219
  const body = shapeFiles.flatMap((shapeFile)=>parseFile(shapeFile)?.program.body ?? []);
@@ -271,19 +323,25 @@ function check(options, topology) {
271
323
  }
272
324
  return;
273
325
  }
274
- if (app?.kind === 'vertical') assert(void 0 !== app.api, `${appPath}: vertical must declare its Effect API in .modernjs/ultramodern.json`);
326
+ if (app?.kind === 'vertical') assert(void 0 !== app.api, `${appPath}: vertical must declare its Effect API in topology/reference-topology.json`);
275
327
  const stem = app?.api?.stem ?? node_path.posix.basename(appPath);
276
328
  const rpc = app?.api?.protocol === 'rpc';
277
329
  const contract = `${appPath}/shared/${rpc ? 'rpc' : 'api'}.ts`;
278
- const client = `${appPath}/src/api/${stem}-${rpc ? 'rpc-client' : 'client'}.ts`;
279
330
  const entry = `${appPath}/api/index.ts`;
280
331
  for (const file of [
281
332
  entry,
282
- contract,
283
- client
333
+ contract
284
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`);
285
344
  noPath(`${appPath}/shared/${rpc ? 'api' : 'rpc'}.ts`, `must not emit a ${rpc ? 'REST' : 'RPC'} contract`);
286
- noPath(`${appPath}/src/api/${stem}-${rpc ? 'client' : 'rpc-client'}.ts`, `must not emit a ${rpc ? 'REST' : 'RPC'} client`);
287
345
  if (rpc) {
288
346
  moduleShape(entry, [
289
347
  [
@@ -349,21 +407,24 @@ function check(options, topology) {
349
407
  const violation = microVerticalApiBaselineViolation(stem, absolute(contract), expectation);
350
408
  if (violation) diagnostics.push(`${contract}: ${violation}`);
351
409
  }
352
- moduleShape(client, [
353
- [
354
- effect,
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
+ ],
355
420
  [
356
- 'Effect',
357
- rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
421
+ relativeContract.startsWith('.') ? relativeContract : `./${relativeContract}`,
422
+ []
358
423
  ]
359
- ],
360
- [
361
- rpc ? '../../shared/rpc.ts' : '../../shared/api',
362
- []
363
- ]
364
- ], [
365
- rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
366
- ], [], void 0, true);
424
+ ], [
425
+ rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
426
+ ], [], void 0, appPath);
427
+ }
367
428
  moduleShape(`${appPath}/api/effect-api.ts`, [], [], [
368
429
  [
369
430
  /\bbackendFederationContract\b/u,
@@ -404,13 +465,6 @@ function check(options, topology) {
404
465
  'must enable strictEffectApproach'
405
466
  ]
406
467
  ]);
407
- const packageFile = `${appPath}/package.json`;
408
- if (exists(packageFile)) {
409
- const manifest = JSON.parse(read(packageFile));
410
- assert(manifest.exports?.['./api'] === `./shared/${rpc ? 'rpc' : 'api'}.ts`, `${packageFile}: invalid ./api export`);
411
- assert(manifest.exports?.[`./api/${rpc ? 'rpc-client' : 'client'}`] === `./src/api/${stem}-${rpc ? 'rpc-client' : 'client'}.ts`, `${packageFile}: invalid API client export`);
412
- assert(manifest.exports?.[`./api/${rpc ? 'client' : 'rpc-client'}`] === void 0, `${packageFile}: forbidden opposite protocol client export`);
413
- }
414
468
  });
415
469
  const shell = 'apps/shell-super-app';
416
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));
@@ -0,0 +1,44 @@
1
+ import { parse } from "@babel/parser";
2
+ import traverse, { Hub, NodePath } from "@babel/traverse";
3
+ import * as __rspack_external__babel_types_6e8337dd from "@babel/types";
4
+ const consumerParserPlugins = (filePath)=>/\.[jt]sx$/u.test(filePath) ? [
5
+ "typescript",
6
+ 'jsx'
7
+ ] : [
8
+ "typescript"
9
+ ];
10
+ class SourceSyntaxError extends Error {
11
+ }
12
+ class SourceValidationHub extends Hub {
13
+ buildError(_node, message) {
14
+ return new SourceSyntaxError(message);
15
+ }
16
+ }
17
+ function parseSource(source, filename, syntaxMessage) {
18
+ try {
19
+ return parse(source, {
20
+ sourceType: 'module',
21
+ sourceFilename: filename,
22
+ plugins: consumerParserPlugins(filename)
23
+ });
24
+ } catch (cause) {
25
+ if ('object' == typeof cause && null !== cause && 'code' in cause && 'BABEL_PARSER_SYNTAX_ERROR' === cause.code && 'reasonCode' in cause && 'string' == typeof cause.reasonCode) throw new SourceSyntaxError(syntaxMessage ?? (cause instanceof Error ? cause.message : String(cause)));
26
+ throw cause;
27
+ }
28
+ }
29
+ function traverseSource(file, visitor) {
30
+ const program = NodePath.get({
31
+ hub: new SourceValidationHub(),
32
+ parentPath: void 0,
33
+ parent: file,
34
+ container: file,
35
+ key: 'program'
36
+ });
37
+ program.setContext();
38
+ traverse(file, visitor, program.scope, void 0, program);
39
+ }
40
+ function unwrapExpression(node, includeAssertions = false) {
41
+ while(__rspack_external__babel_types_6e8337dd.isTSAsExpression(node) || __rspack_external__babel_types_6e8337dd.isParenthesizedExpression(node) || __rspack_external__babel_types_6e8337dd.isTSSatisfiesExpression(node) || includeAssertions && (__rspack_external__babel_types_6e8337dd.isTSTypeAssertion(node) || __rspack_external__babel_types_6e8337dd.isTSNonNullExpression(node)))node = node.expression;
42
+ return node;
43
+ }
44
+ export { SourceSyntaxError, consumerParserPlugins, parseSource, traverseSource, unwrapExpression };
@@ -1,23 +1,12 @@
1
1
  import node_fs from "node:fs";
2
2
  import node_path from "node:path";
3
- import { parse } from "@babel/parser";
4
- import traverse, { Hub, NodePath } from "@babel/traverse";
3
+ import { SourceSyntaxError, parseSource, traverseSource, unwrapExpression } from "./source-analysis.js";
5
4
  import * as __rspack_external__babel_types_6e8337dd from "@babel/types";
6
5
  const edge = '@modern-js/bff-effect/effect-edge';
7
6
  const nodeRuntime = '@modern-js/bff-effect/effect';
8
- const sharedRuntime = /^@[^/]+\/shared-contracts\/server\/effect-bff-runtime$/u;
9
- const failure = 'Generated API entries must export defineEffectBff(...) or the server-only shared Effect BFF assembly helper with an explicitly composed handler Layer and an unshadowed executable root; entries must implement handlers through HttpApiBuilder.group.';
10
- const invalidSources = new WeakSet();
11
- function invalidSource(message) {
12
- const error = new Error(message);
13
- invalidSources.add(error);
14
- return error;
15
- }
16
- class SourceValidationHub extends Hub {
17
- buildError(_node, message) {
18
- return invalidSource(message);
19
- }
20
- }
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;
21
10
  function createEffectApiImportResolver(filename) {
22
11
  const directory = node_fs.realpathSync(node_path.dirname(filename));
23
12
  const owner = node_fs.realpathSync(node_path.resolve(directory, '..'));
@@ -49,34 +38,14 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
49
38
  const load = (input)=>{
50
39
  const existing = modules.get(input.id);
51
40
  if (existing) return existing;
52
- if (modules.size >= 64 || input.source.length > 1000000) throw invalidSource('API source budget exceeded');
53
- let file;
54
- try {
55
- file = parse(input.source, {
56
- sourceType: 'module',
57
- sourceFilename: input.id,
58
- plugins: [
59
- "typescript"
60
- ]
61
- });
62
- } catch (cause) {
63
- if ('object' == typeof cause && null !== cause && 'code' in cause && 'BABEL_PARSER_SYNTAX_ERROR' === cause.code && 'reasonCode' in cause && 'string' == typeof cause.reasonCode) throw invalidSource('Invalid API syntax');
64
- throw cause;
65
- }
41
+ if (modules.size >= MAX_API_SOURCE_MODULES || input.source.length > 1000000) throw new SourceSyntaxError('API source budget exceeded');
42
+ const file = parseSource(input.source, input.id, 'Invalid API syntax');
66
43
  const module = {
67
44
  ...input,
68
45
  file
69
46
  };
70
47
  modules.set(input.id, module);
71
- const program = NodePath.get({
72
- hub: new SourceValidationHub(),
73
- parentPath: void 0,
74
- parent: file,
75
- container: file,
76
- key: 'program'
77
- });
78
- program.setContext();
79
- traverse(file, {
48
+ traverseSource(file, {
80
49
  enter (nodePath) {
81
50
  paths.set(nodePath.node, nodePath);
82
51
  owners.set(nodePath.node, module);
@@ -95,7 +64,7 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
95
64
  if (binding) mutated.add(binding);
96
65
  }
97
66
  }
98
- }, program.scope, void 0, program);
67
+ });
99
68
  return module;
100
69
  };
101
70
  try {
@@ -104,10 +73,7 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
104
73
  source,
105
74
  resolveImport
106
75
  });
107
- const unwrap = (node)=>{
108
- while(__rspack_external__babel_types_6e8337dd.isParenthesizedExpression(node) || __rspack_external__babel_types_6e8337dd.isTSSatisfiesExpression(node) || __rspack_external__babel_types_6e8337dd.isTSAsExpression(node) || __rspack_external__babel_types_6e8337dd.isTSTypeAssertion(node) || __rspack_external__babel_types_6e8337dd.isTSNonNullExpression(node))node = node.expression;
109
- return node;
110
- };
76
+ const unwrap = (node)=>unwrapExpression(node, true);
111
77
  const binding = (node)=>{
112
78
  node = unwrap(node);
113
79
  if (!__rspack_external__babel_types_6e8337dd.isIdentifier(node)) return;
@@ -236,7 +202,7 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
236
202
  if (!__rspack_external__babel_types_6e8337dd.isBlockStatement(body)) return body;
237
203
  const statements = body.body;
238
204
  const last = statements.at(-1);
239
- 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;
240
206
  };
241
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, {
242
208
  name: 'pipe'
@@ -325,7 +291,7 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
325
291
  if (value?.name === 'defineEffectBff' && [
326
292
  edge,
327
293
  nodeRuntime
328
- ].includes(value.specifier) || value?.name === 'assembleEffectBffRuntime' && sharedRuntime.test(value.specifier)) {
294
+ ].includes(value.specifier) || value?.name === 'assembleEffectBffRuntime' && value.specifier === assembly) {
329
295
  if (1 !== node.arguments.length) return false;
330
296
  const values = properties(node.arguments[0]);
331
297
  if (!values) return false;
@@ -348,7 +314,7 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
348
314
  const root = entry.file.program.body.find((statement)=>__rspack_external__babel_types_6e8337dd.isExportDefaultDeclaration(statement));
349
315
  return root && runtime(root.declaration) ? void 0 : failure;
350
316
  } catch (cause) {
351
- if ('object' == typeof cause && null !== cause && invalidSources.has(cause)) return failure;
317
+ if (cause instanceof SourceSyntaxError) return failure;
352
318
  const message = 'object' == typeof cause && null !== cause && 'message' in cause && 'string' == typeof cause.message ? cause.message : String(cause);
353
319
  throw new Error(`Strict Effect API analysis failed: ${message}`, {
354
320
  cause
@@ -1,55 +1,25 @@
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";
4
- import { parse } from "@babel/parser";
5
- import traverse, { Hub, NodePath } from "@babel/traverse";
5
+ import { exports as external_resolve_exports_exports } from "resolve.exports";
6
6
  import { baselinePublicIdentityIsExact } from "./microvertical-api-owner.js";
7
+ import { SourceSyntaxError, parseSource, traverseSource, unwrapExpression } from "./source-analysis.js";
7
8
  import * as __rspack_external__babel_types_6e8337dd from "@babel/types";
8
- const consumerParserPlugins = (filePath)=>filePath.endsWith('.tsx') || filePath.endsWith('.jsx') ? [
9
- "typescript",
10
- 'jsx'
11
- ] : [
12
- "typescript"
13
- ];
14
- class ConsumerSyntaxError extends Error {
15
- }
16
- class ConsumerValidationHub extends Hub {
17
- buildError(_node, message) {
18
- return new ConsumerSyntaxError(message);
19
- }
20
- }
21
9
  function parseConsumer(filePath) {
22
10
  if (node_fs.statSync(filePath).size > 1000000) throw new Error(`${filePath}: consumer source exceeds 1 MB analysis budget`);
23
- let file;
24
- try {
25
- file = parse(node_fs.readFileSync(filePath, 'utf8'), {
26
- sourceType: 'module',
27
- sourceFilename: filePath,
28
- plugins: consumerParserPlugins(filePath)
29
- });
30
- } catch (error) {
31
- if ('object' == typeof error && null !== error && 'code' in error && 'BABEL_PARSER_SYNTAX_ERROR' === error.code) throw new ConsumerSyntaxError(error instanceof Error ? error.message : String(error));
32
- throw error;
33
- }
34
- const program = NodePath.get({
35
- hub: new ConsumerValidationHub(),
36
- parentPath: void 0,
37
- parent: file,
38
- container: file,
39
- key: 'program'
40
- });
41
- program.setContext();
42
- traverse(file, {
11
+ const file = parseSource(node_fs.readFileSync(filePath, 'utf8'), filePath);
12
+ traverseSource(file, {
43
13
  AssignmentExpression (p) {
44
- throw new ConsumerSyntaxError(`contract bindings must be immutable at ${p.node.start}`);
14
+ throw new SourceSyntaxError(`contract bindings must be immutable at ${p.node.start}`);
45
15
  },
46
16
  UpdateExpression (p) {
47
- throw new ConsumerSyntaxError(`contract bindings must be immutable at ${p.node.start}`);
17
+ throw new SourceSyntaxError(`contract bindings must be immutable at ${p.node.start}`);
48
18
  },
49
19
  TSModuleDeclaration () {
50
- throw new ConsumerSyntaxError('contract bindings must not be merged with namespaces');
20
+ throw new SourceSyntaxError('contract bindings must not be merged with namespaces');
51
21
  }
52
- }, program.scope, void 0, program);
22
+ });
53
23
  return file;
54
24
  }
55
25
  const camelCaseStem = (stem)=>stem.replaceAll(/-([a-z0-9])/gu, (_match, letter)=>letter.toUpperCase());
@@ -74,11 +44,6 @@ const accessPath = (node)=>{
74
44
  }
75
45
  };
76
46
  const isAccessPath = (node, expected)=>accessPath(node)?.join('.') === expected.join('.');
77
- const unwrapExpression = (expression)=>{
78
- let current = expression;
79
- while(__rspack_external__babel_types_6e8337dd.isTSAsExpression(current) || __rspack_external__babel_types_6e8337dd.isParenthesizedExpression(current) || __rspack_external__babel_types_6e8337dd.isTSSatisfiesExpression(current))current = current.expression;
80
- return current;
81
- };
82
47
  const stringLiteral = (expression)=>{
83
48
  if (null == expression) return;
84
49
  const unwrapped = unwrapExpression(expression);
@@ -119,16 +84,81 @@ const exportedConst = (sourceFile, name)=>{
119
84
  }))) return localConst(sourceFile, name);
120
85
  }
121
86
  };
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, '');
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, '');
126
102
  for (const candidate of [
127
103
  `${stem}.ts`,
128
104
  `${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;
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
+ }
132
162
  };
133
163
  const createModuleReader = (root)=>{
134
164
  const modules = new Map([
@@ -137,14 +167,15 @@ const createModuleReader = (root)=>{
137
167
  root
138
168
  ]
139
169
  ]);
140
- return (filePath)=>{
170
+ return (filePath, boundary)=>{
141
171
  if (modules.has(filePath)) return modules.get(filePath);
142
172
  if (modules.size >= 256) return;
143
173
  let module;
144
174
  try {
145
175
  module = {
146
176
  file: parseConsumer(filePath),
147
- path: filePath
177
+ path: filePath,
178
+ boundary
148
179
  };
149
180
  } catch {
150
181
  module = void 0;
@@ -174,7 +205,7 @@ const reexportedBinding = (module, name)=>{
174
205
  if (!__rspack_external__babel_types_6e8337dd.isExportNamedDeclaration(statement) || 'type' === statement.exportKind) continue;
175
206
  const specifier = statement.specifiers.find((value)=>__rspack_external__babel_types_6e8337dd.isExportSpecifier(value) && 'type' !== value.exportKind && propertyName(value.exported) === name);
176
207
  if (void 0 !== specifier && __rspack_external__babel_types_6e8337dd.isExportSpecifier(specifier)) return {
177
- local: specifier.local.name,
208
+ local: __rspack_external__babel_types_6e8337dd.isIdentifier(specifier.local) ? specifier.local.name : specifier.local.value,
178
209
  ...statement.source ? {
179
210
  specifier: statement.source.value
180
211
  } : {}
@@ -189,7 +220,7 @@ const defaultExport = (module)=>{
189
220
  return __rspack_external__babel_types_6e8337dd.isExpression(declaration) ? declaration : void 0;
190
221
  }
191
222
  };
192
- const resolveBinding = (module, name, scope, readModule, seen)=>{
223
+ const resolveBinding = (module, name, scope, readModule, workspace, seen)=>{
193
224
  const key = `${scope}:${module.path}#${name}`;
194
225
  if (seen.has(key) || seen.size >= 512) return;
195
226
  seen.add(key);
@@ -199,9 +230,9 @@ const resolveBinding = (module, name, scope, readModule, seen)=>{
199
230
  module
200
231
  };
201
232
  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);
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);
205
236
  };
206
237
  if ('local' === scope) {
207
238
  const imported = importedBinding(module, name);
@@ -209,14 +240,14 @@ const resolveBinding = (module, name, scope, readModule, seen)=>{
209
240
  }
210
241
  if ('default' === name) {
211
242
  const exported = defaultExport(module);
212
- if ('string' == typeof exported) return resolveBinding(module, exported, 'local', readModule, seen);
243
+ if ('string' == typeof exported) return resolveBinding(module, exported, 'local', readModule, workspace, seen);
213
244
  return void 0 === exported ? void 0 : {
214
245
  expression: exported,
215
246
  module
216
247
  };
217
248
  }
218
249
  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);
250
+ if (void 0 !== reexported) return void 0 === reexported.specifier ? resolveBinding(module, reexported.local, 'local', readModule, workspace, seen) : throughModule(reexported.specifier, reexported.local);
220
251
  for (const statement of module.file.program.body){
221
252
  if (!__rspack_external__babel_types_6e8337dd.isExportAllDeclaration(statement) || 'type' === statement.exportKind) continue;
222
253
  const resolved = throughModule(statement.source.value, name);
@@ -370,6 +401,7 @@ const nonEndpointCombinators = {
370
401
  };
371
402
  const reachableEndpoints = (rootModule, declaration)=>{
372
403
  const readModule = createModuleReader(rootModule);
404
+ const workspace = containingWorkspace(rootModule.path);
373
405
  const active = new Set();
374
406
  const endpoints = [];
375
407
  const identities = new Set();
@@ -391,7 +423,7 @@ const reachableEndpoints = (rootModule, declaration)=>{
391
423
  active.add(node);
392
424
  try {
393
425
  if (__rspack_external__babel_types_6e8337dd.isIdentifier(node)) {
394
- const resolved = resolveBinding(module, node.name, 'local', readModule, new Set());
426
+ const resolved = resolveBinding(module, node.name, 'local', readModule, workspace, new Set());
395
427
  return void 0 !== resolved && visit(resolved.expression, resolved.module, kind, group);
396
428
  }
397
429
  if ('endpoint' === kind) {
@@ -608,15 +640,17 @@ const configuredMicroVerticalApiStem = (verticalPath, verticals)=>{
608
640
  };
609
641
  const microVerticalApiBaselineViolation = (stem, filePath, expectation)=>{
610
642
  try {
611
- const sourceFile = parseConsumer(filePath);
643
+ const realPath = node_fs.realpathSync(filePath);
644
+ const sourceFile = parseConsumer(realPath);
612
645
  if (!baselinePublicIdentityIsExact(filePath, expectation)) return 'MicroVertical baseline imports must resolve the exact framework owner public export and schema identity';
613
646
  return validateParsedContract({
614
647
  file: sourceFile,
615
- path: filePath
648
+ path: realPath,
649
+ boundary: sourceBoundary(realPath, containingWorkspace(realPath))
616
650
  }, stem, expectation);
617
651
  } catch (error) {
618
- if (error instanceof ConsumerSyntaxError) return `MicroVertical root contract must be valid TypeScript syntax (${error.message})`;
652
+ if (error instanceof SourceSyntaxError) return `MicroVertical root contract must be valid TypeScript syntax (${error.message})`;
619
653
  throw error;
620
654
  }
621
655
  };
622
- export { configuredMicroVerticalApiStem, consumerParserPlugins, microVerticalApiBaselineViolation };
656
+ export { configuredMicroVerticalApiStem, microVerticalApiBaselineViolation };