ushman-equiv 0.4.0
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/AGENTS.md +81 -0
- package/LICENSE.md +21 -0
- package/README.md +201 -0
- package/bin/ushman-equiv +19 -0
- package/dist/analysis-context.d.ts +102 -0
- package/dist/analysis-context.d.ts.map +1 -0
- package/dist/analysis-context.js +708 -0
- package/dist/ast-guards.d.ts +24 -0
- package/dist/ast-guards.d.ts.map +1 -0
- package/dist/ast-guards.js +83 -0
- package/dist/candidate-boot.d.ts +30 -0
- package/dist/candidate-boot.d.ts.map +1 -0
- package/dist/candidate-boot.js +262 -0
- package/dist/canonicalize.d.ts +19 -0
- package/dist/canonicalize.d.ts.map +1 -0
- package/dist/canonicalize.js +525 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +312 -0
- package/dist/equiv-execution-context.d.ts +25 -0
- package/dist/equiv-execution-context.d.ts.map +1 -0
- package/dist/equiv-execution-context.js +82 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/run.d.ts +8 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +129 -0
- package/dist/shared.d.ts +9 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +47 -0
- package/dist/tier-i-import-graph.d.ts +7 -0
- package/dist/tier-i-import-graph.d.ts.map +1 -0
- package/dist/tier-i-import-graph.js +34 -0
- package/dist/tier-l-child-runtime.d.ts +2 -0
- package/dist/tier-l-child-runtime.d.ts.map +1 -0
- package/dist/tier-l-child-runtime.js +62 -0
- package/dist/tier-l-module-load.d.ts +6 -0
- package/dist/tier-l-module-load.d.ts.map +1 -0
- package/dist/tier-l-module-load.js +139 -0
- package/dist/tier-l-stub-source.d.ts +11 -0
- package/dist/tier-l-stub-source.d.ts.map +1 -0
- package/dist/tier-l-stub-source.js +246 -0
- package/dist/tier-r-replay.d.ts +6 -0
- package/dist/tier-r-replay.d.ts.map +1 -0
- package/dist/tier-r-replay.js +382 -0
- package/dist/tier-s-symbol-diff.d.ts +19 -0
- package/dist/tier-s-symbol-diff.d.ts.map +1 -0
- package/dist/tier-s-symbol-diff.js +156 -0
- package/dist/types.d.ts +91 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +19 -0
- package/dist/workspace.d.ts +63 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +459 -0
- package/package.json +64 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
import { parseArgs } from 'node:util';
|
|
6
|
+
import { renderEquivReport, runEquiv, writeEquivResult } from "./run.js";
|
|
7
|
+
import { resolvePackageVersion } from "./shared.js";
|
|
8
|
+
import { emitSymbolBaseline } from "./tier-s-symbol-diff.js";
|
|
9
|
+
import { MAX_MAX_CONCURRENCY, WorkspaceLayoutError } from "./workspace.js";
|
|
10
|
+
const DEFAULT_MAX_OLD_SPACE_SIZE_MB = 8192;
|
|
11
|
+
const MAX_OLD_SPACE_SIZE_PATTERN = /(?:^|\s)--max-old-space-size(?:=|\s)\d+(?:\s|$)/u;
|
|
12
|
+
/**
|
|
13
|
+
* The in-zip CLI runs directly inside the LLM sandbox. Multi-MB bundles can
|
|
14
|
+
* exhaust Node's default heap during AST traversal (Tier I), module-load
|
|
15
|
+
* (Tier L), and per-fixture replay (Tier R) — so direct execution respawns
|
|
16
|
+
* once with an 8 GB old-space default unless the operator has already set
|
|
17
|
+
* one. Mirrors @ushman/verify's runWithHeapGuard.
|
|
18
|
+
*/
|
|
19
|
+
const hasMaxOldSpaceSize = (nodeOptions) => typeof nodeOptions === 'string' && MAX_OLD_SPACE_SIZE_PATTERN.test(nodeOptions);
|
|
20
|
+
const withDefaultMaxOldSpaceSize = (nodeOptions) => [`--max-old-space-size=${DEFAULT_MAX_OLD_SPACE_SIZE_MB}`, nodeOptions?.trim()]
|
|
21
|
+
.filter((value) => Boolean(value))
|
|
22
|
+
.join(' ');
|
|
23
|
+
export const runWithHeapGuard = async (runner) => {
|
|
24
|
+
if (hasMaxOldSpaceSize(process.env.NODE_OPTIONS)) {
|
|
25
|
+
return runner();
|
|
26
|
+
}
|
|
27
|
+
const child = spawn(process.execPath, [...process.execArgv, ...process.argv.slice(1)], {
|
|
28
|
+
env: {
|
|
29
|
+
...process.env,
|
|
30
|
+
NODE_OPTIONS: withDefaultMaxOldSpaceSize(process.env.NODE_OPTIONS),
|
|
31
|
+
},
|
|
32
|
+
stdio: 'inherit',
|
|
33
|
+
});
|
|
34
|
+
return await new Promise((resolve, reject) => {
|
|
35
|
+
child.once('error', reject);
|
|
36
|
+
child.once('exit', (code, signal) => {
|
|
37
|
+
if (signal) {
|
|
38
|
+
resolve(2);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
resolve(code ?? 0);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
const renderHelpText = (mode = 'main') => mode === 'emit-baseline'
|
|
46
|
+
? [
|
|
47
|
+
'Usage:',
|
|
48
|
+
' ushman-equiv emit-baseline [<workspace> | --workspace=<path>] [--out=<path>]',
|
|
49
|
+
' [--entrypoint=<path>] [--filter=<glob-or-/regex/>]',
|
|
50
|
+
' [--src-roots=<comma-list>]',
|
|
51
|
+
'',
|
|
52
|
+
'Options:',
|
|
53
|
+
' --workspace Workspace root to baseline. Defaults to the current directory.',
|
|
54
|
+
' --out Output file for ushman-equiv-baseline/v1 JSON.',
|
|
55
|
+
' --entrypoint Override the default root index.html entrypoint discovery.',
|
|
56
|
+
' --filter Match symbol names with a glob or /regex/.',
|
|
57
|
+
' --src-roots Comma-separated source roots. Defaults to src.',
|
|
58
|
+
].join('\n')
|
|
59
|
+
: [
|
|
60
|
+
'Usage:',
|
|
61
|
+
' ushman-equiv [<workspace> | --workspace=<path>] [--tiers=I,L,S,R | --tier=I]',
|
|
62
|
+
' [--filter=<glob-or-/regex/>]',
|
|
63
|
+
' [--entrypoint=<path>] [--baseline=<path>]',
|
|
64
|
+
' [--fixtures=<dir>] [--modules=<dir>]',
|
|
65
|
+
' [--src-roots=<comma-list>] [--mode=preview|dev] [--max-concurrency=<n>]',
|
|
66
|
+
' [--write-result] [--json]',
|
|
67
|
+
' ushman-equiv emit-baseline [<workspace> | --workspace=<path>] [--out=<path>]',
|
|
68
|
+
'',
|
|
69
|
+
'Paths are resolved relative to the current working directory unless documented otherwise.',
|
|
70
|
+
'--baseline may be workspace-relative or absolute.',
|
|
71
|
+
'--filter matches source-file paths for tier I, symbol names for tier S, and fixture names for tier R.',
|
|
72
|
+
'Glob syntax supports *, ?, and **. Use /pattern/ for regex.',
|
|
73
|
+
'',
|
|
74
|
+
'Flags:',
|
|
75
|
+
' --workspace Workspace root. Defaults to the current directory.',
|
|
76
|
+
' --mode Boot mode for tiers L and R. Defaults to preview.',
|
|
77
|
+
` --max-concurrency Limit replay concurrency. Max ${MAX_MAX_CONCURRENCY}.`,
|
|
78
|
+
' --json Print the full report as JSON.',
|
|
79
|
+
' --write-result Write .lab/equiv/result.json inside the workspace.',
|
|
80
|
+
' -h, --help Show this help text.',
|
|
81
|
+
' -v, --version Show the installed ushman-equiv version.',
|
|
82
|
+
].join('\n');
|
|
83
|
+
const parseTierValues = (tier, tiersValue) => {
|
|
84
|
+
const values = [...(tier ?? []), ...(tiersValue ? tiersValue.split(',') : [])].filter(Boolean);
|
|
85
|
+
if (values.length === 0) {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
const unique = new Set();
|
|
89
|
+
for (const value of values) {
|
|
90
|
+
if (value === 'I' || value === 'L' || value === 'R' || value === 'S') {
|
|
91
|
+
unique.add(value);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
throw new Error(`Unsupported tier value: ${value}`);
|
|
95
|
+
}
|
|
96
|
+
return [...unique];
|
|
97
|
+
};
|
|
98
|
+
const parsePositiveIntegerOption = (value, flagName) => {
|
|
99
|
+
if (value === undefined) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
if (!/^\d+$/u.test(value)) {
|
|
103
|
+
throw new Error(`${flagName} must be a positive integer.`);
|
|
104
|
+
}
|
|
105
|
+
const parsed = Number.parseInt(value, 10);
|
|
106
|
+
if (parsed <= 0) {
|
|
107
|
+
throw new Error(`${flagName} must be greater than zero.`);
|
|
108
|
+
}
|
|
109
|
+
if (parsed > MAX_MAX_CONCURRENCY) {
|
|
110
|
+
throw new Error(`${flagName} must be less than or equal to ${MAX_MAX_CONCURRENCY}.`);
|
|
111
|
+
}
|
|
112
|
+
return parsed;
|
|
113
|
+
};
|
|
114
|
+
const parseMode = (value) => {
|
|
115
|
+
if (value === undefined) {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
if (value === 'preview' || value === 'dev') {
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
121
|
+
throw new Error(`Unsupported mode value: ${value}`);
|
|
122
|
+
};
|
|
123
|
+
const isHelpSignal = (error) => error instanceof Error && error.message === '__USHMAN_EQUIV_HELP__';
|
|
124
|
+
const resolveWorkspaceFlag = ({ positionals, workspaceFlag, }) => {
|
|
125
|
+
if (positionals.length > 1) {
|
|
126
|
+
throw new Error(`Expected at most one workspace path, received ${positionals.length}: ${positionals.join(', ')}`);
|
|
127
|
+
}
|
|
128
|
+
const positionalWorkspace = positionals[0];
|
|
129
|
+
if (workspaceFlag && positionalWorkspace && path.resolve(workspaceFlag) !== path.resolve(positionalWorkspace)) {
|
|
130
|
+
throw new Error('Cannot provide conflicting workspace values via both --workspace and a positional path.');
|
|
131
|
+
}
|
|
132
|
+
return path.resolve(workspaceFlag ?? positionalWorkspace ?? '.');
|
|
133
|
+
};
|
|
134
|
+
const parseCommonRunOptions = (argv) => {
|
|
135
|
+
const parsed = parseArgs({
|
|
136
|
+
allowPositionals: true,
|
|
137
|
+
args: argv,
|
|
138
|
+
options: {
|
|
139
|
+
baseline: { type: 'string' },
|
|
140
|
+
entrypoint: { type: 'string' },
|
|
141
|
+
filter: { type: 'string' },
|
|
142
|
+
fixtures: { type: 'string' },
|
|
143
|
+
help: { short: 'h', type: 'boolean' },
|
|
144
|
+
json: { type: 'boolean' },
|
|
145
|
+
'max-concurrency': { type: 'string' },
|
|
146
|
+
mode: { type: 'string' },
|
|
147
|
+
modules: { type: 'string' },
|
|
148
|
+
'src-roots': { type: 'string' },
|
|
149
|
+
tier: { multiple: true, type: 'string' },
|
|
150
|
+
tiers: { type: 'string' },
|
|
151
|
+
version: { short: 'v', type: 'boolean' },
|
|
152
|
+
workspace: { type: 'string' },
|
|
153
|
+
'write-result': { type: 'boolean' },
|
|
154
|
+
},
|
|
155
|
+
strict: true,
|
|
156
|
+
});
|
|
157
|
+
if (parsed.values.help) {
|
|
158
|
+
process.stdout.write(`${renderHelpText()}\n`);
|
|
159
|
+
throw new Error('__USHMAN_EQUIV_HELP__');
|
|
160
|
+
}
|
|
161
|
+
if (parsed.values.version) {
|
|
162
|
+
throw new Error('__USHMAN_EQUIV_VERSION__');
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
baselineSymbolsPath: parsed.values.baseline,
|
|
166
|
+
entrypoint: parsed.values.entrypoint,
|
|
167
|
+
filter: parsed.values.filter,
|
|
168
|
+
fixturesDir: parsed.values.fixtures,
|
|
169
|
+
json: parsed.values.json ?? false,
|
|
170
|
+
maxConcurrency: parsePositiveIntegerOption(parsed.values['max-concurrency'], '--max-concurrency'),
|
|
171
|
+
mode: parseMode(parsed.values.mode),
|
|
172
|
+
modulesDir: parsed.values.modules,
|
|
173
|
+
srcRoots: parsed.values['src-roots']
|
|
174
|
+
?.split(',')
|
|
175
|
+
.map((value) => value.trim())
|
|
176
|
+
.filter(Boolean),
|
|
177
|
+
tiers: parseTierValues(parsed.values.tier, parsed.values.tiers),
|
|
178
|
+
workspaceRoot: resolveWorkspaceFlag({
|
|
179
|
+
positionals: parsed.positionals,
|
|
180
|
+
workspaceFlag: parsed.values.workspace,
|
|
181
|
+
}),
|
|
182
|
+
writeResult: parsed.values['write-result'] ?? false,
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
const parseEmitBaselineOptions = (argv) => {
|
|
186
|
+
const parsed = parseArgs({
|
|
187
|
+
allowPositionals: true,
|
|
188
|
+
args: argv,
|
|
189
|
+
options: {
|
|
190
|
+
entrypoint: { type: 'string' },
|
|
191
|
+
filter: { type: 'string' },
|
|
192
|
+
help: { short: 'h', type: 'boolean' },
|
|
193
|
+
out: { type: 'string' },
|
|
194
|
+
'src-roots': { type: 'string' },
|
|
195
|
+
version: { short: 'v', type: 'boolean' },
|
|
196
|
+
workspace: { type: 'string' },
|
|
197
|
+
},
|
|
198
|
+
strict: true,
|
|
199
|
+
});
|
|
200
|
+
if (parsed.values.help) {
|
|
201
|
+
process.stdout.write(`${renderHelpText('emit-baseline')}\n`);
|
|
202
|
+
throw new Error('__USHMAN_EQUIV_HELP__');
|
|
203
|
+
}
|
|
204
|
+
if (parsed.values.version) {
|
|
205
|
+
throw new Error('__USHMAN_EQUIV_VERSION__');
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
entrypoint: parsed.values.entrypoint,
|
|
209
|
+
filter: parsed.values.filter,
|
|
210
|
+
outputPath: parsed.values.out ? path.resolve(parsed.values.out) : undefined,
|
|
211
|
+
srcRoots: parsed.values['src-roots']
|
|
212
|
+
?.split(',')
|
|
213
|
+
.map((value) => value.trim())
|
|
214
|
+
.filter(Boolean),
|
|
215
|
+
workspaceRoot: resolveWorkspaceFlag({
|
|
216
|
+
positionals: parsed.positionals,
|
|
217
|
+
workspaceFlag: parsed.values.workspace,
|
|
218
|
+
}),
|
|
219
|
+
};
|
|
220
|
+
};
|
|
221
|
+
const runEmitBaselineCommand = async (argv) => {
|
|
222
|
+
let options;
|
|
223
|
+
try {
|
|
224
|
+
options = parseEmitBaselineOptions(argv.slice(1));
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
if (isHelpSignal(error)) {
|
|
228
|
+
return 0;
|
|
229
|
+
}
|
|
230
|
+
if (error instanceof Error && error.message === '__USHMAN_EQUIV_VERSION__') {
|
|
231
|
+
process.stdout.write(`${await resolvePackageVersion()}\n`);
|
|
232
|
+
return 0;
|
|
233
|
+
}
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
const result = await emitSymbolBaseline(options);
|
|
238
|
+
process.stdout.write(`schema=ushman-equiv-baseline/v1 symbols=${result.symbolCount} out=${result.outputPath}\n`);
|
|
239
|
+
return 0;
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
if (error instanceof WorkspaceLayoutError) {
|
|
243
|
+
process.stderr.write(`${error.message}\n`);
|
|
244
|
+
return error.exitCode;
|
|
245
|
+
}
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
const parseRunOptions = (argv) => {
|
|
250
|
+
try {
|
|
251
|
+
return parseCommonRunOptions(argv);
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
if (isHelpSignal(error)) {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
if (error instanceof Error && error.message === '__USHMAN_EQUIV_VERSION__') {
|
|
258
|
+
return '__USHMAN_EQUIV_VERSION__';
|
|
259
|
+
}
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
const runEquivCommand = async (options) => {
|
|
264
|
+
try {
|
|
265
|
+
const report = await runEquiv(options);
|
|
266
|
+
process.stdout.write(options.json ? `${JSON.stringify(report, null, 2)}\n` : `${renderEquivReport(report)}\n`);
|
|
267
|
+
if (options.writeResult) {
|
|
268
|
+
await writeEquivResult({
|
|
269
|
+
report,
|
|
270
|
+
workspaceRoot: options.workspaceRoot,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
return report.verdict === 'red' ? 1 : 0;
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
if (error instanceof WorkspaceLayoutError) {
|
|
277
|
+
process.stderr.write(`${error.message}\n`);
|
|
278
|
+
return error.exitCode;
|
|
279
|
+
}
|
|
280
|
+
throw error;
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
export const main = async (argv = process.argv.slice(2)) => {
|
|
284
|
+
if (argv[0] === 'emit-baseline') {
|
|
285
|
+
return runEmitBaselineCommand(argv);
|
|
286
|
+
}
|
|
287
|
+
const options = parseRunOptions(argv);
|
|
288
|
+
if (!options) {
|
|
289
|
+
return 0;
|
|
290
|
+
}
|
|
291
|
+
if (options === '__USHMAN_EQUIV_VERSION__') {
|
|
292
|
+
process.stdout.write(`${await resolvePackageVersion()}\n`);
|
|
293
|
+
return 0;
|
|
294
|
+
}
|
|
295
|
+
return runEquivCommand(options);
|
|
296
|
+
};
|
|
297
|
+
const directExecutionHref = process.argv[1] ? pathToFileURL(process.argv[1]).href : null;
|
|
298
|
+
if (directExecutionHref === import.meta.url) {
|
|
299
|
+
runWithHeapGuard(() => main()).then((code) => {
|
|
300
|
+
process.exit(code);
|
|
301
|
+
}, (error) => {
|
|
302
|
+
if (isHelpSignal(error)) {
|
|
303
|
+
process.exit(0);
|
|
304
|
+
}
|
|
305
|
+
if (error instanceof WorkspaceLayoutError) {
|
|
306
|
+
process.stderr.write(`${error.message}\n`);
|
|
307
|
+
process.exit(error.exitCode);
|
|
308
|
+
}
|
|
309
|
+
process.stderr.write(`${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`);
|
|
310
|
+
process.exit(2);
|
|
311
|
+
});
|
|
312
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CandidateBootSmokeResult } from './candidate-boot.ts';
|
|
2
|
+
import { type AnalysisContextSeed, type CandidateSymbolAnalysisOptions, type CandidateSymbolCollection, type ImportGraphAnalysis, type SharedAnalysisContext } from './analysis-context.ts';
|
|
3
|
+
import type { EquivMode, ImportGraphOptions } from './types.ts';
|
|
4
|
+
type CandidateBootOptions = {
|
|
5
|
+
readonly mode?: EquivMode;
|
|
6
|
+
readonly workspaceRoot: string;
|
|
7
|
+
};
|
|
8
|
+
type EquivExecutionContextDependencies = {
|
|
9
|
+
readonly createAnalysisContext?: (options: AnalysisContextSeed) => Promise<SharedAnalysisContext>;
|
|
10
|
+
readonly smokeCandidateBoot?: (options: CandidateBootOptions) => Promise<CandidateBootSmokeResult>;
|
|
11
|
+
};
|
|
12
|
+
export type EquivExecutionContext = {
|
|
13
|
+
readonly getAnalysisContext: (options: AnalysisContextSeed) => Promise<SharedAnalysisContext>;
|
|
14
|
+
readonly getCandidateBoot: (options: CandidateBootOptions) => Promise<CandidateBootSmokeResult>;
|
|
15
|
+
readonly getCandidateSymbols: (options: CandidateSymbolAnalysisOptions) => Promise<CandidateSymbolCollection>;
|
|
16
|
+
readonly getImportGraph: (options: ImportGraphOptions) => Promise<ImportGraphAnalysis>;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Creates the internal execution context shared by `runEquiv()` and tier-local
|
|
20
|
+
* helpers so boot smoke, import-graph analysis, and symbol analysis are reused
|
|
21
|
+
* across tiers instead of recomputed or tunneled through private option fields.
|
|
22
|
+
*/
|
|
23
|
+
export declare const createEquivExecutionContext: (dependencies?: EquivExecutionContextDependencies) => EquivExecutionContext;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=equiv-execution-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"equiv-execution-context.d.ts","sourceRoot":"","sources":["../src/equiv-execution-context.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAEpE,OAAO,EAGH,KAAK,mBAAmB,EACxB,KAAK,8BAA8B,EACnC,KAAK,yBAAyB,EAC9B,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC7B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAQhE,KAAK,oBAAoB,GAAG;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAClC,CAAC;AAEF,KAAK,iCAAiC,GAAG;IACrC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAClG,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,wBAAwB,CAAC,CAAC;CACtG,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAChC,QAAQ,CAAC,kBAAkB,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC9F,QAAQ,CAAC,gBAAgB,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,wBAAwB,CAAC,CAAC;IAChG,QAAQ,CAAC,mBAAmB,EAAE,CAC1B,OAAO,EAAE,8BAA8B,KACtC,OAAO,CAAC,yBAAyB,CAAC,CAAC;IACxC,QAAQ,CAAC,cAAc,EAAE,CAAC,OAAO,EAAE,kBAAkB,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;CAC1F,CAAC;AAkEF;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,GACpC,eAAc,iCAAsC,KACrD,qBAqCF,CAAC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { smokeCandidateBoot } from "./candidate-boot.js";
|
|
3
|
+
import { createAnalysisContext, selectCandidateSymbols, } from "./analysis-context.js";
|
|
4
|
+
import { DEFAULT_EQUIV_MODE } from "./workspace.js";
|
|
5
|
+
const normalizeWorkspaceInputPath = (workspaceRoot, candidatePath) => path.isAbsolute(candidatePath) ? path.resolve(candidatePath) : path.resolve(workspaceRoot, candidatePath);
|
|
6
|
+
const normalizeWorkspaceRoot = (workspaceRoot) => path.resolve(workspaceRoot);
|
|
7
|
+
const normalizeStringArray = (values) => values ? [...new Set(values)].sort() : [];
|
|
8
|
+
const memoizePromise = (cache, key, loader) => {
|
|
9
|
+
const cached = cache.get(key);
|
|
10
|
+
if (cached) {
|
|
11
|
+
return cached;
|
|
12
|
+
}
|
|
13
|
+
const pending = loader().catch((error) => {
|
|
14
|
+
cache.delete(key);
|
|
15
|
+
throw error;
|
|
16
|
+
});
|
|
17
|
+
cache.set(key, pending);
|
|
18
|
+
return pending;
|
|
19
|
+
};
|
|
20
|
+
const createAnalysisKey = (options) => JSON.stringify({
|
|
21
|
+
entrypoint: options.entrypoint === undefined
|
|
22
|
+
? null
|
|
23
|
+
: normalizeWorkspaceInputPath(options.workspaceRoot, options.entrypoint),
|
|
24
|
+
srcRoots: normalizeStringArray(options.srcRoots).map((srcRoot) => normalizeWorkspaceInputPath(options.workspaceRoot, srcRoot)),
|
|
25
|
+
workspaceRoot: normalizeWorkspaceRoot(options.workspaceRoot),
|
|
26
|
+
});
|
|
27
|
+
const createCandidateSymbolsKey = (options) => JSON.stringify({
|
|
28
|
+
entrypoint: options.entrypoint === undefined
|
|
29
|
+
? null
|
|
30
|
+
: normalizeWorkspaceInputPath(options.workspaceRoot, options.entrypoint),
|
|
31
|
+
filter: options.filter ?? null,
|
|
32
|
+
srcRoots: normalizeStringArray(options.srcRoots).map((srcRoot) => normalizeWorkspaceInputPath(options.workspaceRoot, srcRoot)),
|
|
33
|
+
workspaceRoot: normalizeWorkspaceRoot(options.workspaceRoot),
|
|
34
|
+
});
|
|
35
|
+
const createImportGraphKey = (options) => JSON.stringify({
|
|
36
|
+
entrypoint: options.entrypoint === undefined
|
|
37
|
+
? null
|
|
38
|
+
: normalizeWorkspaceInputPath(options.workspaceRoot, options.entrypoint),
|
|
39
|
+
filter: options.filter ?? null,
|
|
40
|
+
srcRoots: normalizeStringArray(options.srcRoots).map((srcRoot) => normalizeWorkspaceInputPath(options.workspaceRoot, srcRoot)),
|
|
41
|
+
workspaceRoot: normalizeWorkspaceRoot(options.workspaceRoot),
|
|
42
|
+
});
|
|
43
|
+
const createCandidateBootKey = (options) => JSON.stringify({
|
|
44
|
+
mode: options.mode ?? DEFAULT_EQUIV_MODE,
|
|
45
|
+
workspaceRoot: normalizeWorkspaceRoot(options.workspaceRoot),
|
|
46
|
+
});
|
|
47
|
+
/**
|
|
48
|
+
* Creates the internal execution context shared by `runEquiv()` and tier-local
|
|
49
|
+
* helpers so boot smoke, import-graph analysis, and symbol analysis are reused
|
|
50
|
+
* across tiers instead of recomputed or tunneled through private option fields.
|
|
51
|
+
*/
|
|
52
|
+
export const createEquivExecutionContext = (dependencies = {}) => {
|
|
53
|
+
const analysisContextCache = new Map();
|
|
54
|
+
const importGraphCache = new Map();
|
|
55
|
+
const candidateSymbolsCache = new Map();
|
|
56
|
+
const candidateBootCache = new Map();
|
|
57
|
+
const loadAnalysisContext = (options) => memoizePromise(analysisContextCache, createAnalysisKey(options), async () => (dependencies.createAnalysisContext ?? createAnalysisContext)(options));
|
|
58
|
+
return {
|
|
59
|
+
getAnalysisContext(options) {
|
|
60
|
+
return loadAnalysisContext(options);
|
|
61
|
+
},
|
|
62
|
+
getCandidateBoot(options) {
|
|
63
|
+
return memoizePromise(candidateBootCache, createCandidateBootKey(options), async () => (dependencies.smokeCandidateBoot ?? smokeCandidateBoot)(options));
|
|
64
|
+
},
|
|
65
|
+
getCandidateSymbols(options) {
|
|
66
|
+
return memoizePromise(candidateSymbolsCache, createCandidateSymbolsKey(options), async () => {
|
|
67
|
+
const analysisContext = await loadAnalysisContext(options);
|
|
68
|
+
const index = await analysisContext.getSymbolIndex();
|
|
69
|
+
return selectCandidateSymbols({
|
|
70
|
+
filter: options.filter,
|
|
71
|
+
index,
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
getImportGraph(options) {
|
|
76
|
+
return memoizePromise(importGraphCache, createImportGraphKey(options), async () => {
|
|
77
|
+
const analysisContext = await loadAnalysisContext(options);
|
|
78
|
+
return analysisContext.getImportGraphAnalysis(options.filter);
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { canonicalize } from './canonicalize.ts';
|
|
2
|
+
export { main as cliMain } from './cli.ts';
|
|
3
|
+
export { createEquivExecutionContext } from './equiv-execution-context.ts';
|
|
4
|
+
export { renderEquivReport, runEquiv, writeEquivResult } from './run.ts';
|
|
5
|
+
export { checkImportGraph, checkImportGraphWithContext } from './tier-i-import-graph.ts';
|
|
6
|
+
export { checkModuleLoad, checkModuleLoadWithContext } from './tier-l-module-load.ts';
|
|
7
|
+
export { checkReplay, checkReplayWithContext } from './tier-r-replay.ts';
|
|
8
|
+
export { checkSymbolDiff, checkSymbolDiffWithContext, emitSymbolBaseline } from './tier-s-symbol-diff.ts';
|
|
9
|
+
export type { BareImportUsage, CandidateSymbolCollection, ImportGraphAnalysis, SharedAnalysisContext, WorkspaceSymbolIndex, } from './analysis-context.ts';
|
|
10
|
+
export type { EquivExecutionContext } from './equiv-execution-context.ts';
|
|
11
|
+
export type { EquivCheckResult, EquivFinding, EquivMode, EquivOptions, EquivReport, EquivSeverity, EquivTier, ImportGraphOptions, ModuleLoadOptions, ReplayFixtureDocument, ReplayOptions, SymbolBaselineDeclaration, SymbolBaselineDeclKind, SymbolBaselineDocument, SymbolDiffOptions, } from './types.ts';
|
|
12
|
+
export { ALL_EQUIV_MODES, ALL_TIERS, EQUIV_BASELINE_SCHEMA_NAME, EQUIV_BASELINE_SCHEMA_VERSION, EQUIV_REPORT_VERSION, EQUIV_RESULT_SCHEMA_NAME, EQUIV_RESULT_SCHEMA_VERSION, verdictForChecks, } from './types.ts';
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,IAAI,IAAI,OAAO,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,2BAA2B,EAAE,MAAM,8BAA8B,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AACzF,OAAO,EAAE,eAAe,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AACtF,OAAO,EAAE,WAAW,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAE,eAAe,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC1G,YAAY,EACR,eAAe,EACf,yBAAyB,EACzB,mBAAmB,EACnB,qBAAqB,EACrB,oBAAoB,GACvB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC1E,YAAY,EACR,gBAAgB,EAChB,YAAY,EACZ,SAAS,EACT,YAAY,EACZ,WAAW,EACX,aAAa,EACb,SAAS,EACT,kBAAkB,EAClB,iBAAiB,EACjB,qBAAqB,EACrB,aAAa,EACb,yBAAyB,EACzB,sBAAsB,EACtB,sBAAsB,EACtB,iBAAiB,GACpB,MAAM,YAAY,CAAC;AACpB,OAAO,EACH,eAAe,EACf,SAAS,EACT,0BAA0B,EAC1B,6BAA6B,EAC7B,oBAAoB,EACpB,wBAAwB,EACxB,2BAA2B,EAC3B,gBAAgB,GACnB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Public surface of ushman-equiv.
|
|
2
|
+
// Pure-Node Tier I/L/S/R equivalence checks for ushman cleanup workspaces.
|
|
3
|
+
export { canonicalize } from "./canonicalize.js";
|
|
4
|
+
export { main as cliMain } from "./cli.js";
|
|
5
|
+
export { createEquivExecutionContext } from "./equiv-execution-context.js";
|
|
6
|
+
export { renderEquivReport, runEquiv, writeEquivResult } from "./run.js";
|
|
7
|
+
export { checkImportGraph, checkImportGraphWithContext } from "./tier-i-import-graph.js";
|
|
8
|
+
export { checkModuleLoad, checkModuleLoadWithContext } from "./tier-l-module-load.js";
|
|
9
|
+
export { checkReplay, checkReplayWithContext } from "./tier-r-replay.js";
|
|
10
|
+
export { checkSymbolDiff, checkSymbolDiffWithContext, emitSymbolBaseline } from "./tier-s-symbol-diff.js";
|
|
11
|
+
export { ALL_EQUIV_MODES, ALL_TIERS, EQUIV_BASELINE_SCHEMA_NAME, EQUIV_BASELINE_SCHEMA_VERSION, EQUIV_REPORT_VERSION, EQUIV_RESULT_SCHEMA_NAME, EQUIV_RESULT_SCHEMA_VERSION, verdictForChecks, } from "./types.js";
|
package/dist/run.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { EquivOptions, EquivReport } from './types.ts';
|
|
2
|
+
export declare const runEquiv: (options: EquivOptions) => Promise<EquivReport>;
|
|
3
|
+
export declare const renderEquivReport: (report: EquivReport) => string;
|
|
4
|
+
export declare const writeEquivResult: ({ report, workspaceRoot, }: {
|
|
5
|
+
readonly report: EquivReport;
|
|
6
|
+
readonly workspaceRoot: string;
|
|
7
|
+
}) => Promise<void>;
|
|
8
|
+
//# sourceMappingURL=run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAoB,YAAY,EAAE,WAAW,EAAa,MAAM,YAAY,CAAC;AAiFzF,eAAO,MAAM,QAAQ,GAAU,SAAS,YAAY,KAAG,OAAO,CAAC,WAAW,CA+BzE,CAAC;AAcF,eAAO,MAAM,iBAAiB,GAAI,QAAQ,WAAW,KAAG,MAkBvD,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAU,4BAGpC;IACC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAClC,KAAG,OAAO,CAAC,IAAI,CAEf,CAAC"}
|
package/dist/run.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { buildValidatorResultRecord, openLedger } from 'ushman-ledger';
|
|
2
|
+
import { createEquivExecutionContext } from "./equiv-execution-context.js";
|
|
3
|
+
import { indentMultiline, resolvePackageVersion } from "./shared.js";
|
|
4
|
+
import { checkImportGraphWithContext } from "./tier-i-import-graph.js";
|
|
5
|
+
import { checkModuleLoadWithContext } from "./tier-l-module-load.js";
|
|
6
|
+
import { checkReplayWithContext } from "./tier-r-replay.js";
|
|
7
|
+
import { checkSymbolDiffWithContext } from "./tier-s-symbol-diff.js";
|
|
8
|
+
import { ALL_TIERS, EQUIV_REPORT_VERSION, EQUIV_RESULT_SCHEMA_NAME, EQUIV_RESULT_SCHEMA_VERSION, verdictForChecks, } from "./types.js";
|
|
9
|
+
import { assertV4Workspace, DEFAULT_EQUIV_MODE, defaultResultPath, writeTextFileAtomically } from "./workspace.js";
|
|
10
|
+
const timestamp = () => new Date().toISOString().replace(/\.\d+Z$/u, 'Z');
|
|
11
|
+
const normalizeTiers = (tiers) => {
|
|
12
|
+
if (!tiers || tiers.length === 0) {
|
|
13
|
+
return ALL_TIERS;
|
|
14
|
+
}
|
|
15
|
+
const unique = new Set(tiers);
|
|
16
|
+
return ALL_TIERS.filter((tier) => unique.has(tier));
|
|
17
|
+
};
|
|
18
|
+
const wrapUnexpectedTierError = (tier, error) => ({
|
|
19
|
+
details: [
|
|
20
|
+
{
|
|
21
|
+
evidence: error instanceof Error ? (error.stack ?? error.message) : String(error),
|
|
22
|
+
message: `Equiv crashed while running tier ${tier}.`,
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
elapsedMs: 0,
|
|
26
|
+
severity: 'red',
|
|
27
|
+
summary: `tier=${tier} crashed`,
|
|
28
|
+
tier,
|
|
29
|
+
});
|
|
30
|
+
const TIER_HANDLERS = {
|
|
31
|
+
I: checkImportGraphWithContext,
|
|
32
|
+
L: checkModuleLoadWithContext,
|
|
33
|
+
R: checkReplayWithContext,
|
|
34
|
+
S: checkSymbolDiffWithContext,
|
|
35
|
+
};
|
|
36
|
+
const recordValidatorResult = async ({ report, version, workspaceRoot, }) => {
|
|
37
|
+
try {
|
|
38
|
+
const ledger = await openLedger(workspaceRoot);
|
|
39
|
+
await ledger.record(buildValidatorResultRecord({
|
|
40
|
+
details: {
|
|
41
|
+
checks: report.checks,
|
|
42
|
+
schemaVersion: report.schemaVersion,
|
|
43
|
+
tiers: report.tiers,
|
|
44
|
+
},
|
|
45
|
+
emitter: {
|
|
46
|
+
tool: 'ushman-equiv',
|
|
47
|
+
version,
|
|
48
|
+
},
|
|
49
|
+
kind: 'validator-result',
|
|
50
|
+
metrics: {
|
|
51
|
+
checkCount: report.checks.length,
|
|
52
|
+
mode: report.mode,
|
|
53
|
+
verdict: report.verdict,
|
|
54
|
+
},
|
|
55
|
+
phase: 'equiv',
|
|
56
|
+
summary: `equiv ${report.verdict}`,
|
|
57
|
+
validator: 'equiv',
|
|
58
|
+
verdict: report.verdict,
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
process.emitWarning(`ushman-equiv could not record the validator result in the ledger: ${error instanceof Error ? error.message : String(error)}`);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
export const runEquiv = async (options) => {
|
|
66
|
+
await assertV4Workspace(options.workspaceRoot);
|
|
67
|
+
const tiers = normalizeTiers(options.tiers);
|
|
68
|
+
const version = await resolvePackageVersion();
|
|
69
|
+
const executionContext = createEquivExecutionContext();
|
|
70
|
+
const checks = [];
|
|
71
|
+
for (const tier of tiers) {
|
|
72
|
+
try {
|
|
73
|
+
checks.push(await TIER_HANDLERS[tier](options, executionContext));
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
checks.push(wrapUnexpectedTierError(tier, error));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const report = {
|
|
80
|
+
checks,
|
|
81
|
+
createdAt: timestamp(),
|
|
82
|
+
mode: options.mode ?? DEFAULT_EQUIV_MODE,
|
|
83
|
+
reportVersion: EQUIV_REPORT_VERSION,
|
|
84
|
+
schemaName: EQUIV_RESULT_SCHEMA_NAME,
|
|
85
|
+
schemaVersion: EQUIV_RESULT_SCHEMA_VERSION,
|
|
86
|
+
tiers,
|
|
87
|
+
ushmanEquivVersion: version,
|
|
88
|
+
verdict: verdictForChecks(checks),
|
|
89
|
+
workspace: options.workspaceRoot,
|
|
90
|
+
};
|
|
91
|
+
await recordValidatorResult({
|
|
92
|
+
report,
|
|
93
|
+
version,
|
|
94
|
+
workspaceRoot: options.workspaceRoot,
|
|
95
|
+
});
|
|
96
|
+
return report;
|
|
97
|
+
};
|
|
98
|
+
const renderDetail = (detail) => {
|
|
99
|
+
const prefix = [detail.path, detail.symbol].filter(Boolean).join(' ');
|
|
100
|
+
const evidence = detail.evidence === undefined ? null : indentMultiline(JSON.stringify(detail.evidence, null, 2), ' ');
|
|
101
|
+
return [
|
|
102
|
+
prefix ? ` - ${prefix}: ${detail.message}` : ` - ${detail.message}`,
|
|
103
|
+
evidence ? ` ${evidence}` : null,
|
|
104
|
+
]
|
|
105
|
+
.filter((line) => Boolean(line))
|
|
106
|
+
.join('\n');
|
|
107
|
+
};
|
|
108
|
+
export const renderEquivReport = (report) => {
|
|
109
|
+
const lines = [
|
|
110
|
+
`ushman equiv @ ${report.createdAt}`,
|
|
111
|
+
`workspace: ${report.workspace}`,
|
|
112
|
+
`mode: ${report.mode}`,
|
|
113
|
+
`report: v${report.reportVersion}`,
|
|
114
|
+
`schema: ${report.schemaName}@${report.schemaVersion}`,
|
|
115
|
+
`package: ${report.ushmanEquivVersion}`,
|
|
116
|
+
`verdict: ${report.verdict.toUpperCase()}`,
|
|
117
|
+
'',
|
|
118
|
+
];
|
|
119
|
+
for (const check of report.checks) {
|
|
120
|
+
lines.push(`[${check.severity.toUpperCase()}] ${check.tier}: ${check.summary} (${check.elapsedMs}ms)`);
|
|
121
|
+
for (const detail of check.details ?? []) {
|
|
122
|
+
lines.push(renderDetail(detail));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return lines.join('\n');
|
|
126
|
+
};
|
|
127
|
+
export const writeEquivResult = async ({ report, workspaceRoot, }) => {
|
|
128
|
+
await writeTextFileAtomically(defaultResultPath(workspaceRoot), `${JSON.stringify(report, null, 2)}\n`);
|
|
129
|
+
};
|
package/dist/shared.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const compareStrings: (left: string, right: string) => number;
|
|
2
|
+
export declare const indentMultiline: (value: string, indent: string) => string;
|
|
3
|
+
export declare const readPositiveIntegerEnv: (name: string, fallback: number) => number;
|
|
4
|
+
export declare const captureChildText: ({ maxBytes, stream, }: {
|
|
5
|
+
readonly maxBytes: number;
|
|
6
|
+
readonly stream: NodeJS.ReadableStream | null | undefined;
|
|
7
|
+
}) => (() => string);
|
|
8
|
+
export declare const resolvePackageVersion: () => Promise<string>;
|
|
9
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc,GAAI,MAAM,MAAM,EAAE,OAAO,MAAM,KAAG,MAQ5D,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,OAAO,MAAM,EAAE,QAAQ,MAAM,KAAG,MAI7C,CAAC;AAEpB,eAAO,MAAM,sBAAsB,GAAI,MAAM,MAAM,EAAE,UAAU,MAAM,KAAG,MAOvE,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,uBAG9B;IACC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,cAAc,GAAG,IAAI,GAAG,SAAS,CAAC;CAC7D,KAAG,CAAC,MAAM,MAAM,CAahB,CAAC;AAIF,eAAO,MAAM,qBAAqB,QAAa,OAAO,CAAC,MAAM,CAU5D,CAAC"}
|
package/dist/shared.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
export const compareStrings = (left, right) => {
|
|
3
|
+
if (left < right) {
|
|
4
|
+
return -1;
|
|
5
|
+
}
|
|
6
|
+
if (left > right) {
|
|
7
|
+
return 1;
|
|
8
|
+
}
|
|
9
|
+
return 0;
|
|
10
|
+
};
|
|
11
|
+
export const indentMultiline = (value, indent) => value
|
|
12
|
+
.split('\n')
|
|
13
|
+
.map((line) => `${indent}${line}`)
|
|
14
|
+
.join('\n');
|
|
15
|
+
export const readPositiveIntegerEnv = (name, fallback) => {
|
|
16
|
+
const rawValue = process.env[name];
|
|
17
|
+
if (!rawValue) {
|
|
18
|
+
return fallback;
|
|
19
|
+
}
|
|
20
|
+
const parsed = Number.parseInt(rawValue, 10);
|
|
21
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
22
|
+
};
|
|
23
|
+
export const captureChildText = ({ maxBytes, stream, }) => {
|
|
24
|
+
let text = '';
|
|
25
|
+
if (!stream) {
|
|
26
|
+
return () => text.trim();
|
|
27
|
+
}
|
|
28
|
+
stream.setEncoding('utf8');
|
|
29
|
+
stream.on('data', (chunk) => {
|
|
30
|
+
text += chunk;
|
|
31
|
+
if (text.length > maxBytes) {
|
|
32
|
+
text = `...<truncated>\n${text.slice(-maxBytes)}`;
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
return () => text.trim();
|
|
36
|
+
};
|
|
37
|
+
const FALLBACK_PACKAGE_VERSION = '0.1.0';
|
|
38
|
+
export const resolvePackageVersion = async () => {
|
|
39
|
+
try {
|
|
40
|
+
const packageJson = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8'));
|
|
41
|
+
return packageJson.version ?? FALLBACK_PACKAGE_VERSION;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Bundled single-file harnesses do not always carry a sibling package.json.
|
|
45
|
+
return FALLBACK_PACKAGE_VERSION;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type EquivExecutionContext } from './equiv-execution-context.ts';
|
|
2
|
+
import type { EquivCheckResult, ImportGraphOptions } from './types.ts';
|
|
3
|
+
declare const checkImportGraphWithContext: (options: ImportGraphOptions, context: EquivExecutionContext) => Promise<EquivCheckResult>;
|
|
4
|
+
export declare const checkImportGraph: (options: ImportGraphOptions) => Promise<EquivCheckResult>;
|
|
5
|
+
export { checkImportGraphWithContext };
|
|
6
|
+
export type { BareImportUsage, ImportGraphAnalysis } from './analysis-context.ts';
|
|
7
|
+
//# sourceMappingURL=tier-i-import-graph.d.ts.map
|