@bleedingdev/modern-js-code-tools 3.9.0-ultramodern.2 → 3.9.0-ultramodern.20
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/bin/modern-api-check-files.mjs +7 -0
- package/bin/modern-api-check.mjs +4 -0
- package/bin/modern-i18n-check.mjs +4 -0
- package/dist/cjs/cli/microvertical-api-check.cjs +100 -0
- package/dist/cjs/cli/oxlint.cjs +9 -5
- package/dist/cjs/cli/workspace-source-check.cjs +90 -17
- package/dist/cjs/microvertical-api-baseline.cjs +505 -0
- package/dist/cjs/microvertical-api-boundary.cjs +586 -0
- package/dist/cjs/microvertical-api-owner.cjs +217 -0
- package/dist/cjs/module-graph.cjs +321 -0
- package/dist/cjs/oxlint-plugin/rules/strict-effect-api-boundaries.cjs +8 -6
- package/dist/cjs/source-analysis.cjs +104 -0
- package/dist/cjs/strict-effect-runtime.cjs +376 -0
- package/dist/esm/cli/microvertical-api-check.js +52 -0
- package/dist/esm/cli/oxlint.js +10 -6
- package/dist/esm/cli/workspace-source-check.js +86 -13
- package/dist/esm/microvertical-api-baseline.js +464 -0
- package/dist/esm/microvertical-api-boundary.js +525 -0
- package/dist/esm/microvertical-api-owner.js +162 -0
- package/dist/esm/module-graph.js +260 -0
- package/dist/esm/oxlint-plugin/rules/strict-effect-api-boundaries.js +8 -6
- package/dist/esm/source-analysis.js +44 -0
- package/dist/esm/strict-effect-runtime.js +324 -0
- package/dist/esm-node/cli/microvertical-api-check.js +53 -0
- package/dist/esm-node/cli/oxlint.js +10 -6
- package/dist/esm-node/cli/workspace-source-check.js +86 -13
- package/dist/esm-node/microvertical-api-baseline.js +465 -0
- package/dist/esm-node/microvertical-api-boundary.js +526 -0
- package/dist/esm-node/microvertical-api-owner.js +163 -0
- package/dist/esm-node/module-graph.js +261 -0
- package/dist/esm-node/oxlint-plugin/rules/strict-effect-api-boundaries.js +8 -6
- package/dist/esm-node/source-analysis.js +45 -0
- package/dist/esm-node/strict-effect-runtime.js +325 -0
- package/dist/types/cli/microvertical-api-check.d.ts +1 -0
- package/dist/types/cli/workspace-source-check.d.ts +1 -1
- package/dist/types/microvertical-api-baseline.d.ts +23 -0
- package/dist/types/microvertical-api-boundary.d.ts +46 -0
- package/dist/types/microvertical-api-owner.d.ts +6 -0
- package/dist/types/module-graph.d.ts +52 -0
- package/dist/types/source-analysis.d.ts +10 -0
- package/dist/types/strict-effect-runtime.d.ts +10 -0
- package/package.json +29 -6
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import node_fs from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import node_path from "node:path";
|
|
4
|
+
import { parse } from "@babel/parser";
|
|
5
|
+
import * as __rspack_external__babel_types_6e8337dd from "@babel/types";
|
|
6
|
+
const baselinePackage = '@modern-js/bff-effect/microvertical-api';
|
|
7
|
+
const ownerName = '@modern-js/bff-effect';
|
|
8
|
+
const within = (root, file)=>file.startsWith(`${root}${node_path.sep}`);
|
|
9
|
+
function resolveBaselinePackageDirectory(fromFile) {
|
|
10
|
+
const lookup = createRequire(fromFile).resolve.paths(baselinePackage);
|
|
11
|
+
for (const directory of lookup ?? []){
|
|
12
|
+
const candidate = node_path.join(directory, ownerName);
|
|
13
|
+
if (!node_fs.existsSync(candidate)) continue;
|
|
14
|
+
const root = node_fs.realpathSync(candidate);
|
|
15
|
+
const manifest = JSON.parse(node_fs.readFileSync(node_path.join(root, 'package.json'), 'utf8'));
|
|
16
|
+
if (![
|
|
17
|
+
ownerName,
|
|
18
|
+
'@bleedingdev/modern-js-bff-effect'
|
|
19
|
+
].includes(manifest.name)) throw new Error(`${fromFile}: ${baselinePackage} resolved to unrelated package ${String(manifest.name)}`);
|
|
20
|
+
const target = importTarget(manifest.exports?.['./microvertical-api']);
|
|
21
|
+
if (!target?.startsWith('./')) throw new Error(`${fromFile}: ${baselinePackage} has no public Node import export`);
|
|
22
|
+
node_fs.statSync(node_path.resolve(root, target));
|
|
23
|
+
return root;
|
|
24
|
+
}
|
|
25
|
+
throw new Error(`${fromFile}: cannot resolve installed ${baselinePackage}`);
|
|
26
|
+
}
|
|
27
|
+
function importTarget(value) {
|
|
28
|
+
if ('string' == typeof value) return value;
|
|
29
|
+
if (!value || 'object' != typeof value || Array.isArray(value)) return;
|
|
30
|
+
for (const [condition, target] of Object.entries(value))if ([
|
|
31
|
+
'node',
|
|
32
|
+
'import',
|
|
33
|
+
'default'
|
|
34
|
+
].includes(condition)) {
|
|
35
|
+
const resolved = importTarget(target);
|
|
36
|
+
if (resolved) return resolved;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function baselinePublicIdentityIsExact(fromFile, expectation) {
|
|
40
|
+
const owner = node_fs.realpathSync(expectation.baselinePackageDirectory);
|
|
41
|
+
const manifest = JSON.parse(node_fs.readFileSync(node_path.join(owner, 'package.json'), 'utf8'));
|
|
42
|
+
if (expectation.baselinePackage !== baselinePackage || ![
|
|
43
|
+
ownerName,
|
|
44
|
+
'@bleedingdev/modern-js-bff-effect'
|
|
45
|
+
].includes(manifest.name)) return false;
|
|
46
|
+
let resolvedOwner;
|
|
47
|
+
try {
|
|
48
|
+
resolvedOwner = resolveBaselinePackageDirectory(fromFile);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (error instanceof Error && error.message.includes('resolved to unrelated package')) return false;
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
if (resolvedOwner !== owner) return false;
|
|
54
|
+
const target = importTarget(manifest.exports?.['./microvertical-api']);
|
|
55
|
+
if (!target?.startsWith('./')) return false;
|
|
56
|
+
const modules = new Map();
|
|
57
|
+
const load = (filename)=>{
|
|
58
|
+
const real = node_fs.realpathSync(filename);
|
|
59
|
+
if (!within(owner, real)) return;
|
|
60
|
+
let file = modules.get(real);
|
|
61
|
+
if (!file) {
|
|
62
|
+
if (modules.size >= 64 || node_fs.statSync(real).size > 1000000) throw new Error(`${real}: public export analysis budget exceeded`);
|
|
63
|
+
file = parse(node_fs.readFileSync(real, 'utf8'), {
|
|
64
|
+
sourceType: 'module',
|
|
65
|
+
sourceFilename: real,
|
|
66
|
+
plugins: [
|
|
67
|
+
"typescript"
|
|
68
|
+
]
|
|
69
|
+
});
|
|
70
|
+
modules.set(real, file);
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
file,
|
|
74
|
+
real
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
const follow = (from, specifier)=>{
|
|
78
|
+
if (!specifier.startsWith('.')) return;
|
|
79
|
+
const candidate = node_path.resolve(node_path.dirname(from), specifier);
|
|
80
|
+
if (!within(owner, candidate)) return;
|
|
81
|
+
return [
|
|
82
|
+
candidate,
|
|
83
|
+
`${candidate}.ts`,
|
|
84
|
+
`${candidate}.js`,
|
|
85
|
+
node_path.join(candidate, 'index.ts'),
|
|
86
|
+
node_path.join(candidate, 'index.js')
|
|
87
|
+
].find((file)=>node_fs.existsSync(file) && node_fs.statSync(file).isFile());
|
|
88
|
+
};
|
|
89
|
+
const exportedOwner = (filename, name, seen = new Set())=>{
|
|
90
|
+
const loaded = load(filename);
|
|
91
|
+
if (!loaded) return;
|
|
92
|
+
const { file, real } = loaded;
|
|
93
|
+
const key = `${real}:${name}`;
|
|
94
|
+
if (seen.has(key)) return;
|
|
95
|
+
seen = new Set([
|
|
96
|
+
...seen,
|
|
97
|
+
key
|
|
98
|
+
]);
|
|
99
|
+
const explicit = [];
|
|
100
|
+
const stars = [];
|
|
101
|
+
for (const statement of file.program.body){
|
|
102
|
+
if (__rspack_external__babel_types_6e8337dd.isExportAllDeclaration(statement) && 'type' !== statement.exportKind) {
|
|
103
|
+
const next = follow(real, statement.source.value);
|
|
104
|
+
if (!next) return;
|
|
105
|
+
stars.push(next);
|
|
106
|
+
}
|
|
107
|
+
if (!__rspack_external__babel_types_6e8337dd.isExportNamedDeclaration(statement) || 'type' === statement.exportKind) continue;
|
|
108
|
+
const declaration = statement.declaration;
|
|
109
|
+
if (__rspack_external__babel_types_6e8337dd.isVariableDeclaration(declaration) && declaration.declarations.some((value)=>__rspack_external__babel_types_6e8337dd.isIdentifier(value.id, {
|
|
110
|
+
name
|
|
111
|
+
}))) explicit.push(real);
|
|
112
|
+
if ((__rspack_external__babel_types_6e8337dd.isFunctionDeclaration(declaration) || __rspack_external__babel_types_6e8337dd.isClassDeclaration(declaration)) && __rspack_external__babel_types_6e8337dd.isIdentifier(declaration.id, {
|
|
113
|
+
name
|
|
114
|
+
})) explicit.push(real);
|
|
115
|
+
for (const value of statement.specifiers)if (__rspack_external__babel_types_6e8337dd.isExportSpecifier(value) && 'type' !== value.exportKind && __rspack_external__babel_types_6e8337dd.isIdentifier(value.exported, {
|
|
116
|
+
name
|
|
117
|
+
})) {
|
|
118
|
+
if (!__rspack_external__babel_types_6e8337dd.isIdentifier(value.local, {
|
|
119
|
+
name
|
|
120
|
+
})) {
|
|
121
|
+
explicit.push(void 0);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (statement.source) {
|
|
125
|
+
const next = follow(real, statement.source.value);
|
|
126
|
+
explicit.push(next ? exportedOwner(next, name, seen) : void 0);
|
|
127
|
+
} else {
|
|
128
|
+
const local = file.program.body.find((item)=>__rspack_external__babel_types_6e8337dd.isVariableDeclaration(item) && item.declarations.some((value)=>__rspack_external__babel_types_6e8337dd.isIdentifier(value.id, {
|
|
129
|
+
name
|
|
130
|
+
})) || (__rspack_external__babel_types_6e8337dd.isFunctionDeclaration(item) || __rspack_external__babel_types_6e8337dd.isClassDeclaration(item)) && __rspack_external__babel_types_6e8337dd.isIdentifier(item.id, {
|
|
131
|
+
name
|
|
132
|
+
}));
|
|
133
|
+
if (local) {
|
|
134
|
+
explicit.push(real);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const imported = file.program.body.find((item)=>__rspack_external__babel_types_6e8337dd.isImportDeclaration(item) && 'type' !== item.importKind && item.specifiers.some((value)=>__rspack_external__babel_types_6e8337dd.isImportSpecifier(value) && 'type' !== value.importKind && __rspack_external__babel_types_6e8337dd.isIdentifier(value.local, {
|
|
138
|
+
name
|
|
139
|
+
}) && __rspack_external__babel_types_6e8337dd.isIdentifier(value.imported, {
|
|
140
|
+
name
|
|
141
|
+
})));
|
|
142
|
+
const next = __rspack_external__babel_types_6e8337dd.isImportDeclaration(imported) ? follow(real, imported.source.value) : void 0;
|
|
143
|
+
explicit.push(next ? exportedOwner(next, name, seen) : void 0);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (explicit.length) return 1 === explicit.length ? explicit[0] : void 0;
|
|
148
|
+
const owners = stars.map((next)=>exportedOwner(next, name, seen));
|
|
149
|
+
const unique = new Set(owners.filter((value)=>void 0 !== value));
|
|
150
|
+
return 1 === unique.size ? [
|
|
151
|
+
...unique
|
|
152
|
+
][0] : void 0;
|
|
153
|
+
};
|
|
154
|
+
const entry = node_path.resolve(owner, target);
|
|
155
|
+
const owners = [
|
|
156
|
+
'MicroVerticalBuildMarkerSchema',
|
|
157
|
+
'MicroVerticalReadinessSchema',
|
|
158
|
+
'createMicroVerticalOperationContext'
|
|
159
|
+
].map((name)=>exportedOwner(entry, name));
|
|
160
|
+
return void 0 !== owners[0] && owners.every((value)=>value === owners[0]);
|
|
161
|
+
}
|
|
162
|
+
export { baselinePackage, baselinePublicIdentityIsExact, resolveBaselinePackageDirectory };
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import node_fs from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import node_path from "node:path";
|
|
4
|
+
import { exports as external_resolve_exports_exports } from "resolve.exports";
|
|
5
|
+
import { SourceSyntaxError, parseSource, traverseSource } from "./source-analysis.js";
|
|
6
|
+
import * as __rspack_external__babel_types_6e8337dd from "@babel/types";
|
|
7
|
+
const MAX_MODULES = 256;
|
|
8
|
+
const MAX_LOOKUPS = 512;
|
|
9
|
+
function parseConsumer(filePath) {
|
|
10
|
+
if (node_fs.statSync(filePath).size > 1000000) throw new Error(`${filePath}: consumer source exceeds 1 MB analysis budget`);
|
|
11
|
+
const file = parseSource(node_fs.readFileSync(filePath, 'utf8'), filePath);
|
|
12
|
+
traverseSource(file, {
|
|
13
|
+
AssignmentExpression (p) {
|
|
14
|
+
throw new SourceSyntaxError(`contract bindings must be immutable at ${p.node.start}`);
|
|
15
|
+
},
|
|
16
|
+
UpdateExpression (p) {
|
|
17
|
+
throw new SourceSyntaxError(`contract bindings must be immutable at ${p.node.start}`);
|
|
18
|
+
},
|
|
19
|
+
TSModuleDeclaration () {
|
|
20
|
+
throw new SourceSyntaxError('contract bindings must not be merged with namespaces');
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
return file;
|
|
24
|
+
}
|
|
25
|
+
const propertyName = (node)=>{
|
|
26
|
+
if (void 0 !== node && (__rspack_external__babel_types_6e8337dd.isIdentifier(node) || __rspack_external__babel_types_6e8337dd.isStringLiteral(node) || __rspack_external__babel_types_6e8337dd.isNumericLiteral(node))) return __rspack_external__babel_types_6e8337dd.isIdentifier(node) ? node.name : String(node.value);
|
|
27
|
+
};
|
|
28
|
+
const localConst = (sourceFile, name)=>{
|
|
29
|
+
for (const item of sourceFile.program.body){
|
|
30
|
+
const statement = __rspack_external__babel_types_6e8337dd.isExportNamedDeclaration(item) ? item.declaration : item;
|
|
31
|
+
if (!__rspack_external__babel_types_6e8337dd.isVariableDeclaration(statement) || 'const' !== statement.kind) continue;
|
|
32
|
+
const matches = statement.declarations.filter((declaration)=>__rspack_external__babel_types_6e8337dd.isIdentifier(declaration.id, {
|
|
33
|
+
name
|
|
34
|
+
}));
|
|
35
|
+
if (1 === matches.length) return matches[0];
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const exportedConst = (sourceFile, name)=>{
|
|
39
|
+
for (const statement of sourceFile.program.body)if (__rspack_external__babel_types_6e8337dd.isExportNamedDeclaration(statement) && 'type' !== statement.exportKind) {
|
|
40
|
+
if (__rspack_external__babel_types_6e8337dd.isVariableDeclaration(statement.declaration) && 'const' === statement.declaration.kind) {
|
|
41
|
+
const declaration = statement.declaration.declarations.find((value)=>__rspack_external__babel_types_6e8337dd.isIdentifier(value.id, {
|
|
42
|
+
name
|
|
43
|
+
}));
|
|
44
|
+
if (declaration) return declaration;
|
|
45
|
+
}
|
|
46
|
+
if (!statement.source && statement.specifiers.some((value)=>__rspack_external__babel_types_6e8337dd.isExportSpecifier(value) && 'type' !== value.exportKind && __rspack_external__babel_types_6e8337dd.isIdentifier(value.local, {
|
|
47
|
+
name
|
|
48
|
+
}) && __rspack_external__babel_types_6e8337dd.isIdentifier(value.exported, {
|
|
49
|
+
name
|
|
50
|
+
}))) return localConst(sourceFile, name);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const inside = (file, boundary)=>file === boundary || file.startsWith(`${boundary}${node_path.sep}`);
|
|
54
|
+
const containingWorkspace = (file)=>{
|
|
55
|
+
for(let directory = node_path.dirname(file);; directory = node_path.dirname(directory)){
|
|
56
|
+
if (node_fs.existsSync(node_path.join(directory, 'pnpm-workspace.yaml'))) return node_fs.realpathSync(directory);
|
|
57
|
+
if (directory === node_path.dirname(directory)) return;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const sourceBoundary = (file, workspace)=>{
|
|
61
|
+
for(let directory = node_path.dirname(file);; directory = node_path.dirname(directory)){
|
|
62
|
+
if (node_fs.existsSync(node_path.join(directory, 'package.json')) && directory !== workspace) return node_fs.realpathSync(directory);
|
|
63
|
+
if (directory === workspace || directory === node_path.dirname(directory)) return node_fs.realpathSync(node_path.dirname(file));
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const sourceFileAt = (target, boundary)=>{
|
|
67
|
+
const stem = target.replace(/\.(?:[cm]?[jt]sx?)$/u, '');
|
|
68
|
+
for (const candidate of [
|
|
69
|
+
`${stem}.ts`,
|
|
70
|
+
`${stem}.tsx`,
|
|
71
|
+
`${stem}.mts`,
|
|
72
|
+
`${stem}.cts`,
|
|
73
|
+
target,
|
|
74
|
+
node_path.join(target, 'index.ts'),
|
|
75
|
+
node_path.join(target, 'index.tsx')
|
|
76
|
+
])try {
|
|
77
|
+
const real = node_fs.realpathSync(candidate);
|
|
78
|
+
if (inside(real, boundary) && node_fs.statSync(real).isFile()) return real;
|
|
79
|
+
} catch {}
|
|
80
|
+
};
|
|
81
|
+
const packageEntry = (specifier)=>{
|
|
82
|
+
if (specifier.startsWith('.') || specifier.startsWith('/') || specifier.startsWith('#')) return;
|
|
83
|
+
const parts = specifier.split('/');
|
|
84
|
+
const count = specifier.startsWith('@') ? 2 : 1;
|
|
85
|
+
if (parts.length < count || parts.slice(0, count).some((part)=>!part)) return;
|
|
86
|
+
return {
|
|
87
|
+
name: parts.slice(0, count).join('/'),
|
|
88
|
+
exportKey: parts.length === count ? '.' : `./${parts.slice(count).join('/')}`
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
const resolveModulePath = (module, specifier, workspace)=>{
|
|
92
|
+
if (/^\.\.?\//u.test(specifier)) {
|
|
93
|
+
const file = sourceFileAt(node_path.resolve(node_path.dirname(module.path), specifier), module.boundary);
|
|
94
|
+
return void 0 === file ? void 0 : {
|
|
95
|
+
kind: 'module',
|
|
96
|
+
path: file,
|
|
97
|
+
boundary: module.boundary
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const entry = packageEntry(specifier);
|
|
101
|
+
if (!entry) return;
|
|
102
|
+
if (!workspace) return {
|
|
103
|
+
kind: 'external'
|
|
104
|
+
};
|
|
105
|
+
const paths = createRequire(module.path).resolve.paths(entry.name) ?? [];
|
|
106
|
+
for (const lookup of paths){
|
|
107
|
+
const directory = node_path.join(lookup, entry.name);
|
|
108
|
+
const manifest = node_path.join(directory, 'package.json');
|
|
109
|
+
if (!node_fs.existsSync(manifest)) continue;
|
|
110
|
+
const boundary = node_fs.realpathSync(directory);
|
|
111
|
+
if (!inside(boundary, workspace) || boundary.split(node_path.sep).includes('node_modules')) break;
|
|
112
|
+
const pkg = JSON.parse(node_fs.readFileSync(manifest, 'utf8'));
|
|
113
|
+
if (pkg.name !== entry.name || void 0 === pkg.exports) return;
|
|
114
|
+
let target;
|
|
115
|
+
try {
|
|
116
|
+
const targets = external_resolve_exports_exports(pkg, entry.exportKey, {
|
|
117
|
+
conditions: [
|
|
118
|
+
'modern:source'
|
|
119
|
+
]
|
|
120
|
+
});
|
|
121
|
+
target = Array.isArray(targets) ? targets[0] : void 0;
|
|
122
|
+
} catch {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (!target || !target.startsWith('./')) return;
|
|
126
|
+
const file = sourceFileAt(node_path.resolve(boundary, target), boundary);
|
|
127
|
+
return void 0 === file ? void 0 : {
|
|
128
|
+
kind: 'module',
|
|
129
|
+
path: file,
|
|
130
|
+
boundary
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
kind: 'external'
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
const importedBinding = (module, name)=>{
|
|
138
|
+
for (const statement of module.file.program.body)if (__rspack_external__babel_types_6e8337dd.isImportDeclaration(statement) && 'type' !== statement.importKind) {
|
|
139
|
+
for (const specifier of statement.specifiers)if (__rspack_external__babel_types_6e8337dd.isIdentifier(specifier.local, {
|
|
140
|
+
name
|
|
141
|
+
})) {
|
|
142
|
+
if (__rspack_external__babel_types_6e8337dd.isImportSpecifier(specifier) && 'type' !== specifier.importKind) return {
|
|
143
|
+
imported: propertyName(specifier.imported) ?? name,
|
|
144
|
+
specifier: statement.source.value
|
|
145
|
+
};
|
|
146
|
+
if (__rspack_external__babel_types_6e8337dd.isImportDefaultSpecifier(specifier)) return {
|
|
147
|
+
imported: 'default',
|
|
148
|
+
specifier: statement.source.value
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
const reexportedBinding = (module, name)=>{
|
|
154
|
+
for (const statement of module.file.program.body){
|
|
155
|
+
if (!__rspack_external__babel_types_6e8337dd.isExportNamedDeclaration(statement) || 'type' === statement.exportKind) continue;
|
|
156
|
+
const specifier = statement.specifiers.find((value)=>__rspack_external__babel_types_6e8337dd.isExportSpecifier(value) && 'type' !== value.exportKind && propertyName(value.exported) === name);
|
|
157
|
+
if (void 0 !== specifier && __rspack_external__babel_types_6e8337dd.isExportSpecifier(specifier)) return {
|
|
158
|
+
local: __rspack_external__babel_types_6e8337dd.isIdentifier(specifier.local) ? specifier.local.name : specifier.local.value,
|
|
159
|
+
...statement.source ? {
|
|
160
|
+
specifier: statement.source.value
|
|
161
|
+
} : {}
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
const defaultExport = (module)=>{
|
|
166
|
+
for (const statement of module.file.program.body){
|
|
167
|
+
if (!__rspack_external__babel_types_6e8337dd.isExportDefaultDeclaration(statement)) continue;
|
|
168
|
+
const declaration = statement.declaration;
|
|
169
|
+
if (__rspack_external__babel_types_6e8337dd.isIdentifier(declaration)) return declaration.name;
|
|
170
|
+
return __rspack_external__babel_types_6e8337dd.isExpression(declaration) ? declaration : void 0;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
function createModuleGraph(filePath) {
|
|
174
|
+
const realPath = node_fs.realpathSync(filePath);
|
|
175
|
+
const workspace = containingWorkspace(realPath);
|
|
176
|
+
const root = {
|
|
177
|
+
file: parseConsumer(realPath),
|
|
178
|
+
path: realPath,
|
|
179
|
+
boundary: sourceBoundary(realPath, workspace)
|
|
180
|
+
};
|
|
181
|
+
const modules = new Map([
|
|
182
|
+
[
|
|
183
|
+
root.path,
|
|
184
|
+
root
|
|
185
|
+
]
|
|
186
|
+
]);
|
|
187
|
+
const readModule = (modulePath, boundary)=>{
|
|
188
|
+
if (modules.has(modulePath)) return modules.get(modulePath);
|
|
189
|
+
if (modules.size >= MAX_MODULES) return;
|
|
190
|
+
let module;
|
|
191
|
+
try {
|
|
192
|
+
module = {
|
|
193
|
+
file: parseConsumer(modulePath),
|
|
194
|
+
path: modulePath,
|
|
195
|
+
boundary
|
|
196
|
+
};
|
|
197
|
+
} catch {
|
|
198
|
+
module = void 0;
|
|
199
|
+
}
|
|
200
|
+
modules.set(modulePath, module);
|
|
201
|
+
return module;
|
|
202
|
+
};
|
|
203
|
+
const resolveBinding = (module, name, scope, chain, seen)=>{
|
|
204
|
+
const key = `${scope}:${module.path}#${name}`;
|
|
205
|
+
if (seen.has(key) || seen.size >= MAX_LOOKUPS) return;
|
|
206
|
+
seen.add(key);
|
|
207
|
+
const last = chain.at(-1);
|
|
208
|
+
const hops = last?.path === module.path && last.name === name ? chain : [
|
|
209
|
+
...chain,
|
|
210
|
+
{
|
|
211
|
+
path: module.path,
|
|
212
|
+
name
|
|
213
|
+
}
|
|
214
|
+
];
|
|
215
|
+
const declaration = 'export' === scope ? exportedConst(module.file, name) : localConst(module.file, name);
|
|
216
|
+
if (declaration?.init) return {
|
|
217
|
+
kind: 'declaration',
|
|
218
|
+
expression: declaration.init,
|
|
219
|
+
module,
|
|
220
|
+
chain: hops
|
|
221
|
+
};
|
|
222
|
+
const throughModule = (specifier, imported, allowExternal)=>{
|
|
223
|
+
const target = resolveModulePath(module, specifier, workspace);
|
|
224
|
+
if (target?.kind === 'external') return allowExternal ? {
|
|
225
|
+
kind: 'external',
|
|
226
|
+
specifier,
|
|
227
|
+
name: imported,
|
|
228
|
+
chain: hops
|
|
229
|
+
} : void 0;
|
|
230
|
+
const next = target && readModule(target.path, target.boundary);
|
|
231
|
+
return void 0 === next ? void 0 : resolveBinding(next, imported, 'export', hops, seen);
|
|
232
|
+
};
|
|
233
|
+
if ('local' === scope) {
|
|
234
|
+
const imported = importedBinding(module, name);
|
|
235
|
+
return void 0 === imported ? void 0 : throughModule(imported.specifier, imported.imported, true);
|
|
236
|
+
}
|
|
237
|
+
if ('default' === name) {
|
|
238
|
+
const exported = defaultExport(module);
|
|
239
|
+
if ('string' == typeof exported) return resolveBinding(module, exported, 'local', hops, seen);
|
|
240
|
+
return void 0 === exported ? void 0 : {
|
|
241
|
+
kind: 'declaration',
|
|
242
|
+
expression: exported,
|
|
243
|
+
module,
|
|
244
|
+
chain: hops
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const reexported = reexportedBinding(module, name);
|
|
248
|
+
if (void 0 !== reexported) return void 0 === reexported.specifier ? resolveBinding(module, reexported.local, 'local', hops, seen) : throughModule(reexported.specifier, reexported.local, true);
|
|
249
|
+
for (const statement of module.file.program.body){
|
|
250
|
+
if (!__rspack_external__babel_types_6e8337dd.isExportAllDeclaration(statement) || 'type' === statement.exportKind) continue;
|
|
251
|
+
const resolved = throughModule(statement.source.value, name, false);
|
|
252
|
+
if (void 0 !== resolved) return resolved;
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
return {
|
|
256
|
+
root,
|
|
257
|
+
resolve: (module, name, scope)=>resolveBinding(module, name, scope, [], new Set())
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
export { createModuleGraph, exportedConst, localConst, parseConsumer, propertyName };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createEffectApiImportResolver, strictEffectRuntimeTopologyViolation } from "../../strict-effect-runtime.js";
|
|
1
2
|
import { getSourceText, isApiEntryFile, isApiSourceFile, isForbiddenGeneratedEffectPath, isSharedApiContractFile, normalizeFilename, reportMissingProgramPattern, reportProgramPattern } from "../ast.js";
|
|
2
3
|
const createStrictEffectApiBoundariesRule = ()=>({
|
|
3
4
|
meta: {
|
|
@@ -17,15 +18,16 @@ const createStrictEffectApiBoundariesRule = ()=>({
|
|
|
17
18
|
node,
|
|
18
19
|
message: 'Generated UltraModern workspaces use direct api/index.ts, shared/api.ts and src/api/* paths; api/effect, api/lambda, shared/effect and src/effect are forbidden.'
|
|
19
20
|
});
|
|
20
|
-
reportProgramPattern(context, node, source, /@modern-js\/plugin-bff\/hono-server/u, 'UltraModern API workspaces must not import Hono server helpers; use @modern-js/
|
|
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.');
|
|
21
22
|
reportProgramPattern(context, node, source, /\bruntimeFramework\s*(?::|=)\s*['"]hono['"]/u, 'UltraModern API apps must use bff.runtimeFramework: "effect".');
|
|
22
|
-
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.');
|
|
23
24
|
reportProgramPattern(context, node, source, /\bstrictEffectApproach\s*(?::|=)\s*false\b/u, 'UltraModern API apps must keep strictEffectApproach enabled.');
|
|
24
25
|
if (isApiEntryFile(filename)) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
const violation = strictEffectRuntimeTopologyViolation(source, createEffectApiImportResolver(filename));
|
|
27
|
+
if (violation) context.report({
|
|
28
|
+
node,
|
|
29
|
+
message: violation
|
|
30
|
+
});
|
|
29
31
|
}
|
|
30
32
|
if (isSharedApiContractFile(filename)) {
|
|
31
33
|
reportMissingProgramPattern(context, node, source, /\bHttpApi\.make\b/u, 'Generated shared API contracts must declare an HttpApi contract.');
|
|
@@ -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 };
|