@bleedingdev/modern-js-code-tools 3.9.0-ultramodern.17 → 3.9.0-ultramodern.19

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.
@@ -0,0 +1,261 @@
1
+ import "node:module";
2
+ import node_fs from "node:fs";
3
+ import { createRequire } from "node:module";
4
+ import node_path from "node:path";
5
+ import { exports as external_resolve_exports_exports } from "resolve.exports";
6
+ import { SourceSyntaxError, parseSource, traverseSource } from "./source-analysis.js";
7
+ import * as __rspack_external__babel_types_6e8337dd from "@babel/types";
8
+ const MAX_MODULES = 256;
9
+ const MAX_LOOKUPS = 512;
10
+ function parseConsumer(filePath) {
11
+ if (node_fs.statSync(filePath).size > 1000000) throw new Error(`${filePath}: consumer source exceeds 1 MB analysis budget`);
12
+ const file = parseSource(node_fs.readFileSync(filePath, 'utf8'), filePath);
13
+ traverseSource(file, {
14
+ AssignmentExpression (p) {
15
+ throw new SourceSyntaxError(`contract bindings must be immutable at ${p.node.start}`);
16
+ },
17
+ UpdateExpression (p) {
18
+ throw new SourceSyntaxError(`contract bindings must be immutable at ${p.node.start}`);
19
+ },
20
+ TSModuleDeclaration () {
21
+ throw new SourceSyntaxError('contract bindings must not be merged with namespaces');
22
+ }
23
+ });
24
+ return file;
25
+ }
26
+ const propertyName = (node)=>{
27
+ 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);
28
+ };
29
+ const localConst = (sourceFile, name)=>{
30
+ for (const item of sourceFile.program.body){
31
+ const statement = __rspack_external__babel_types_6e8337dd.isExportNamedDeclaration(item) ? item.declaration : item;
32
+ if (!__rspack_external__babel_types_6e8337dd.isVariableDeclaration(statement) || 'const' !== statement.kind) continue;
33
+ const matches = statement.declarations.filter((declaration)=>__rspack_external__babel_types_6e8337dd.isIdentifier(declaration.id, {
34
+ name
35
+ }));
36
+ if (1 === matches.length) return matches[0];
37
+ }
38
+ };
39
+ const exportedConst = (sourceFile, name)=>{
40
+ for (const statement of sourceFile.program.body)if (__rspack_external__babel_types_6e8337dd.isExportNamedDeclaration(statement) && 'type' !== statement.exportKind) {
41
+ if (__rspack_external__babel_types_6e8337dd.isVariableDeclaration(statement.declaration) && 'const' === statement.declaration.kind) {
42
+ const declaration = statement.declaration.declarations.find((value)=>__rspack_external__babel_types_6e8337dd.isIdentifier(value.id, {
43
+ name
44
+ }));
45
+ if (declaration) return declaration;
46
+ }
47
+ 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, {
48
+ name
49
+ }) && __rspack_external__babel_types_6e8337dd.isIdentifier(value.exported, {
50
+ name
51
+ }))) return localConst(sourceFile, name);
52
+ }
53
+ };
54
+ const inside = (file, boundary)=>file === boundary || file.startsWith(`${boundary}${node_path.sep}`);
55
+ const containingWorkspace = (file)=>{
56
+ for(let directory = node_path.dirname(file);; directory = node_path.dirname(directory)){
57
+ if (node_fs.existsSync(node_path.join(directory, 'pnpm-workspace.yaml'))) return node_fs.realpathSync(directory);
58
+ if (directory === node_path.dirname(directory)) return;
59
+ }
60
+ };
61
+ const sourceBoundary = (file, workspace)=>{
62
+ for(let directory = node_path.dirname(file);; directory = node_path.dirname(directory)){
63
+ if (node_fs.existsSync(node_path.join(directory, 'package.json')) && directory !== workspace) return node_fs.realpathSync(directory);
64
+ if (directory === workspace || directory === node_path.dirname(directory)) return node_fs.realpathSync(node_path.dirname(file));
65
+ }
66
+ };
67
+ const sourceFileAt = (target, boundary)=>{
68
+ const stem = target.replace(/\.(?:[cm]?[jt]sx?)$/u, '');
69
+ for (const candidate of [
70
+ `${stem}.ts`,
71
+ `${stem}.tsx`,
72
+ `${stem}.mts`,
73
+ `${stem}.cts`,
74
+ target,
75
+ node_path.join(target, 'index.ts'),
76
+ node_path.join(target, 'index.tsx')
77
+ ])try {
78
+ const real = node_fs.realpathSync(candidate);
79
+ if (inside(real, boundary) && node_fs.statSync(real).isFile()) return real;
80
+ } catch {}
81
+ };
82
+ const packageEntry = (specifier)=>{
83
+ if (specifier.startsWith('.') || specifier.startsWith('/') || specifier.startsWith('#')) return;
84
+ const parts = specifier.split('/');
85
+ const count = specifier.startsWith('@') ? 2 : 1;
86
+ if (parts.length < count || parts.slice(0, count).some((part)=>!part)) return;
87
+ return {
88
+ name: parts.slice(0, count).join('/'),
89
+ exportKey: parts.length === count ? '.' : `./${parts.slice(count).join('/')}`
90
+ };
91
+ };
92
+ const resolveModulePath = (module, specifier, workspace)=>{
93
+ if (/^\.\.?\//u.test(specifier)) {
94
+ const file = sourceFileAt(node_path.resolve(node_path.dirname(module.path), specifier), module.boundary);
95
+ return void 0 === file ? void 0 : {
96
+ kind: 'module',
97
+ path: file,
98
+ boundary: module.boundary
99
+ };
100
+ }
101
+ const entry = packageEntry(specifier);
102
+ if (!entry) return;
103
+ if (!workspace) return {
104
+ kind: 'external'
105
+ };
106
+ const paths = createRequire(module.path).resolve.paths(entry.name) ?? [];
107
+ for (const lookup of paths){
108
+ const directory = node_path.join(lookup, entry.name);
109
+ const manifest = node_path.join(directory, 'package.json');
110
+ if (!node_fs.existsSync(manifest)) continue;
111
+ const boundary = node_fs.realpathSync(directory);
112
+ if (!inside(boundary, workspace) || boundary.split(node_path.sep).includes('node_modules')) break;
113
+ const pkg = JSON.parse(node_fs.readFileSync(manifest, 'utf8'));
114
+ if (pkg.name !== entry.name || void 0 === pkg.exports) return;
115
+ let target;
116
+ try {
117
+ const targets = external_resolve_exports_exports(pkg, entry.exportKey, {
118
+ conditions: [
119
+ 'modern:source'
120
+ ]
121
+ });
122
+ target = Array.isArray(targets) ? targets[0] : void 0;
123
+ } catch {
124
+ return;
125
+ }
126
+ if (!target || !target.startsWith('./')) return;
127
+ const file = sourceFileAt(node_path.resolve(boundary, target), boundary);
128
+ return void 0 === file ? void 0 : {
129
+ kind: 'module',
130
+ path: file,
131
+ boundary
132
+ };
133
+ }
134
+ return {
135
+ kind: 'external'
136
+ };
137
+ };
138
+ const importedBinding = (module, name)=>{
139
+ for (const statement of module.file.program.body)if (__rspack_external__babel_types_6e8337dd.isImportDeclaration(statement) && 'type' !== statement.importKind) {
140
+ for (const specifier of statement.specifiers)if (__rspack_external__babel_types_6e8337dd.isIdentifier(specifier.local, {
141
+ name
142
+ })) {
143
+ if (__rspack_external__babel_types_6e8337dd.isImportSpecifier(specifier) && 'type' !== specifier.importKind) return {
144
+ imported: propertyName(specifier.imported) ?? name,
145
+ specifier: statement.source.value
146
+ };
147
+ if (__rspack_external__babel_types_6e8337dd.isImportDefaultSpecifier(specifier)) return {
148
+ imported: 'default',
149
+ specifier: statement.source.value
150
+ };
151
+ }
152
+ }
153
+ };
154
+ const reexportedBinding = (module, name)=>{
155
+ for (const statement of module.file.program.body){
156
+ if (!__rspack_external__babel_types_6e8337dd.isExportNamedDeclaration(statement) || 'type' === statement.exportKind) continue;
157
+ const specifier = statement.specifiers.find((value)=>__rspack_external__babel_types_6e8337dd.isExportSpecifier(value) && 'type' !== value.exportKind && propertyName(value.exported) === name);
158
+ if (void 0 !== specifier && __rspack_external__babel_types_6e8337dd.isExportSpecifier(specifier)) return {
159
+ local: __rspack_external__babel_types_6e8337dd.isIdentifier(specifier.local) ? specifier.local.name : specifier.local.value,
160
+ ...statement.source ? {
161
+ specifier: statement.source.value
162
+ } : {}
163
+ };
164
+ }
165
+ };
166
+ const defaultExport = (module)=>{
167
+ for (const statement of module.file.program.body){
168
+ if (!__rspack_external__babel_types_6e8337dd.isExportDefaultDeclaration(statement)) continue;
169
+ const declaration = statement.declaration;
170
+ if (__rspack_external__babel_types_6e8337dd.isIdentifier(declaration)) return declaration.name;
171
+ return __rspack_external__babel_types_6e8337dd.isExpression(declaration) ? declaration : void 0;
172
+ }
173
+ };
174
+ function createModuleGraph(filePath) {
175
+ const realPath = node_fs.realpathSync(filePath);
176
+ const workspace = containingWorkspace(realPath);
177
+ const root = {
178
+ file: parseConsumer(realPath),
179
+ path: realPath,
180
+ boundary: sourceBoundary(realPath, workspace)
181
+ };
182
+ const modules = new Map([
183
+ [
184
+ root.path,
185
+ root
186
+ ]
187
+ ]);
188
+ const readModule = (modulePath, boundary)=>{
189
+ if (modules.has(modulePath)) return modules.get(modulePath);
190
+ if (modules.size >= MAX_MODULES) return;
191
+ let module;
192
+ try {
193
+ module = {
194
+ file: parseConsumer(modulePath),
195
+ path: modulePath,
196
+ boundary
197
+ };
198
+ } catch {
199
+ module = void 0;
200
+ }
201
+ modules.set(modulePath, module);
202
+ return module;
203
+ };
204
+ const resolveBinding = (module, name, scope, chain, seen)=>{
205
+ const key = `${scope}:${module.path}#${name}`;
206
+ if (seen.has(key) || seen.size >= MAX_LOOKUPS) return;
207
+ seen.add(key);
208
+ const last = chain.at(-1);
209
+ const hops = last?.path === module.path && last.name === name ? chain : [
210
+ ...chain,
211
+ {
212
+ path: module.path,
213
+ name
214
+ }
215
+ ];
216
+ const declaration = 'export' === scope ? exportedConst(module.file, name) : localConst(module.file, name);
217
+ if (declaration?.init) return {
218
+ kind: 'declaration',
219
+ expression: declaration.init,
220
+ module,
221
+ chain: hops
222
+ };
223
+ const throughModule = (specifier, imported, allowExternal)=>{
224
+ const target = resolveModulePath(module, specifier, workspace);
225
+ if (target?.kind === 'external') return allowExternal ? {
226
+ kind: 'external',
227
+ specifier,
228
+ name: imported,
229
+ chain: hops
230
+ } : void 0;
231
+ const next = target && readModule(target.path, target.boundary);
232
+ return void 0 === next ? void 0 : resolveBinding(next, imported, 'export', hops, seen);
233
+ };
234
+ if ('local' === scope) {
235
+ const imported = importedBinding(module, name);
236
+ return void 0 === imported ? void 0 : throughModule(imported.specifier, imported.imported, true);
237
+ }
238
+ if ('default' === name) {
239
+ const exported = defaultExport(module);
240
+ if ('string' == typeof exported) return resolveBinding(module, exported, 'local', hops, seen);
241
+ return void 0 === exported ? void 0 : {
242
+ kind: 'declaration',
243
+ expression: exported,
244
+ module,
245
+ chain: hops
246
+ };
247
+ }
248
+ const reexported = reexportedBinding(module, name);
249
+ if (void 0 !== reexported) return void 0 === reexported.specifier ? resolveBinding(module, reexported.local, 'local', hops, seen) : throughModule(reexported.specifier, reexported.local, true);
250
+ for (const statement of module.file.program.body){
251
+ if (!__rspack_external__babel_types_6e8337dd.isExportAllDeclaration(statement) || 'type' === statement.exportKind) continue;
252
+ const resolved = throughModule(statement.source.value, name, false);
253
+ if (void 0 !== resolved) return resolved;
254
+ }
255
+ };
256
+ return {
257
+ root,
258
+ resolve: (module, name, scope)=>resolveBinding(module, name, scope, [], new Set())
259
+ };
260
+ }
261
+ export { createModuleGraph, exportedConst, localConst, parseConsumer, propertyName };
@@ -1 +1 @@
1
- export declare function runMicroVerticalApiCheckCli(args?: string[], filesOnly?: boolean): number;
1
+ export declare function runMicroVerticalApiCheckCli(args?: string[], filesOnly?: boolean): Promise<number>;
@@ -17,4 +17,4 @@ export type WorkspaceSourceCheckOptions = {
17
17
  readonly pluralCategories?: Readonly<Record<string, readonly string[]>>;
18
18
  };
