@bleedingdev/modern-js-code-tools 3.9.0-ultramodern.11 → 3.9.0-ultramodern.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/microvertical-api-baseline.cjs +108 -78
- package/dist/cjs/microvertical-api-boundary.cjs +90 -36
- package/dist/cjs/oxlint-plugin/rules/strict-effect-api-boundaries.cjs +1 -1
- package/dist/cjs/source-analysis.cjs +104 -0
- package/dist/cjs/strict-effect-runtime.cjs +12 -47
- package/dist/esm/microvertical-api-baseline.js +99 -65
- package/dist/esm/microvertical-api-boundary.js +90 -36
- package/dist/esm/oxlint-plugin/rules/strict-effect-api-boundaries.js +1 -1
- package/dist/esm/source-analysis.js +44 -0
- package/dist/esm/strict-effect-runtime.js +12 -46
- package/dist/esm-node/microvertical-api-baseline.js +99 -65
- package/dist/esm-node/microvertical-api-boundary.js +90 -36
- package/dist/esm-node/oxlint-plugin/rules/strict-effect-api-boundaries.js +1 -1
- package/dist/esm-node/source-analysis.js +45 -0
- package/dist/esm-node/strict-effect-runtime.js +12 -46
- package/dist/types/microvertical-api-baseline.d.ts +0 -5
- package/dist/types/source-analysis.d.ts +10 -0
- package/package.json +8 -7
|
@@ -2,8 +2,9 @@ import "node:module";
|
|
|
2
2
|
import node_fs from "node:fs";
|
|
3
3
|
import node_path from "node:path";
|
|
4
4
|
import { parse } from "@babel/parser";
|
|
5
|
-
import { configuredMicroVerticalApiStem,
|
|
5
|
+
import { configuredMicroVerticalApiStem, microVerticalApiBaselineViolation } from "./microvertical-api-baseline.js";
|
|
6
6
|
import { baselinePackage, resolveBaselinePackageDirectory } from "./microvertical-api-owner.js";
|
|
7
|
+
import { consumerParserPlugins } from "./source-analysis.js";
|
|
7
8
|
import { createEffectApiImportResolver, strictEffectRuntimeTopologyViolation } from "./strict-effect-runtime.js";
|
|
8
9
|
import * as __rspack_external__babel_types_6e8337dd from "@babel/types";
|
|
9
10
|
const ignored = new Set([
|
|
@@ -20,13 +21,42 @@ const normalize = (file)=>file.split(node_path.sep).join('/').replace(/^\.\//u,
|
|
|
20
21
|
const errorMessage = (error)=>error instanceof Error ? error.message : String(error);
|
|
21
22
|
const apiApp = (app)=>'vertical' === app.kind && 'ui-only' !== app.surfaceProfile && 'horizontal-remote' !== app.deliveryUnitKind;
|
|
22
23
|
function configuredApps(root, supplied) {
|
|
23
|
-
const
|
|
24
|
-
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');
|
|
25
55
|
const paths = new Set();
|
|
26
56
|
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('
|
|
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');
|
|
28
58
|
const key = normalize(app.path);
|
|
29
|
-
if (paths.has(key)) throw new Error(
|
|
59
|
+
if (paths.has(key)) throw new Error(`topology/reference-topology.json: duplicate app path ${key}`);
|
|
30
60
|
paths.add(key);
|
|
31
61
|
for (const name of [
|
|
32
62
|
'id',
|
|
@@ -35,7 +65,7 @@ function configuredApps(root, supplied) {
|
|
|
35
65
|
'surfaceProfile',
|
|
36
66
|
'deliveryUnitKind'
|
|
37
67
|
])if (void 0 !== app[name] && 'string' != typeof app[name]) throw new Error(`${key}: ${name} must be a string`);
|
|
38
|
-
|
|
68
|
+
const allowedFields = {
|
|
39
69
|
kind: [
|
|
40
70
|
'shell',
|
|
41
71
|
'vertical'
|
|
@@ -49,7 +79,11 @@ function configuredApps(root, supplied) {
|
|
|
49
79
|
'microvertical',
|
|
50
80
|
'horizontal-remote'
|
|
51
81
|
]
|
|
52
|
-
}
|
|
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
|
+
}
|
|
53
87
|
if (void 0 !== app.api) {
|
|
54
88
|
if (!app.api || 'object' != typeof app.api || Array.isArray(app.api)) throw new Error(`${key}: api must be an object`);
|
|
55
89
|
if (void 0 !== app.api.protocol && ![
|
|
@@ -137,12 +171,27 @@ function check(options, topology) {
|
|
|
137
171
|
`${base}/index.tsx`
|
|
138
172
|
].find((candidate)=>exists(candidate));
|
|
139
173
|
};
|
|
140
|
-
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)=>{
|
|
141
189
|
const order = [];
|
|
142
190
|
const seen = new Set();
|
|
143
191
|
const queue = [
|
|
144
192
|
file
|
|
145
193
|
];
|
|
194
|
+
const owner = node_fs.realpathSync(absolute(appPath));
|
|
146
195
|
while(queue.length > 0 && order.length < 128){
|
|
147
196
|
const current = queue.shift();
|
|
148
197
|
if (seen.has(current)) continue;
|
|
@@ -153,16 +202,19 @@ function check(options, topology) {
|
|
|
153
202
|
for (const statement of ast.program.body){
|
|
154
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;
|
|
155
204
|
const resolved = void 0 === source ? void 0 : resolveRelative(current, source);
|
|
156
|
-
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
|
+
}
|
|
157
209
|
}
|
|
158
210
|
}
|
|
159
211
|
return order;
|
|
160
212
|
};
|
|
161
|
-
const moduleShape = (file, imports, calls, patterns = [], defaultExport,
|
|
213
|
+
const moduleShape = (file, imports, calls, patterns = [], defaultExport, composedAppPath)=>{
|
|
162
214
|
if (!exists(file)) return;
|
|
163
215
|
const ast = parseFile(file);
|
|
164
216
|
if (!ast) return;
|
|
165
|
-
const shapeFiles =
|
|
217
|
+
const shapeFiles = composedAppPath ? composedModules(file, composedAppPath) : [
|
|
166
218
|
file
|
|
167
219
|
];
|
|
168
220
|
const body = shapeFiles.flatMap((shapeFile)=>parseFile(shapeFile)?.program.body ?? []);
|
|
@@ -272,19 +324,25 @@ function check(options, topology) {
|
|
|
272
324
|
}
|
|
273
325
|
return;
|
|
274
326
|
}
|
|
275
|
-
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`);
|
|
276
328
|
const stem = app?.api?.stem ?? node_path.posix.basename(appPath);
|
|
277
329
|
const rpc = app?.api?.protocol === 'rpc';
|
|
278
330
|
const contract = `${appPath}/shared/${rpc ? 'rpc' : 'api'}.ts`;
|
|
279
|
-
const client = `${appPath}/src/api/${stem}-${rpc ? 'rpc-client' : 'client'}.ts`;
|
|
280
331
|
const entry = `${appPath}/api/index.ts`;
|
|
281
332
|
for (const file of [
|
|
282
333
|
entry,
|
|
283
|
-
contract
|
|
284
|
-
client
|
|
334
|
+
contract
|
|
285
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`);
|
|
286
345
|
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
346
|
if (rpc) {
|
|
289
347
|
moduleShape(entry, [
|
|
290
348
|
[
|
|
@@ -350,21 +408,24 @@ function check(options, topology) {
|
|
|
350
408
|
const violation = microVerticalApiBaselineViolation(stem, absolute(contract), expectation);
|
|
351
409
|
if (violation) diagnostics.push(`${contract}: ${violation}`);
|
|
352
410
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
+
],
|
|
356
421
|
[
|
|
357
|
-
'
|
|
358
|
-
|
|
422
|
+
relativeContract.startsWith('.') ? relativeContract : `./${relativeContract}`,
|
|
423
|
+
[]
|
|
359
424
|
]
|
|
360
|
-
],
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
]
|
|
365
|
-
], [
|
|
366
|
-
rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
|
|
367
|
-
], [], void 0, true);
|
|
425
|
+
], [
|
|
426
|
+
rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
|
|
427
|
+
], [], void 0, appPath);
|
|
428
|
+
}
|
|
368
429
|
moduleShape(`${appPath}/api/effect-api.ts`, [], [], [
|
|
369
430
|
[
|
|
370
431
|
/\bbackendFederationContract\b/u,
|
|
@@ -405,13 +466,6 @@ function check(options, topology) {
|
|
|
405
466
|
'must enable strictEffectApproach'
|
|
406
467
|
]
|
|
407
468
|
]);
|
|
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
469
|
});
|
|
416
470
|
const shell = 'apps/shell-super-app';
|
|
417
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));
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { parse } from "@babel/parser";
|
|
3
|
+
import traverse, { Hub, NodePath } from "@babel/traverse";
|
|
4
|
+
import * as __rspack_external__babel_types_6e8337dd from "@babel/types";
|
|
5
|
+
const consumerParserPlugins = (filePath)=>/\.[jt]sx$/u.test(filePath) ? [
|
|
6
|
+
"typescript",
|
|
7
|
+
'jsx'
|
|
8
|
+
] : [
|
|
9
|
+
"typescript"
|
|
10
|
+
];
|
|
11
|
+
class SourceSyntaxError extends Error {
|
|
12
|
+
}
|
|
13
|
+
class SourceValidationHub extends Hub {
|
|
14
|
+
buildError(_node, message) {
|
|
15
|
+
return new SourceSyntaxError(message);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function parseSource(source, filename, syntaxMessage) {
|
|
19
|
+
try {
|
|
20
|
+
return parse(source, {
|
|
21
|
+
sourceType: 'module',
|
|
22
|
+
sourceFilename: filename,
|
|
23
|
+
plugins: consumerParserPlugins(filename)
|
|
24
|
+
});
|
|
25
|
+
} catch (cause) {
|
|
26
|
+
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)));
|
|
27
|
+
throw cause;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function traverseSource(file, visitor) {
|
|
31
|
+
const program = NodePath.get({
|
|
32
|
+
hub: new SourceValidationHub(),
|
|
33
|
+
parentPath: void 0,
|
|
34
|
+
parent: file,
|
|
35
|
+
container: file,
|
|
36
|
+
key: 'program'
|
|
37
|
+
});
|
|
38
|
+
program.setContext();
|
|
39
|
+
traverse(file, visitor, program.scope, void 0, program);
|
|
40
|
+
}
|
|
41
|
+
function unwrapExpression(node, includeAssertions = false) {
|
|
42
|
+
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;
|
|
43
|
+
return node;
|
|
44
|
+
}
|
|
45
|
+
export { SourceSyntaxError, consumerParserPlugins, parseSource, traverseSource, unwrapExpression };
|
|
@@ -1,24 +1,13 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import node_fs from "node:fs";
|
|
3
3
|
import node_path from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
import traverse, { Hub, NodePath } from "@babel/traverse";
|
|
4
|
+
import { SourceSyntaxError, parseSource, traverseSource, unwrapExpression } from "./source-analysis.js";
|
|
6
5
|
import * as __rspack_external__babel_types_6e8337dd from "@babel/types";
|
|
7
6
|
const edge = '@modern-js/bff-effect/effect-edge';
|
|
8
7
|
const nodeRuntime = '@modern-js/bff-effect/effect';
|
|
9
|
-
const
|
|
10
|
-
const failure = 'Generated API entries must export defineEffectBff(...) or the
|
|
11
|
-
const
|
|
12
|
-
function invalidSource(message) {
|
|
13
|
-
const error = new Error(message);
|
|
14
|
-
invalidSources.add(error);
|
|
15
|
-
return error;
|
|
16
|
-
}
|
|
17
|
-
class SourceValidationHub extends Hub {
|
|
18
|
-
buildError(_node, message) {
|
|
19
|
-
return invalidSource(message);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
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;
|
|
22
11
|
function createEffectApiImportResolver(filename) {
|
|
23
12
|
const directory = node_fs.realpathSync(node_path.dirname(filename));
|
|
24
13
|
const owner = node_fs.realpathSync(node_path.resolve(directory, '..'));
|
|
@@ -50,34 +39,14 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
|
|
|
50
39
|
const load = (input)=>{
|
|
51
40
|
const existing = modules.get(input.id);
|
|
52
41
|
if (existing) return existing;
|
|
53
|
-
if (modules.size >=
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
file = parse(input.source, {
|
|
57
|
-
sourceType: 'module',
|
|
58
|
-
sourceFilename: input.id,
|
|
59
|
-
plugins: [
|
|
60
|
-
"typescript"
|
|
61
|
-
]
|
|
62
|
-
});
|
|
63
|
-
} catch (cause) {
|
|
64
|
-
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');
|
|
65
|
-
throw cause;
|
|
66
|
-
}
|
|
42
|
+
if (modules.size >= MAX_API_SOURCE_MODULES || input.source.length > 1000000) throw new SourceSyntaxError('API source budget exceeded');
|
|
43
|
+
const file = parseSource(input.source, input.id, 'Invalid API syntax');
|
|
67
44
|
const module = {
|
|
68
45
|
...input,
|
|
69
46
|
file
|
|
70
47
|
};
|
|
71
48
|
modules.set(input.id, module);
|
|
72
|
-
|
|
73
|
-
hub: new SourceValidationHub(),
|
|
74
|
-
parentPath: void 0,
|
|
75
|
-
parent: file,
|
|
76
|
-
container: file,
|
|
77
|
-
key: 'program'
|
|
78
|
-
});
|
|
79
|
-
program.setContext();
|
|
80
|
-
traverse(file, {
|
|
49
|
+
traverseSource(file, {
|
|
81
50
|
enter (nodePath) {
|
|
82
51
|
paths.set(nodePath.node, nodePath);
|
|
83
52
|
owners.set(nodePath.node, module);
|
|
@@ -96,7 +65,7 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
|
|
|
96
65
|
if (binding) mutated.add(binding);
|
|
97
66
|
}
|
|
98
67
|
}
|
|
99
|
-
}
|
|
68
|
+
});
|
|
100
69
|
return module;
|
|
101
70
|
};
|
|
102
71
|
try {
|
|
@@ -105,10 +74,7 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
|
|
|
105
74
|
source,
|
|
106
75
|
resolveImport
|
|
107
76
|
});
|
|
108
|
-
const unwrap = (node)=>
|
|
109
|
-
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;
|
|
110
|
-
return node;
|
|
111
|
-
};
|
|
77
|
+
const unwrap = (node)=>unwrapExpression(node, true);
|
|
112
78
|
const binding = (node)=>{
|
|
113
79
|
node = unwrap(node);
|
|
114
80
|
if (!__rspack_external__babel_types_6e8337dd.isIdentifier(node)) return;
|
|
@@ -237,7 +203,7 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
|
|
|
237
203
|
if (!__rspack_external__babel_types_6e8337dd.isBlockStatement(body)) return body;
|
|
238
204
|
const statements = body.body;
|
|
239
205
|
const last = statements.at(-1);
|
|
240
|
-
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;
|
|
241
207
|
};
|
|
242
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, {
|
|
243
209
|
name: 'pipe'
|
|
@@ -326,7 +292,7 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
|
|
|
326
292
|
if (value?.name === 'defineEffectBff' && [
|
|
327
293
|
edge,
|
|
328
294
|
nodeRuntime
|
|
329
|
-
].includes(value.specifier) || value?.name === 'assembleEffectBffRuntime' &&
|
|
295
|
+
].includes(value.specifier) || value?.name === 'assembleEffectBffRuntime' && value.specifier === assembly) {
|
|
330
296
|
if (1 !== node.arguments.length) return false;
|
|
331
297
|
const values = properties(node.arguments[0]);
|
|
332
298
|
if (!values) return false;
|
|
@@ -349,7 +315,7 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
|
|
|
349
315
|
const root = entry.file.program.body.find((statement)=>__rspack_external__babel_types_6e8337dd.isExportDefaultDeclaration(statement));
|
|
350
316
|
return root && runtime(root.declaration) ? void 0 : failure;
|
|
351
317
|
} catch (cause) {
|
|
352
|
-
if (
|
|
318
|
+
if (cause instanceof SourceSyntaxError) return failure;
|
|
353
319
|
const message = 'object' == typeof cause && null !== cause && 'message' in cause && 'string' == typeof cause.message ? cause.message : String(cause);
|
|
354
320
|
throw new Error(`Strict Effect API analysis failed: ${message}`, {
|
|
355
321
|
cause
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Only `.tsx` is JSX. Enabling JSX for a `.ts` file misreads a generic arrow
|
|
3
|
-
* such as `const f = <T>(value: T) => value` as an unclosed JSX element.
|
|
4
|
-
*/
|
|
5
|
-
export declare const consumerParserPlugins: (filePath: string) => ('typescript' | 'jsx')[];
|
|
6
1
|
export interface MicroVerticalApiBaselineExpectation {
|
|
7
2
|
/** Optional override for operation IDs outside reachable endpoint naming conventions. */
|
|
8
3
|
readonly operationPaths?: Readonly<Record<string, string>>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Visitor } from '@babel/traverse';
|
|
2
|
+
import * as t from '@babel/types';
|
|
3
|
+
/** Only JSX extensions enable JSX: generic arrows in .ts must remain valid. */
|
|
4
|
+
export declare const consumerParserPlugins: (filePath: string) => ('typescript' | 'jsx')[];
|
|
5
|
+
/** Only parser/binder diagnostics are policy violations; tool failures escape. */
|
|
6
|
+
export declare class SourceSyntaxError extends Error {
|
|
7
|
+
}
|
|
8
|
+
export declare function parseSource(source: string, filename: string, syntaxMessage?: string): t.File;
|
|
9
|
+
export declare function traverseSource(file: t.File, visitor: Visitor): void;
|
|
10
|
+
export declare function unwrapExpression(node: t.Node, includeAssertions?: boolean): t.Node;
|
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.13",
|
|
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,
|