@systemfsoftware/arethetypeswrong-cli 1.1.1 → 2.0.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.
@@ -0,0 +1,624 @@
1
+ import { Context, Effect, Layer, Schema } from "effect";
2
+ import * as Command from "effect/unstable/cli/Command";
3
+ import * as Config from "effect/Config";
4
+ import * as Effect$1 from "effect/Effect";
5
+ import * as Option from "effect/Option";
6
+ import * as S from "effect/Schema";
7
+ import * as Argument from "effect/unstable/cli/Argument";
8
+ import * as Flag from "effect/unstable/cli/Flag";
9
+ import { CheckPackage, CheckPackageLive, CheckResultSchema, PackageStoreAdapterStub } from "@systemfsoftware/arethetypeswrong-core";
10
+ import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner";
11
+ import * as PlatformFs from "effect/FileSystem";
12
+ import * as PlatformPathMod from "effect/Path";
13
+ import * as ChildProcess from "effect/unstable/process/ChildProcess";
14
+ import * as PlatformTerminal from "effect/Terminal";
15
+ //#region src/Filesystem.schema.ts
16
+ var FileNotFoundError = class extends Schema.TaggedError()("FileNotFoundError", { filePath: Schema.String }) {};
17
+ Schema.TaggedError()("DirectoryError", { directoryPath: Schema.String });
18
+ //#endregion
19
+ //#region src/FilesystemAdapter.ts
20
+ var CliFilesystem = class extends Context.Service()("@systemfsoftware/arethetypeswrong-cli/filesystem.adapter/Filesystem") {};
21
+ const fromPlatform = (fs, path) => ({
22
+ fileExists: (filePath) => fs.exists(filePath).pipe(Effect.orElseSucceed(() => false)),
23
+ isDirectory: (filePath) => fs.stat(filePath).pipe(Effect.map((s) => s.type === "directory"), Effect.orElseSucceed(() => false)),
24
+ readUtf8: (filePath) => fs.readFileString(filePath).pipe(Effect.mapError(() => new FileNotFoundError({ filePath }))),
25
+ readBytes: (filePath) => fs.readFile(filePath).pipe(Effect.mapError(() => new FileNotFoundError({ filePath }))),
26
+ deleteFile: (filePath) => fs.remove(filePath).pipe(Effect.orElseSucceed(() => void 0)),
27
+ resolve: (...segments) => path.resolve(...segments),
28
+ join: (...segments) => path.join(...segments)
29
+ });
30
+ const FilesystemLive = Layer.effect(CliFilesystem, Effect.gen(function* () {
31
+ const fs = yield* PlatformFs.FileSystem;
32
+ const path = yield* PlatformPathMod.Path;
33
+ return fromPlatform(fs, path);
34
+ }));
35
+ //#endregion
36
+ //#region src/GetExitCode.schema.ts
37
+ /**
38
+ * The exit-code decision's input. `result` is the core's own `CheckResult`, not
39
+ * an opaque payload: the decision reads `types` and `problems` off it, so the
40
+ * shape it relies on is stated here and a mismatch fails at the boundary.
41
+ */
42
+ var ComputeExitCodeCommand = class extends S.TaggedClass()("ComputeExitCodeCommand", {
43
+ result: CheckResultSchema,
44
+ ignoreRules: S.Array(S.String),
45
+ ignoreResolutions: S.Array(S.String)
46
+ }) {};
47
+ var ComputeExitCodeDecision = class extends S.TaggedClass()("ComputeExitCodeDecision", { exitCode: S.Number }) {};
48
+ //#endregion
49
+ //#region src/ProblemUtils.ts
50
+ const problemFlagForKind = (kind) => {
51
+ switch (kind) {
52
+ case "NoResolution": return "no-resolution";
53
+ case "UntypedResolution": return "untyped-resolution";
54
+ case "FalseCJS": return "false-cjs";
55
+ case "FalseESM": return "false-esm";
56
+ case "CJSResolvesToESM": return "cjs-resolves-to-esm";
57
+ case "FallbackCondition": return "fallback-condition";
58
+ case "CJSOnlyExportsDefault": return "cjs-only-exports-default";
59
+ case "NamedExports": return "named-exports";
60
+ case "FalseExportDefault": return "false-export-default";
61
+ case "MissingExportEquals": return "missing-export-equals";
62
+ case "UnexpectedModuleSyntax": return "unexpected-module-syntax";
63
+ case "InternalResolutionError": return "internal-resolution-error";
64
+ }
65
+ };
66
+ const CliFormat = [
67
+ "auto",
68
+ "table",
69
+ "table-flipped",
70
+ "ascii",
71
+ "json"
72
+ ];
73
+ const CliProfile = [
74
+ "strict",
75
+ "node16",
76
+ "esm-only"
77
+ ];
78
+ //#endregion
79
+ //#region src/GetExitCode.ts
80
+ const isVisibleProblem = (problem, ignoredRules, ignoredResolutions) => {
81
+ const ruleIgnored = ignoredRules.has(problemFlagForKind(problem.kind));
82
+ const resolutionIgnored = "resolutionKind" in problem && ignoredResolutions.has(problem.resolutionKind);
83
+ return !ruleIgnored && !resolutionIgnored;
84
+ };
85
+ const computeExitCode = (command) => {
86
+ const result = command.result;
87
+ if (result.types === false) return new ComputeExitCodeDecision({ exitCode: 0 });
88
+ const ignoredRules = new Set(command.ignoreRules);
89
+ const ignoredResolutions = new Set(command.ignoreResolutions);
90
+ return new ComputeExitCodeDecision({ exitCode: result.problems.some((p) => isVisibleProblem(p, ignoredRules, ignoredResolutions)) ? 1 : 0 });
91
+ };
92
+ //#endregion
93
+ //#region src/PackRunner.schema.ts
94
+ /** A failed `npm pack`, carrying the spawn failure it came from. */
95
+ var PackRunnerFailed = class extends Schema.TaggedError()("PackRunnerFailed", {
96
+ message: Schema.String,
97
+ cause: Schema.optional(Schema.Unknown)
98
+ }) {};
99
+ //#endregion
100
+ //#region src/PackRunnerAdapter.ts
101
+ var PackRunner = class extends Context.Service()("@systemfsoftware/arethetypeswrong-cli/pack-runner.adapter/PackRunner") {};
102
+ const PackRunnerLive = Layer.succeed(PackRunner, { pack: (cwd) => Effect.gen(function* () {
103
+ const spawner = yield* ChildProcessSpawner;
104
+ const cmd = ChildProcess.make("npm", ["pack", "--ignore-scripts"]).pipe(ChildProcess.setCwd(cwd));
105
+ const tarballName = (yield* spawner.string(cmd).pipe(Effect.mapError((e) => new PackRunnerFailed({
106
+ message: `npm pack failed in ${cwd}`,
107
+ cause: e
108
+ })))).trim().split("\n").pop() ?? "";
109
+ if (!tarballName) return yield* Effect.fail(new PackRunnerFailed({ message: "npm pack produced no tarball name" }));
110
+ return { tarballPath: tarballName };
111
+ }) });
112
+ //#endregion
113
+ //#region src/Profiles.schema.ts
114
+ /** The resolution kinds a profile may silence. */
115
+ const IgnoredResolution = S.Literals([
116
+ "node10",
117
+ "node16-cjs",
118
+ "node16-esm",
119
+ "bundler"
120
+ ]);
121
+ /** The profiles the CLI offers. */
122
+ const ProfileName = S.Literals([
123
+ "strict",
124
+ "node16",
125
+ "esm-only"
126
+ ]);
127
+ /**
128
+ * The request the profile decision receives. It carries the resolutions the
129
+ * caller already silenced - the only field the decision reads - so the decision
130
+ * never widens an opaque payload back to a shape it guessed.
131
+ */
132
+ var ApplyProfileCommand = class extends S.TaggedClass()("ApplyProfileCommand", {
133
+ profileName: ProfileName,
134
+ ignoreResolutions: S.optional(S.Array(IgnoredResolution))
135
+ }) {};
136
+ var ApplyProfileDecision = class extends S.TaggedClass()("ApplyProfileDecision", { ignoreResolutions: S.Array(IgnoredResolution) }) {};
137
+ //#endregion
138
+ //#region src/Profiles.ts
139
+ const profileIgnoreResolutions = {
140
+ strict: [],
141
+ node16: ["node10"],
142
+ "esm-only": ["node10", "node16-cjs"]
143
+ };
144
+ /**
145
+ * Merge the caller's silenced resolutions with the profile's own. Order is the
146
+ * caller's first, then the profile's, and duplicates are kept: the list is a
147
+ * record of what was asked for, not a set.
148
+ */
149
+ const applyProfile = (command) => new ApplyProfileDecision({ ignoreResolutions: [...command.ignoreResolutions ?? [], ...profileIgnoreResolutions[command.profileName]] });
150
+ //#endregion
151
+ //#region src/Registry.schema.ts
152
+ /**
153
+ * The packument fields this CLI reads. Declared so the response is decoded
154
+ * rather than asserted: a registry that changes shape fails here, naming the
155
+ * field, instead of surfacing as a member access on `any` further downstream.
156
+ */
157
+ const RegistryDocument = S.Struct({
158
+ name: S.String,
159
+ version: S.String,
160
+ dist: S.Struct({ tarball: S.String })
161
+ });
162
+ /** A registry request that never produced a tarball, carrying its cause. */
163
+ var RegistryFetchError = class extends S.TaggedError()("RegistryFetchError", {
164
+ message: S.String,
165
+ cause: S.optional(S.Unknown)
166
+ }) {};
167
+ //#endregion
168
+ //#region src/RenderTable.ts
169
+ const cellWidth = (cell) => visibleWidth(cell);
170
+ const visibleWidth = (s) => {
171
+ let w = 0;
172
+ for (const ch of s) {
173
+ if (ch === "\x1B") continue;
174
+ w += 1;
175
+ }
176
+ return w;
177
+ };
178
+ const computeColumnWidths = (header, rows) => {
179
+ const widths = header.map((h) => cellWidth(h));
180
+ for (const row of rows) for (let i = 0; i < row.length; i++) {
181
+ const cell = row[i] ?? "";
182
+ const w = cellWidth(cell);
183
+ if (w > (widths[i] ?? 0)) widths[i] = w;
184
+ }
185
+ return widths;
186
+ };
187
+ const renderCell = (cell, width) => cell.padEnd(width);
188
+ const renderTable = (header, rows, gap = 2) => {
189
+ if (header.length === 0) return "";
190
+ const widths = computeColumnWidths(header, rows);
191
+ const gapText = " ".repeat(gap);
192
+ const renderRow = (cells) => {
193
+ const parts = [];
194
+ for (let i = 0; i < cells.length; i++) {
195
+ if (i > 0) parts.push(gapText);
196
+ parts.push(renderCell(cells[i] ?? "", widths[i] ?? 0));
197
+ }
198
+ return parts.join("");
199
+ };
200
+ return [renderRow(header), ...rows.map(renderRow)].join("\n");
201
+ };
202
+ const renderFlippedTable = (header, rows, gap = 2) => {
203
+ if (header.length === 0) return "";
204
+ if (rows.length === 0) return header.join("\n");
205
+ const numCols = rows[0]?.length ?? 0;
206
+ if (numCols === 0) return header.join("\n");
207
+ const transposed = [];
208
+ for (let col = 0; col < numCols; col++) {
209
+ const newRow = [];
210
+ for (const row of rows) newRow.push(row[col] ?? "");
211
+ newRow.unshift(header[col] ?? "");
212
+ transposed.push(newRow);
213
+ }
214
+ return renderTable(transposed[0] ?? [], transposed.slice(1), gap);
215
+ };
216
+ //#endregion
217
+ //#region src/RenderAnsi.ts
218
+ const colorCode = (c) => {
219
+ switch (c) {
220
+ case "red": return "31";
221
+ case "green": return "32";
222
+ case "yellow": return "33";
223
+ case "blue": return "34";
224
+ case "magenta": return "35";
225
+ case "cyan": return "36";
226
+ case "gray": return "90";
227
+ case "white": return "37";
228
+ }
229
+ };
230
+ const annotate = (text, anno) => {
231
+ if (anno.color === void 0 && !anno.bold) return text;
232
+ const parts = [];
233
+ if (anno.bold) parts.push("1");
234
+ if (anno.color !== void 0) parts.push(colorCode(anno.color));
235
+ return `\u001b[${parts.join(";")}m${text}\u001b[0m`;
236
+ };
237
+ new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
238
+ const colorizeCell = (cell, color, annotations) => {
239
+ if (!color) return cell;
240
+ let out = cell;
241
+ for (const [marker, anno] of Object.entries(annotations)) if (out.includes(marker)) out = out.split(marker).join(annotate(marker, anno));
242
+ return out;
243
+ };
244
+ //#endregion
245
+ //#region src/RenderTyped.ts
246
+ const resolutionKindOrder = [
247
+ "node10",
248
+ "node16-cjs",
249
+ "node16-esm",
250
+ "bundler"
251
+ ];
252
+ const symbolForProblem = (p, useEmoji) => {
253
+ return (useEmoji ? {
254
+ NoResolution: "✘",
255
+ UntypedResolution: "◌",
256
+ FalseESM: "✘",
257
+ FalseCJS: "✘",
258
+ CJSResolvesToESM: "✘",
259
+ NamedExports: "✘",
260
+ FallbackCondition: "⚠",
261
+ FalseExportDefault: "✘",
262
+ MissingExportEquals: "✘",
263
+ UnexpectedModuleSyntax: "✘",
264
+ InternalResolutionError: "✘",
265
+ CJSOnlyExportsDefault: "✘"
266
+ } : {
267
+ NoResolution: "X",
268
+ UntypedResolution: "-",
269
+ FalseESM: "X",
270
+ FalseCJS: "X",
271
+ CJSResolvesToESM: "X",
272
+ NamedExports: "X",
273
+ FallbackCondition: "!",
274
+ FalseExportDefault: "X",
275
+ MissingExportEquals: "X",
276
+ UnexpectedModuleSyntax: "X",
277
+ InternalResolutionError: "X",
278
+ CJSOnlyExportsDefault: "X"
279
+ })[p.kind] ?? "?";
280
+ };
281
+ const cellKey = (entrypoint, resolutionKind) => `${entrypoint}\u0000${resolutionKind}`;
282
+ /**
283
+ * Bucket every problem into the (entrypoint x resolutionKind) cells it belongs to in one pass.
284
+ * A problem carrying neither field is global and lands in every cell, which is why the walk is
285
+ * over the problem's own axes rather than over the cells.
286
+ */
287
+ const partitionProblemsByCell = (entrypoints, problems) => {
288
+ const cells = /* @__PURE__ */ new Map();
289
+ for (const entrypoint of entrypoints) for (const resolutionKind of resolutionKindOrder) cells.set(cellKey(entrypoint, resolutionKind), []);
290
+ for (const problem of problems) {
291
+ const axisEntrypoints = "entrypoint" in problem ? [problem.entrypoint] : entrypoints;
292
+ const axisKinds = "resolutionKind" in problem ? [problem.resolutionKind] : resolutionKindOrder;
293
+ for (const entrypoint of axisEntrypoints) for (const resolutionKind of axisKinds) cells.get(cellKey(entrypoint, resolutionKind))?.push(problem);
294
+ }
295
+ return cells;
296
+ };
297
+ const problemsForCell = (cells, entrypoint, resolutionKind) => cells.get(cellKey(entrypoint, resolutionKind)) ?? [];
298
+ const renderTypedAnalysis = (entrypoints, problems, opts, annotations = {}) => {
299
+ if (entrypoints.length === 0) return "No entrypoints found.";
300
+ const header = ["Entrypoint", ...resolutionKindOrder];
301
+ const cells = partitionProblemsByCell(entrypoints, problems);
302
+ const rows = entrypoints.map((entrypoint) => {
303
+ const row = [entrypoint];
304
+ for (const rk of resolutionKindOrder) {
305
+ const relevant = problemsForCell(cells, entrypoint, rk);
306
+ if (relevant.length === 0) {
307
+ row.push(opts.useEmoji ? "✔" : "OK");
308
+ continue;
309
+ }
310
+ const symbols = relevant.map((p) => symbolForProblem(p, opts.useEmoji)).join("");
311
+ row.push(symbols);
312
+ }
313
+ return row.map((c) => colorizeCell(c, opts.color, annotations));
314
+ });
315
+ return opts.flipped ? renderFlippedTable(header, rows) : renderTable(header, rows);
316
+ };
317
+ //#endregion
318
+ //#region src/RenderAscii.ts
319
+ const renderAsciiAnalysis = (entrypoints, problems, opts) => {
320
+ if (entrypoints.length === 0) return "No entrypoints found.";
321
+ const header = ["Entrypoint", ...resolutionKindOrder];
322
+ const cells = partitionProblemsByCell(entrypoints, problems);
323
+ const rows = entrypoints.map((entrypoint) => {
324
+ const row = [entrypoint];
325
+ for (const rk of resolutionKindOrder) {
326
+ const relevant = problemsForCell(cells, entrypoint, rk);
327
+ if (relevant.length === 0) {
328
+ row.push("OK");
329
+ continue;
330
+ }
331
+ row.push(relevant.map((p) => symbolForProblem(p, opts.useEmoji)).join(""));
332
+ }
333
+ return row;
334
+ });
335
+ return renderTable(header, rows);
336
+ };
337
+ //#endregion
338
+ //#region src/RenderJson.ts
339
+ const renderJson = (value, options = { pretty: true }) => {
340
+ if (options.pretty) return JSON.stringify(value, null, 2);
341
+ return JSON.stringify(value);
342
+ };
343
+ //#endregion
344
+ //#region src/RenderUntyped.ts
345
+ const renderUntyped = (ctx) => {
346
+ const lines = [];
347
+ lines.push(`Package ${ctx.packageName}@${ctx.packageVersion} has no types.`);
348
+ if (ctx.typesPackageName !== null) lines.push(`Install @types/${ctx.typesPackageName} for TypeScript support.`);
349
+ else lines.push("No @types package found.");
350
+ return lines.join("\n");
351
+ };
352
+ //#endregion
353
+ //#region src/Render.ts
354
+ const isUntyped = (r) => "types" in r && r.types === false;
355
+ const visibleProblems = (analysis, options) => analysis.problems.filter((p) => !options.ignoreRules.includes(problemFlagForKind(p.kind)));
356
+ const renderAnalysis = (result, options, annotations = {}) => {
357
+ if (options.quiet) return "";
358
+ const format = resolveFormat(options);
359
+ if (format === "json") {
360
+ if (isUntyped(result)) return renderJson({ analysis: result }, { pretty: true });
361
+ const visible = visibleProblems(result, options);
362
+ return renderJson({
363
+ analysis: result,
364
+ problems: visible,
365
+ ...options.summary ? { summary: renderSummary(visible) } : {}
366
+ }, { pretty: true });
367
+ }
368
+ if (isUntyped(result)) return renderUntyped({
369
+ packageName: result.packageName,
370
+ packageVersion: result.packageVersion,
371
+ typesPackageName: null
372
+ });
373
+ const visible = visibleProblems(result, options);
374
+ if (options.summary) return renderSummary(visible) + "\n" + renderAnalysis(result, {
375
+ ...options,
376
+ summary: false
377
+ }, annotations);
378
+ const entrypointNames = Object.keys(result.entrypoints);
379
+ switch (format) {
380
+ case "ascii": return renderAsciiAnalysis(entrypointNames, visible, { useEmoji: options.useEmoji });
381
+ case "table-flipped": return renderTypedAnalysis(entrypointNames, visible, {
382
+ flipped: true,
383
+ useEmoji: options.useEmoji,
384
+ color: options.color
385
+ }, annotations);
386
+ case "table": return renderTypedAnalysis(entrypointNames, visible, {
387
+ flipped: false,
388
+ useEmoji: options.useEmoji,
389
+ color: options.color
390
+ }, annotations);
391
+ }
392
+ };
393
+ const resolveFormat = (options) => {
394
+ if (options.format === "json") return "json";
395
+ if (options.format === "ascii") return "ascii";
396
+ if (options.format === "table") return "table";
397
+ if (options.format === "table-flipped") return "table-flipped";
398
+ if (options.isTTY && options.terminalWidth >= 100) return "table-flipped";
399
+ return "ascii";
400
+ };
401
+ const renderSummary = (problems) => {
402
+ if (problems.length === 0) return "No problems found.";
403
+ const grouped = {};
404
+ for (const p of problems) {
405
+ grouped[p.kind] = grouped[p.kind] ?? [];
406
+ grouped[p.kind].push(p);
407
+ }
408
+ return Object.entries(grouped).map(([kind, list]) => `${kind}: ${list.length}`).join("\n");
409
+ };
410
+ //#endregion
411
+ //#region src/StdinAdapter.ts
412
+ var Stdin = class extends Context.Service()("@systemfsoftware/arethetypeswrong-cli/stdin.adapter/Stdin") {};
413
+ const fromTerminal = (terminal) => ({ confirm: (question) => Effect.gen(function* () {
414
+ yield* terminal.display(question).pipe(Effect.orElseSucceed(() => void 0));
415
+ const answer = (yield* terminal.readLine.pipe(Effect.orElseSucceed(() => ""))).trim();
416
+ return answer === "" || answer.toLowerCase().startsWith("y");
417
+ }) });
418
+ const StdinLive = Layer.effect(Stdin, Effect.gen(function* () {
419
+ const terminal = yield* PlatformTerminal.Terminal;
420
+ return fromTerminal(terminal);
421
+ }));
422
+ //#endregion
423
+ //#region src/TerminalAdapter.ts
424
+ var Terminal = class extends Context.Service()("@systemfsoftware/arethetypeswrong-cli/terminal.adapter/Terminal") {};
425
+ const TerminalLive = Layer.effect(Terminal, Effect.gen(function* () {
426
+ const terminal = yield* PlatformTerminal.Terminal;
427
+ return {
428
+ isTty: process.stdout.isTTY === true,
429
+ env: process.env,
430
+ stdout: { write: (text) => terminal.display(text).pipe(Effect.as(void 0), Effect.orElseSucceed(() => void 0)) },
431
+ stderr: { write: (text) => Effect.sync(() => {
432
+ process.stderr.write(text);
433
+ }) },
434
+ exit: (code) => Effect.sync(() => process.exit(code))
435
+ };
436
+ }));
437
+ //#endregion
438
+ //#region src/AttwExecutor.ts
439
+ const prepareAnalysis = (request, result) => {
440
+ const profileDecision = request.profile !== void 0 ? applyProfile(new ApplyProfileCommand(request.ignoreResolutions === void 0 ? { profileName: request.profile } : {
441
+ profileName: request.profile,
442
+ ignoreResolutions: request.ignoreResolutions
443
+ })) : void 0;
444
+ const profileApplied = profileDecision !== void 0 ? {
445
+ ...request,
446
+ ignoreResolutions: profileDecision.ignoreResolutions
447
+ } : request;
448
+ return {
449
+ result,
450
+ ignoreRules: profileApplied.ignoreRules ?? [],
451
+ ignoreResolutions: profileApplied.ignoreResolutions ?? []
452
+ };
453
+ };
454
+ const acquireTarball = (request) => Effect.gen(function* () {
455
+ const fs = yield* CliFilesystem;
456
+ const target = request.fileOrDirectory;
457
+ if (request.pack) {
458
+ const packed = yield* (yield* PackRunner).pack(target).pipe(Effect.orDie);
459
+ const tarballPath = fs.join(target, packed.tarballPath);
460
+ const bytes = yield* fs.readBytes(tarballPath).pipe(Effect.orDie);
461
+ yield* fs.deleteFile(tarballPath);
462
+ return {
463
+ bytes,
464
+ ref: {
465
+ packageName: target,
466
+ packageVersion: "local",
467
+ tarballUrl: `file://${tarballPath}`
468
+ }
469
+ };
470
+ }
471
+ if (target.endsWith(".tgz") || target.endsWith(".tar.gz")) return {
472
+ bytes: yield* fs.readBytes(target).pipe(Effect.orDie),
473
+ ref: {
474
+ packageName: target,
475
+ packageVersion: "local",
476
+ tarballUrl: `file://${target}`
477
+ }
478
+ };
479
+ const npmTarget = request.fromNpm ? target : /^[a-z@]/.test(target) && !target.includes("/") ? target : `file:${target}`;
480
+ if (!npmTarget.startsWith("file:")) {
481
+ const [name, version = "latest"] = npmTarget.split("@").filter(Boolean);
482
+ const registryJson = yield* Effect.tryPromise({
483
+ try: async () => {
484
+ const res = await fetch(`${request.registry.replace(/\/$/, "")}/${encodeURIComponent(name ?? npmTarget)}/${version}`);
485
+ if (res.status === 404) throw new RegistryFetchError({ message: `Package not found: ${npmTarget}` });
486
+ if (!res.ok) throw new RegistryFetchError({ message: `Registry returned ${res.status} for ${npmTarget}` });
487
+ return await res.json();
488
+ },
489
+ catch: (e) => e instanceof RegistryFetchError ? e : new RegistryFetchError({
490
+ message: `Registry request failed for ${npmTarget}`,
491
+ cause: e
492
+ })
493
+ }).pipe(Effect.orDie);
494
+ const registry = yield* Schema.decodeUnknownEffect(RegistryDocument)(registryJson).pipe(Effect.orDie);
495
+ return {
496
+ bytes: yield* Effect.tryPromise({
497
+ try: async () => {
498
+ const res = await fetch(registry.dist.tarball);
499
+ if (!res.ok) throw new RegistryFetchError({ message: `Tarball fetch returned ${res.status}` });
500
+ return new Uint8Array(await res.arrayBuffer());
501
+ },
502
+ catch: (e) => e instanceof RegistryFetchError ? e : new RegistryFetchError({
503
+ message: `Tarball fetch failed for ${npmTarget}`,
504
+ cause: e
505
+ })
506
+ }).pipe(Effect.orDie),
507
+ ref: {
508
+ packageName: registry.name,
509
+ packageVersion: registry.version,
510
+ tarballUrl: registry.dist.tarball
511
+ }
512
+ };
513
+ }
514
+ return {
515
+ bytes: yield* fs.readBytes(target).pipe(Effect.orDie),
516
+ ref: {
517
+ packageName: target,
518
+ packageVersion: "local",
519
+ tarballUrl: `file://${target}`
520
+ }
521
+ };
522
+ });
523
+ const runAttw = (request) => Effect.gen(function* () {
524
+ const terminal = yield* Terminal;
525
+ const { bytes, ref } = yield* acquireTarball(request);
526
+ const storeLayer = PackageStoreAdapterStub(ref, bytes);
527
+ const checkPackageLayer = CheckPackageLive.pipe(Layer.provide(storeLayer));
528
+ const result = yield* Effect.gen(function* () {
529
+ return yield* (yield* CheckPackage).execute(request.fileOrDirectory, {
530
+ entrypoints: request.entrypoints?.length ? [...request.entrypoints] : void 0,
531
+ includeEntrypoints: request.includeEntrypoints?.length ? [...request.includeEntrypoints] : void 0,
532
+ excludeEntrypoints: request.excludeEntrypoints?.length ? [...request.excludeEntrypoints] : void 0,
533
+ entrypointsLegacy: request.entrypointsLegacy
534
+ });
535
+ }).pipe(Effect.provide(Layer.mergeAll(checkPackageLayer, storeLayer)), Effect.catch(() => Effect.succeed({
536
+ packageName: request.fileOrDirectory,
537
+ packageVersion: "error",
538
+ types: false
539
+ })));
540
+ const prepared = prepareAnalysis(request, result);
541
+ const exitDecision = computeExitCode(new ComputeExitCodeCommand({
542
+ result: prepared.result,
543
+ ignoreRules: [...prepared.ignoreRules],
544
+ ignoreResolutions: [...prepared.ignoreResolutions]
545
+ }));
546
+ if (!request.quiet) {
547
+ const output = renderAnalysis(prepared.result, {
548
+ format: request.format ?? "auto",
549
+ color: request.color ?? true,
550
+ summary: request.summary ?? true,
551
+ ignoreRules: prepared.ignoreRules,
552
+ useEmoji: request.emoji ?? true,
553
+ quiet: request.quiet ?? false,
554
+ terminalWidth: 120,
555
+ isTTY: true
556
+ });
557
+ yield* terminal.stdout.write(output);
558
+ }
559
+ return exitDecision.exitCode;
560
+ });
561
+ //#endregion
562
+ //#region src/AttwHandler.ts
563
+ const formatOptions = () => Flag.choice("format", CliFormat).pipe(Flag.withAlias("f"), Flag.withDefault("auto"));
564
+ const profileOptions = () => Flag.choice("profile", CliProfile).pipe(Flag.withDefault("strict"));
565
+ /**
566
+ * The only flag a `.attw.json` is ever written for. Without the fallback config
567
+ * the file's `ignoreRules` reaches nothing: the config layer is present, but a
568
+ * flag that never consults a `Config` cannot see it, so a project's ignore list
569
+ * was read, parsed, and then discarded — the build went red on exactly the
570
+ * problems the file existed to waive.
571
+ */
572
+ const ignoreRulesOptions = () => Flag.optional(Flag.atLeast(1)(Flag.string("ignore-rules").pipe(Flag.withAlias("ignore-rule"))).pipe(Flag.withFallbackConfig(Config.schema(Config.Array(S.String), "ignoreRules"))));
573
+ /**
574
+ * `--definitely-typed` is tri-state: absent | `true` | a version-or-path string.
575
+ * Commander parsed this with `new Option('--definitely-typed [version]')` and
576
+ * `default(true)`. We model the decoded value as a tagged union and translate.
577
+ */
578
+ const definitelyTypedOptions = () => Flag.optional(Flag.string("definitely-typed")).pipe(Flag.withDescription("Specify the version range of @types to use. Pass `false` to disable."));
579
+ const registryOptions = () => Flag.string("registry").pipe(Flag.withDescription("URL of the npm registry to read packages from with --from-npm (default: https://registry.npmjs.org)"), Flag.withFallbackConfig(Config.string("registry").pipe(Config.withDefault("https://registry.npmjs.org"))));
580
+ const unwrap = (opt) => Option.isSome(opt) ? opt.value : void 0;
581
+ const attwCommand = Command.make("attw", {
582
+ fileOrDirectory: Argument.optional(Argument.string("file-directory-or-package-spec")),
583
+ pack: Flag.boolean("pack").pipe(Flag.withAlias("P"), Flag.withDescription("Run `npm pack` in the specified directory and delete the resulting .tgz file afterwards")),
584
+ fromNpm: Flag.boolean("from-npm").pipe(Flag.withAlias("p"), Flag.withDescription("Read from the npm registry instead of a local file")),
585
+ definitelyTyped: definitelyTypedOptions(),
586
+ format: formatOptions(),
587
+ quiet: Flag.boolean("quiet").pipe(Flag.withAlias("q"), Flag.withDescription("Don't print anything to STDOUT (overrides all other options)")),
588
+ entrypoints: Flag.optional(Flag.atLeast(1)(Flag.string("entrypoints"))),
589
+ includeEntrypoints: Flag.optional(Flag.atLeast(1)(Flag.string("include-entrypoints"))),
590
+ excludeEntrypoints: Flag.optional(Flag.atLeast(1)(Flag.string("exclude-entrypoints"))),
591
+ entrypointsLegacy: Flag.boolean("entrypoints-legacy"),
592
+ ignoreRules: ignoreRulesOptions(),
593
+ profile: profileOptions(),
594
+ summary: Flag.boolean("summary").pipe(Flag.withDefault(true)),
595
+ emoji: Flag.boolean("emoji").pipe(Flag.withDefault(true)),
596
+ color: Flag.boolean("color").pipe(Flag.withDefault(true)),
597
+ registry: registryOptions()
598
+ }, (config) => Effect$1.gen(function* () {
599
+ const input = {
600
+ fileOrDirectory: unwrap(config.fileOrDirectory) ?? ".",
601
+ pack: config.pack,
602
+ fromNpm: config.fromNpm,
603
+ definitelyTyped: unwrap(config.definitelyTyped),
604
+ format: config.format,
605
+ quiet: config.quiet,
606
+ entrypoints: unwrap(config.entrypoints),
607
+ includeEntrypoints: unwrap(config.includeEntrypoints),
608
+ excludeEntrypoints: unwrap(config.excludeEntrypoints),
609
+ entrypointsLegacy: config.entrypointsLegacy,
610
+ ignoreRules: unwrap(config.ignoreRules),
611
+ profile: config.profile,
612
+ summary: config.summary,
613
+ emoji: config.emoji,
614
+ color: config.color,
615
+ registry: config.registry
616
+ };
617
+ const exitCode = yield* runAttw(input);
618
+ yield* Effect$1.sync(() => {
619
+ process.exitCode = exitCode;
620
+ });
621
+ return exitCode;
622
+ }));
623
+ //#endregion
624
+ export { FilesystemLive as a, PackRunnerLive as i, TerminalLive as n, StdinLive as r, attwCommand as t };