@systemfsoftware/arethetypeswrong-cli 1.1.1 → 3.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.
- package/CHANGELOG.md +76 -0
- package/LICENSE +214 -2
- package/dist/AttwHandler-CidnXJbU.mjs +624 -0
- package/dist/AttwHandler.d.ts +86 -0
- package/dist/AttwHandler.mjs +2 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.mjs +57 -0
- package/package.json +29 -29
- package/dist/getExitCode.d.ts +0 -6
- package/dist/getExitCode.mjs +0 -14
- package/dist/index-BRUs6L5O.d.ts +0 -36
- package/dist/index.d.ts +0 -1
- package/dist/index.mjs +0 -199
- package/dist/problemUtils-DrDcGcsf.mjs +0 -29
- package/dist/render/index.d.ts +0 -2
- package/dist/render/index.mjs +0 -2
- package/dist/render-CTRBsIjQ.mjs +0 -118
|
@@ -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 };
|