@bleedingdev/modern-js-code-tools 3.9.0-ultramodern.12 → 3.9.0-ultramodern.14

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.
@@ -21,13 +21,42 @@ const normalize = (file)=>file.split(node_path.sep).join('/').replace(/^\.\//u,
21
21
  const errorMessage = (error)=>error instanceof Error ? error.message : String(error);
22
22
  const apiApp = (app)=>'vertical' === app.kind && 'ui-only' !== app.surfaceProfile && 'horizontal-remote' !== app.deliveryUnitKind;
23
23
  function configuredApps(root, supplied) {
24
- const source = supplied ?? JSON.parse(node_fs.readFileSync(node_path.join(root, '.modernjs/ultramodern.json'), 'utf8')).topology?.apps;
25
- if (!Array.isArray(source)) throw new Error('.modernjs/ultramodern.json: topology.apps must be an array');
24
+ const topology = supplied ? void 0 : JSON.parse(node_fs.readFileSync(node_path.join(root, 'topology/reference-topology.json'), 'utf8'));
25
+ 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');
26
+ const entries = topology?.shell ? [
27
+ topology.shell,
28
+ ...topology.shells ?? [],
29
+ ...topology.verticals ?? []
30
+ ] : void 0;
31
+ const source = supplied ?? entries?.map((entry)=>{
32
+ 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');
33
+ const packageFile = node_path.join(root, entry.path, 'package.json');
34
+ const manifest = JSON.parse(node_fs.readFileSync(packageFile, 'utf8'));
35
+ 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`);
36
+ const api = entry.api;
37
+ const stem = api?.stem ?? api?.readiness?.endpoint?.match(/^\/([a-z0-9]+(?:-[a-z0-9]+)*)\/readiness$/u)?.[1];
38
+ return {
39
+ id: entry.id,
40
+ kind: entry.kind,
41
+ path: entry.path,
42
+ package: manifest.name,
43
+ surfaceProfile: entry.surfaceProfile,
44
+ deliveryUnitKind: entry.deliveryUnitKind,
45
+ ...void 0 === api ? {} : {
46
+ api: {
47
+ stem,
48
+ prefix: api.bff?.prefix,
49
+ protocol: api.protocol
50
+ }
51
+ }
52
+ };
53
+ });
54
+ if (!Array.isArray(source)) throw new Error('topology/reference-topology.json: shell and verticals must describe apps');
26
55
  const paths = new Set();
27
56
  for (const app of source){
28
- 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');
57
+ 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');
29
58
  const key = normalize(app.path);
30
- if (paths.has(key)) throw new Error(`.modernjs/ultramodern.json: duplicate app path ${key}`);
59
+ if (paths.has(key)) throw new Error(`topology/reference-topology.json: duplicate app path ${key}`);
31
60
  paths.add(key);
32
61
  for (const name of [
33
62
  'id',
@@ -36,7 +65,7 @@ function configuredApps(root, supplied) {
36
65
  'surfaceProfile',
37
66
  'deliveryUnitKind'
38
67
  ])if (void 0 !== app[name] && 'string' != typeof app[name]) throw new Error(`${key}: ${name} must be a string`);
39
- for (const [field, allowed] of Object.entries({
68
+ const allowedFields = {
40
69
  kind: [
41
70
  'shell',
42
71
  'vertical'
@@ -50,7 +79,11 @@ function configuredApps(root, supplied) {
50
79
  'microvertical',
51
80
  'horizontal-remote'
52
81
  ]
53
- }))if (void 0 !== app[field] && !allowed.includes(app[field])) throw new Error(`${key}: invalid ${field}`);
82
+ };
83
+ for (const field of Object.keys(allowedFields)){
84
+ const value = app[field];
85
+ if (void 0 !== value && !allowedFields[field].includes(value)) throw new Error(`${key}: invalid ${field}`);
86
+ }
54
87
  if (void 0 !== app.api) {
55
88
  if (!app.api || 'object' != typeof app.api || Array.isArray(app.api)) throw new Error(`${key}: api must be an object`);
56
89
  if (void 0 !== app.api.protocol && ![
@@ -138,12 +171,27 @@ function check(options, topology) {
138
171
  `${base}/index.tsx`
139
172
  ].find((candidate)=>exists(candidate));
140
173
  };
141
- const composedModules = (file)=>{
174
+ const clientExportFile = (appPath, exported)=>{
175
+ if ('string' != typeof exported || !exported.startsWith('./') || exported.split(/[\\/]/u).includes('..') || !/\.[cm]?[jt]sx?$/u.test(exported)) return;
176
+ const appDirectory = absolute(appPath);
177
+ const candidate = node_path.resolve(appDirectory, exported);
178
+ try {
179
+ const workspace = node_fs.realpathSync(root);
180
+ const owner = node_fs.realpathSync(appDirectory);
181
+ const real = node_fs.realpathSync(candidate);
182
+ if (!owner.startsWith(`${workspace}${node_path.sep}`) || !real.startsWith(`${owner}${node_path.sep}`) || !node_fs.statSync(real).isFile()) return;
183
+ } catch {
184
+ return;
185
+ }
186
+ return normalize(node_path.relative(root, candidate));
187
+ };
188
+ const composedModules = (file, appPath)=>{
142
189
  const order = [];
143
190
  const seen = new Set();
144
191
  const queue = [
145
192
  file
146
193
  ];
194
+ const owner = node_fs.realpathSync(absolute(appPath));
147
195
  while(queue.length > 0 && order.length < 128){
148
196
  const current = queue.shift();
149
197
  if (seen.has(current)) continue;
@@ -154,16 +202,19 @@ function check(options, topology) {
154
202
  for (const statement of ast.program.body){
155
203
  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;
156
204
  const resolved = void 0 === source ? void 0 : resolveRelative(current, source);
157
- if (void 0 !== resolved && !seen.has(resolved)) queue.push(resolved);
205
+ if (void 0 !== resolved && !seen.has(resolved)) {
206
+ const real = node_fs.realpathSync(absolute(resolved));
207
+ if (real.startsWith(`${owner}${node_path.sep}`)) queue.push(resolved);
208
+ }
158
209
  }
159
210
  }
160
211
  return order;
161
212
  };
162
- const moduleShape = (file, imports, calls, patterns = [], defaultExport, composed = false)=>{
213
+ const moduleShape = (file, imports, calls, patterns = [], defaultExport, composedAppPath)=>{
163
214
  if (!exists(file)) return;
164
215
  const ast = parseFile(file);
165
216
  if (!ast) return;
166
- const shapeFiles = composed ? composedModules(file) : [
217
+ const shapeFiles = composedAppPath ? composedModules(file, composedAppPath) : [
167
218
  file
168
219
  ];
169
220
  const body = shapeFiles.flatMap((shapeFile)=>parseFile(shapeFile)?.program.body ?? []);
@@ -273,19 +324,25 @@ function check(options, topology) {
273
324
  }
274
325
  return;
275
326
  }
276
- if (app?.kind === 'vertical') assert(void 0 !== app.api, `${appPath}: vertical must declare its Effect API in .modernjs/ultramodern.json`);
327
+ if (app?.kind === 'vertical') assert(void 0 !== app.api, `${appPath}: vertical must declare its Effect API in topology/reference-topology.json`);
277
328
  const stem = app?.api?.stem ?? node_path.posix.basename(appPath);
278
329
  const rpc = app?.api?.protocol === 'rpc';
279
330
  const contract = `${appPath}/shared/${rpc ? 'rpc' : 'api'}.ts`;
280
- const client = `${appPath}/src/api/${stem}-${rpc ? 'rpc-client' : 'client'}.ts`;
281
331
  const entry = `${appPath}/api/index.ts`;
282
332
  for (const file of [
283
333
  entry,
284
- contract,
285
- client
334
+ contract
286
335
  ])assert(exists(file), `${file}: required API surface is missing`);
336
+ const packageFile = `${appPath}/package.json`;
337
+ assert(exists(packageFile), `${packageFile}: required API package is missing`);
338
+ if (!exists(packageFile)) return;
339
+ const manifest = JSON.parse(read(packageFile));
340
+ assert(manifest.exports?.['./api'] === `./shared/${rpc ? 'rpc' : 'api'}.ts`, `${packageFile}: invalid ./api export`);
341
+ const clientKey = `./api/${rpc ? 'rpc-client' : 'client'}`;
342
+ const client = clientExportFile(appPath, manifest.exports?.[clientKey]);
343
+ assert(void 0 !== client, `${packageFile}: invalid API client export`);
344
+ assert(manifest.exports?.[`./api/${rpc ? 'client' : 'rpc-client'}`] === void 0, `${packageFile}: forbidden opposite protocol client export`);
287
345
  noPath(`${appPath}/shared/${rpc ? 'api' : 'rpc'}.ts`, `must not emit a ${rpc ? 'REST' : 'RPC'} contract`);
288
- noPath(`${appPath}/src/api/${stem}-${rpc ? 'client' : 'rpc-client'}.ts`, `must not emit a ${rpc ? 'REST' : 'RPC'} client`);
289
346
  if (rpc) {
290
347
  moduleShape(entry, [
291
348
  [
@@ -351,21 +408,24 @@ function check(options, topology) {
351
408
  const violation = microVerticalApiBaselineViolation(stem, absolute(contract), expectation);
352
409
  if (violation) diagnostics.push(`${contract}: ${violation}`);
353
410
  }
354
- moduleShape(client, [
355
- [
356
- effect,
411
+ if (client) {
412
+ const relativeContract = node_path.posix.relative(node_path.posix.dirname(client), contract);
413
+ moduleShape(client, [
414
+ [
415
+ effect,
416
+ [
417
+ 'Effect',
418
+ rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
419
+ ]
420
+ ],
357
421
  [
358
- 'Effect',
359
- rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
422
+ relativeContract.startsWith('.') ? relativeContract : `./${relativeContract}`,
423
+ []
360
424
  ]
361
- ],
362
- [
363
- rpc ? '../../shared/rpc.ts' : '../../shared/api',
364
- []
365
- ]
366
- ], [
367
- rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
368
- ], [], void 0, true);
425
+ ], [
426
+ rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
427
+ ], [], void 0, appPath);
428
+ }
369
429
  moduleShape(`${appPath}/api/effect-api.ts`, [], [], [
370
430
  [
371
431
  /\bbackendFederationContract\b/u,
@@ -406,13 +466,6 @@ function check(options, topology) {
406
466
  'must enable strictEffectApproach'
407
467
  ]
408
468
  ]);
409
- const packageFile = `${appPath}/package.json`;
410
- if (exists(packageFile)) {
411
- const manifest = JSON.parse(read(packageFile));
412
- assert(manifest.exports?.['./api'] === `./shared/${rpc ? 'rpc' : 'api'}.ts`, `${packageFile}: invalid ./api export`);
413
- assert(manifest.exports?.[`./api/${rpc ? 'rpc-client' : 'client'}`] === `./src/api/${stem}-${rpc ? 'rpc-client' : 'client'}.ts`, `${packageFile}: invalid API client export`);
414
- assert(manifest.exports?.[`./api/${rpc ? 'client' : 'rpc-client'}`] === void 0, `${packageFile}: forbidden opposite protocol client export`);
415
- }
416
469
  });
417
470
  const shell = 'apps/shell-super-app';
418
471
  const verticals = apps.filter((app)=>app.path.startsWith('verticals/') && apiApp(app));
@@ -21,7 +21,7 @@ const createStrictEffectApiBoundariesRule = ()=>({
21
21
  });
22
22
  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.');
23
23
  reportProgramPattern(context, node, source, /\bruntimeFramework\s*(?::|=)\s*['"]hono['"]/u, 'UltraModern API apps must use bff.runtimeFramework: "effect".');
24
- 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.');
24
+ 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.');
25
25
  reportProgramPattern(context, node, source, /\bstrictEffectApproach\s*(?::|=)\s*false\b/u, 'UltraModern API apps must keep strictEffectApproach enabled.');
26
26
  if (isApiEntryFile(filename)) {
27
27
  const violation = strictEffectRuntimeTopologyViolation(source, createEffectApiImportResolver(filename));
@@ -5,8 +5,9 @@ import { SourceSyntaxError, parseSource, traverseSource, unwrapExpression } from
5
5
  import * as __rspack_external__babel_types_6e8337dd from "@babel/types";
6
6
  const edge = '@modern-js/bff-effect/effect-edge';
7
7
  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.';
8
+ const assembly = '@modern-js/bff-effect/assembly';
9
+ 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.';
10
+ const MAX_API_SOURCE_MODULES = 256;
10
11
  function createEffectApiImportResolver(filename) {
11
12
  const directory = node_fs.realpathSync(node_path.dirname(filename));
12
13
  const owner = node_fs.realpathSync(node_path.resolve(directory, '..'));
@@ -38,7 +39,7 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
38
39
  const load = (input)=>{
39
40
  const existing = modules.get(input.id);
40
41
  if (existing) return existing;
41
- if (modules.size >= 64 || input.source.length > 1000000) throw new SourceSyntaxError('API source budget exceeded');
42
+ if (modules.size >= MAX_API_SOURCE_MODULES || input.source.length > 1000000) throw new SourceSyntaxError('API source budget exceeded');
42
43
  const file = parseSource(input.source, input.id, 'Invalid API syntax');
43
44
  const module = {
44
45
  ...input,
@@ -202,7 +203,7 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
202
203
  if (!__rspack_external__babel_types_6e8337dd.isBlockStatement(body)) return body;
203
204
  const statements = body.body;
204
205
  const last = statements.at(-1);
205
- 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;
206
+ 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;
206
207
  };
207
208
  const pipe = (node, check)=>__rspack_external__babel_types_6e8337dd.isMemberExpression(node.callee) && !node.callee.computed && __rspack_external__babel_types_6e8337dd.isIdentifier(node.callee.property, {
208
209
  name: 'pipe'
@@ -291,7 +292,7 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
291
292
  if (value?.name === 'defineEffectBff' && [
292
293
  edge,
293
294
  nodeRuntime
294
- ].includes(value.specifier) || value?.name === 'assembleEffectBffRuntime' && sharedRuntime.test(value.specifier)) {
295
+ ].includes(value.specifier) || value?.name === 'assembleEffectBffRuntime' && value.specifier === assembly) {
295
296
  if (1 !== node.arguments.length) return false;
296
297
  const values = properties(node.arguments[0]);
297
298
  if (!values) return false;
package/package.json CHANGED
@@ -23,7 +23,7 @@
23
23
  "engines": {
24
24
  "node": ">=20"
25
25
  },
26
- "version": "3.9.0-ultramodern.12",
26
+ "version": "3.9.0-ultramodern.14",
27
27
  "types": "./dist/types/index.d.ts",
28
28
  "main": "./dist/esm-node/index.js",
29
29
  "typesVersions": {
@@ -72,15 +72,16 @@
72
72
  "bin"
73
73
  ],
74
74
  "dependencies": {
75
- "@babel/parser": "^8.0.4",
76
- "@babel/traverse": "^8.0.4",
77
- "@babel/types": "^8.0.4",
78
- "oxlint": "1.81.0"
75
+ "@babel/parser": "^8.0.6",
76
+ "@babel/traverse": "^8.0.6",
77
+ "@babel/types": "^8.0.6",
78
+ "oxlint": "1.85.0",
79
+ "resolve.exports": "2.0.3"
79
80
  },
80
81
  "devDependencies": {
81
- "@rslib/core": "1.0.0",
82
+ "@rslib/core": "1.0.2",
82
83
  "@scripts/rstest-config": "2.66.0",
83
- "@types/node": "^26.4.1",
84
+ "@types/node": "^26.6.2",
84
85
  "@typescript/native-preview": "7.0.0-dev.20260707.2"
85
86
  },
86
87
  "sideEffects": false,