@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
|
@@ -42,9 +42,11 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
42
42
|
});
|
|
43
43
|
const external_node_fs_namespaceObject = require("node:fs");
|
|
44
44
|
var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_namespaceObject);
|
|
45
|
+
const external_node_module_namespaceObject = require("node:module");
|
|
45
46
|
const external_node_path_namespaceObject = require("node:path");
|
|
46
47
|
var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
|
|
47
48
|
const types_namespaceObject = require("@babel/types");
|
|
49
|
+
const external_resolve_exports_namespaceObject = require("resolve.exports");
|
|
48
50
|
const external_microvertical_api_owner_cjs_namespaceObject = require("./microvertical-api-owner.cjs");
|
|
49
51
|
const external_source_analysis_cjs_namespaceObject = require("./source-analysis.cjs");
|
|
50
52
|
function parseConsumer(filePath) {
|
|
@@ -125,16 +127,81 @@ const exportedConst = (sourceFile, name)=>{
|
|
|
125
127
|
}))) return localConst(sourceFile, name);
|
|
126
128
|
}
|
|
127
129
|
};
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
const inside = (file, boundary)=>file === boundary || file.startsWith(`${boundary}${external_node_path_default().sep}`);
|
|
131
|
+
const containingWorkspace = (file)=>{
|
|
132
|
+
for(let directory = external_node_path_default().dirname(file);; directory = external_node_path_default().dirname(directory)){
|
|
133
|
+
if (external_node_fs_default().existsSync(external_node_path_default().join(directory, 'pnpm-workspace.yaml'))) return external_node_fs_default().realpathSync(directory);
|
|
134
|
+
if (directory === external_node_path_default().dirname(directory)) return;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
const sourceBoundary = (file, workspace)=>{
|
|
138
|
+
for(let directory = external_node_path_default().dirname(file);; directory = external_node_path_default().dirname(directory)){
|
|
139
|
+
if (external_node_fs_default().existsSync(external_node_path_default().join(directory, 'package.json')) && directory !== workspace) return external_node_fs_default().realpathSync(directory);
|
|
140
|
+
if (directory === workspace || directory === external_node_path_default().dirname(directory)) return external_node_fs_default().realpathSync(external_node_path_default().dirname(file));
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
const sourceFileAt = (target, boundary)=>{
|
|
144
|
+
const stem = target.replace(/\.(?:[cm]?[jt]sx?)$/u, '');
|
|
132
145
|
for (const candidate of [
|
|
133
146
|
`${stem}.ts`,
|
|
134
147
|
`${stem}.tsx`,
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
148
|
+
`${stem}.mts`,
|
|
149
|
+
`${stem}.cts`,
|
|
150
|
+
target,
|
|
151
|
+
external_node_path_default().join(target, 'index.ts'),
|
|
152
|
+
external_node_path_default().join(target, 'index.tsx')
|
|
153
|
+
])try {
|
|
154
|
+
const real = external_node_fs_default().realpathSync(candidate);
|
|
155
|
+
if (inside(real, boundary) && external_node_fs_default().statSync(real).isFile()) return real;
|
|
156
|
+
} catch {}
|
|
157
|
+
};
|
|
158
|
+
const packageEntry = (specifier)=>{
|
|
159
|
+
if (specifier.startsWith('.') || specifier.startsWith('/') || specifier.startsWith('#')) return;
|
|
160
|
+
const parts = specifier.split('/');
|
|
161
|
+
const count = specifier.startsWith('@') ? 2 : 1;
|
|
162
|
+
if (parts.length < count || parts.slice(0, count).some((part)=>!part)) return;
|
|
163
|
+
return {
|
|
164
|
+
name: parts.slice(0, count).join('/'),
|
|
165
|
+
exportKey: parts.length === count ? '.' : `./${parts.slice(count).join('/')}`
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
const resolveModulePath = (module, specifier, workspace)=>{
|
|
169
|
+
if (/^\.\.?\//u.test(specifier)) {
|
|
170
|
+
const file = sourceFileAt(external_node_path_default().resolve(external_node_path_default().dirname(module.path), specifier), module.boundary);
|
|
171
|
+
return void 0 === file ? void 0 : {
|
|
172
|
+
path: file,
|
|
173
|
+
boundary: module.boundary
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
const entry = packageEntry(specifier);
|
|
177
|
+
if (!entry || !workspace) return;
|
|
178
|
+
const paths = (0, external_node_module_namespaceObject.createRequire)(module.path).resolve.paths(entry.name) ?? [];
|
|
179
|
+
for (const lookup of paths){
|
|
180
|
+
const directory = external_node_path_default().join(lookup, entry.name);
|
|
181
|
+
const manifest = external_node_path_default().join(directory, 'package.json');
|
|
182
|
+
if (!external_node_fs_default().existsSync(manifest)) continue;
|
|
183
|
+
const boundary = external_node_fs_default().realpathSync(directory);
|
|
184
|
+
if (!inside(boundary, workspace) || boundary.split(external_node_path_default().sep).includes('node_modules')) break;
|
|
185
|
+
const pkg = JSON.parse(external_node_fs_default().readFileSync(manifest, 'utf8'));
|
|
186
|
+
if (pkg.name !== entry.name || void 0 === pkg.exports) break;
|
|
187
|
+
let target;
|
|
188
|
+
try {
|
|
189
|
+
const targets = (0, external_resolve_exports_namespaceObject.exports)(pkg, entry.exportKey, {
|
|
190
|
+
conditions: [
|
|
191
|
+
'modern:source'
|
|
192
|
+
]
|
|
193
|
+
});
|
|
194
|
+
target = Array.isArray(targets) ? targets[0] : void 0;
|
|
195
|
+
} catch {
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
if (!target || !target.startsWith('./')) break;
|
|
199
|
+
const file = sourceFileAt(external_node_path_default().resolve(boundary, target), boundary);
|
|
200
|
+
return void 0 === file ? void 0 : {
|
|
201
|
+
path: file,
|
|
202
|
+
boundary
|
|
203
|
+
};
|
|
204
|
+
}
|
|
138
205
|
};
|
|
139
206
|
const createModuleReader = (root)=>{
|
|
140
207
|
const modules = new Map([
|
|
@@ -143,14 +210,15 @@ const createModuleReader = (root)=>{
|
|
|
143
210
|
root
|
|
144
211
|
]
|
|
145
212
|
]);
|
|
146
|
-
return (filePath)=>{
|
|
213
|
+
return (filePath, boundary)=>{
|
|
147
214
|
if (modules.has(filePath)) return modules.get(filePath);
|
|
148
215
|
if (modules.size >= 256) return;
|
|
149
216
|
let module;
|
|
150
217
|
try {
|
|
151
218
|
module = {
|
|
152
219
|
file: parseConsumer(filePath),
|
|
153
|
-
path: filePath
|
|
220
|
+
path: filePath,
|
|
221
|
+
boundary
|
|
154
222
|
};
|
|
155
223
|
} catch {
|
|
156
224
|
module = void 0;
|
|
@@ -180,7 +248,7 @@ const reexportedBinding = (module, name)=>{
|
|
|
180
248
|
if (!types_namespaceObject.isExportNamedDeclaration(statement) || 'type' === statement.exportKind) continue;
|
|
181
249
|
const specifier = statement.specifiers.find((value)=>types_namespaceObject.isExportSpecifier(value) && 'type' !== value.exportKind && propertyName(value.exported) === name);
|
|
182
250
|
if (void 0 !== specifier && types_namespaceObject.isExportSpecifier(specifier)) return {
|
|
183
|
-
local: specifier.local.name,
|
|
251
|
+
local: types_namespaceObject.isIdentifier(specifier.local) ? specifier.local.name : specifier.local.value,
|
|
184
252
|
...statement.source ? {
|
|
185
253
|
specifier: statement.source.value
|
|
186
254
|
} : {}
|
|
@@ -195,7 +263,7 @@ const defaultExport = (module)=>{
|
|
|
195
263
|
return types_namespaceObject.isExpression(declaration) ? declaration : void 0;
|
|
196
264
|
}
|
|
197
265
|
};
|
|
198
|
-
const resolveBinding = (module, name, scope, readModule, seen)=>{
|
|
266
|
+
const resolveBinding = (module, name, scope, readModule, workspace, seen)=>{
|
|
199
267
|
const key = `${scope}:${module.path}#${name}`;
|
|
200
268
|
if (seen.has(key) || seen.size >= 512) return;
|
|
201
269
|
seen.add(key);
|
|
@@ -205,9 +273,9 @@ const resolveBinding = (module, name, scope, readModule, seen)=>{
|
|
|
205
273
|
module
|
|
206
274
|
};
|
|
207
275
|
const throughModule = (specifier, imported)=>{
|
|
208
|
-
const
|
|
209
|
-
const target =
|
|
210
|
-
return void 0 === target ? void 0 : resolveBinding(target, imported, 'export', readModule, seen);
|
|
276
|
+
const resolved = resolveModulePath(module, specifier, workspace);
|
|
277
|
+
const target = resolved && readModule(resolved.path, resolved.boundary);
|
|
278
|
+
return void 0 === target ? void 0 : resolveBinding(target, imported, 'export', readModule, workspace, seen);
|
|
211
279
|
};
|
|
212
280
|
if ('local' === scope) {
|
|
213
281
|
const imported = importedBinding(module, name);
|
|
@@ -215,14 +283,14 @@ const resolveBinding = (module, name, scope, readModule, seen)=>{
|
|
|
215
283
|
}
|
|
216
284
|
if ('default' === name) {
|
|
217
285
|
const exported = defaultExport(module);
|
|
218
|
-
if ('string' == typeof exported) return resolveBinding(module, exported, 'local', readModule, seen);
|
|
286
|
+
if ('string' == typeof exported) return resolveBinding(module, exported, 'local', readModule, workspace, seen);
|
|
219
287
|
return void 0 === exported ? void 0 : {
|
|
220
288
|
expression: exported,
|
|
221
289
|
module
|
|
222
290
|
};
|
|
223
291
|
}
|
|
224
292
|
const reexported = reexportedBinding(module, name);
|
|
225
|
-
if (void 0 !== reexported) return void 0 === reexported.specifier ? resolveBinding(module, reexported.local, 'local', readModule, seen) : throughModule(reexported.specifier, reexported.local);
|
|
293
|
+
if (void 0 !== reexported) return void 0 === reexported.specifier ? resolveBinding(module, reexported.local, 'local', readModule, workspace, seen) : throughModule(reexported.specifier, reexported.local);
|
|
226
294
|
for (const statement of module.file.program.body){
|
|
227
295
|
if (!types_namespaceObject.isExportAllDeclaration(statement) || 'type' === statement.exportKind) continue;
|
|
228
296
|
const resolved = throughModule(statement.source.value, name);
|
|
@@ -376,6 +444,7 @@ const nonEndpointCombinators = {
|
|
|
376
444
|
};
|
|
377
445
|
const reachableEndpoints = (rootModule, declaration)=>{
|
|
378
446
|
const readModule = createModuleReader(rootModule);
|
|
447
|
+
const workspace = containingWorkspace(rootModule.path);
|
|
379
448
|
const active = new Set();
|
|
380
449
|
const endpoints = [];
|
|
381
450
|
const identities = new Set();
|
|
@@ -397,7 +466,7 @@ const reachableEndpoints = (rootModule, declaration)=>{
|
|
|
397
466
|
active.add(node);
|
|
398
467
|
try {
|
|
399
468
|
if (types_namespaceObject.isIdentifier(node)) {
|
|
400
|
-
const resolved = resolveBinding(module, node.name, 'local', readModule, new Set());
|
|
469
|
+
const resolved = resolveBinding(module, node.name, 'local', readModule, workspace, new Set());
|
|
401
470
|
return void 0 !== resolved && visit(resolved.expression, resolved.module, kind, group);
|
|
402
471
|
}
|
|
403
472
|
if ('endpoint' === kind) {
|
|
@@ -614,11 +683,13 @@ const configuredMicroVerticalApiStem = (verticalPath, verticals)=>{
|
|
|
614
683
|
};
|
|
615
684
|
const microVerticalApiBaselineViolation = (stem, filePath, expectation)=>{
|
|
616
685
|
try {
|
|
617
|
-
const
|
|
686
|
+
const realPath = external_node_fs_default().realpathSync(filePath);
|
|
687
|
+
const sourceFile = parseConsumer(realPath);
|
|
618
688
|
if (!(0, external_microvertical_api_owner_cjs_namespaceObject.baselinePublicIdentityIsExact)(filePath, expectation)) return 'MicroVertical baseline imports must resolve the exact framework owner public export and schema identity';
|
|
619
689
|
return validateParsedContract({
|
|
620
690
|
file: sourceFile,
|
|
621
|
-
path:
|
|
691
|
+
path: realPath,
|
|
692
|
+
boundary: sourceBoundary(realPath, containingWorkspace(realPath))
|
|
622
693
|
}, stem, expectation);
|
|
623
694
|
} catch (error) {
|
|
624
695
|
if (error instanceof external_source_analysis_cjs_namespaceObject.SourceSyntaxError) return `MicroVertical root contract must be valid TypeScript syntax (${error.message})`;
|
|
@@ -66,13 +66,42 @@ const normalize = (file)=>file.split(external_node_path_default().sep).join('/')
|
|
|
66
66
|
const errorMessage = (error)=>error instanceof Error ? error.message : String(error);
|
|
67
67
|
const apiApp = (app)=>'vertical' === app.kind && 'ui-only' !== app.surfaceProfile && 'horizontal-remote' !== app.deliveryUnitKind;
|
|
68
68
|
function configuredApps(root, supplied) {
|
|
69
|
-
const
|
|
70
|
-
if (!Array.isArray(
|
|
69
|
+
const topology = supplied ? void 0 : JSON.parse(external_node_fs_default().readFileSync(external_node_path_default().join(root, 'topology/reference-topology.json'), 'utf8'));
|
|
70
|
+
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');
|
|
71
|
+
const entries = topology?.shell ? [
|
|
72
|
+
topology.shell,
|
|
73
|
+
...topology.shells ?? [],
|
|
74
|
+
...topology.verticals ?? []
|
|
75
|
+
] : void 0;
|
|
76
|
+
const source = supplied ?? entries?.map((entry)=>{
|
|
77
|
+
if (!entry || 'string' != typeof entry.path || !entry.path || external_node_path_default().isAbsolute(entry.path) || entry.path.split(/[\\/]/u).includes('..')) throw new Error('topology/reference-topology.json: each app must have an explicit workspace path');
|
|
78
|
+
const packageFile = external_node_path_default().join(root, entry.path, 'package.json');
|
|
79
|
+
const manifest = JSON.parse(external_node_fs_default().readFileSync(packageFile, 'utf8'));
|
|
80
|
+
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`);
|
|
81
|
+
const api = entry.api;
|
|
82
|
+
const stem = api?.stem ?? api?.readiness?.endpoint?.match(/^\/([a-z0-9]+(?:-[a-z0-9]+)*)\/readiness$/u)?.[1];
|
|
83
|
+
return {
|
|
84
|
+
id: entry.id,
|
|
85
|
+
kind: entry.kind,
|
|
86
|
+
path: entry.path,
|
|
87
|
+
package: manifest.name,
|
|
88
|
+
surfaceProfile: entry.surfaceProfile,
|
|
89
|
+
deliveryUnitKind: entry.deliveryUnitKind,
|
|
90
|
+
...void 0 === api ? {} : {
|
|
91
|
+
api: {
|
|
92
|
+
stem,
|
|
93
|
+
prefix: api.bff?.prefix,
|
|
94
|
+
protocol: api.protocol
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
if (!Array.isArray(source)) throw new Error('topology/reference-topology.json: shell and verticals must describe apps');
|
|
71
100
|
const paths = new Set();
|
|
72
101
|
for (const app of source){
|
|
73
|
-
if (!app || 'object' != typeof app || 'string' != typeof app.path || !app.path || external_node_path_default().isAbsolute(app.path) || app.path.split(/[\\/]/u).includes('..')) throw new Error('
|
|
102
|
+
if (!app || 'object' != typeof app || 'string' != typeof app.path || !app.path || external_node_path_default().isAbsolute(app.path) || app.path.split(/[\\/]/u).includes('..')) throw new Error('topology/reference-topology.json: each app must have a relative workspace path');
|
|
74
103
|
const key = normalize(app.path);
|
|
75
|
-
if (paths.has(key)) throw new Error(
|
|
104
|
+
if (paths.has(key)) throw new Error(`topology/reference-topology.json: duplicate app path ${key}`);
|
|
76
105
|
paths.add(key);
|
|
77
106
|
for (const name of [
|
|
78
107
|
'id',
|
|
@@ -81,7 +110,7 @@ function configuredApps(root, supplied) {
|
|
|
81
110
|
'surfaceProfile',
|
|
82
111
|
'deliveryUnitKind'
|
|
83
112
|
])if (void 0 !== app[name] && 'string' != typeof app[name]) throw new Error(`${key}: ${name} must be a string`);
|
|
84
|
-
|
|
113
|
+
const allowedFields = {
|
|
85
114
|
kind: [
|
|
86
115
|
'shell',
|
|
87
116
|
'vertical'
|
|
@@ -95,7 +124,11 @@ function configuredApps(root, supplied) {
|
|
|
95
124
|
'microvertical',
|
|
96
125
|
'horizontal-remote'
|
|
97
126
|
]
|
|
98
|
-
}
|
|
127
|
+
};
|
|
128
|
+
for (const field of Object.keys(allowedFields)){
|
|
129
|
+
const value = app[field];
|
|
130
|
+
if (void 0 !== value && !allowedFields[field].includes(value)) throw new Error(`${key}: invalid ${field}`);
|
|
131
|
+
}
|
|
99
132
|
if (void 0 !== app.api) {
|
|
100
133
|
if (!app.api || 'object' != typeof app.api || Array.isArray(app.api)) throw new Error(`${key}: api must be an object`);
|
|
101
134
|
if (void 0 !== app.api.protocol && ![
|
|
@@ -183,12 +216,27 @@ function check(options, topology) {
|
|
|
183
216
|
`${base}/index.tsx`
|
|
184
217
|
].find((candidate)=>exists(candidate));
|
|
185
218
|
};
|
|
186
|
-
const
|
|
219
|
+
const clientExportFile = (appPath, exported)=>{
|
|
220
|
+
if ('string' != typeof exported || !exported.startsWith('./') || exported.split(/[\\/]/u).includes('..') || !/\.[cm]?[jt]sx?$/u.test(exported)) return;
|
|
221
|
+
const appDirectory = absolute(appPath);
|
|
222
|
+
const candidate = external_node_path_default().resolve(appDirectory, exported);
|
|
223
|
+
try {
|
|
224
|
+
const workspace = external_node_fs_default().realpathSync(root);
|
|
225
|
+
const owner = external_node_fs_default().realpathSync(appDirectory);
|
|
226
|
+
const real = external_node_fs_default().realpathSync(candidate);
|
|
227
|
+
if (!owner.startsWith(`${workspace}${external_node_path_default().sep}`) || !real.startsWith(`${owner}${external_node_path_default().sep}`) || !external_node_fs_default().statSync(real).isFile()) return;
|
|
228
|
+
} catch {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
return normalize(external_node_path_default().relative(root, candidate));
|
|
232
|
+
};
|
|
233
|
+
const composedModules = (file, appPath)=>{
|
|
187
234
|
const order = [];
|
|
188
235
|
const seen = new Set();
|
|
189
236
|
const queue = [
|
|
190
237
|
file
|
|
191
238
|
];
|
|
239
|
+
const owner = external_node_fs_default().realpathSync(absolute(appPath));
|
|
192
240
|
while(queue.length > 0 && order.length < 128){
|
|
193
241
|
const current = queue.shift();
|
|
194
242
|
if (seen.has(current)) continue;
|
|
@@ -199,16 +247,19 @@ function check(options, topology) {
|
|
|
199
247
|
for (const statement of ast.program.body){
|
|
200
248
|
const source = (types_namespaceObject.isImportDeclaration(statement) || types_namespaceObject.isExportNamedDeclaration(statement) || types_namespaceObject.isExportAllDeclaration(statement)) && statement.source ? statement.source.value : void 0;
|
|
201
249
|
const resolved = void 0 === source ? void 0 : resolveRelative(current, source);
|
|
202
|
-
if (void 0 !== resolved && !seen.has(resolved))
|
|
250
|
+
if (void 0 !== resolved && !seen.has(resolved)) {
|
|
251
|
+
const real = external_node_fs_default().realpathSync(absolute(resolved));
|
|
252
|
+
if (real.startsWith(`${owner}${external_node_path_default().sep}`)) queue.push(resolved);
|
|
253
|
+
}
|
|
203
254
|
}
|
|
204
255
|
}
|
|
205
256
|
return order;
|
|
206
257
|
};
|
|
207
|
-
const moduleShape = (file, imports, calls, patterns = [], defaultExport,
|
|
258
|
+
const moduleShape = (file, imports, calls, patterns = [], defaultExport, composedAppPath)=>{
|
|
208
259
|
if (!exists(file)) return;
|
|
209
260
|
const ast = parseFile(file);
|
|
210
261
|
if (!ast) return;
|
|
211
|
-
const shapeFiles =
|
|
262
|
+
const shapeFiles = composedAppPath ? composedModules(file, composedAppPath) : [
|
|
212
263
|
file
|
|
213
264
|
];
|
|
214
265
|
const body = shapeFiles.flatMap((shapeFile)=>parseFile(shapeFile)?.program.body ?? []);
|
|
@@ -318,19 +369,25 @@ function check(options, topology) {
|
|
|
318
369
|
}
|
|
319
370
|
return;
|
|
320
371
|
}
|
|
321
|
-
if (app?.kind === 'vertical') assert(void 0 !== app.api, `${appPath}: vertical must declare its Effect API in
|
|
372
|
+
if (app?.kind === 'vertical') assert(void 0 !== app.api, `${appPath}: vertical must declare its Effect API in topology/reference-topology.json`);
|
|
322
373
|
const stem = app?.api?.stem ?? external_node_path_default().posix.basename(appPath);
|
|
323
374
|
const rpc = app?.api?.protocol === 'rpc';
|
|
324
375
|
const contract = `${appPath}/shared/${rpc ? 'rpc' : 'api'}.ts`;
|
|
325
|
-
const client = `${appPath}/src/api/${stem}-${rpc ? 'rpc-client' : 'client'}.ts`;
|
|
326
376
|
const entry = `${appPath}/api/index.ts`;
|
|
327
377
|
for (const file of [
|
|
328
378
|
entry,
|
|
329
|
-
contract
|
|
330
|
-
client
|
|
379
|
+
contract
|
|
331
380
|
])assert(exists(file), `${file}: required API surface is missing`);
|
|
381
|
+
const packageFile = `${appPath}/package.json`;
|
|
382
|
+
assert(exists(packageFile), `${packageFile}: required API package is missing`);
|
|
383
|
+
if (!exists(packageFile)) return;
|
|
384
|
+
const manifest = JSON.parse(read(packageFile));
|
|
385
|
+
assert(manifest.exports?.['./api'] === `./shared/${rpc ? 'rpc' : 'api'}.ts`, `${packageFile}: invalid ./api export`);
|
|
386
|
+
const clientKey = `./api/${rpc ? 'rpc-client' : 'client'}`;
|
|
387
|
+
const client = clientExportFile(appPath, manifest.exports?.[clientKey]);
|
|
388
|
+
assert(void 0 !== client, `${packageFile}: invalid API client export`);
|
|
389
|
+
assert(manifest.exports?.[`./api/${rpc ? 'client' : 'rpc-client'}`] === void 0, `${packageFile}: forbidden opposite protocol client export`);
|
|
332
390
|
noPath(`${appPath}/shared/${rpc ? 'api' : 'rpc'}.ts`, `must not emit a ${rpc ? 'REST' : 'RPC'} contract`);
|
|
333
|
-
noPath(`${appPath}/src/api/${stem}-${rpc ? 'client' : 'rpc-client'}.ts`, `must not emit a ${rpc ? 'REST' : 'RPC'} client`);
|
|
334
391
|
if (rpc) {
|
|
335
392
|
moduleShape(entry, [
|
|
336
393
|
[
|
|
@@ -396,21 +453,24 @@ function check(options, topology) {
|
|
|
396
453
|
const violation = (0, external_microvertical_api_baseline_cjs_namespaceObject.microVerticalApiBaselineViolation)(stem, absolute(contract), expectation);
|
|
397
454
|
if (violation) diagnostics.push(`${contract}: ${violation}`);
|
|
398
455
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
456
|
+
if (client) {
|
|
457
|
+
const relativeContract = external_node_path_default().posix.relative(external_node_path_default().posix.dirname(client), contract);
|
|
458
|
+
moduleShape(client, [
|
|
459
|
+
[
|
|
460
|
+
effect,
|
|
461
|
+
[
|
|
462
|
+
'Effect',
|
|
463
|
+
rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
|
|
464
|
+
]
|
|
465
|
+
],
|
|
402
466
|
[
|
|
403
|
-
'
|
|
404
|
-
|
|
467
|
+
relativeContract.startsWith('.') ? relativeContract : `./${relativeContract}`,
|
|
468
|
+
[]
|
|
405
469
|
]
|
|
406
|
-
],
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
]
|
|
411
|
-
], [
|
|
412
|
-
rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
|
|
413
|
-
], [], void 0, true);
|
|
470
|
+
], [
|
|
471
|
+
rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
|
|
472
|
+
], [], void 0, appPath);
|
|
473
|
+
}
|
|
414
474
|
moduleShape(`${appPath}/api/effect-api.ts`, [], [], [
|
|
415
475
|
[
|
|
416
476
|
/\bbackendFederationContract\b/u,
|
|
@@ -451,13 +511,6 @@ function check(options, topology) {
|
|
|
451
511
|
'must enable strictEffectApproach'
|
|
452
512
|
]
|
|
453
513
|
]);
|
|
454
|
-
const packageFile = `${appPath}/package.json`;
|
|
455
|
-
if (exists(packageFile)) {
|
|
456
|
-
const manifest = JSON.parse(read(packageFile));
|
|
457
|
-
assert(manifest.exports?.['./api'] === `./shared/${rpc ? 'rpc' : 'api'}.ts`, `${packageFile}: invalid ./api export`);
|
|
458
|
-
assert(manifest.exports?.[`./api/${rpc ? 'rpc-client' : 'client'}`] === `./src/api/${stem}-${rpc ? 'rpc-client' : 'client'}.ts`, `${packageFile}: invalid API client export`);
|
|
459
|
-
assert(manifest.exports?.[`./api/${rpc ? 'client' : 'rpc-client'}`] === void 0, `${packageFile}: forbidden opposite protocol client export`);
|
|
460
|
-
}
|
|
461
514
|
});
|
|
462
515
|
const shell = 'apps/shell-super-app';
|
|
463
516
|
const verticals = apps.filter((app)=>app.path.startsWith('verticals/') && apiApp(app));
|
|
@@ -52,7 +52,7 @@ const createStrictEffectApiBoundariesRule = ()=>({
|
|
|
52
52
|
});
|
|
53
53
|
(0, external_ast_cjs_namespaceObject.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.');
|
|
54
54
|
(0, external_ast_cjs_namespaceObject.reportProgramPattern)(context, node, source, /\bruntimeFramework\s*(?::|=)\s*['"]hono['"]/u, 'UltraModern API apps must use bff.runtimeFramework: "effect".');
|
|
55
|
-
(0, external_ast_cjs_namespaceObject.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.');
|
|
55
|
+
(0, external_ast_cjs_namespaceObject.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.');
|
|
56
56
|
(0, external_ast_cjs_namespaceObject.reportProgramPattern)(context, node, source, /\bstrictEffectApproach\s*(?::|=)\s*false\b/u, 'UltraModern API apps must keep strictEffectApproach enabled.');
|
|
57
57
|
if ((0, external_ast_cjs_namespaceObject.isApiEntryFile)(filename)) {
|
|
58
58
|
const violation = (0, external_strict_effect_runtime_cjs_namespaceObject.strictEffectRuntimeTopologyViolation)(source, (0, external_strict_effect_runtime_cjs_namespaceObject.createEffectApiImportResolver)(filename));
|
|
@@ -48,8 +48,9 @@ const types_namespaceObject = require("@babel/types");
|
|
|
48
48
|
const external_source_analysis_cjs_namespaceObject = require("./source-analysis.cjs");
|
|
49
49
|
const edge = '@modern-js/bff-effect/effect-edge';
|
|
50
50
|
const nodeRuntime = '@modern-js/bff-effect/effect';
|
|
51
|
-
const
|
|
52
|
-
const failure = 'Generated API entries must export defineEffectBff(...) or the
|
|
51
|
+
const assembly = '@modern-js/bff-effect/assembly';
|
|
52
|
+
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.';
|
|
53
|
+
const MAX_API_SOURCE_MODULES = 256;
|
|
53
54
|
function createEffectApiImportResolver(filename) {
|
|
54
55
|
const directory = external_node_fs_default().realpathSync(external_node_path_default().dirname(filename));
|
|
55
56
|
const owner = external_node_fs_default().realpathSync(external_node_path_default().resolve(directory, '..'));
|
|
@@ -81,7 +82,7 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
|
|
|
81
82
|
const load = (input)=>{
|
|
82
83
|
const existing = modules.get(input.id);
|
|
83
84
|
if (existing) return existing;
|
|
84
|
-
if (modules.size >=
|
|
85
|
+
if (modules.size >= MAX_API_SOURCE_MODULES || input.source.length > 1000000) throw new external_source_analysis_cjs_namespaceObject.SourceSyntaxError('API source budget exceeded');
|
|
85
86
|
const file = (0, external_source_analysis_cjs_namespaceObject.parseSource)(input.source, input.id, 'Invalid API syntax');
|
|
86
87
|
const module = {
|
|
87
88
|
...input,
|
|
@@ -245,7 +246,7 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
|
|
|
245
246
|
if (!types_namespaceObject.isBlockStatement(body)) return body;
|
|
246
247
|
const statements = body.body;
|
|
247
248
|
const last = statements.at(-1);
|
|
248
|
-
return statements.slice(0, -1).every((statement)=>types_namespaceObject.isVariableDeclaration(statement)) && types_namespaceObject.isReturnStatement(last) ? last.argument : void 0;
|
|
249
|
+
return statements.slice(0, -1).every((statement)=>types_namespaceObject.isVariableDeclaration(statement) || types_namespaceObject.isTSTypeAliasDeclaration(statement) || types_namespaceObject.isTSInterfaceDeclaration(statement)) && types_namespaceObject.isReturnStatement(last) ? last.argument : void 0;
|
|
249
250
|
};
|
|
250
251
|
const pipe = (node, check)=>types_namespaceObject.isMemberExpression(node.callee) && !node.callee.computed && types_namespaceObject.isIdentifier(node.callee.property, {
|
|
251
252
|
name: 'pipe'
|
|
@@ -334,7 +335,7 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
|
|
|
334
335
|
if (value?.name === 'defineEffectBff' && [
|
|
335
336
|
edge,
|
|
336
337
|
nodeRuntime
|
|
337
|
-
].includes(value.specifier) || value?.name === 'assembleEffectBffRuntime' &&
|
|
338
|
+
].includes(value.specifier) || value?.name === 'assembleEffectBffRuntime' && value.specifier === assembly) {
|
|
338
339
|
if (1 !== node.arguments.length) return false;
|
|
339
340
|
const values = properties(node.arguments[0]);
|
|
340
341
|
if (!values) return false;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import node_fs from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
2
3
|
import node_path from "node:path";
|
|
4
|
+
import { exports as external_resolve_exports_exports } from "resolve.exports";
|
|
3
5
|
import { baselinePublicIdentityIsExact } from "./microvertical-api-owner.js";
|
|
4
6
|
import { SourceSyntaxError, parseSource, traverseSource, unwrapExpression } from "./source-analysis.js";
|
|
5
7
|
import * as __rspack_external__babel_types_6e8337dd from "@babel/types";
|
|
@@ -81,16 +83,81 @@ const exportedConst = (sourceFile, name)=>{
|
|
|
81
83
|
}))) return localConst(sourceFile, name);
|
|
82
84
|
}
|
|
83
85
|
};
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
const inside = (file, boundary)=>file === boundary || file.startsWith(`${boundary}${node_path.sep}`);
|
|
87
|
+
const containingWorkspace = (file)=>{
|
|
88
|
+
for(let directory = node_path.dirname(file);; directory = node_path.dirname(directory)){
|
|
89
|
+
if (node_fs.existsSync(node_path.join(directory, 'pnpm-workspace.yaml'))) return node_fs.realpathSync(directory);
|
|
90
|
+
if (directory === node_path.dirname(directory)) return;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
const sourceBoundary = (file, workspace)=>{
|
|
94
|
+
for(let directory = node_path.dirname(file);; directory = node_path.dirname(directory)){
|
|
95
|
+
if (node_fs.existsSync(node_path.join(directory, 'package.json')) && directory !== workspace) return node_fs.realpathSync(directory);
|
|
96
|
+
if (directory === workspace || directory === node_path.dirname(directory)) return node_fs.realpathSync(node_path.dirname(file));
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
const sourceFileAt = (target, boundary)=>{
|
|
100
|
+
const stem = target.replace(/\.(?:[cm]?[jt]sx?)$/u, '');
|
|
88
101
|
for (const candidate of [
|
|
89
102
|
`${stem}.ts`,
|
|
90
103
|
`${stem}.tsx`,
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
104
|
+
`${stem}.mts`,
|
|
105
|
+
`${stem}.cts`,
|
|
106
|
+
target,
|
|
107
|
+
node_path.join(target, 'index.ts'),
|
|
108
|
+
node_path.join(target, 'index.tsx')
|
|
109
|
+
])try {
|
|
110
|
+
const real = node_fs.realpathSync(candidate);
|
|
111
|
+
if (inside(real, boundary) && node_fs.statSync(real).isFile()) return real;
|
|
112
|
+
} catch {}
|
|
113
|
+
};
|
|
114
|
+
const packageEntry = (specifier)=>{
|
|
115
|
+
if (specifier.startsWith('.') || specifier.startsWith('/') || specifier.startsWith('#')) return;
|
|
116
|
+
const parts = specifier.split('/');
|
|
117
|
+
const count = specifier.startsWith('@') ? 2 : 1;
|
|
118
|
+
if (parts.length < count || parts.slice(0, count).some((part)=>!part)) return;
|
|
119
|
+
return {
|
|
120
|
+
name: parts.slice(0, count).join('/'),
|
|
121
|
+
exportKey: parts.length === count ? '.' : `./${parts.slice(count).join('/')}`
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
const resolveModulePath = (module, specifier, workspace)=>{
|
|
125
|
+
if (/^\.\.?\//u.test(specifier)) {
|
|
126
|
+
const file = sourceFileAt(node_path.resolve(node_path.dirname(module.path), specifier), module.boundary);
|
|
127
|
+
return void 0 === file ? void 0 : {
|
|
128
|
+
path: file,
|
|
129
|
+
boundary: module.boundary
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const entry = packageEntry(specifier);
|
|
133
|
+
if (!entry || !workspace) return;
|
|
134
|
+
const paths = createRequire(module.path).resolve.paths(entry.name) ?? [];
|
|
135
|
+
for (const lookup of paths){
|
|
136
|
+
const directory = node_path.join(lookup, entry.name);
|
|
137
|
+
const manifest = node_path.join(directory, 'package.json');
|
|
138
|
+
if (!node_fs.existsSync(manifest)) continue;
|
|
139
|
+
const boundary = node_fs.realpathSync(directory);
|
|
140
|
+
if (!inside(boundary, workspace) || boundary.split(node_path.sep).includes('node_modules')) break;
|
|
141
|
+
const pkg = JSON.parse(node_fs.readFileSync(manifest, 'utf8'));
|
|
142
|
+
if (pkg.name !== entry.name || void 0 === pkg.exports) break;
|
|
143
|
+
let target;
|
|
144
|
+
try {
|
|
145
|
+
const targets = external_resolve_exports_exports(pkg, entry.exportKey, {
|
|
146
|
+
conditions: [
|
|
147
|
+
'modern:source'
|
|
148
|
+
]
|
|
149
|
+
});
|
|
150
|
+
target = Array.isArray(targets) ? targets[0] : void 0;
|
|
151
|
+
} catch {
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
if (!target || !target.startsWith('./')) break;
|
|
155
|
+
const file = sourceFileAt(node_path.resolve(boundary, target), boundary);
|
|
156
|
+
return void 0 === file ? void 0 : {
|
|
157
|
+
path: file,
|
|
158
|
+
boundary
|
|
159
|
+
};
|
|
160
|
+
}
|
|
94
161
|
};
|
|
95
162
|
const createModuleReader = (root)=>{
|
|
96
163
|
const modules = new Map([
|
|
@@ -99,14 +166,15 @@ const createModuleReader = (root)=>{
|
|
|
99
166
|
root
|
|
100
167
|
]
|
|
101
168
|
]);
|
|
102
|
-
return (filePath)=>{
|
|
169
|
+
return (filePath, boundary)=>{
|
|
103
170
|
if (modules.has(filePath)) return modules.get(filePath);
|
|
104
171
|
if (modules.size >= 256) return;
|
|
105
172
|
let module;
|
|
106
173
|
try {
|
|
107
174
|
module = {
|
|
108
175
|
file: parseConsumer(filePath),
|
|
109
|
-
path: filePath
|
|
176
|
+
path: filePath,
|
|
177
|
+
boundary
|
|
110
178
|
};
|
|
111
179
|
} catch {
|
|
112
180
|
module = void 0;
|
|
@@ -136,7 +204,7 @@ const reexportedBinding = (module, name)=>{
|
|
|
136
204
|
if (!__rspack_external__babel_types_6e8337dd.isExportNamedDeclaration(statement) || 'type' === statement.exportKind) continue;
|
|
137
205
|
const specifier = statement.specifiers.find((value)=>__rspack_external__babel_types_6e8337dd.isExportSpecifier(value) && 'type' !== value.exportKind && propertyName(value.exported) === name);
|
|
138
206
|
if (void 0 !== specifier && __rspack_external__babel_types_6e8337dd.isExportSpecifier(specifier)) return {
|
|
139
|
-
local: specifier.local.name,
|
|
207
|
+
local: __rspack_external__babel_types_6e8337dd.isIdentifier(specifier.local) ? specifier.local.name : specifier.local.value,
|
|
140
208
|
...statement.source ? {
|
|
141
209
|
specifier: statement.source.value
|
|
142
210
|
} : {}
|
|
@@ -151,7 +219,7 @@ const defaultExport = (module)=>{
|
|
|
151
219
|
return __rspack_external__babel_types_6e8337dd.isExpression(declaration) ? declaration : void 0;
|
|
152
220
|
}
|
|
153
221
|
};
|
|
154
|
-
const resolveBinding = (module, name, scope, readModule, seen)=>{
|
|
222
|
+
const resolveBinding = (module, name, scope, readModule, workspace, seen)=>{
|
|
155
223
|
const key = `${scope}:${module.path}#${name}`;
|
|
156
224
|
if (seen.has(key) || seen.size >= 512) return;
|
|
157
225
|
seen.add(key);
|
|
@@ -161,9 +229,9 @@ const resolveBinding = (module, name, scope, readModule, seen)=>{
|
|
|
161
229
|
module
|
|
162
230
|
};
|
|
163
231
|
const throughModule = (specifier, imported)=>{
|
|
164
|
-
const
|
|
165
|
-
const target =
|
|
166
|
-
return void 0 === target ? void 0 : resolveBinding(target, imported, 'export', readModule, seen);
|
|
232
|
+
const resolved = resolveModulePath(module, specifier, workspace);
|
|
233
|
+
const target = resolved && readModule(resolved.path, resolved.boundary);
|
|
234
|
+
return void 0 === target ? void 0 : resolveBinding(target, imported, 'export', readModule, workspace, seen);
|
|
167
235
|
};
|
|
168
236
|
if ('local' === scope) {
|
|
169
237
|
const imported = importedBinding(module, name);
|
|
@@ -171,14 +239,14 @@ const resolveBinding = (module, name, scope, readModule, seen)=>{
|
|
|
171
239
|
}
|
|
172
240
|
if ('default' === name) {
|
|
173
241
|
const exported = defaultExport(module);
|
|
174
|
-
if ('string' == typeof exported) return resolveBinding(module, exported, 'local', readModule, seen);
|
|
242
|
+
if ('string' == typeof exported) return resolveBinding(module, exported, 'local', readModule, workspace, seen);
|
|
175
243
|
return void 0 === exported ? void 0 : {
|
|
176
244
|
expression: exported,
|
|
177
245
|
module
|
|
178
246
|
};
|
|
179
247
|
}
|
|
180
248
|
const reexported = reexportedBinding(module, name);
|
|
181
|
-
if (void 0 !== reexported) return void 0 === reexported.specifier ? resolveBinding(module, reexported.local, 'local', readModule, seen) : throughModule(reexported.specifier, reexported.local);
|
|
249
|
+
if (void 0 !== reexported) return void 0 === reexported.specifier ? resolveBinding(module, reexported.local, 'local', readModule, workspace, seen) : throughModule(reexported.specifier, reexported.local);
|
|
182
250
|
for (const statement of module.file.program.body){
|
|
183
251
|
if (!__rspack_external__babel_types_6e8337dd.isExportAllDeclaration(statement) || 'type' === statement.exportKind) continue;
|
|
184
252
|
const resolved = throughModule(statement.source.value, name);
|
|
@@ -332,6 +400,7 @@ const nonEndpointCombinators = {
|
|
|
332
400
|
};
|
|
333
401
|
const reachableEndpoints = (rootModule, declaration)=>{
|
|
334
402
|
const readModule = createModuleReader(rootModule);
|
|
403
|
+
const workspace = containingWorkspace(rootModule.path);
|
|
335
404
|
const active = new Set();
|
|
336
405
|
const endpoints = [];
|
|
337
406
|
const identities = new Set();
|
|
@@ -353,7 +422,7 @@ const reachableEndpoints = (rootModule, declaration)=>{
|
|
|
353
422
|
active.add(node);
|
|
354
423
|
try {
|
|
355
424
|
if (__rspack_external__babel_types_6e8337dd.isIdentifier(node)) {
|
|
356
|
-
const resolved = resolveBinding(module, node.name, 'local', readModule, new Set());
|
|
425
|
+
const resolved = resolveBinding(module, node.name, 'local', readModule, workspace, new Set());
|
|
357
426
|
return void 0 !== resolved && visit(resolved.expression, resolved.module, kind, group);
|
|
358
427
|
}
|
|
359
428
|
if ('endpoint' === kind) {
|
|
@@ -570,11 +639,13 @@ const configuredMicroVerticalApiStem = (verticalPath, verticals)=>{
|
|
|
570
639
|
};
|
|
571
640
|
const microVerticalApiBaselineViolation = (stem, filePath, expectation)=>{
|
|
572
641
|
try {
|
|
573
|
-
const
|
|
642
|
+
const realPath = node_fs.realpathSync(filePath);
|
|
643
|
+
const sourceFile = parseConsumer(realPath);
|
|
574
644
|
if (!baselinePublicIdentityIsExact(filePath, expectation)) return 'MicroVertical baseline imports must resolve the exact framework owner public export and schema identity';
|
|
575
645
|
return validateParsedContract({
|
|
576
646
|
file: sourceFile,
|
|
577
|
-
path:
|
|
647
|
+
path: realPath,
|
|
648
|
+
boundary: sourceBoundary(realPath, containingWorkspace(realPath))
|
|
578
649
|
}, stem, expectation);
|
|
579
650
|
} catch (error) {
|
|
580
651
|
if (error instanceof SourceSyntaxError) return `MicroVertical root contract must be valid TypeScript syntax (${error.message})`;
|