19
19
  export declare const runWorkspaceSourceCheck: ({ cwd, sourceRoots, locales, pluralCategories, }?: WorkspaceSourceCheckOptions) => number;
20
- export declare const main: () => void;
20
+ export declare const runWorkspaceSourceCheckCli: (args?: readonly string[]) => number;
@@ -1,5 +1,8 @@
1
+ import { type ModuleGraph } from './module-graph.js';
1
2
  export type { MicroVerticalApiBaselineExpectation, MicroVerticalTopologyEntry, } from './microvertical-api-baseline.js';
2
3
  export { configuredMicroVerticalApiStem, microVerticalApiBaselineViolation, } from './microvertical-api-baseline.js';
4
+ export type { ModuleGraph, ModuleGraphHop, ResolvedBinding, SourceModule, } from './module-graph.js';
5
+ export { createModuleGraph } from './module-graph.js';
3
6
  export interface MicroVerticalConfiguredApp {
4
7
  readonly path: string;
5
8
  readonly id?: string;
@@ -15,11 +18,22 @@ export interface MicroVerticalConfiguredApp {
15
18
  readonly additionalPaths?: Readonly<Record<string, string>>;
16
19
  };
17
20
  }
21
+ export interface MicroVerticalApiContractRuleContext {
22
+ /** Workspace-relative app directory. */
23
+ readonly appPath: string;
24
+ readonly protocol: 'rest' | 'rpc';
25
+ /** Graph rooted at the app's shared contract (`graph.root`). */
26
+ readonly graph: ModuleGraph;
27
+ }
28
+ /** A consumer-owned contract rule; each returned message is a violation. */
29
+ export type MicroVerticalApiContractRule = (context: MicroVerticalApiContractRuleContext) => readonly string[];
18
30
  export interface MicroVerticalApiCheckOptions {
19
31
  readonly workspaceRoot: string;
20
32
  readonly configuredApps?: readonly MicroVerticalConfiguredApp[];
21
33
  /** Explicit expected installed owner, useful for isolated/non-hoisted installations. */
22
34
  readonly baselinePackageDirectory?: string;
35
+ /** Rules run once per API app against its shared contract's module graph. */
36
+ readonly contractRules?: readonly MicroVerticalApiContractRule[];
23
37
  }
