@konvert7/klint 0.10.0 → 0.12.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/cli.ts +43 -3
- package/core/config.schema.ts +2 -2
- package/core/runner.ts +53 -11
- package/package.json +8 -1
- package/tools/prepare-native-release.ts +168 -0
- package/tools/semantic-release-root-npm.js +52 -0
package/cli.ts
CHANGED
|
@@ -13,7 +13,7 @@ import * as clack from "@clack/prompts";
|
|
|
13
13
|
import { parse as parseYaml } from "yaml";
|
|
14
14
|
import { applyFixes } from "./core/fixer";
|
|
15
15
|
import { resolveNativePackageBinary } from "./core/native-binary";
|
|
16
|
-
import { runKlint } from "./core/runner";
|
|
16
|
+
import { type KlintDebugEvent, runKlint } from "./core/runner";
|
|
17
17
|
import type { ArchConfig, KlintConfig, KlintRule, RuleConfigValue } from "./core/types";
|
|
18
18
|
import { BUILT_IN_PLUGINS } from "./plugins/index";
|
|
19
19
|
import { BUILT_IN_RULES } from "./rules/index";
|
|
@@ -40,12 +40,14 @@ export async function main(opts: CliOptions = {}): Promise<void> {
|
|
|
40
40
|
let rulesFile = opts.rulesFile;
|
|
41
41
|
let fix = false;
|
|
42
42
|
let json = false;
|
|
43
|
+
let debug = false;
|
|
43
44
|
|
|
44
45
|
for (let i = 0; i < args.length; i++) {
|
|
45
46
|
if (args[i] === "--config" && args[i + 1]) configDir = resolve(args[++i]);
|
|
46
47
|
else if (args[i] === "--rules" && args[i + 1]) rulesFile = resolve(args[++i]);
|
|
47
48
|
else if (args[i] === "--fix") fix = true;
|
|
48
49
|
else if (args[i] === "--json") json = true;
|
|
50
|
+
else if (args[i] === "--debug" || args[i] === "-debug") debug = true;
|
|
49
51
|
else if (args[i] === "--help" || args[i] === "-h") {
|
|
50
52
|
printHelp();
|
|
51
53
|
process.exit(0);
|
|
@@ -111,7 +113,8 @@ export async function main(opts: CliOptions = {}): Promise<void> {
|
|
|
111
113
|
rules: allRules,
|
|
112
114
|
arch: raw.arch as ArchConfig | undefined,
|
|
113
115
|
},
|
|
114
|
-
customRules
|
|
116
|
+
customRules,
|
|
117
|
+
{ onDebug: debug ? writeDebugEvent : undefined }
|
|
115
118
|
);
|
|
116
119
|
|
|
117
120
|
if (json) {
|
|
@@ -140,7 +143,8 @@ export async function main(opts: CliOptions = {}): Promise<void> {
|
|
|
140
143
|
rules: allRules,
|
|
141
144
|
arch: raw.arch as ArchConfig | undefined,
|
|
142
145
|
},
|
|
143
|
-
customRules
|
|
146
|
+
customRules,
|
|
147
|
+
{ onDebug: debug ? writeDebugEvent : undefined }
|
|
144
148
|
);
|
|
145
149
|
if (current.every((v) => !v.fix)) break;
|
|
146
150
|
}
|
|
@@ -212,6 +216,11 @@ function runRustEngine({
|
|
|
212
216
|
}
|
|
213
217
|
|
|
214
218
|
const command = resolveRustEngineCommand(configDir);
|
|
219
|
+
if (process.argv.includes("--debug") || process.argv.includes("-debug")) {
|
|
220
|
+
process.stderr.write(
|
|
221
|
+
`[klint:debug] rust engine: ${command.bin} ${command.args.join(" ")}\n`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
215
224
|
const result = spawnSync(command.bin, command.args, {
|
|
216
225
|
cwd: import.meta.dir,
|
|
217
226
|
encoding: "utf-8",
|
|
@@ -222,6 +231,36 @@ function runRustEngine({
|
|
|
222
231
|
process.exit(result.status ?? 1);
|
|
223
232
|
}
|
|
224
233
|
|
|
234
|
+
function writeDebugEvent(event: KlintDebugEvent): void {
|
|
235
|
+
switch (event.type) {
|
|
236
|
+
case "walk:start":
|
|
237
|
+
process.stderr.write(`[klint:debug] walk ${event.dir}\n`);
|
|
238
|
+
break;
|
|
239
|
+
case "walk:done":
|
|
240
|
+
process.stderr.write(`[klint:debug] walked ${event.dir} (${event.files} files)\n`);
|
|
241
|
+
break;
|
|
242
|
+
case "files:resolved":
|
|
243
|
+
process.stderr.write(`[klint:debug] resolved ${event.files} files\n`);
|
|
244
|
+
break;
|
|
245
|
+
case "rule:start":
|
|
246
|
+
process.stderr.write(
|
|
247
|
+
`[klint:debug] rule ${event.rule} start (${event.files} files)\n`
|
|
248
|
+
);
|
|
249
|
+
break;
|
|
250
|
+
case "rule:done":
|
|
251
|
+
process.stderr.write(
|
|
252
|
+
`[klint:debug] rule ${event.rule} done (${event.violations} violations)\n`
|
|
253
|
+
);
|
|
254
|
+
break;
|
|
255
|
+
case "arch:start":
|
|
256
|
+
process.stderr.write(`[klint:debug] arch start (${event.files} files)\n`);
|
|
257
|
+
break;
|
|
258
|
+
case "arch:done":
|
|
259
|
+
process.stderr.write(`[klint:debug] arch done (${event.violations} violations)\n`);
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
225
264
|
interface RustEngineCommand {
|
|
226
265
|
bin: string;
|
|
227
266
|
args: string[];
|
|
@@ -396,6 +435,7 @@ function printHelp(): void {
|
|
|
396
435
|
" --rules <file> custom rules file (default: <configDir>/klint.rules.ts if present)",
|
|
397
436
|
" --fix apply auto-fixes for fixable violations in-place",
|
|
398
437
|
" --json emit structured JSON to stdout (for agent/CI consumption)",
|
|
438
|
+
" --debug print file resolution and rule progress to stderr",
|
|
399
439
|
"",
|
|
400
440
|
" install-skill install the rule-authoring skill into agent config directories",
|
|
401
441
|
" --agents <list> comma-separated: claude,opencode,cursor,codex (default: all)",
|
package/core/config.schema.ts
CHANGED
|
@@ -115,7 +115,7 @@ const ArchForbiddenPatternRuleSchema = z
|
|
|
115
115
|
pattern: z
|
|
116
116
|
.string()
|
|
117
117
|
.describe(
|
|
118
|
-
"Literal substring to search for in source. No regex — exact substring match (
|
|
118
|
+
"Literal substring to search for in source. No regex — exact substring match (for example, a console logging call)."
|
|
119
119
|
),
|
|
120
120
|
in: StringOrStringArray.describe(
|
|
121
121
|
"Layer name(s) or glob(s) the pattern is forbidden in. Files outside the scope are not checked."
|
|
@@ -153,7 +153,7 @@ const ArchForbiddenRuleSchema = z
|
|
|
153
153
|
)
|
|
154
154
|
.meta({
|
|
155
155
|
examples: [
|
|
156
|
-
'arch:\n forbidden:\n - pattern: "
|
|
156
|
+
'arch:\n forbidden:\n - pattern: "debugLog("\n in: core\n message: "Leaks into the agent event stream"',
|
|
157
157
|
'arch:\n forbidden:\n - jsx-element: ["button", "input", "label"]\n in: ["src/app/**/*.tsx", "src/components/**/*.tsx", "!src/components/ui/**"]\n message: "Use the design-system primitives in @/components/ui/* instead of raw HTML elements."',
|
|
158
158
|
],
|
|
159
159
|
});
|
package/core/runner.ts
CHANGED
|
@@ -12,23 +12,49 @@ import type {
|
|
|
12
12
|
Violation,
|
|
13
13
|
} from "./types";
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
export type KlintDebugEvent =
|
|
16
|
+
| { type: "walk:start"; dir: string }
|
|
17
|
+
| { type: "walk:done"; dir: string; files: number }
|
|
18
|
+
| { type: "files:resolved"; files: number }
|
|
19
|
+
| { type: "rule:start"; rule: string; files: number }
|
|
20
|
+
| { type: "rule:done"; rule: string; violations: number }
|
|
21
|
+
| { type: "arch:start"; files: number }
|
|
22
|
+
| { type: "arch:done"; violations: number };
|
|
23
|
+
|
|
24
|
+
export interface RunKlintOptions {
|
|
25
|
+
onDebug?: (event: KlintDebugEvent) => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function walk(dir: string, root: string, excludes: string[] = []): string[] {
|
|
16
29
|
const out: string[] = [];
|
|
17
30
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
18
31
|
const full = join(dir, entry.name);
|
|
19
|
-
|
|
20
|
-
|
|
32
|
+
const rel = relative(root, full).replaceAll("\\", "/");
|
|
33
|
+
if (entry.isDirectory()) {
|
|
34
|
+
if (excludes.some((pattern) => matchPattern(rel, pattern))) continue;
|
|
35
|
+
out.push(...walk(full, root, excludes));
|
|
36
|
+
} else if (/\.(tsx?|jsx?|mts|cts)$/.test(entry.name))
|
|
21
37
|
out.push(full.replaceAll("\\", "/"));
|
|
22
38
|
}
|
|
23
39
|
return out;
|
|
24
40
|
}
|
|
25
41
|
|
|
26
|
-
function resolveFiles(
|
|
42
|
+
function resolveFiles(
|
|
43
|
+
include: string[],
|
|
44
|
+
root: string,
|
|
45
|
+
onDebug?: (event: KlintDebugEvent) => void
|
|
46
|
+
): string[] {
|
|
27
47
|
const files = new Set<string>();
|
|
48
|
+
const excludes = include.filter((p) => p.startsWith("!")).map((p) => p.slice(1));
|
|
28
49
|
for (const pattern of include) {
|
|
29
|
-
|
|
50
|
+
if (pattern.startsWith("!")) continue;
|
|
51
|
+
const base = pattern.split("/**")[0].split("/*")[0];
|
|
52
|
+
const dir = resolve(root, base === "**" ? "." : base);
|
|
30
53
|
try {
|
|
31
|
-
|
|
54
|
+
onDebug?.({ type: "walk:start", dir });
|
|
55
|
+
const before = files.size;
|
|
56
|
+
for (const file of walk(dir, root, excludes)) files.add(file);
|
|
57
|
+
onDebug?.({ type: "walk:done", dir, files: files.size - before });
|
|
32
58
|
} catch {
|
|
33
59
|
// directory doesn't exist — skip
|
|
34
60
|
}
|
|
@@ -41,8 +67,17 @@ function matchPattern(relPath: string, pattern: string): boolean {
|
|
|
41
67
|
const p = pattern.replaceAll("\\", "/");
|
|
42
68
|
if (p === "." || p === "**") return true;
|
|
43
69
|
if (p.endsWith("/**")) return norm.startsWith(`${p.slice(0, -3)}/`);
|
|
44
|
-
if (p.
|
|
45
|
-
return
|
|
70
|
+
if (!p.includes("*")) return norm === p || norm.startsWith(`${p}/`);
|
|
71
|
+
return globToRegExp(p).test(norm);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function globToRegExp(pattern: string): RegExp {
|
|
75
|
+
const escaped = pattern
|
|
76
|
+
.replace(/[.+?^${}()|[\]\\]/g, "\\$&")
|
|
77
|
+
.replaceAll("**/", "(?:.*/)?")
|
|
78
|
+
.replaceAll("**", ".*")
|
|
79
|
+
.replaceAll("*", "[^/]*");
|
|
80
|
+
return new RegExp(`^${escaped}$`);
|
|
46
81
|
}
|
|
47
82
|
|
|
48
83
|
function applyPatterns(files: string[], patterns: string[], root: string): string[] {
|
|
@@ -66,7 +101,8 @@ function resolveInclude(value: RuleConfigValue): string[] | undefined {
|
|
|
66
101
|
|
|
67
102
|
export function runKlint(
|
|
68
103
|
config: KlintConfig,
|
|
69
|
-
customRules: Record<string, KlintRule> = {}
|
|
104
|
+
customRules: Record<string, KlintRule> = {},
|
|
105
|
+
options: RunKlintOptions = {}
|
|
70
106
|
): Violation[] {
|
|
71
107
|
clearAstCache();
|
|
72
108
|
|
|
@@ -94,10 +130,11 @@ export function runKlint(
|
|
|
94
130
|
};
|
|
95
131
|
|
|
96
132
|
const allFiles = applyPatterns(
|
|
97
|
-
resolveFiles(config.include, config.root),
|
|
133
|
+
resolveFiles(config.include, config.root, options.onDebug),
|
|
98
134
|
config.include,
|
|
99
135
|
config.root
|
|
100
136
|
);
|
|
137
|
+
options.onDebug?.({ type: "files:resolved", files: allFiles.length });
|
|
101
138
|
const fileContents = new Map(allFiles.map((f) => [f, readFileSync(f, "utf-8")]));
|
|
102
139
|
const violations: Violation[] = [];
|
|
103
140
|
|
|
@@ -112,12 +149,17 @@ export function runKlint(
|
|
|
112
149
|
const files = include ? applyPatterns(allFiles, include, config.root) : allFiles;
|
|
113
150
|
|
|
114
151
|
const batch: Omit<Violation, "severity">[] = [];
|
|
152
|
+
options.onDebug?.({ type: "rule:start", rule: ruleName, files: files.length });
|
|
115
153
|
rule.check({ files, root: config.root, fileContents }, batch);
|
|
154
|
+
options.onDebug?.({ type: "rule:done", rule: ruleName, violations: batch.length });
|
|
116
155
|
for (const v of batch) violations.push({ ...v, rule: ruleName, severity });
|
|
117
156
|
}
|
|
118
157
|
|
|
119
158
|
if (config.arch) {
|
|
120
|
-
|
|
159
|
+
options.onDebug?.({ type: "arch:start", files: allFiles.length });
|
|
160
|
+
const archViolations = runArchRules(config.arch, allFiles, fileContents, config.root);
|
|
161
|
+
options.onDebug?.({ type: "arch:done", violations: archViolations.length });
|
|
162
|
+
violations.push(...archViolations);
|
|
121
163
|
}
|
|
122
164
|
|
|
123
165
|
return violations;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@konvert7/klint",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Architecture-as-Code linter for TypeScript. Enforce layer boundaries, import rules, and singleton patterns in YAML.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"pack:check": "bun tests/pack-check.ts",
|
|
40
40
|
"native:stage": "bun tests/stage-native-binary.ts",
|
|
41
41
|
"native:smoke": "bun tests/smoke-native-binary.ts",
|
|
42
|
+
"native:release:dry-run": "bun tools/prepare-native-release.ts --version 0.0.0-dry-run",
|
|
42
43
|
"check": "biome check",
|
|
43
44
|
"check-write": "biome check --write",
|
|
44
45
|
"knip": "knip-bun",
|
|
@@ -59,6 +60,12 @@
|
|
|
59
60
|
"yaml": "^2.9.0",
|
|
60
61
|
"zod": "^4.4.3"
|
|
61
62
|
},
|
|
63
|
+
"optionalDependencies": {
|
|
64
|
+
"@konvert7/klint-darwin-arm64": "*",
|
|
65
|
+
"@konvert7/klint-darwin-x64": "*",
|
|
66
|
+
"@konvert7/klint-linux-x64": "*",
|
|
67
|
+
"@konvert7/klint-win32-x64": "*"
|
|
68
|
+
},
|
|
62
69
|
"devDependencies": {
|
|
63
70
|
"@biomejs/biome": "2.4.7",
|
|
64
71
|
"@commitlint/cli": "^20.5.3",
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import {
|
|
2
|
+
copyFileSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
mkdtempSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
rmSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from "node:fs";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { dirname, join, resolve } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
import { nativePackages } from "../core/native-binary";
|
|
14
|
+
|
|
15
|
+
interface PackageJson {
|
|
16
|
+
name: string;
|
|
17
|
+
version: string;
|
|
18
|
+
private?: boolean;
|
|
19
|
+
optionalDependencies?: Record<string, string>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface Options {
|
|
23
|
+
version: string;
|
|
24
|
+
outDir: string;
|
|
25
|
+
requireBinaries: boolean;
|
|
26
|
+
keep: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const root = fileURLToPath(new URL("..", import.meta.url));
|
|
30
|
+
const options = parseArgs();
|
|
31
|
+
|
|
32
|
+
if (existsSync(options.outDir)) {
|
|
33
|
+
rmSync(options.outDir, { recursive: true, force: true });
|
|
34
|
+
}
|
|
35
|
+
mkdirSync(options.outDir, { recursive: true });
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const releaseRootPackageJson = prepareRootPackageJson();
|
|
39
|
+
const nativeResults = nativePackages().map(prepareNativePackage);
|
|
40
|
+
|
|
41
|
+
validateRootPackageJson(releaseRootPackageJson);
|
|
42
|
+
for (const result of nativeResults) validateNativePackage(result.packageJson);
|
|
43
|
+
|
|
44
|
+
const missing = nativeResults
|
|
45
|
+
.filter((result) => !result.binaryCopied)
|
|
46
|
+
.map((result) => result.packageJson.name);
|
|
47
|
+
|
|
48
|
+
if (options.requireBinaries && missing.length > 0) {
|
|
49
|
+
fail(`Missing native release binaries:\n${missing.join("\n")}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const missingMessage =
|
|
53
|
+
missing.length > 0
|
|
54
|
+
? `missing binaries: ${missing.join(", ")}`
|
|
55
|
+
: "missing binaries: none";
|
|
56
|
+
const outputLines = [
|
|
57
|
+
`native release dry-run ok (${options.version})`,
|
|
58
|
+
`output: ${options.outDir}`,
|
|
59
|
+
`native packages: ${nativeResults.length}`,
|
|
60
|
+
missingMessage,
|
|
61
|
+
];
|
|
62
|
+
process.stdout.write(`${outputLines.join("\n")}\n`);
|
|
63
|
+
} finally {
|
|
64
|
+
if (!options.keep) {
|
|
65
|
+
rmSync(options.outDir, { recursive: true, force: true });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function prepareRootPackageJson(): PackageJson {
|
|
70
|
+
const packageJson = readPackageJson(join(root, "package.json"));
|
|
71
|
+
packageJson.version = options.version;
|
|
72
|
+
packageJson.optionalDependencies = Object.fromEntries(
|
|
73
|
+
nativePackages().map((nativePackage) => [nativePackage.packageName, options.version])
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const outPath = join(options.outDir, "package.json");
|
|
77
|
+
writeJson(outPath, packageJson);
|
|
78
|
+
return packageJson;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function prepareNativePackage(nativePackage: ReturnType<typeof nativePackages>[number]): {
|
|
82
|
+
binaryCopied: boolean;
|
|
83
|
+
packageJson: PackageJson;
|
|
84
|
+
} {
|
|
85
|
+
const packageDirName = `${nativePackage.platform}-${nativePackage.arch}`;
|
|
86
|
+
const sourceDir = join(root, "npm", "native", packageDirName);
|
|
87
|
+
const outDir = join(options.outDir, "npm", "native", packageDirName);
|
|
88
|
+
mkdirSync(outDir, { recursive: true });
|
|
89
|
+
|
|
90
|
+
const packageJson = readPackageJson(join(sourceDir, "package.json"));
|
|
91
|
+
packageJson.version = options.version;
|
|
92
|
+
packageJson.private = false;
|
|
93
|
+
writeJson(join(outDir, "package.json"), packageJson);
|
|
94
|
+
|
|
95
|
+
const sourceBinary = join(sourceDir, nativePackage.binaryPath);
|
|
96
|
+
const outBinary = join(outDir, nativePackage.binaryPath);
|
|
97
|
+
const binaryCopied = existsSync(sourceBinary);
|
|
98
|
+
if (binaryCopied) {
|
|
99
|
+
mkdirSync(dirname(outBinary), { recursive: true });
|
|
100
|
+
copyFileSync(sourceBinary, outBinary);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { binaryCopied, packageJson };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function validateRootPackageJson(packageJson: PackageJson): void {
|
|
107
|
+
const optionalDependencies = packageJson.optionalDependencies ?? {};
|
|
108
|
+
for (const nativePackage of nativePackages()) {
|
|
109
|
+
if (optionalDependencies[nativePackage.packageName] !== options.version) {
|
|
110
|
+
fail(
|
|
111
|
+
`Root optional dependency ${nativePackage.packageName} is not pinned to ${options.version}`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function validateNativePackage(packageJson: PackageJson): void {
|
|
118
|
+
if (packageJson.version !== options.version) {
|
|
119
|
+
fail(
|
|
120
|
+
`${packageJson.name} version is ${packageJson.version}, expected ${options.version}`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
if (packageJson.private !== false) {
|
|
124
|
+
fail(`${packageJson.name} private must be false in release prep output`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function readPackageJson(path: string): PackageJson {
|
|
129
|
+
try {
|
|
130
|
+
return JSON.parse(readFileSync(path, "utf-8")) as PackageJson;
|
|
131
|
+
} catch (error) {
|
|
132
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
133
|
+
fail(`Failed to read package JSON at ${path}: ${message}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function writeJson(path: string, value: unknown): void {
|
|
138
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function parseArgs(): Options {
|
|
142
|
+
const args = process.argv.slice(2);
|
|
143
|
+
const version = valueAfter(args, "--version");
|
|
144
|
+
if (!version) {
|
|
145
|
+
fail(
|
|
146
|
+
"Usage: bun tools/prepare-native-release.ts --version <version> [--out <dir>] [--require-binaries] [--keep]"
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
version,
|
|
152
|
+
outDir: resolve(
|
|
153
|
+
valueAfter(args, "--out") ?? mkdtempSync(join(tmpdir(), "klint-native-release-"))
|
|
154
|
+
),
|
|
155
|
+
requireBinaries: args.includes("--require-binaries"),
|
|
156
|
+
keep: args.includes("--keep"),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function valueAfter(args: string[], name: string): string | undefined {
|
|
161
|
+
const index = args.indexOf(name);
|
|
162
|
+
return index >= 0 ? args[index + 1] : undefined;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function fail(message: string): never {
|
|
166
|
+
process.stderr.write(`${message}\n`);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
export async function verifyConditions(_, context) {
|
|
6
|
+
const cwd = context.cwd ?? process.cwd();
|
|
7
|
+
const packageJson = readPackageJson(cwd);
|
|
8
|
+
|
|
9
|
+
if (!packageJson.name) {
|
|
10
|
+
throw new Error("Root package.json must have a name before npm publish");
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function prepare(_, context) {
|
|
15
|
+
const cwd = context.cwd ?? process.cwd();
|
|
16
|
+
const version = context.nextRelease?.version;
|
|
17
|
+
|
|
18
|
+
if (!version) {
|
|
19
|
+
throw new Error("semantic-release did not provide nextRelease.version");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const packageJson = readPackageJson(cwd);
|
|
23
|
+
packageJson.version = version;
|
|
24
|
+
writePackageJson(cwd, packageJson);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function publish(_, context) {
|
|
28
|
+
const cwd = context.cwd ?? process.cwd();
|
|
29
|
+
|
|
30
|
+
if (context.options?.dryRun) {
|
|
31
|
+
context.logger?.log("Dry run: skipping npm publish");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const result = spawnSync("npm", ["publish", "--access", "public"], {
|
|
36
|
+
cwd,
|
|
37
|
+
encoding: "utf-8",
|
|
38
|
+
stdio: "inherit",
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if ((result.status ?? -1) !== 0) {
|
|
42
|
+
throw new Error(`npm publish failed with exit code ${result.status ?? -1}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function readPackageJson(cwd) {
|
|
47
|
+
return JSON.parse(readFileSync(join(cwd, "package.json"), "utf-8"));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function writePackageJson(cwd, packageJson) {
|
|
51
|
+
writeFileSync(join(cwd, "package.json"), `${JSON.stringify(packageJson, null, 2)}\n`);
|
|
52
|
+
}
|