@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.
- 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
|
@@ -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
|
|
25
|
-
if (!Array.isArray(
|
|
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('
|
|
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(
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
|
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))
|
|
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,
|
|
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 =
|
|
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
|
|
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
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
'
|
|
359
|
-
|
|
422
|
+
relativeContract.startsWith('.') ? relativeContract : `./${relativeContract}`,
|
|
423
|
+
[]
|
|
360
424
|
]
|
|
361
|
-
],
|
|
362
|
-
|
|
363
|
-
|
|
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
|
|
9
|
-
const failure = 'Generated API entries must export defineEffectBff(...) or the
|
|
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 >=
|
|
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' &&
|
|
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.
|
|
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.
|
|
76
|
-
"@babel/traverse": "^8.0.
|
|
77
|
-
"@babel/types": "^8.0.
|
|
78
|
-
"oxlint": "1.
|
|
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.
|
|
82
|
+
"@rslib/core": "1.0.2",
|
|
82
83
|
"@scripts/rstest-config": "2.66.0",
|
|
83
|
-
"@types/node": "^26.
|
|
84
|
+
"@types/node": "^26.6.2",
|
|
84
85
|
"@typescript/native-preview": "7.0.0-dev.20260707.2"
|
|
85
86
|
},
|
|
86
87
|
"sideEffects": false,
|