@nt-ai-lab/opencode-skillz 0.3.5 → 0.3.6
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/commands/implement.md +2 -1
- package/dist/tools/lint.d.ts +14 -2
- package/dist/tools/lint.js +205 -67
- package/package.json +5 -5
- package/scripts/lint-ts.mjs +2 -212
package/commands/implement.md
CHANGED
|
@@ -10,8 +10,9 @@ Implement the required task to the highest standards possible. Do not take short
|
|
|
10
10
|
|
|
11
11
|
## Linting
|
|
12
12
|
|
|
13
|
-
After each small TypeScript code change, call `nt_skillz_lint` for the changed `.ts` or `.tsx` file or files.
|
|
13
|
+
After each small TypeScript code change, call `nt_skillz_lint` with only the `files` argument for the changed `.ts` or `.tsx` file or files from that step.
|
|
14
14
|
|
|
15
|
+
- do not pass `base` or `head` during normal implementation work
|
|
15
16
|
- all lint errors on new code must be addressed before continuing
|
|
16
17
|
- if the lint fails on existing code, ignore the error unless it is very close to the new code
|
|
17
18
|
- line-length limits do not count as existing code; if new code causes a file-length lint error, it must be fixed
|
package/dist/tools/lint.d.ts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
interface PortableLintRequest {
|
|
2
|
+
repositoryRoot?: string;
|
|
3
|
+
files?: string[];
|
|
4
|
+
base?: string;
|
|
5
|
+
head?: string;
|
|
6
|
+
}
|
|
7
|
+
interface PortableLintOutcome {
|
|
8
|
+
exitCode: number;
|
|
9
|
+
output: string;
|
|
10
|
+
}
|
|
2
11
|
export declare const LINT_TOOL_NAME = "nt_skillz_lint";
|
|
12
|
+
export declare function runPortableLint(request: PortableLintRequest): Promise<PortableLintOutcome>;
|
|
13
|
+
export declare function runPortableLintFromCommandLine(commandLineArguments: string[]): Promise<number>;
|
|
3
14
|
export declare const lintTool: {
|
|
4
15
|
description: string;
|
|
5
16
|
args: {
|
|
@@ -11,5 +22,6 @@ export declare const lintTool: {
|
|
|
11
22
|
files?: string[] | undefined;
|
|
12
23
|
base?: string | undefined;
|
|
13
24
|
head?: string | undefined;
|
|
14
|
-
}, context: ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
|
|
25
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
|
|
15
26
|
};
|
|
27
|
+
export {};
|
package/dist/tools/lint.js
CHANGED
|
@@ -1,89 +1,219 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
2
5
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
import {
|
|
4
|
-
|
|
6
|
+
import { parseArgs } from "node:util";
|
|
7
|
+
import { ESLint } from "eslint";
|
|
8
|
+
import { tool } from "@opencode-ai/plugin";
|
|
9
|
+
class UsageError extends Error {
|
|
5
10
|
constructor(message) {
|
|
6
11
|
super(message);
|
|
7
12
|
}
|
|
8
13
|
}
|
|
9
|
-
class
|
|
14
|
+
class GitCommandError extends Error {
|
|
10
15
|
constructor(message) {
|
|
11
16
|
super(message);
|
|
12
17
|
}
|
|
13
18
|
}
|
|
14
|
-
|
|
19
|
+
class MissingLocalDependencyError extends Error {
|
|
20
|
+
constructor(message) {
|
|
21
|
+
super(message);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
class LintExecutionError extends Error {
|
|
25
|
+
constructor(message) {
|
|
26
|
+
super(message);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const toolDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
const toolRepositoryRoot = path.resolve(toolDirectory, "..", "..");
|
|
31
|
+
const eslintCliPath = path.join(toolRepositoryRoot, "node_modules", "eslint", "bin", "eslint.js");
|
|
32
|
+
const eslintConfigPath = path.join(toolRepositoryRoot, "scripts", "living-architecture-eslint.config.mjs");
|
|
33
|
+
const gitBinaryPath = "/usr/bin/git";
|
|
15
34
|
export const LINT_TOOL_NAME = "nt_skillz_lint";
|
|
16
|
-
function
|
|
17
|
-
if (
|
|
18
|
-
|
|
35
|
+
function normalizeOptionalText(value) {
|
|
36
|
+
if (typeof value !== "string") {
|
|
37
|
+
return undefined;
|
|
19
38
|
}
|
|
20
|
-
|
|
21
|
-
|
|
39
|
+
const trimmedValue = value.trim();
|
|
40
|
+
if (!trimmedValue) {
|
|
41
|
+
return undefined;
|
|
22
42
|
}
|
|
43
|
+
return trimmedValue;
|
|
23
44
|
}
|
|
24
|
-
function
|
|
25
|
-
const
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
45
|
+
function resolveDirectory(directoryPath) {
|
|
46
|
+
const absoluteDirectoryPath = path.resolve(directoryPath);
|
|
47
|
+
if (!fs.existsSync(absoluteDirectoryPath)) {
|
|
48
|
+
throw new UsageError(`Expected repository path to exist. Got ${absoluteDirectoryPath}.`);
|
|
49
|
+
}
|
|
50
|
+
if (!fs.statSync(absoluteDirectoryPath).isDirectory()) {
|
|
51
|
+
throw new UsageError(`Expected repository path to be a directory. Got ${absoluteDirectoryPath}.`);
|
|
52
|
+
}
|
|
53
|
+
return absoluteDirectoryPath;
|
|
54
|
+
}
|
|
55
|
+
function ensureSupportedFilePath(filePath) {
|
|
56
|
+
if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) {
|
|
57
|
+
return;
|
|
31
58
|
}
|
|
32
|
-
|
|
33
|
-
|
|
59
|
+
throw new UsageError(`Expected a TypeScript file path ending in .ts or .tsx. Got ${filePath}.`);
|
|
60
|
+
}
|
|
61
|
+
function normalizeFilePaths(filePaths) {
|
|
62
|
+
const normalizedFilePaths = (filePaths ?? []).map((filePath) => filePath.trim()).filter(Boolean);
|
|
63
|
+
normalizedFilePaths.forEach(ensureSupportedFilePath);
|
|
64
|
+
return normalizedFilePaths;
|
|
65
|
+
}
|
|
66
|
+
function ensureLocalDependencies() {
|
|
67
|
+
if (fs.existsSync(eslintCliPath)) {
|
|
68
|
+
return;
|
|
34
69
|
}
|
|
35
|
-
|
|
70
|
+
throw new MissingLocalDependencyError(`Expected ESLint dependencies to be installed in ${toolRepositoryRoot}.`);
|
|
36
71
|
}
|
|
37
|
-
function
|
|
38
|
-
|
|
39
|
-
|
|
72
|
+
function runGitCommand(repositoryRoot, gitArguments) {
|
|
73
|
+
const gitResult = spawnSync(gitBinaryPath, ["-C", repositoryRoot, ...gitArguments], { encoding: "utf8" });
|
|
74
|
+
if (gitResult.error) {
|
|
75
|
+
throw new GitCommandError(`Expected git command to run. Got ${gitResult.error.message}.`);
|
|
40
76
|
}
|
|
41
|
-
if (
|
|
42
|
-
return
|
|
77
|
+
if (gitResult.status === 0) {
|
|
78
|
+
return gitResult.stdout;
|
|
43
79
|
}
|
|
44
|
-
|
|
80
|
+
const errorOutput = gitResult.stderr.trim() || gitResult.stdout.trim() || "git command failed";
|
|
81
|
+
throw new GitCommandError(`Expected git command to succeed. Got ${errorOutput}.`);
|
|
82
|
+
}
|
|
83
|
+
function readChangedTypeScriptFiles(repositoryRoot, baseReference, headReference) {
|
|
84
|
+
const diffRange = `${baseReference}...${headReference}`;
|
|
85
|
+
const output = runGitCommand(repositoryRoot, [
|
|
86
|
+
"diff",
|
|
87
|
+
"--name-only",
|
|
88
|
+
"--diff-filter=ACMR",
|
|
89
|
+
diffRange,
|
|
90
|
+
"--",
|
|
91
|
+
"*.ts",
|
|
92
|
+
"*.tsx",
|
|
93
|
+
]);
|
|
94
|
+
return output.split("\n").filter(Boolean);
|
|
45
95
|
}
|
|
46
|
-
function
|
|
47
|
-
const
|
|
48
|
-
|
|
96
|
+
function readTrackedAndUntrackedTypeScriptFiles(repositoryRoot) {
|
|
97
|
+
const output = runGitCommand(repositoryRoot, [
|
|
98
|
+
"ls-files",
|
|
99
|
+
"--cached",
|
|
100
|
+
"--others",
|
|
101
|
+
"--exclude-standard",
|
|
102
|
+
"--",
|
|
103
|
+
"*.ts",
|
|
104
|
+
"*.tsx",
|
|
105
|
+
]);
|
|
106
|
+
return output.split("\n").filter(Boolean);
|
|
49
107
|
}
|
|
50
|
-
function
|
|
51
|
-
if (
|
|
52
|
-
|
|
108
|
+
function ensureValidLintRequest(baseReference, headReference, filePaths) {
|
|
109
|
+
if (baseReference && filePaths.length > 0) {
|
|
110
|
+
throw new UsageError("Expected either file paths or base reference. Got both.");
|
|
111
|
+
}
|
|
112
|
+
if (!baseReference && headReference) {
|
|
113
|
+
throw new UsageError("Expected head reference to be used together with base reference.");
|
|
53
114
|
}
|
|
54
|
-
return 1;
|
|
55
115
|
}
|
|
56
|
-
function
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
116
|
+
function resolveLintTargets(repositoryRoot, baseReference, headReference, filePaths) {
|
|
117
|
+
if (filePaths.length > 0) {
|
|
118
|
+
return filePaths;
|
|
119
|
+
}
|
|
120
|
+
if (baseReference) {
|
|
121
|
+
return readChangedTypeScriptFiles(repositoryRoot, baseReference, headReference ?? "HEAD");
|
|
122
|
+
}
|
|
123
|
+
return readTrackedAndUntrackedTypeScriptFiles(repositoryRoot);
|
|
124
|
+
}
|
|
125
|
+
function createLintTitle(filePaths, baseReference) {
|
|
126
|
+
if (filePaths.length > 0) {
|
|
127
|
+
return `Lint ${filePaths.length} TypeScript file(s)`;
|
|
128
|
+
}
|
|
129
|
+
if (baseReference) {
|
|
130
|
+
return `Lint TypeScript changes from ${baseReference}`;
|
|
131
|
+
}
|
|
132
|
+
return "Lint current TypeScript files";
|
|
133
|
+
}
|
|
134
|
+
async function runEslint(repositoryRoot, lintTargets) {
|
|
135
|
+
const previousLintRepositoryRoot = process.env.NT_SKILLZ_LINT_REPO_ROOT;
|
|
136
|
+
process.env.NT_SKILLZ_LINT_REPO_ROOT = repositoryRoot;
|
|
137
|
+
const eslint = new ESLint({
|
|
138
|
+
cwd: repositoryRoot,
|
|
139
|
+
errorOnUnmatchedPattern: false,
|
|
140
|
+
overrideConfigFile: eslintConfigPath,
|
|
141
|
+
warnIgnored: false,
|
|
142
|
+
});
|
|
143
|
+
try {
|
|
144
|
+
const lintResults = await eslint.lintFiles(lintTargets);
|
|
145
|
+
const formatter = await eslint.loadFormatter("stylish");
|
|
146
|
+
const formattedOutput = (await formatter.format(lintResults)).trim();
|
|
147
|
+
const errorCount = lintResults.reduce((count, lintResult) => count + lintResult.errorCount + lintResult.fatalErrorCount, 0);
|
|
148
|
+
return {
|
|
149
|
+
exitCode: errorCount > 0 ? 1 : 0,
|
|
150
|
+
output: formattedOutput,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
if (previousLintRepositoryRoot) {
|
|
155
|
+
process.env.NT_SKILLZ_LINT_REPO_ROOT = previousLintRepositoryRoot;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
delete process.env.NT_SKILLZ_LINT_REPO_ROOT;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function parsePortableLintCommandLine(commandLineArguments) {
|
|
163
|
+
const parsedArguments = parseArgs({
|
|
164
|
+
args: commandLineArguments,
|
|
165
|
+
options: {
|
|
166
|
+
repo: { type: "string" },
|
|
167
|
+
base: { type: "string" },
|
|
168
|
+
head: { type: "string" },
|
|
169
|
+
help: {
|
|
170
|
+
type: "boolean",
|
|
171
|
+
short: "h",
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
allowPositionals: true,
|
|
80
175
|
});
|
|
176
|
+
if (parsedArguments.values.help) {
|
|
177
|
+
process.stdout.write([
|
|
178
|
+
"Usage: ./scripts/lint-ts.sh [--repo PATH] [--base REF] [--head REF] [file ...]",
|
|
179
|
+
"",
|
|
180
|
+
"Examples:",
|
|
181
|
+
" ./scripts/lint-ts.sh --repo ../living-architecture --base origin/main",
|
|
182
|
+
" ./scripts/lint-ts.sh --repo ../living-architecture packages/example/src/example.ts",
|
|
183
|
+
" ./scripts/lint-ts.sh src/example.ts",
|
|
184
|
+
].join("\n") + "\n");
|
|
185
|
+
process.exit(0);
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
repositoryRoot: resolveDirectory(parsedArguments.values.repo ?? process.cwd()),
|
|
189
|
+
files: normalizeFilePaths(parsedArguments.positionals),
|
|
190
|
+
base: normalizeOptionalText(parsedArguments.values.base),
|
|
191
|
+
head: normalizeOptionalText(parsedArguments.values.head),
|
|
192
|
+
};
|
|
81
193
|
}
|
|
82
|
-
function
|
|
194
|
+
export async function runPortableLint(request) {
|
|
195
|
+
ensureLocalDependencies();
|
|
196
|
+
const repositoryRoot = resolveDirectory(request.repositoryRoot ?? process.cwd());
|
|
197
|
+
const baseReference = normalizeOptionalText(request.base);
|
|
198
|
+
const headReference = normalizeOptionalText(request.head);
|
|
199
|
+
const filePaths = normalizeFilePaths(request.files);
|
|
200
|
+
ensureValidLintRequest(baseReference, headReference, filePaths);
|
|
201
|
+
const lintTargets = resolveLintTargets(repositoryRoot, baseReference, headReference, filePaths);
|
|
202
|
+
if (lintTargets.length === 0) {
|
|
203
|
+
return {
|
|
204
|
+
exitCode: 0,
|
|
205
|
+
output: "No TypeScript files matched.",
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
return runEslint(repositoryRoot, lintTargets);
|
|
209
|
+
}
|
|
210
|
+
export async function runPortableLintFromCommandLine(commandLineArguments) {
|
|
211
|
+
const request = parsePortableLintCommandLine(commandLineArguments);
|
|
212
|
+
const outcome = await runPortableLint(request);
|
|
83
213
|
if (outcome.output) {
|
|
84
|
-
|
|
214
|
+
process.stdout.write(`${outcome.output}\n`);
|
|
85
215
|
}
|
|
86
|
-
return
|
|
216
|
+
return outcome.exitCode;
|
|
87
217
|
}
|
|
88
218
|
export const lintTool = tool({
|
|
89
219
|
description: "Run bundled TypeScript lint rules against current project files.",
|
|
@@ -93,18 +223,26 @@ export const lintTool = tool({
|
|
|
93
223
|
head: tool.schema.string().optional().describe("Optional head git reference used with base."),
|
|
94
224
|
},
|
|
95
225
|
async execute(request, context) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const
|
|
226
|
+
const filePaths = normalizeFilePaths(request.files);
|
|
227
|
+
const baseReference = normalizeOptionalText(request.base);
|
|
228
|
+
const headReference = normalizeOptionalText(request.head);
|
|
229
|
+
ensureValidLintRequest(baseReference, headReference, filePaths);
|
|
230
|
+
context.metadata({ title: createLintTitle(filePaths, baseReference) });
|
|
231
|
+
const outcome = await runPortableLint({
|
|
232
|
+
repositoryRoot: context.worktree,
|
|
233
|
+
files: filePaths,
|
|
234
|
+
base: baseReference,
|
|
235
|
+
head: headReference,
|
|
236
|
+
});
|
|
99
237
|
if (outcome.exitCode !== 0) {
|
|
100
|
-
throw new
|
|
238
|
+
throw new LintExecutionError(outcome.output || "Lint failed.");
|
|
101
239
|
}
|
|
102
240
|
return {
|
|
103
241
|
output: outcome.output || "Lint passed.",
|
|
104
242
|
metadata: {
|
|
105
|
-
base:
|
|
106
|
-
fileCount:
|
|
107
|
-
head:
|
|
243
|
+
base: baseReference ?? null,
|
|
244
|
+
fileCount: filePaths.length,
|
|
245
|
+
head: headReference ?? null,
|
|
108
246
|
},
|
|
109
247
|
};
|
|
110
248
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nt-ai-lab/opencode-skillz",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "Bundled OpenCode commands and agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -24,14 +24,11 @@
|
|
|
24
24
|
"README.md"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@opencode-ai/plugin": "^1.14.28"
|
|
28
|
-
},
|
|
29
|
-
"devDependencies": {
|
|
30
27
|
"@eslint-community/eslint-plugin-eslint-comments": "^4.5.0",
|
|
31
28
|
"@eslint/js": "^9.39.0",
|
|
29
|
+
"@opencode-ai/plugin": "^1.14.28",
|
|
32
30
|
"@stylistic/eslint-plugin": "^5.6.1",
|
|
33
31
|
"@vitest/eslint-plugin": "^1.0.1",
|
|
34
|
-
"@types/node": "^24.7.2",
|
|
35
32
|
"eslint": "^9.39.0",
|
|
36
33
|
"eslint-plugin-import": "^2.32.0",
|
|
37
34
|
"eslint-plugin-sonarjs": "^3.0.5",
|
|
@@ -39,6 +36,9 @@
|
|
|
39
36
|
"typescript": "^5.9.3",
|
|
40
37
|
"typescript-eslint": "^8.50.1"
|
|
41
38
|
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^24.7.2"
|
|
41
|
+
},
|
|
42
42
|
"publishConfig": {
|
|
43
43
|
"access": "public"
|
|
44
44
|
}
|
package/scripts/lint-ts.mjs
CHANGED
|
@@ -1,218 +1,8 @@
|
|
|
1
|
-
import fs from 'node:fs'
|
|
2
|
-
import path from 'node:path'
|
|
3
1
|
import process from 'node:process'
|
|
4
|
-
import {
|
|
5
|
-
import { fileURLToPath } from 'node:url'
|
|
6
|
-
import { parseArgs } from 'node:util'
|
|
7
|
-
|
|
8
|
-
class UsageError extends Error {
|
|
9
|
-
constructor(message) {
|
|
10
|
-
super(message)
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
class GitCommandError extends Error {
|
|
15
|
-
constructor(message) {
|
|
16
|
-
super(message)
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
class MissingLocalDependencyError extends Error {
|
|
21
|
-
constructor(message) {
|
|
22
|
-
super(message)
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
class LintExecutionError extends Error {
|
|
27
|
-
constructor(message) {
|
|
28
|
-
super(message)
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const scriptDirectory = path.dirname(fileURLToPath(import.meta.url))
|
|
33
|
-
const toolRepositoryRoot = path.resolve(scriptDirectory, '..')
|
|
34
|
-
const eslintCliPath = path.join(toolRepositoryRoot, 'node_modules', 'eslint', 'bin', 'eslint.js')
|
|
35
|
-
const eslintConfigPath = path.join(toolRepositoryRoot, 'scripts', 'living-architecture-eslint.config.mjs')
|
|
36
|
-
|
|
37
|
-
const printUsage = () => {
|
|
38
|
-
const usage = [
|
|
39
|
-
'Usage: ./scripts/lint-ts.sh [--repo PATH] [--base REF] [--head REF] [file ...]',
|
|
40
|
-
'',
|
|
41
|
-
'Examples:',
|
|
42
|
-
' ./scripts/lint-ts.sh --repo ../living-architecture --base origin/main',
|
|
43
|
-
' ./scripts/lint-ts.sh --repo ../living-architecture packages/example/src/example.ts',
|
|
44
|
-
' ./scripts/lint-ts.sh src/example.ts',
|
|
45
|
-
].join('\n')
|
|
46
|
-
|
|
47
|
-
process.stdout.write(`${usage}\n`)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const parseCommandLine = () => {
|
|
51
|
-
return parseArgs({
|
|
52
|
-
options: {
|
|
53
|
-
repo: { type: 'string' },
|
|
54
|
-
base: { type: 'string' },
|
|
55
|
-
head: { type: 'string' },
|
|
56
|
-
help: { type: 'boolean', short: 'h' },
|
|
57
|
-
},
|
|
58
|
-
allowPositionals: true,
|
|
59
|
-
})
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const resolveDirectory = (directoryPath) => {
|
|
63
|
-
const absoluteDirectoryPath = path.resolve(directoryPath)
|
|
64
|
-
|
|
65
|
-
if (!fs.existsSync(absoluteDirectoryPath)) {
|
|
66
|
-
throw new UsageError(`Expected repository path to exist. Got ${absoluteDirectoryPath}.`)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (!fs.statSync(absoluteDirectoryPath).isDirectory()) {
|
|
70
|
-
throw new UsageError(`Expected repository path to be a directory. Got ${absoluteDirectoryPath}.`)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return absoluteDirectoryPath
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const ensureSupportedFilePath = (filePath) => {
|
|
77
|
-
if (filePath.endsWith('.ts') || filePath.endsWith('.tsx')) {
|
|
78
|
-
return
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
throw new UsageError(`Expected a TypeScript file path ending in .ts or .tsx. Got ${filePath}.`)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const runGit = (repositoryRoot, args) => {
|
|
85
|
-
const gitResult = spawnSync('git', ['-C', repositoryRoot, ...args], {
|
|
86
|
-
encoding: 'utf8',
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
if (gitResult.error) {
|
|
90
|
-
throw new GitCommandError(`Expected git command to run. Got ${gitResult.error.message}.`)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (gitResult.status === 0) {
|
|
94
|
-
return gitResult.stdout
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const errorOutput = gitResult.stderr.trim() || gitResult.stdout.trim() || 'git command failed'
|
|
98
|
-
throw new GitCommandError(`Expected git command to succeed. Got ${errorOutput}.`)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const readChangedTypeScriptFiles = (repositoryRoot, baseReference, headReference) => {
|
|
102
|
-
const diffRange = `${baseReference}...${headReference}`
|
|
103
|
-
const output = runGit(repositoryRoot, [
|
|
104
|
-
'diff',
|
|
105
|
-
'--name-only',
|
|
106
|
-
'--diff-filter=ACMR',
|
|
107
|
-
diffRange,
|
|
108
|
-
'--',
|
|
109
|
-
'*.ts',
|
|
110
|
-
'*.tsx',
|
|
111
|
-
])
|
|
112
|
-
|
|
113
|
-
return output.split('\n').filter(Boolean)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const readTrackedAndUntrackedTypeScriptFiles = (repositoryRoot) => {
|
|
117
|
-
const output = runGit(repositoryRoot, [
|
|
118
|
-
'ls-files',
|
|
119
|
-
'--cached',
|
|
120
|
-
'--others',
|
|
121
|
-
'--exclude-standard',
|
|
122
|
-
'--',
|
|
123
|
-
'*.ts',
|
|
124
|
-
'*.tsx',
|
|
125
|
-
])
|
|
126
|
-
|
|
127
|
-
return output.split('\n').filter(Boolean)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const resolveLintTargets = (repositoryRoot, baseReference, headReference, positionalArguments) => {
|
|
131
|
-
if (positionalArguments.length > 0) {
|
|
132
|
-
positionalArguments.forEach(ensureSupportedFilePath)
|
|
133
|
-
return positionalArguments
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (baseReference) {
|
|
137
|
-
return readChangedTypeScriptFiles(repositoryRoot, baseReference, headReference)
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return readTrackedAndUntrackedTypeScriptFiles(repositoryRoot)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const ensureValidArguments = (baseReference, headReference, positionalArguments) => {
|
|
144
|
-
if (baseReference && positionalArguments.length > 0) {
|
|
145
|
-
throw new UsageError('Expected either explicit file paths or --base. Got both.')
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (!baseReference && headReference !== 'HEAD') {
|
|
149
|
-
throw new UsageError('Expected --head to be used together with --base.')
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const ensureLocalDependencies = () => {
|
|
154
|
-
if (fs.existsSync(eslintCliPath)) {
|
|
155
|
-
return
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
throw new MissingLocalDependencyError(
|
|
159
|
-
`Expected ESLint dependencies to be installed in ${toolRepositoryRoot}. Run npm install in this repository.`,
|
|
160
|
-
)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const runEslint = (repositoryRoot, lintTargets) => {
|
|
164
|
-
const eslintResult = spawnSync(
|
|
165
|
-
process.execPath,
|
|
166
|
-
[eslintCliPath, '--no-config-lookup', '--no-warn-ignored', '--config', eslintConfigPath, ...lintTargets],
|
|
167
|
-
{
|
|
168
|
-
cwd: repositoryRoot,
|
|
169
|
-
env: {
|
|
170
|
-
...process.env,
|
|
171
|
-
NT_SKILLZ_LINT_REPO_ROOT: repositoryRoot,
|
|
172
|
-
},
|
|
173
|
-
stdio: 'inherit',
|
|
174
|
-
},
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
if (eslintResult.error) {
|
|
178
|
-
throw new LintExecutionError(`Expected ESLint to run. Got ${eslintResult.error.message}.`)
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (typeof eslintResult.status === 'number') {
|
|
182
|
-
return eslintResult.status
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
throw new LintExecutionError(`Expected ESLint to exit with a status code. Got signal ${eslintResult.signal}.`)
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const main = () => {
|
|
189
|
-
const parsedArguments = parseCommandLine()
|
|
190
|
-
|
|
191
|
-
if (parsedArguments.values.help) {
|
|
192
|
-
printUsage()
|
|
193
|
-
return 0
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const repositoryRoot = resolveDirectory(parsedArguments.values.repo ?? process.cwd())
|
|
197
|
-
const baseReference = parsedArguments.values.base
|
|
198
|
-
const headReference = parsedArguments.values.head ?? 'HEAD'
|
|
199
|
-
const positionalArguments = parsedArguments.positionals
|
|
200
|
-
|
|
201
|
-
ensureValidArguments(baseReference, headReference, positionalArguments)
|
|
202
|
-
ensureLocalDependencies()
|
|
203
|
-
|
|
204
|
-
const lintTargets = resolveLintTargets(repositoryRoot, baseReference, headReference, positionalArguments)
|
|
205
|
-
|
|
206
|
-
if (lintTargets.length === 0) {
|
|
207
|
-
process.stdout.write('No TypeScript files matched.\n')
|
|
208
|
-
return 0
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return runEslint(repositoryRoot, lintTargets)
|
|
212
|
-
}
|
|
2
|
+
import { runPortableLintFromCommandLine } from '../dist/tools/lint.js'
|
|
213
3
|
|
|
214
4
|
try {
|
|
215
|
-
process.exitCode =
|
|
5
|
+
process.exitCode = await runPortableLintFromCommandLine(process.argv.slice(2))
|
|
216
6
|
} catch (error) {
|
|
217
7
|
const message = error instanceof Error ? error.message : `Expected an error message. Got ${String(error)}.`
|
|
218
8
|
process.stderr.write(`${message}\n`)
|