@geonosis/lint-parity 1.2.0 → 1.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/dist/{chunk-3PCNMLVE.js → chunk-D7OGKQ2C.js} +150 -18
- package/dist/index.d.ts +46 -2
- package/dist/index.js +5 -1
- package/dist/parity-cli.js +28 -2
- package/package.json +4 -1
|
@@ -1,16 +1,100 @@
|
|
|
1
|
+
// src/envelope.ts
|
|
2
|
+
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import { dirname, join } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
var ENVELOPES_DIR = ".geonosis/envelopes";
|
|
6
|
+
var envelopePath = (root, tool) => join(root, ENVELOPES_DIR, `${tool}.json`);
|
|
7
|
+
var UnbalancedEnvelope = class extends Error {
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "UnbalancedEnvelope";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var isCount = (value) => Number.isSafeInteger(value) && value >= 0;
|
|
14
|
+
var unbalancedMessage = (envelope, next) => `${envelope.tool}: considered ${envelope.considered} but accounts for ${envelope.read + envelope.refused.length + envelope.excused.length} \u2014 ${envelope.read} read + ${envelope.refused.length} refused + ${envelope.excused.length} excused. A run that has lost count of its own inputs cannot say what it measured, so no verdict was rendered and no envelope was written. Next: ${next}`;
|
|
15
|
+
var writeEnvelope = ({
|
|
16
|
+
envelope,
|
|
17
|
+
next,
|
|
18
|
+
root
|
|
19
|
+
}) => {
|
|
20
|
+
if (envelope.tool.trim() === "") {
|
|
21
|
+
throw new UnbalancedEnvelope(
|
|
22
|
+
`an envelope with no tool name cannot be filed or reported against. Next: ${next}`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
if (envelope.version.trim() === "") {
|
|
26
|
+
throw new UnbalancedEnvelope(
|
|
27
|
+
`${envelope.tool}: an envelope that cannot name the build that wrote it dates nothing, and a stale one reads exactly like a fresh one. Next: ${next}`
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
if (!isCount(envelope.considered) || !isCount(envelope.read)) {
|
|
31
|
+
throw new UnbalancedEnvelope(
|
|
32
|
+
`${envelope.tool}: considered ${envelope.considered} and read ${envelope.read} \u2014 a census is a whole number of things, and arithmetic over anything else balances by accident. Next: ${next}`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
if (envelope.considered !== envelope.read + envelope.refused.length + envelope.excused.length) {
|
|
36
|
+
throw new UnbalancedEnvelope(unbalancedMessage(envelope, next));
|
|
37
|
+
}
|
|
38
|
+
const at = envelopePath(root, envelope.tool);
|
|
39
|
+
mkdirSync(dirname(at), { recursive: true });
|
|
40
|
+
writeFileSync(at, `${JSON.stringify(envelope, void 0, 2)}
|
|
41
|
+
`);
|
|
42
|
+
return at;
|
|
43
|
+
};
|
|
44
|
+
var UNKNOWN = "unknown";
|
|
45
|
+
var versionIn = (dir) => {
|
|
46
|
+
try {
|
|
47
|
+
const manifest = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
48
|
+
return typeof manifest.version === "string" ? manifest.version : void 0;
|
|
49
|
+
} catch {
|
|
50
|
+
return void 0;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var versionOf = (moduleUrl) => {
|
|
54
|
+
let dir = dirname(fileURLToPath(moduleUrl));
|
|
55
|
+
for (; ; ) {
|
|
56
|
+
const found = versionIn(dir);
|
|
57
|
+
if (found !== void 0) return found;
|
|
58
|
+
const up = dirname(dir);
|
|
59
|
+
if (up === dir) return UNKNOWN;
|
|
60
|
+
dir = up;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
var PARITY_TOOL = "lint-parity";
|
|
64
|
+
var PARITY_NEXT = "oxlint --debug=files --config <each config> <paths> and diff the two lists \u2014 a parity run only compares the files BOTH configs opened, and the ones only one of them opened are the silence this refuses over";
|
|
65
|
+
var parityEnvelope = (census, findings, durationMs) => ({
|
|
66
|
+
considered: census.both.length + census.onlyInA.length + census.onlyInB.length,
|
|
67
|
+
durationMs,
|
|
68
|
+
excused: [
|
|
69
|
+
...census.onlyInA.map((path) => ({
|
|
70
|
+
path,
|
|
71
|
+
reason: "config B would not lint this file, so the two runs cannot be compared over it"
|
|
72
|
+
})),
|
|
73
|
+
...census.onlyInB.map((path) => ({
|
|
74
|
+
path,
|
|
75
|
+
reason: "config A would not lint this file, so the two runs cannot be compared over it"
|
|
76
|
+
}))
|
|
77
|
+
],
|
|
78
|
+
findings,
|
|
79
|
+
read: census.both.length,
|
|
80
|
+
refused: [],
|
|
81
|
+
tool: PARITY_TOOL,
|
|
82
|
+
version: versionOf(import.meta.url)
|
|
83
|
+
});
|
|
84
|
+
|
|
1
85
|
// src/parity.ts
|
|
2
86
|
import { spawnSync } from "child_process";
|
|
3
87
|
import {
|
|
4
88
|
cpSync,
|
|
5
89
|
existsSync,
|
|
6
|
-
mkdirSync,
|
|
90
|
+
mkdirSync as mkdirSync2,
|
|
7
91
|
mkdtempSync,
|
|
8
|
-
readFileSync,
|
|
92
|
+
readFileSync as readFileSync2,
|
|
9
93
|
rmSync,
|
|
10
|
-
writeFileSync
|
|
94
|
+
writeFileSync as writeFileSync2
|
|
11
95
|
} from "fs";
|
|
12
96
|
import { tmpdir } from "os";
|
|
13
|
-
import { dirname, join, resolve } from "path";
|
|
97
|
+
import { dirname as dirname2, join as join2, resolve } from "path";
|
|
14
98
|
var ANSI = /\[[0-9;]*m/g;
|
|
15
99
|
var SUMMARY_COUNT = /^(\d+) problems?$/m;
|
|
16
100
|
var RULE_IN_LINE = /\[(?:Error|Warning)\/([^\]]+)\]$/;
|
|
@@ -21,7 +105,7 @@ var resolveOxlint = (from) => {
|
|
|
21
105
|
for (; ; ) {
|
|
22
106
|
const bin = resolve(dir, "node_modules/.bin/oxlint");
|
|
23
107
|
if (existsSync(bin)) return bin;
|
|
24
|
-
const parent =
|
|
108
|
+
const parent = dirname2(dir);
|
|
25
109
|
if (parent === dir) return "oxlint";
|
|
26
110
|
dir = parent;
|
|
27
111
|
}
|
|
@@ -156,8 +240,13 @@ var formatSummary = (parity) => {
|
|
|
156
240
|
].join("\n");
|
|
157
241
|
};
|
|
158
242
|
var rulesNamedBy = (config) => {
|
|
159
|
-
const parsed = JSON.parse(
|
|
160
|
-
return
|
|
243
|
+
const parsed = JSON.parse(readFileSync2(config, "utf8"));
|
|
244
|
+
return [
|
|
245
|
+
.../* @__PURE__ */ new Set([
|
|
246
|
+
...Object.keys(parsed.rules ?? {}),
|
|
247
|
+
...(parsed.overrides ?? []).flatMap((one) => Object.keys(one.rules ?? {}))
|
|
248
|
+
])
|
|
249
|
+
];
|
|
161
250
|
};
|
|
162
251
|
var ruleIdOf = (line) => {
|
|
163
252
|
const token = RULE_IN_LINE.exec(line)?.[1];
|
|
@@ -192,10 +281,10 @@ var verdictOf = (changed, claimed) => {
|
|
|
192
281
|
return claimed ? "unproven" : "unclaimed";
|
|
193
282
|
};
|
|
194
283
|
var readManifest = (corpus) => {
|
|
195
|
-
const path =
|
|
284
|
+
const path = join2(corpus, MANIFEST_FILE);
|
|
196
285
|
let parsed;
|
|
197
286
|
try {
|
|
198
|
-
parsed = JSON.parse(
|
|
287
|
+
parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
199
288
|
} catch (error) {
|
|
200
289
|
throw new ParityError(`could not read ${path}: ${error.message}`);
|
|
201
290
|
}
|
|
@@ -214,8 +303,8 @@ var corpusOf = ({
|
|
|
214
303
|
}) => {
|
|
215
304
|
const manifest = readManifest(corpus);
|
|
216
305
|
const exercised = new Set(manifest.rules);
|
|
217
|
-
const root = mkdtempSync(
|
|
218
|
-
const here =
|
|
306
|
+
const root = mkdtempSync(join2(tmpdir(), "geonosis-corpus-"));
|
|
307
|
+
const here = join2(root, "corpus");
|
|
219
308
|
const foundIn = (config, side) => {
|
|
220
309
|
const counts = /* @__PURE__ */ new Map();
|
|
221
310
|
for (const finding of readRun(
|
|
@@ -390,6 +479,44 @@ var lint = ({
|
|
|
390
479
|
throw new ParityError(`could not run ${oxlint}: ${run.error.message}`);
|
|
391
480
|
return { code: run.status ?? -1, output: `${run.stdout ?? ""}${run.stderr ?? ""}` };
|
|
392
481
|
};
|
|
482
|
+
var filesLintedBy = ({
|
|
483
|
+
config,
|
|
484
|
+
cwd,
|
|
485
|
+
oxlint,
|
|
486
|
+
paths
|
|
487
|
+
}) => {
|
|
488
|
+
const run = spawnSync(oxlint, ["--debug=files", "--config", config, ...paths], {
|
|
489
|
+
cwd,
|
|
490
|
+
encoding: "utf8",
|
|
491
|
+
env: { ...process.env, NO_COLOR: "1" },
|
|
492
|
+
maxBuffer: 64 * 1024 * 1024
|
|
493
|
+
});
|
|
494
|
+
if (run.error !== void 0) {
|
|
495
|
+
throw new ParityError(`could not run ${oxlint}: ${run.error.message}`);
|
|
496
|
+
}
|
|
497
|
+
if (run.status !== 0) {
|
|
498
|
+
throw new ParityError(
|
|
499
|
+
`oxlint --debug=files exited ${String(run.status)} under ${config}, so which files it would read is not known:
|
|
500
|
+
${`${run.stdout ?? ""}${run.stderr ?? ""}`.trim()}`
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
return (run.stdout ?? "").split("\n").map((line) => line.trim().replace(`${cwd}/`, "").replace(/^\.\//, "")).filter((line) => line !== "").toSorted();
|
|
504
|
+
};
|
|
505
|
+
var fileCensus = ({
|
|
506
|
+
configA,
|
|
507
|
+
configB,
|
|
508
|
+
cwd,
|
|
509
|
+
oxlint,
|
|
510
|
+
paths
|
|
511
|
+
}) => {
|
|
512
|
+
const inA = filesLintedBy({ config: configA, cwd, oxlint, paths });
|
|
513
|
+
const inB = new Set(filesLintedBy({ config: configB, cwd, oxlint, paths }));
|
|
514
|
+
return {
|
|
515
|
+
both: inA.filter((file) => inB.has(file)),
|
|
516
|
+
onlyInA: inA.filter((file) => !inB.has(file)),
|
|
517
|
+
onlyInB: [...inB].filter((file) => !inA.includes(file)).toSorted()
|
|
518
|
+
};
|
|
519
|
+
};
|
|
393
520
|
var parityOf = ({
|
|
394
521
|
configA,
|
|
395
522
|
configB,
|
|
@@ -403,18 +530,21 @@ var parityOf = ({
|
|
|
403
530
|
b: readRun(lint({ config: configB, cwd, oxlint, paths }), cwd, "B")
|
|
404
531
|
});
|
|
405
532
|
if (out !== void 0) {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
533
|
+
mkdirSync2(out, { recursive: true });
|
|
534
|
+
writeFileSync2(join2(out, "a.txt"), asFile(parity.a));
|
|
535
|
+
writeFileSync2(join2(out, "b.txt"), asFile(parity.b));
|
|
536
|
+
writeFileSync2(join2(out, "only-in-a.txt"), asFile(parity.onlyInA));
|
|
537
|
+
writeFileSync2(join2(out, "only-in-b.txt"), asFile(parity.onlyInB));
|
|
538
|
+
writeFileSync2(join2(out, "changed.txt"), asFile(changedLines(parity.messageChanged)));
|
|
539
|
+
writeFileSync2(join2(out, "SUMMARY.md"), formatSummary(parity));
|
|
413
540
|
}
|
|
414
541
|
return parity;
|
|
415
542
|
};
|
|
416
543
|
|
|
417
544
|
export {
|
|
545
|
+
writeEnvelope,
|
|
546
|
+
PARITY_NEXT,
|
|
547
|
+
parityEnvelope,
|
|
418
548
|
resolveOxlint,
|
|
419
549
|
ParityError,
|
|
420
550
|
normaliseFindings,
|
|
@@ -429,5 +559,7 @@ export {
|
|
|
429
559
|
corpusOf,
|
|
430
560
|
formatCorpus,
|
|
431
561
|
parseParityArgs,
|
|
562
|
+
filesLintedBy,
|
|
563
|
+
fileCensus,
|
|
432
564
|
parityOf
|
|
433
565
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/** The files each config would actually read, split three ways. */
|
|
2
|
+
type FileCensus = {
|
|
3
|
+
both: string[];
|
|
4
|
+
onlyInA: string[];
|
|
5
|
+
onlyInB: string[];
|
|
6
|
+
};
|
|
7
|
+
|
|
1
8
|
/**
|
|
2
9
|
* The consumer's own oxlint, walking up from a directory, not one `npx` might fetch: a run under a
|
|
3
10
|
* different binary from the one the repo gates with compares two things nobody ships. Exported
|
|
@@ -97,7 +104,15 @@ type CorpusReport = {
|
|
|
97
104
|
plugin: string;
|
|
98
105
|
reach: RuleReach[];
|
|
99
106
|
};
|
|
100
|
-
/**
|
|
107
|
+
/**
|
|
108
|
+
* The rule ids an oxlint config enables, exactly as it wrote them — plugin prefix included, and
|
|
109
|
+
* from `overrides[]` as well as from `rules`.
|
|
110
|
+
*
|
|
111
|
+
* A config's base block is only its first layer: an overrides entry may name a rule the base block
|
|
112
|
+
* never mentions, and oxlint runs it on every file that entry's globs claim. Reading `rules` alone
|
|
113
|
+
* left such a rule out of the reach scope entirely — it fired, and the report about which rules
|
|
114
|
+
* reach anything did not have a row for it.
|
|
115
|
+
*/
|
|
101
116
|
declare const rulesNamedBy: (config: string) => string[];
|
|
102
117
|
/**
|
|
103
118
|
* The plugin-qualified id behind a finding's `plugin(rule)` token, in the `plugin/rule` shape a
|
|
@@ -166,6 +181,35 @@ type ParityArgs = {
|
|
|
166
181
|
paths: string[];
|
|
167
182
|
};
|
|
168
183
|
declare const parseParityArgs: (argv: string[]) => ParityArgs;
|
|
184
|
+
/**
|
|
185
|
+
* The files a config would actually lint, asked of oxlint itself.
|
|
186
|
+
*
|
|
187
|
+
* `--debug=files` prints one path per line and exits 0 without linting anything: measured here,
|
|
188
|
+
* `--config narrow.json` (whose `ignorePatterns` name `b.ts`) printed one file where
|
|
189
|
+
* `--config wide.json` printed two. Nothing else in a `--format=unix` run carries this — that
|
|
190
|
+
* output is findings and only findings, which is why a parity run could report agreement over a
|
|
191
|
+
* tree the second config never opened.
|
|
192
|
+
*
|
|
193
|
+
* The argv is deliberately the same SHAPE `lint` uses, config and paths passed through exactly as
|
|
194
|
+
* they arrived. It has to be: oxlint's ignore resolution is sensitive to that shape — an absolute
|
|
195
|
+
* `--config` with a relative path argument applies no `ignorePatterns` at all, where the same
|
|
196
|
+
* config named relatively does. A census taken under different arguments would describe a run
|
|
197
|
+
* nobody made, which is the failure this whole envelope is about.
|
|
198
|
+
*/
|
|
199
|
+
declare const filesLintedBy: ({ config, cwd, oxlint, paths, }: {
|
|
200
|
+
config: string;
|
|
201
|
+
cwd: string;
|
|
202
|
+
oxlint: string;
|
|
203
|
+
paths: string[];
|
|
204
|
+
}) => string[];
|
|
205
|
+
/** Which files both configs would read, and which each would read alone. */
|
|
206
|
+
declare const fileCensus: ({ configA, configB, cwd, oxlint, paths, }: {
|
|
207
|
+
configA: string;
|
|
208
|
+
configB: string;
|
|
209
|
+
cwd: string;
|
|
210
|
+
oxlint: string;
|
|
211
|
+
paths: string[];
|
|
212
|
+
}) => FileCensus;
|
|
169
213
|
declare const parityOf: ({ configA, configB, cwd, out, oxlint, paths, }: {
|
|
170
214
|
configA: string;
|
|
171
215
|
configB: string;
|
|
@@ -175,4 +219,4 @@ declare const parityOf: ({ configA, configB, cwd, out, oxlint, paths, }: {
|
|
|
175
219
|
paths: string[];
|
|
176
220
|
}) => Parity;
|
|
177
221
|
|
|
178
|
-
export { type CorpusClaim, type CorpusManifest, type CorpusReport, MANIFEST_FILE, type MessageChange, type Parity, type ParityArgs, ParityError, type RuleReach, type RuleTally, compareFindings, corpusOf, formatCorpus, formatSummary, manifestOf, normaliseFindings, parityOf, parseParityArgs, readManifest, readRun, resolveOxlint, ruleIdOf, rulesNamedBy };
|
|
222
|
+
export { type CorpusClaim, type CorpusManifest, type CorpusReport, type FileCensus, MANIFEST_FILE, type MessageChange, type Parity, type ParityArgs, ParityError, type RuleReach, type RuleTally, compareFindings, corpusOf, fileCensus, filesLintedBy, formatCorpus, formatSummary, manifestOf, normaliseFindings, parityOf, parseParityArgs, readManifest, readRun, resolveOxlint, ruleIdOf, rulesNamedBy };
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
ParityError,
|
|
4
4
|
compareFindings,
|
|
5
5
|
corpusOf,
|
|
6
|
+
fileCensus,
|
|
7
|
+
filesLintedBy,
|
|
6
8
|
formatCorpus,
|
|
7
9
|
formatSummary,
|
|
8
10
|
manifestOf,
|
|
@@ -14,12 +16,14 @@ import {
|
|
|
14
16
|
resolveOxlint,
|
|
15
17
|
ruleIdOf,
|
|
16
18
|
rulesNamedBy
|
|
17
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-D7OGKQ2C.js";
|
|
18
20
|
export {
|
|
19
21
|
MANIFEST_FILE,
|
|
20
22
|
ParityError,
|
|
21
23
|
compareFindings,
|
|
22
24
|
corpusOf,
|
|
25
|
+
fileCensus,
|
|
26
|
+
filesLintedBy,
|
|
23
27
|
formatCorpus,
|
|
24
28
|
formatSummary,
|
|
25
29
|
manifestOf,
|
package/dist/parity-cli.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
+
PARITY_NEXT,
|
|
2
3
|
corpusOf,
|
|
4
|
+
fileCensus,
|
|
3
5
|
formatCorpus,
|
|
4
6
|
formatSummary,
|
|
7
|
+
parityEnvelope,
|
|
5
8
|
parityOf,
|
|
6
9
|
parseParityArgs,
|
|
7
|
-
resolveOxlint
|
|
8
|
-
|
|
10
|
+
resolveOxlint,
|
|
11
|
+
writeEnvelope
|
|
12
|
+
} from "./chunk-D7OGKQ2C.js";
|
|
9
13
|
|
|
10
14
|
// src/parity-cli.ts
|
|
11
15
|
import { mkdirSync, writeFileSync } from "fs";
|
|
@@ -37,6 +41,16 @@ path \u2014 a config in a temp dir cannot find a plugin installed in the workspa
|
|
|
37
41
|
refused rather than counted as zero findings.`;
|
|
38
42
|
var main = () => {
|
|
39
43
|
const argv = process.argv.slice(2);
|
|
44
|
+
if (argv.includes("--print-config-shape")) {
|
|
45
|
+
process.stdout.write(
|
|
46
|
+
`geonosis-lint-parity reads no configuration file.
|
|
47
|
+
|
|
48
|
+
The two configs it compares are .oxlintrc.json files named on the command line with --a and --b,
|
|
49
|
+
and it reads them as oxlint does rather than interpreting them itself.
|
|
50
|
+
`
|
|
51
|
+
);
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
40
54
|
if (argv.includes("--help") || argv.includes("-h") || argv.length === 0) {
|
|
41
55
|
process.stdout.write(`${USAGE}
|
|
42
56
|
`);
|
|
@@ -66,6 +80,7 @@ var main = () => {
|
|
|
66
80
|
}
|
|
67
81
|
return report.neither.length > 0 || !report.claimsHeld ? 1 : 0;
|
|
68
82
|
}
|
|
83
|
+
const startedAt = Date.now();
|
|
69
84
|
const parity = parityOf({
|
|
70
85
|
configA,
|
|
71
86
|
configB,
|
|
@@ -75,6 +90,17 @@ var main = () => {
|
|
|
75
90
|
paths: args.paths
|
|
76
91
|
});
|
|
77
92
|
process.stdout.write(`${formatSummary(parity)}
|
|
93
|
+
`);
|
|
94
|
+
const at = writeEnvelope({
|
|
95
|
+
envelope: parityEnvelope(
|
|
96
|
+
fileCensus({ configA, configB, cwd, oxlint, paths: args.paths }),
|
|
97
|
+
parity.onlyInA,
|
|
98
|
+
Date.now() - startedAt
|
|
99
|
+
),
|
|
100
|
+
next: PARITY_NEXT,
|
|
101
|
+
root: cwd
|
|
102
|
+
});
|
|
103
|
+
process.stdout.write(`envelope: ${at}
|
|
78
104
|
`);
|
|
79
105
|
return parity.lost ? 1 : 0;
|
|
80
106
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geonosis/lint-parity",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"types": "./dist/index.d.ts",
|
|
5
5
|
"description": "Findings parity and rule reach over any two oxlint configs — the diff a fork is deleted on.",
|
|
6
6
|
"keywords": [
|
|
@@ -43,6 +43,9 @@
|
|
|
43
43
|
"publishConfig": {
|
|
44
44
|
"access": "public"
|
|
45
45
|
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@geonosis/ratchet": "1.4.0"
|
|
48
|
+
},
|
|
46
49
|
"scripts": {
|
|
47
50
|
"build": "tsup",
|
|
48
51
|
"typecheck": "tsc --noEmit"
|