ts-suppress 0.2.0 → 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/README.md +60 -39
- package/dist/ast.js +11 -0
- package/dist/cli.js +3 -2
- package/dist/commands/init.js +1 -0
- package/dist/commands/suppress.js +0 -1
- package/dist/diagnostics.js +15 -9
- package/dist/project.js +13 -6
- package/dist/scope.js +19 -18
- package/dist/suppressions.js +3 -2
- package/dist/test-helpers.js +36 -0
- package/dist/types.js +0 -1
- package/package.json +15 -21
package/README.md
CHANGED
|
@@ -4,67 +4,88 @@ Incremental TypeScript strictness adoption via bulk error suppression.
|
|
|
4
4
|
|
|
5
5
|
Instead of scattering `@ts-ignore` or `@ts-expect-error` comments throughout your codebase, `ts-suppress` captures all TypeScript errors into a single `.ts-suppressions.json` file. This lets you enable stricter compiler options immediately and fix errors at your own pace.
|
|
6
6
|
|
|
7
|
-
## How It Works
|
|
8
|
-
|
|
9
|
-
Each suppression is a fingerprint of a TypeScript error, consisting of:
|
|
10
|
-
|
|
11
|
-
- **file** — relative path to the source file
|
|
12
|
-
- **code** — TypeScript error code (e.g. `2322`)
|
|
13
|
-
- **hash** — hex hash of the diagnostic message text
|
|
14
|
-
- **scope** — dot-separated scope chain (e.g. `MyClass.myMethod`)
|
|
15
|
-
|
|
16
|
-
The `check` command diffs the current diagnostics against the suppression file and reports:
|
|
17
|
-
|
|
18
|
-
- **Unsuppressed errors** — new errors not yet in the suppression file
|
|
19
|
-
- **Stale suppressions** — entries that no longer match any current error (i.e. errors that have been fixed)
|
|
20
|
-
|
|
21
7
|
## Install
|
|
22
8
|
|
|
23
9
|
```bash
|
|
24
|
-
|
|
10
|
+
npm install -D ts-suppress
|
|
25
11
|
```
|
|
26
12
|
|
|
27
|
-
## Usage
|
|
28
|
-
|
|
29
|
-
### `init`
|
|
30
|
-
|
|
31
|
-
Create an empty `.ts-suppressions.json`:
|
|
32
|
-
|
|
33
13
|
```bash
|
|
34
|
-
|
|
14
|
+
pnpm add -D ts-suppress
|
|
35
15
|
```
|
|
36
16
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
17
|
+
```bash
|
|
18
|
+
yarn add -D ts-suppress
|
|
19
|
+
```
|
|
40
20
|
|
|
41
21
|
```bash
|
|
42
|
-
|
|
22
|
+
bun add -d ts-suppress
|
|
43
23
|
```
|
|
44
24
|
|
|
45
|
-
|
|
25
|
+
> **Note:** TypeScript >= 5.9.3 is a peer dependency.
|
|
46
26
|
|
|
47
|
-
|
|
27
|
+
## Usage
|
|
48
28
|
|
|
49
29
|
```bash
|
|
50
|
-
|
|
51
|
-
|
|
30
|
+
# Create an empty .ts-suppressions.json
|
|
31
|
+
npx ts-suppress init
|
|
52
32
|
|
|
53
|
-
|
|
33
|
+
# Snapshot all current TypeScript errors
|
|
34
|
+
npx ts-suppress suppress
|
|
54
35
|
|
|
55
|
-
|
|
36
|
+
# Verify all errors are suppressed and no suppressions are stale (useful in CI)
|
|
37
|
+
npx ts-suppress check
|
|
56
38
|
|
|
57
|
-
|
|
58
|
-
|
|
39
|
+
# Add new suppressions and remove stale ones in a single pass
|
|
40
|
+
npx ts-suppress update
|
|
59
41
|
```
|
|
60
42
|
|
|
61
|
-
Also available as `bunx ts-suppress fix`.
|
|
62
|
-
|
|
63
43
|
## Typical Workflow
|
|
64
44
|
|
|
65
45
|
1. Enable a stricter TypeScript option (e.g. `"strict": true`)
|
|
66
|
-
2. Run `
|
|
46
|
+
2. Run `npx ts-suppress suppress` to baseline all existing errors
|
|
67
47
|
3. Commit `.ts-suppressions.json`
|
|
68
|
-
4. Add `
|
|
48
|
+
4. Add `npx ts-suppress check` to CI
|
|
69
49
|
5. Fix errors over time — `check` will flag stale suppressions as you go
|
|
70
|
-
6. Run `
|
|
50
|
+
6. Run `npx ts-suppress update` to sync the suppression file after fixing errors
|
|
51
|
+
|
|
52
|
+
## How It Works
|
|
53
|
+
|
|
54
|
+
Each suppression is a fingerprint of a TypeScript error, consisting of:
|
|
55
|
+
|
|
56
|
+
- **file** — relative path to the source file
|
|
57
|
+
- **code** — TypeScript error code (e.g. `2322`)
|
|
58
|
+
- **hash** — hex hash of the diagnostic message text
|
|
59
|
+
- **scope** — dot-separated scope chain (e.g. `MyClass.myMethod`)
|
|
60
|
+
|
|
61
|
+
The `check` command diffs the current diagnostics against the suppression file and reports:
|
|
62
|
+
|
|
63
|
+
- **Unsuppressed errors** — new errors not yet in the suppression file
|
|
64
|
+
- **Stale suppressions** — entries that no longer match any current error (i.e. errors that have been fixed)
|
|
65
|
+
|
|
66
|
+
## Comparison with ts-bulk-suppress
|
|
67
|
+
|
|
68
|
+
ts-suppress is inspired by [ts-bulk-suppress](https://github.com/tiktok/ts-bulk-suppress) by TikTok and shares the same core idea: capture TypeScript errors into an external file instead of scattering `@ts-ignore` comments. The two tools take different approaches to the problem.
|
|
69
|
+
|
|
70
|
+
| | ts-suppress | ts-bulk-suppress |
|
|
71
|
+
| ------------------------ | ---------------------------------------------------------- | --------------------------------------------------------------------- |
|
|
72
|
+
| **Suppression file** | Single `.ts-suppressions.json` | `.ts-bulk-suppressions.json` |
|
|
73
|
+
| **Error identification** | file + error code + message hash + scope | file + error code + scope |
|
|
74
|
+
| **tsc integration** | Standalone — reads diagnostics via TypeScript compiler API | Wraps/intercepts tsc output |
|
|
75
|
+
| **CLI interface** | Separate commands: `init`, `suppress`, `check`, `update` | Flag-based: `--gen-bulk-suppress`, `--changed` |
|
|
76
|
+
| **Runtime dependencies** | 1 (mri) + TypeScript as peer dep | 37 packages |
|
|
77
|
+
| **Maintenance** | Actively maintained | [Last published 2024](https://www.npmjs.com/package/ts-bulk-suppress) |
|
|
78
|
+
|
|
79
|
+
### Key differences
|
|
80
|
+
|
|
81
|
+
- **Hash-based fingerprinting** — ts-suppress includes a SHA-256 hash of the diagnostic message text in each suppression entry. This means two errors on the same line with the same error code but different messages are tracked independently, reducing false matches.
|
|
82
|
+
- **No tsc patching** — ts-suppress uses the TypeScript compiler API directly to collect diagnostics rather than wrapping or intercepting tsc. This avoids coupling to tsc's output format.
|
|
83
|
+
- **Explicit CLI commands** — Each operation (`init`, `suppress`, `check`, `update`) is a separate command rather than a flag, making the workflow easier to script and understand.
|
|
84
|
+
|
|
85
|
+
## Acknowledgements
|
|
86
|
+
|
|
87
|
+
Inspired by [ts-bulk-suppress](https://github.com/tiktok/ts-bulk-suppress) by TikTok.
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
MIT
|
package/dist/ast.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
/** Find the most specific (deepest) AST node at the given position in a source file. */
|
|
3
|
+
export function findNodeAtPosition(sourceFile, position) {
|
|
4
|
+
function visit(node) {
|
|
5
|
+
if (position >= node.getStart(sourceFile) && position < node.getEnd()) {
|
|
6
|
+
return ts.forEachChild(node, visit) ?? node;
|
|
7
|
+
}
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
return visit(sourceFile);
|
|
11
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import mri from "mri";
|
|
2
3
|
import { createProject } from "./project.js";
|
|
3
4
|
import { runCheck } from "./commands/check.js";
|
|
4
5
|
import { runInit } from "./commands/init.js";
|
|
@@ -16,7 +17,7 @@ function printHelp() {
|
|
|
16
17
|
const lines = commands.map(([name, desc]) => ` ${name.padEnd(longest + 4)}${desc}`);
|
|
17
18
|
console.log(`ts-suppress v${VERSION}\nIncremental TypeScript strictness adoption via bulk error suppression\n\nCommands:\n${lines.join("\n")}\n\nRun ts-suppress <command> --help for details.`);
|
|
18
19
|
}
|
|
19
|
-
const args =
|
|
20
|
+
const args = mri(process.argv.slice(2), {
|
|
20
21
|
boolean: ["help", "version"],
|
|
21
22
|
alias: { h: "help", v: "version" },
|
|
22
23
|
});
|
package/dist/commands/init.js
CHANGED
|
@@ -2,4 +2,5 @@ import { writeSuppressions, SUPPRESSIONS_FILENAME } from "../suppressions.js";
|
|
|
2
2
|
export async function runInit() {
|
|
3
3
|
await writeSuppressions(process.cwd(), []);
|
|
4
4
|
console.log(`Created ${SUPPRESSIONS_FILENAME}`);
|
|
5
|
+
console.log(`\nTip: Add ${SUPPRESSIONS_FILENAME} to your formatter's ignore list (e.g. .prettierignore, .oxfmtignore) to preserve its compact format.`);
|
|
5
6
|
}
|
|
@@ -2,7 +2,6 @@ import { collectDiagnostics } from "../diagnostics.js";
|
|
|
2
2
|
import { writeSuppressions, SUPPRESSIONS_FILENAME } from "../suppressions.js";
|
|
3
3
|
/**
|
|
4
4
|
* Core logic, extracted for testability.
|
|
5
|
-
* Accepts a ts-morph Project and roots separately so tests can pass in-memory projects.
|
|
6
5
|
* outputRoot is where the suppression file is written (may differ from projectRoot in tests).
|
|
7
6
|
*/
|
|
8
7
|
export async function runSuppress(project, projectRoot, outputRoot = projectRoot) {
|
package/dist/diagnostics.js
CHANGED
|
@@ -1,25 +1,31 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
1
2
|
import { relative } from "node:path";
|
|
2
3
|
import { hashMessage } from "./hash.js";
|
|
3
4
|
import { buildScopePath } from "./scope.js";
|
|
5
|
+
import { findNodeAtPosition } from "./ast.js";
|
|
6
|
+
// Only the top-level message is used for fingerprinting; chained sub-messages
|
|
7
|
+
// are diagnostic detail that varies with context and would produce unstable hashes.
|
|
8
|
+
function flattenDiagnosticMessage(messageText) {
|
|
9
|
+
return typeof messageText === "string" ? messageText : messageText.messageText;
|
|
10
|
+
}
|
|
4
11
|
/**
|
|
5
|
-
* Collect all pre-emit diagnostics from a
|
|
12
|
+
* Collect all pre-emit diagnostics from a TypeScript Program as Suppression fingerprints.
|
|
6
13
|
* Project creation is the caller's responsibility — this enables in-memory testing.
|
|
7
14
|
*/
|
|
8
15
|
export function collectDiagnostics(project, projectRoot) {
|
|
9
|
-
const diagnostics =
|
|
16
|
+
const diagnostics = ts.getPreEmitDiagnostics(project.program);
|
|
10
17
|
const suppressions = [];
|
|
11
18
|
for (const diag of diagnostics) {
|
|
12
|
-
const sourceFile = diag.
|
|
19
|
+
const sourceFile = diag.file;
|
|
13
20
|
if (!sourceFile)
|
|
14
21
|
continue;
|
|
15
|
-
const filePath = relative(projectRoot, sourceFile.
|
|
16
|
-
const code = diag.
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const start = diag.getStart();
|
|
22
|
+
const filePath = relative(projectRoot, sourceFile.fileName);
|
|
23
|
+
const code = diag.code;
|
|
24
|
+
const message = flattenDiagnosticMessage(diag.messageText);
|
|
25
|
+
const start = diag.start;
|
|
20
26
|
let scope = "";
|
|
21
27
|
if (start != null) {
|
|
22
|
-
const node = sourceFile
|
|
28
|
+
const node = findNodeAtPosition(sourceFile, start);
|
|
23
29
|
if (node) {
|
|
24
30
|
scope = buildScopePath(node);
|
|
25
31
|
}
|
package/dist/project.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Project } from "ts-morph";
|
|
2
1
|
import ts from "typescript";
|
|
3
2
|
import { dirname } from "node:path";
|
|
4
3
|
/**
|
|
@@ -6,19 +5,27 @@ import { dirname } from "node:path";
|
|
|
6
5
|
* Uses TypeScript's own findConfigFile for correct resolution behavior.
|
|
7
6
|
*/
|
|
8
7
|
export function findTsConfig(cwd) {
|
|
9
|
-
const configPath = ts.findConfigFile(cwd, ts.sys.fileExists, "tsconfig.json");
|
|
8
|
+
const configPath = ts.findConfigFile(cwd, (f) => ts.sys.fileExists(f), "tsconfig.json");
|
|
10
9
|
if (!configPath) {
|
|
11
10
|
throw new Error(`No tsconfig.json found starting from ${cwd}`);
|
|
12
11
|
}
|
|
13
12
|
return configPath;
|
|
14
13
|
}
|
|
15
14
|
/**
|
|
16
|
-
* Create a
|
|
17
|
-
* Returns the
|
|
15
|
+
* Create a TypeScript Program from the nearest tsconfig.json.
|
|
16
|
+
* Returns the Program and the resolved project root (directory containing tsconfig.json).
|
|
18
17
|
*/
|
|
19
18
|
export function createProject(cwd) {
|
|
20
19
|
const tsConfigFilePath = findTsConfig(cwd);
|
|
21
20
|
const projectRoot = dirname(tsConfigFilePath);
|
|
22
|
-
const
|
|
23
|
-
|
|
21
|
+
const configFile = ts.readConfigFile(tsConfigFilePath, (f) => ts.sys.readFile(f));
|
|
22
|
+
if (configFile.error) {
|
|
23
|
+
throw new Error(ts.flattenDiagnosticMessageText(configFile.error.messageText, "\n"));
|
|
24
|
+
}
|
|
25
|
+
const parsed = ts.parseJsonConfigFileContent(configFile.config, ts.sys, projectRoot);
|
|
26
|
+
if (parsed.errors.length > 0) {
|
|
27
|
+
throw new Error(ts.flattenDiagnosticMessageText(parsed.errors[0].messageText, "\n"));
|
|
28
|
+
}
|
|
29
|
+
const program = ts.createProgram(parsed.fileNames, parsed.options);
|
|
30
|
+
return { project: { program }, projectRoot };
|
|
24
31
|
}
|
package/dist/scope.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { Node } from "ts-morph";
|
|
1
|
+
import ts from "typescript";
|
|
3
2
|
/**
|
|
4
3
|
* Build a dot-separated scope path by walking up the AST from a node.
|
|
5
4
|
* Returns empty string for module-level code.
|
|
@@ -19,34 +18,36 @@ export function buildScopePath(node) {
|
|
|
19
18
|
if (name != null) {
|
|
20
19
|
parts.unshift(name);
|
|
21
20
|
}
|
|
22
|
-
current = current.
|
|
21
|
+
current = current.parent;
|
|
23
22
|
}
|
|
24
23
|
return parts.join(".");
|
|
25
24
|
}
|
|
26
25
|
function getScopeName(node) {
|
|
27
|
-
if (
|
|
28
|
-
return node.
|
|
26
|
+
if (ts.isFunctionDeclaration(node)) {
|
|
27
|
+
return node.name?.text ?? null;
|
|
29
28
|
}
|
|
30
|
-
if (
|
|
31
|
-
return node.
|
|
29
|
+
if (ts.isMethodDeclaration(node)) {
|
|
30
|
+
return ts.isIdentifier(node.name) ? node.name.text : node.name.getText();
|
|
32
31
|
}
|
|
33
|
-
if (
|
|
34
|
-
return node.
|
|
32
|
+
if (ts.isClassDeclaration(node)) {
|
|
33
|
+
return node.name?.text ?? null;
|
|
35
34
|
}
|
|
36
|
-
if (
|
|
37
|
-
|
|
35
|
+
if (ts.isGetAccessorDeclaration(node)) {
|
|
36
|
+
const name = ts.isIdentifier(node.name) ? node.name.text : node.name.getText();
|
|
37
|
+
return `get:${name}`;
|
|
38
38
|
}
|
|
39
|
-
if (
|
|
40
|
-
|
|
39
|
+
if (ts.isSetAccessorDeclaration(node)) {
|
|
40
|
+
const name = ts.isIdentifier(node.name) ? node.name.text : node.name.getText();
|
|
41
|
+
return `set:${name}`;
|
|
41
42
|
}
|
|
42
|
-
if (
|
|
43
|
+
if (ts.isConstructorDeclaration(node)) {
|
|
43
44
|
return "constructor";
|
|
44
45
|
}
|
|
45
46
|
// Arrow function or function expression assigned to a variable
|
|
46
|
-
if (
|
|
47
|
-
const parent = node.
|
|
48
|
-
if (parent &&
|
|
49
|
-
return parent.
|
|
47
|
+
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
|
|
48
|
+
const parent = node.parent;
|
|
49
|
+
if (parent && ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) {
|
|
50
|
+
return parent.name.text;
|
|
50
51
|
}
|
|
51
52
|
return null; // anonymous, no scope name
|
|
52
53
|
}
|
package/dist/suppressions.js
CHANGED
|
@@ -42,8 +42,9 @@ export async function readSuppressions(projectRoot) {
|
|
|
42
42
|
export async function writeSuppressions(projectRoot, suppressions) {
|
|
43
43
|
const filePath = resolve(projectRoot, SUPPRESSIONS_FILENAME);
|
|
44
44
|
const sorted = [...suppressions].sort(compareSuppression);
|
|
45
|
-
const
|
|
46
|
-
|
|
45
|
+
const lines = sorted.map((s) => " " + JSON.stringify(s));
|
|
46
|
+
const content = `{"suppressions": [\n${lines.join(",\n")}\n]}\n`;
|
|
47
|
+
await writeFile(filePath, content);
|
|
47
48
|
}
|
|
48
49
|
/**
|
|
49
50
|
* Diff existing suppressions against current diagnostics.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
export function createInMemoryProject(files) {
|
|
3
|
+
const fileMap = new Map();
|
|
4
|
+
const fileNames = [];
|
|
5
|
+
for (const [name, content] of Object.entries(files)) {
|
|
6
|
+
const fullPath = `/${name}`;
|
|
7
|
+
fileMap.set(fullPath, content);
|
|
8
|
+
fileNames.push(fullPath);
|
|
9
|
+
}
|
|
10
|
+
const options = {
|
|
11
|
+
strict: true,
|
|
12
|
+
target: ts.ScriptTarget.ESNext,
|
|
13
|
+
lib: ["lib.esnext.d.ts"],
|
|
14
|
+
moduleDetection: ts.ModuleDetectionKind.Force,
|
|
15
|
+
types: [],
|
|
16
|
+
};
|
|
17
|
+
const host = ts.createCompilerHost(options);
|
|
18
|
+
const originalGetSourceFile = host.getSourceFile.bind(host);
|
|
19
|
+
const originalFileExists = host.fileExists.bind(host);
|
|
20
|
+
const originalReadFile = host.readFile.bind(host);
|
|
21
|
+
host.getSourceFile = (fileName, languageVersion) => {
|
|
22
|
+
const content = fileMap.get(fileName);
|
|
23
|
+
if (content != null) {
|
|
24
|
+
return ts.createSourceFile(fileName, content, languageVersion, true);
|
|
25
|
+
}
|
|
26
|
+
return originalGetSourceFile(fileName, languageVersion);
|
|
27
|
+
};
|
|
28
|
+
host.fileExists = (fileName) => {
|
|
29
|
+
return fileMap.has(fileName) || originalFileExists(fileName);
|
|
30
|
+
};
|
|
31
|
+
host.readFile = (fileName) => {
|
|
32
|
+
return fileMap.get(fileName) ?? originalReadFile(fileName);
|
|
33
|
+
};
|
|
34
|
+
const program = ts.createProgram(fileNames, options, host);
|
|
35
|
+
return { program };
|
|
36
|
+
}
|
package/dist/types.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-suppress",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Incremental TypeScript strictness adoption via bulk error suppression",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"migration",
|
|
@@ -31,24 +31,10 @@
|
|
|
31
31
|
"!skills/_artifacts"
|
|
32
32
|
],
|
|
33
33
|
"type": "module",
|
|
34
|
-
"scripts": {
|
|
35
|
-
"build": "tsc -p tsconfig.build.json",
|
|
36
|
-
"test": "bun test",
|
|
37
|
-
"prepublishOnly": "bun run build",
|
|
38
|
-
"fmt": "oxfmt",
|
|
39
|
-
"fmt:check": "oxfmt --check",
|
|
40
|
-
"knip": "knip",
|
|
41
|
-
"lint": "oxlint",
|
|
42
|
-
"lint:fix": "oxlint --fix",
|
|
43
|
-
"prepare": "husky",
|
|
44
|
-
"typecheck": "tsc --noEmit"
|
|
45
|
-
},
|
|
46
34
|
"dependencies": {
|
|
47
|
-
"
|
|
48
|
-
"ts-morph": "^27.0.2"
|
|
35
|
+
"mri": "^1.2.0"
|
|
49
36
|
},
|
|
50
37
|
"devDependencies": {
|
|
51
|
-
"@types/bun": "latest",
|
|
52
38
|
"@types/node": "^24",
|
|
53
39
|
"husky": "^9.1.7",
|
|
54
40
|
"knip": "^5.87.0",
|
|
@@ -56,13 +42,21 @@
|
|
|
56
42
|
"oxfmt": "^0.41.0",
|
|
57
43
|
"oxlint": "^1.56.0",
|
|
58
44
|
"oxlint-tsgolint": "^0.17.0",
|
|
59
|
-
"
|
|
45
|
+
"tsx": "^4.19.4",
|
|
46
|
+
"typescript": "^5.9.3",
|
|
47
|
+
"vitest": "^3.2.1"
|
|
60
48
|
},
|
|
61
49
|
"peerDependencies": {
|
|
62
50
|
"typescript": "^5.9.3"
|
|
63
51
|
},
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "tsc -p tsconfig.build.json",
|
|
54
|
+
"test": "vitest run",
|
|
55
|
+
"fmt": "oxfmt",
|
|
56
|
+
"fmt:check": "oxfmt --check",
|
|
57
|
+
"knip": "knip",
|
|
58
|
+
"lint": "oxlint",
|
|
59
|
+
"lint:fix": "oxlint --fix",
|
|
60
|
+
"typecheck": "tsc --noEmit"
|
|
67
61
|
}
|
|
68
|
-
}
|
|
62
|
+
}
|