@bleedingdev/modern-js-code-tools 3.9.0-ultramodern.3 → 3.9.0-ultramodern.5
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 +4 -0
- package/bin/modern-api-check.mjs +4 -0
- package/dist/cjs/cli/microvertical-api-check.cjs +71 -0
- package/dist/cjs/microvertical-api-baseline.cjs +526 -0
- package/dist/cjs/microvertical-api-boundary.cjs +470 -0
- package/dist/cjs/microvertical-api-owner.cjs +217 -0
- package/dist/cjs/oxlint-plugin/rules/strict-effect-api-boundaries.cjs +1 -1
- package/dist/cjs/strict-effect-runtime.cjs +43 -19
- package/dist/esm/cli/microvertical-api-check.js +33 -0
- package/dist/esm/microvertical-api-baseline.js +474 -0
- package/dist/esm/microvertical-api-boundary.js +412 -0
- package/dist/esm/microvertical-api-owner.js +162 -0
- package/dist/esm/oxlint-plugin/rules/strict-effect-api-boundaries.js +1 -1
- package/dist/esm/strict-effect-runtime.js +43 -19
- package/dist/esm-node/cli/microvertical-api-check.js +34 -0
- package/dist/esm-node/microvertical-api-baseline.js +475 -0
- package/dist/esm-node/microvertical-api-boundary.js +413 -0
- package/dist/esm-node/microvertical-api-owner.js +163 -0
- package/dist/esm-node/oxlint-plugin/rules/strict-effect-api-boundaries.js +1 -1
- package/dist/esm-node/strict-effect-runtime.js +43 -19
- package/dist/types/cli/microvertical-api-check.d.ts +1 -0
- package/dist/types/microvertical-api-baseline.d.ts +23 -0
- package/dist/types/microvertical-api-boundary.d.ts +32 -0
- package/dist/types/microvertical-api-owner.d.ts +6 -0
- package/package.json +15 -2
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __webpack_require__ = {};
|
|
3
|
+
(()=>{
|
|
4
|
+
__webpack_require__.n = (module)=>{
|
|
5
|
+
var getter = module && module.__esModule ? ()=>module['default'] : ()=>module;
|
|
6
|
+
__webpack_require__.d(getter, {
|
|
7
|
+
a: getter
|
|
8
|
+
});
|
|
9
|
+
return getter;
|
|
10
|
+
};
|
|
11
|
+
})();
|
|
12
|
+
(()=>{
|
|
13
|
+
__webpack_require__.d = (exports1, getters, values)=>{
|
|
14
|
+
var define = (defs, kind)=>{
|
|
15
|
+
for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
[kind]: defs[key]
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
define(getters, "get");
|
|
21
|
+
define(values, "value");
|
|
22
|
+
};
|
|
23
|
+
})();
|
|
24
|
+
(()=>{
|
|
25
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
26
|
+
})();
|
|
27
|
+
(()=>{
|
|
28
|
+
__webpack_require__.r = (exports1)=>{
|
|
29
|
+
if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
30
|
+
value: 'Module'
|
|
31
|
+
});
|
|
32
|
+
Object.defineProperty(exports1, '__esModule', {
|
|
33
|
+
value: true
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
})();
|
|
37
|
+
var __webpack_exports__ = {};
|
|
38
|
+
__webpack_require__.r(__webpack_exports__);
|
|
39
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
40
|
+
baselinePackage: ()=>baselinePackage,
|
|
41
|
+
baselinePublicIdentityIsExact: ()=>baselinePublicIdentityIsExact,
|
|
42
|
+
resolveBaselinePackageDirectory: ()=>resolveBaselinePackageDirectory
|
|
43
|
+
});
|
|
44
|
+
const external_node_fs_namespaceObject = require("node:fs");
|
|
45
|
+
var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_namespaceObject);
|
|
46
|
+
const external_node_module_namespaceObject = require("node:module");
|
|
47
|
+
const external_node_path_namespaceObject = require("node:path");
|
|
48
|
+
var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
|
|
49
|
+
const parser_namespaceObject = require("@babel/parser");
|
|
50
|
+
const types_namespaceObject = require("@babel/types");
|
|
51
|
+
const baselinePackage = '@modern-js/bff-effect/microvertical-api';
|
|
52
|
+
const ownerName = '@modern-js/bff-effect';
|
|
53
|
+
const within = (root, file)=>file.startsWith(`${root}${external_node_path_default().sep}`);
|
|
54
|
+
function resolveBaselinePackageDirectory(fromFile) {
|
|
55
|
+
const lookup = (0, external_node_module_namespaceObject.createRequire)(fromFile).resolve.paths(baselinePackage);
|
|
56
|
+
for (const directory of lookup ?? []){
|
|
57
|
+
const candidate = external_node_path_default().join(directory, ownerName);
|
|
58
|
+
if (!external_node_fs_default().existsSync(candidate)) continue;
|
|
59
|
+
const root = external_node_fs_default().realpathSync(candidate);
|
|
60
|
+
const manifest = JSON.parse(external_node_fs_default().readFileSync(external_node_path_default().join(root, 'package.json'), 'utf8'));
|
|
61
|
+
if (![
|
|
62
|
+
ownerName,
|
|
63
|
+
'@bleedingdev/modern-js-bff-effect'
|
|
64
|
+
].includes(manifest.name)) throw new Error(`${fromFile}: ${baselinePackage} resolved to unrelated package ${String(manifest.name)}`);
|
|
65
|
+
const target = importTarget(manifest.exports?.['./microvertical-api']);
|
|
66
|
+
if (!target?.startsWith('./')) throw new Error(`${fromFile}: ${baselinePackage} has no public Node import export`);
|
|
67
|
+
external_node_fs_default().statSync(external_node_path_default().resolve(root, target));
|
|
68
|
+
return root;
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`${fromFile}: cannot resolve installed ${baselinePackage}`);
|
|
71
|
+
}
|
|
72
|
+
function importTarget(value) {
|
|
73
|
+
if ('string' == typeof value) return value;
|
|
74
|
+
if (!value || 'object' != typeof value || Array.isArray(value)) return;
|
|
75
|
+
for (const [condition, target] of Object.entries(value))if ([
|
|
76
|
+
'node',
|
|
77
|
+
'import',
|
|
78
|
+
'default'
|
|
79
|
+
].includes(condition)) {
|
|
80
|
+
const resolved = importTarget(target);
|
|
81
|
+
if (resolved) return resolved;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function baselinePublicIdentityIsExact(fromFile, expectation) {
|
|
85
|
+
const owner = external_node_fs_default().realpathSync(expectation.baselinePackageDirectory);
|
|
86
|
+
const manifest = JSON.parse(external_node_fs_default().readFileSync(external_node_path_default().join(owner, 'package.json'), 'utf8'));
|
|
87
|
+
if (expectation.baselinePackage !== baselinePackage || ![
|
|
88
|
+
ownerName,
|
|
89
|
+
'@bleedingdev/modern-js-bff-effect'
|
|
90
|
+
].includes(manifest.name)) return false;
|
|
91
|
+
let resolvedOwner;
|
|
92
|
+
try {
|
|
93
|
+
resolvedOwner = resolveBaselinePackageDirectory(fromFile);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (error instanceof Error && error.message.includes('resolved to unrelated package')) return false;
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
if (resolvedOwner !== owner) return false;
|
|
99
|
+
const target = importTarget(manifest.exports?.['./microvertical-api']);
|
|
100
|
+
if (!target?.startsWith('./')) return false;
|
|
101
|
+
const modules = new Map();
|
|
102
|
+
const load = (filename)=>{
|
|
103
|
+
const real = external_node_fs_default().realpathSync(filename);
|
|
104
|
+
if (!within(owner, real)) return;
|
|
105
|
+
let file = modules.get(real);
|
|
106
|
+
if (!file) {
|
|
107
|
+
if (modules.size >= 64 || external_node_fs_default().statSync(real).size > 1000000) throw new Error(`${real}: public export analysis budget exceeded`);
|
|
108
|
+
file = (0, parser_namespaceObject.parse)(external_node_fs_default().readFileSync(real, 'utf8'), {
|
|
109
|
+
sourceType: 'module',
|
|
110
|
+
sourceFilename: real,
|
|
111
|
+
plugins: [
|
|
112
|
+
"typescript"
|
|
113
|
+
]
|
|
114
|
+
});
|
|
115
|
+
modules.set(real, file);
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
file,
|
|
119
|
+
real
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
const follow = (from, specifier)=>{
|
|
123
|
+
if (!specifier.startsWith('.')) return;
|
|
124
|
+
const candidate = external_node_path_default().resolve(external_node_path_default().dirname(from), specifier);
|
|
125
|
+
if (!within(owner, candidate)) return;
|
|
126
|
+
return [
|
|
127
|
+
candidate,
|
|
128
|
+
`${candidate}.ts`,
|
|
129
|
+
`${candidate}.js`,
|
|
130
|
+
external_node_path_default().join(candidate, 'index.ts'),
|
|
131
|
+
external_node_path_default().join(candidate, 'index.js')
|
|
132
|
+
].find((file)=>external_node_fs_default().existsSync(file) && external_node_fs_default().statSync(file).isFile());
|
|
133
|
+
};
|
|
134
|
+
const exportedOwner = (filename, name, seen = new Set())=>{
|
|
135
|
+
const loaded = load(filename);
|
|
136
|
+
if (!loaded) return;
|
|
137
|
+
const { file, real } = loaded;
|
|
138
|
+
const key = `${real}:${name}`;
|
|
139
|
+
if (seen.has(key)) return;
|
|
140
|
+
seen = new Set([
|
|
141
|
+
...seen,
|
|
142
|
+
key
|
|
143
|
+
]);
|
|
144
|
+
const explicit = [];
|
|
145
|
+
const stars = [];
|
|
146
|
+
for (const statement of file.program.body){
|
|
147
|
+
if (types_namespaceObject.isExportAllDeclaration(statement) && 'type' !== statement.exportKind) {
|
|
148
|
+
const next = follow(real, statement.source.value);
|
|
149
|
+
if (!next) return;
|
|
150
|
+
stars.push(next);
|
|
151
|
+
}
|
|
152
|
+
if (!types_namespaceObject.isExportNamedDeclaration(statement) || 'type' === statement.exportKind) continue;
|
|
153
|
+
const declaration = statement.declaration;
|
|
154
|
+
if (types_namespaceObject.isVariableDeclaration(declaration) && declaration.declarations.some((value)=>types_namespaceObject.isIdentifier(value.id, {
|
|
155
|
+
name
|
|
156
|
+
}))) explicit.push(real);
|
|
157
|
+
if ((types_namespaceObject.isFunctionDeclaration(declaration) || types_namespaceObject.isClassDeclaration(declaration)) && types_namespaceObject.isIdentifier(declaration.id, {
|
|
158
|
+
name
|
|
159
|
+
})) explicit.push(real);
|
|
160
|
+
for (const value of statement.specifiers)if (types_namespaceObject.isExportSpecifier(value) && 'type' !== value.exportKind && types_namespaceObject.isIdentifier(value.exported, {
|
|
161
|
+
name
|
|
162
|
+
})) {
|
|
163
|
+
if (!types_namespaceObject.isIdentifier(value.local, {
|
|
164
|
+
name
|
|
165
|
+
})) {
|
|
166
|
+
explicit.push(void 0);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (statement.source) {
|
|
170
|
+
const next = follow(real, statement.source.value);
|
|
171
|
+
explicit.push(next ? exportedOwner(next, name, seen) : void 0);
|
|
172
|
+
} else {
|
|
173
|
+
const local = file.program.body.find((item)=>types_namespaceObject.isVariableDeclaration(item) && item.declarations.some((value)=>types_namespaceObject.isIdentifier(value.id, {
|
|
174
|
+
name
|
|
175
|
+
})) || (types_namespaceObject.isFunctionDeclaration(item) || types_namespaceObject.isClassDeclaration(item)) && types_namespaceObject.isIdentifier(item.id, {
|
|
176
|
+
name
|
|
177
|
+
}));
|
|
178
|
+
if (local) {
|
|
179
|
+
explicit.push(real);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const imported = file.program.body.find((item)=>types_namespaceObject.isImportDeclaration(item) && 'type' !== item.importKind && item.specifiers.some((value)=>types_namespaceObject.isImportSpecifier(value) && 'type' !== value.importKind && types_namespaceObject.isIdentifier(value.local, {
|
|
183
|
+
name
|
|
184
|
+
}) && types_namespaceObject.isIdentifier(value.imported, {
|
|
185
|
+
name
|
|
186
|
+
})));
|
|
187
|
+
const next = types_namespaceObject.isImportDeclaration(imported) ? follow(real, imported.source.value) : void 0;
|
|
188
|
+
explicit.push(next ? exportedOwner(next, name, seen) : void 0);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (explicit.length) return 1 === explicit.length ? explicit[0] : void 0;
|
|
193
|
+
const owners = stars.map((next)=>exportedOwner(next, name, seen));
|
|
194
|
+
const unique = new Set(owners.filter((value)=>void 0 !== value));
|
|
195
|
+
return 1 === unique.size ? [
|
|
196
|
+
...unique
|
|
197
|
+
][0] : void 0;
|
|
198
|
+
};
|
|
199
|
+
const entry = external_node_path_default().resolve(owner, target);
|
|
200
|
+
const owners = [
|
|
201
|
+
'MicroVerticalBuildMarkerSchema',
|
|
202
|
+
'MicroVerticalReadinessSchema',
|
|
203
|
+
'createMicroVerticalOperationContext'
|
|
204
|
+
].map((name)=>exportedOwner(entry, name));
|
|
205
|
+
return void 0 !== owners[0] && owners.every((value)=>value === owners[0]);
|
|
206
|
+
}
|
|
207
|
+
exports.baselinePackage = __webpack_exports__.baselinePackage;
|
|
208
|
+
exports.baselinePublicIdentityIsExact = __webpack_exports__.baselinePublicIdentityIsExact;
|
|
209
|
+
exports.resolveBaselinePackageDirectory = __webpack_exports__.resolveBaselinePackageDirectory;
|
|
210
|
+
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
211
|
+
"baselinePackage",
|
|
212
|
+
"baselinePublicIdentityIsExact",
|
|
213
|
+
"resolveBaselinePackageDirectory"
|
|
214
|
+
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
215
|
+
Object.defineProperty(exports, '__esModule', {
|
|
216
|
+
value: true
|
|
217
|
+
});
|
|
@@ -50,7 +50,7 @@ const createStrictEffectApiBoundariesRule = ()=>({
|
|
|
50
50
|
node,
|
|
51
51
|
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.'
|
|
52
52
|
});
|
|
53
|
-
(0, external_ast_cjs_namespaceObject.reportProgramPattern)(context, node, source, /@modern-js\/plugin-bff\/hono-server/u, 'UltraModern API workspaces must not import Hono server helpers; use @modern-js/
|
|
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
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.');
|
|
56
56
|
(0, external_ast_cjs_namespaceObject.reportProgramPattern)(context, node, source, /\bstrictEffectApproach\s*(?::|=)\s*false\b/u, 'UltraModern API apps must keep strictEffectApproach enabled.');
|
|
@@ -48,7 +48,8 @@ const parser_namespaceObject = require("@babel/parser");
|
|
|
48
48
|
const traverse_namespaceObject = require("@babel/traverse");
|
|
49
49
|
var traverse_default = /*#__PURE__*/ __webpack_require__.n(traverse_namespaceObject);
|
|
50
50
|
const types_namespaceObject = require("@babel/types");
|
|
51
|
-
const edge = '@modern-js/
|
|
51
|
+
const edge = '@modern-js/bff-effect/effect-edge';
|
|
52
|
+
const nodeRuntime = '@modern-js/bff-effect/effect';
|
|
52
53
|
const sharedRuntime = /^@[^/]+\/shared-contracts\/server\/effect-bff-runtime$/u;
|
|
53
54
|
const failure = 'Generated API entries must export defineEffectBff(...) or the server-only shared Effect BFF assembly helper with an explicitly composed handler Layer and an unshadowed executable root; entries must implement handlers through HttpApiBuilder.group.';
|
|
54
55
|
const invalidSources = new WeakSet();
|
|
@@ -164,23 +165,38 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
|
|
|
164
165
|
const imported = (node)=>{
|
|
165
166
|
const value = binding(node);
|
|
166
167
|
const decl = value?.path.node;
|
|
167
|
-
if (!decl || !types_namespaceObject.isImportSpecifier(decl) || 'type' === decl.importKind) return;
|
|
168
|
+
if (!decl || !types_namespaceObject.isImportSpecifier(decl) && !types_namespaceObject.isImportNamespaceSpecifier(decl) || types_namespaceObject.isImportSpecifier(decl) && 'type' === decl.importKind) return;
|
|
168
169
|
const statement = value.path.parent;
|
|
169
170
|
if (!types_namespaceObject.isImportDeclaration(statement) || 'type' === statement.importKind) return;
|
|
170
171
|
return {
|
|
171
|
-
|
|
172
|
+
namespace: types_namespaceObject.isImportNamespaceSpecifier(decl),
|
|
173
|
+
name: types_namespaceObject.isImportNamespaceSpecifier(decl) ? '*' : types_namespaceObject.isIdentifier(decl.imported) ? decl.imported.name : decl.imported.value,
|
|
172
174
|
specifier: statement.source.value
|
|
173
175
|
};
|
|
174
176
|
};
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
177
|
+
const namespaceSources = {
|
|
178
|
+
HttpApi: [
|
|
179
|
+
edge,
|
|
180
|
+
'effect/unstable/httpapi'
|
|
181
|
+
],
|
|
182
|
+
HttpApiBuilder: [
|
|
183
|
+
edge,
|
|
184
|
+
'effect/unstable/httpapi'
|
|
185
|
+
],
|
|
186
|
+
HttpRouter: [
|
|
187
|
+
edge,
|
|
188
|
+
'effect/unstable/http'
|
|
189
|
+
]
|
|
190
|
+
};
|
|
191
|
+
const native = (node, name, sources)=>{
|
|
178
192
|
const value = imported(node);
|
|
179
|
-
|
|
193
|
+
if (!value) return false;
|
|
194
|
+
const importedNameMatches = value.namespace ? value.specifier === `effect/${name}` : value.name === name && value.specifier !== `effect/${name}`;
|
|
195
|
+
return importedNameMatches && (sources ?? namespaceSources[name] ?? [
|
|
196
|
+
edge
|
|
197
|
+
]).includes(value.specifier);
|
|
180
198
|
};
|
|
181
|
-
const method = (node, namespace, member, sources
|
|
182
|
-
edge
|
|
183
|
-
])=>{
|
|
199
|
+
const method = (node, namespace, member, sources)=>{
|
|
184
200
|
node = unwrap(node);
|
|
185
201
|
return types_namespaceObject.isMemberExpression(node) && !node.computed && types_namespaceObject.isIdentifier(node.property, {
|
|
186
202
|
name: member
|
|
@@ -258,8 +274,15 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
|
|
|
258
274
|
};
|
|
259
275
|
const layerSources = [
|
|
260
276
|
edge,
|
|
261
|
-
'effect'
|
|
277
|
+
'effect',
|
|
278
|
+
'effect/Layer'
|
|
262
279
|
];
|
|
280
|
+
const returned = (body)=>{
|
|
281
|
+
if (!types_namespaceObject.isBlockStatement(body)) return body;
|
|
282
|
+
const statements = body.body;
|
|
283
|
+
const last = statements.at(-1);
|
|
284
|
+
return statements.slice(0, -1).every((statement)=>types_namespaceObject.isVariableDeclaration(statement)) && types_namespaceObject.isReturnStatement(last) ? last.argument : void 0;
|
|
285
|
+
};
|
|
263
286
|
const pipe = (node, check)=>types_namespaceObject.isMemberExpression(node.callee) && !node.callee.computed && types_namespaceObject.isIdentifier(node.callee.property, {
|
|
264
287
|
name: 'pipe'
|
|
265
288
|
}) && check(node.callee.object) && node.arguments.every((argument)=>method(argument, 'Layer', 'orDie', layerSources) || types_namespaceObject.isCallExpression(argument) && method(argument.callee, 'Layer', 'provide', layerSources) && argument.arguments.length > 0);
|
|
@@ -268,7 +291,7 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
|
|
|
268
291
|
if (!types_namespaceObject.isArrowFunctionExpression(node) && !types_namespaceObject.isFunctionExpression(node)) return false;
|
|
269
292
|
const parameter = node.params[0];
|
|
270
293
|
if (!types_namespaceObject.isIdentifier(parameter)) return false;
|
|
271
|
-
const body =
|
|
294
|
+
const body = returned(node.body);
|
|
272
295
|
const chain = (value, count = 0)=>{
|
|
273
296
|
value = unwrap(value);
|
|
274
297
|
if (types_namespaceObject.isIdentifier(value)) return count > 0 && declaration(value) === parameter;
|
|
@@ -324,7 +347,7 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
|
|
|
324
347
|
const groupInit = groupValue && initialized(groupValue);
|
|
325
348
|
if (!groupInit || !types_namespaceObject.isCallExpression(groupInit) || !method(groupInit.callee, 'RpcGroup', 'make', [
|
|
326
349
|
'effect/unstable/rpc',
|
|
327
|
-
'@modern-js/
|
|
350
|
+
'@modern-js/bff-effect/effect-client'
|
|
328
351
|
])) return false;
|
|
329
352
|
const layerInit = initialized(layer) ?? unwrap(layer);
|
|
330
353
|
if (!types_namespaceObject.isCallExpression(layerInit) || !types_namespaceObject.isMemberExpression(layerInit.callee) || layerInit.callee.computed || !types_namespaceObject.isIdentifier(layerInit.callee.property, {
|
|
@@ -344,7 +367,10 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
|
|
|
344
367
|
if (init) return runtime(init);
|
|
345
368
|
if (!types_namespaceObject.isCallExpression(node)) return false;
|
|
346
369
|
const value = imported(node.callee);
|
|
347
|
-
if (value?.name === 'defineEffectBff' &&
|
|
370
|
+
if (value?.name === 'defineEffectBff' && [
|
|
371
|
+
edge,
|
|
372
|
+
nodeRuntime
|
|
373
|
+
].includes(value.specifier) || value?.name === 'assembleEffectBffRuntime' && sharedRuntime.test(value.specifier)) {
|
|
348
374
|
if (1 !== node.arguments.length) return false;
|
|
349
375
|
const values = properties(node.arguments[0]);
|
|
350
376
|
if (!values) return false;
|
|
@@ -360,11 +386,9 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
|
|
|
360
386
|
}
|
|
361
387
|
const decl = declaration(node.callee);
|
|
362
388
|
const factory = decl && types_namespaceObject.isFunctionDeclaration(decl) ? decl : initialized(node.callee);
|
|
363
|
-
if (!factory || !types_namespaceObject.isFunctionDeclaration(factory) && !types_namespaceObject.isArrowFunctionExpression(factory) && !types_namespaceObject.isFunctionExpression(factory) ||
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
const last = statements.at(-1);
|
|
367
|
-
return statements.slice(0, -1).every((statement)=>types_namespaceObject.isVariableDeclaration(statement)) && !!last && types_namespaceObject.isReturnStatement(last) && !!last.argument && runtime(last.argument);
|
|
389
|
+
if (!factory || !types_namespaceObject.isFunctionDeclaration(factory) && !types_namespaceObject.isArrowFunctionExpression(factory) && !types_namespaceObject.isFunctionExpression(factory) || !factory.body) return false;
|
|
390
|
+
const result = returned(factory.body);
|
|
391
|
+
return !!result && runtime(result);
|
|
368
392
|
});
|
|
369
393
|
const root = entry.file.program.body.find((statement)=>types_namespaceObject.isExportDefaultDeclaration(statement));
|
|
370
394
|
return root && runtime(root.declaration) ? void 0 : failure;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { checkMicroVerticalApiBoundaries, checkMicroVerticalApiConsumerFiles } from "../microvertical-api-boundary.js";
|
|
2
|
+
function runMicroVerticalApiCheckCli(args = process.argv.slice(2), filesOnly = false) {
|
|
3
|
+
let workspaceRoot = process.env.ULTRAMODERN_WORKSPACE_ROOT ?? process.cwd();
|
|
4
|
+
let baselinePackageDirectory;
|
|
5
|
+
for(let index = 0; index < args.length; index += 1){
|
|
6
|
+
const argument = args[index];
|
|
7
|
+
if ('--help' === argument || '-h' === argument) {
|
|
8
|
+
console.log(`${filesOnly ? 'modern-api-check-files' : 'modern-api-check'} [--workspace-root <path>] [--baseline-package-directory <path>]\n${filesOnly ? 'Checks consumer files, contracts, package exports and owner identity. Does not analyze runtime topology; run the strict Effect source phase separately.' : 'Checks consumer files, contracts, package exports, owner identity and runtime topology once per API entry.'}\nWorkspace defaults to ULTRAMODERN_WORKSPACE_ROOT then cwd. Exit codes: 0 valid, 1 consumer violation, 2 tool/configuration failure.`);
|
|
9
|
+
return 0;
|
|
10
|
+
}
|
|
11
|
+
if (![
|
|
12
|
+
'--workspace-root',
|
|
13
|
+
'--baseline-package-directory'
|
|
14
|
+
].includes(argument ?? '') || !args[index + 1] || args[index + 1]?.startsWith('--')) {
|
|
15
|
+
console.error(`Invalid argument: ${argument ?? ''}. Use --help.`);
|
|
16
|
+
return 2;
|
|
17
|
+
}
|
|
18
|
+
const value = args[++index];
|
|
19
|
+
if ('--workspace-root' === argument) workspaceRoot = value;
|
|
20
|
+
else baselinePackageDirectory = value;
|
|
21
|
+
}
|
|
22
|
+
const result = (filesOnly ? checkMicroVerticalApiConsumerFiles : checkMicroVerticalApiBoundaries)({
|
|
23
|
+
workspaceRoot,
|
|
24
|
+
baselinePackageDirectory
|
|
25
|
+
});
|
|
26
|
+
for (const diagnostic of result.diagnostics)console.error(`API violation: ${diagnostic}`);
|
|
27
|
+
for (const error of result.toolErrors)console.error(`API tool error: ${error}`);
|
|
28
|
+
if (result.toolErrors.length) return 2;
|
|
29
|
+
if (result.diagnostics.length) return 1;
|
|
30
|
+
console.log(`UltraModern API ${filesOnly ? 'consumer files' : 'boundary'} check passed.`);
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
export { runMicroVerticalApiCheckCli };
|