24
38
  export interface MicroVerticalApiCheckResult {
25
39
  readonly diagnostics: readonly string[];
@@ -0,0 +1,52 @@
1
+ import * as t from '@babel/types';
2
+ /** A parsed consumer module and the package directory it may traverse privately. */
3
+ export interface SourceModule {
4
+ readonly file: t.File;
5
+ /** Real absolute path. */
6
+ readonly path: string;
7
+ /** Real package directory; relative imports may not leave it. */
8
+ readonly boundary: string;
9
+ }
10
+ /** One `name` looked up in the module at `path`, in traversal order. */
11
+ export interface ModuleGraphHop {
12
+ readonly path: string;
13
+ readonly name: string;
14
+ }
15
+ /**
16
+ * Where a binding ends up. `declaration` is a workspace `const` initializer;
17
+ * `external` is a named import or re-export from a package outside the
18
+ * workspace source graph. `chain` starts at the requesting module and ends at
19
+ * the declaring module (or the last workspace module before the package).
20
+ */
21
+ export type ResolvedBinding = {
22
+ readonly kind: 'declaration';
23
+ readonly expression: t.Expression;
24
+ readonly module: SourceModule;
25
+ readonly chain: readonly ModuleGraphHop[];
26
+ } | {
27
+ readonly kind: 'external';
28
+ readonly specifier: string;
29
+ readonly name: string;
30
+ readonly chain: readonly ModuleGraphHop[];
31
+ };
32
+ export interface ModuleGraph {
33
+ readonly root: SourceModule;
34
+ /**
35
+ * Follow a local binding (`scope: 'local'`) or an export (`'export'`) of
36
+ * `module` through relative imports, re-exports and public workspace
37
+ * package exports. Unresolvable, cyclic or over-budget bindings return
38
+ * undefined so callers fail closed.
39
+ */
40
+ resolve(module: SourceModule, name: string, scope: 'export' | 'local'): ResolvedBinding | undefined;
41
+ }
42
+ /** Resolution follows `const` bindings, so a module that mutates them is unreadable. */
43
+ export declare function parseConsumer(filePath: string): t.File;
44
+ export declare const propertyName: (node: t.Node | undefined) => string | undefined;
45
+ export declare const localConst: (sourceFile: t.File, name: string) => t.VariableDeclarator | undefined;
46
+ export declare const exportedConst: (sourceFile: t.File, name: string) => t.VariableDeclarator | undefined;
47
+ /**
48
+ * The module graph rooted at `filePath`. Each module is parsed once per graph;
49
+ * an unreadable module stays unresolved instead of assumed valid. Throws
50
+ * `SourceSyntaxError` when the root itself is not a readable consumer module.
51
+ */
52
+ export declare function createModuleGraph(filePath: string): ModuleGraph;
package/package.json CHANGED
@@ -23,7 +23,7 @@
23
23
  "engines": {
24
24
  "node": ">=20"
25
25
  },
26
- "version": "3.9.0-ultramodern.17",
26
+ "version": "3.9.0-ultramodern.19",
27
27
  "types": "./dist/types/index.d.ts",
28
28
  "main": "./dist/esm-node/index.js",
29
29
  "typesVersions": {
@@ -91,11 +91,12 @@
91
91
  "modern:source": "./src/index.ts",
92
92
  "bin": {
93
93
  "modern-api-check": "bin/modern-api-check.mjs",
94
- "modern-api-check-files": "bin/modern-api-check-files.mjs"
94
+ "modern-api-check-files": "bin/modern-api-check-files.mjs",
95
+ "modern-i18n-check": "bin/modern-i18n-check.mjs"
95
96
  },
96
97
  "scripts": {
97
98
  "build": "rm -rf dist && rslib build -c rslib.config.mts",
98
99
  "dev": "rslib build -c rslib.config.mts -w",
99
- "test": "rstest --passWithNoTests"
100
+ "test": "rstest"
100
101
  }
101
102
  }