@konvert7/klint 0.37.1 → 1.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.
Files changed (56) hide show
  1. package/README.md +49 -166
  2. package/bin/klint.js +47 -0
  3. package/klint.schema.json +1 -1
  4. package/klint.schema.yaml +1 -1
  5. package/package.json +15 -25
  6. package/cli/command.ts +0 -189
  7. package/cli/commands.ts +0 -90
  8. package/cli/help.ts +0 -21
  9. package/cli/install-skill.ts +0 -260
  10. package/cli/output.ts +0 -178
  11. package/cli/paths.ts +0 -20
  12. package/cli/rust-config.ts +0 -145
  13. package/cli/rust-engine.ts +0 -323
  14. package/cli/stats.ts +0 -92
  15. package/cli.ts +0 -214
  16. package/core/arch-comments.ts +0 -125
  17. package/core/arch.ts +0 -470
  18. package/core/ast.ts +0 -63
  19. package/core/config.schema.ts +0 -413
  20. package/core/fixer.ts +0 -47
  21. package/core/native-binary.ts +0 -123
  22. package/core/paths.ts +0 -9
  23. package/core/rule-helpers.ts +0 -76
  24. package/core/runner.ts +0 -196
  25. package/core/schema-version.ts +0 -46
  26. package/core/skill.ts +0 -164
  27. package/core/stats.ts +0 -160
  28. package/core/types.ts +0 -146
  29. package/plugins/index.ts +0 -6
  30. package/plugins/sonar.ts +0 -27
  31. package/rules/index.ts +0 -26
  32. package/rules/no-async-predicate.ts +0 -51
  33. package/rules/no-consecutive-array-push.ts +0 -61
  34. package/rules/no-date-equality.ts +0 -35
  35. package/rules/no-floating-promise.ts +0 -37
  36. package/rules/no-misused-promises.ts +0 -59
  37. package/rules/no-nested-template-literals.ts +0 -47
  38. package/rules/no-object-in-template.ts +0 -95
  39. package/rules/no-optional-chain-on-non-nullable.ts +0 -47
  40. package/rules/no-single-char-class.ts +0 -113
  41. package/rules/no-string-match.ts +0 -63
  42. package/rules/no-sync-in-async.ts +0 -43
  43. package/rules/no-unguarded-json-parse.ts +0 -35
  44. package/rules/prefer-at.ts +0 -46
  45. package/rules/prefer-nullish-coalescing-assign.ts +0 -111
  46. package/rules/prefer-string-raw-regexp.ts +0 -93
  47. package/rules/prefer-string-raw.ts +0 -39
  48. package/rules/prefer-string-replaceall.ts +0 -67
  49. package/skill/klint-rules/SKILL.md +0 -264
  50. package/tools/generate-homebrew-formula.ts +0 -64
  51. package/tools/prepare-native-release.ts +0 -166
  52. package/tools/prepare-nuget-release.ts +0 -62
  53. package/tools/prepare-python-release.ts +0 -76
  54. package/tools/semantic-release-crates.js +0 -96
  55. package/tools/semantic-release-native-npm.js +0 -121
  56. package/tools/semantic-release-root-npm.js +0 -87
@@ -1,264 +0,0 @@
1
- ---
2
- name: klint-rules
3
- description: Add, modify, or explain klint architecture rules for this repo. Use when asked to enforce a new structural constraint, adjust rule scope, or explain why a violation fired.
4
- argument-hint: <constraint to enforce>
5
- ---
6
-
7
- ## Schema
8
-
9
- The full, authoritative config schema lives in the repo and is the source of truth for every field below:
10
-
11
- - YAML: <https://github.com/konvert7/klint/blob/main/klint.schema.yaml>
12
- - JSON: <https://github.com/konvert7/klint/blob/main/klint.schema.json>
13
-
14
- Wire it into `klint.yaml` for editor autocomplete and validation:
15
-
16
- ```yaml
17
- # yaml-language-server: $schema=./klint.schema.yaml
18
- ```
19
-
20
- ## Workflow
21
-
22
- 1. **Understand the constraint** — if scope is ambiguous, ask: which files/layers are involved? should it block or warn? are there legitimate exceptions?
23
-
24
- 2. **Grep first** — verify the pattern exists and count current occurrences before touching `klint.yaml`:
25
- ```sh
26
- grep -rn "the-pattern" src/ --include="*.ts"
27
- ```
28
-
29
- 3. **Choose the right primitive:**
30
-
31
- | The constraint is... | Primitive |
32
- |---|---|
33
- | Layer X must not import from layer Y | `arch.imports` + `deny` |
34
- | Layer X may only import from Y and Z | `arch.imports` + `allow` |
35
- | Layer X must not import an npm package or `node:` builtin | `arch.imports` + `deny-packages` |
36
- | Pattern P must only appear in one designated file | `arch.singleton` + `pattern` |
37
- | Pattern P must never appear inside a scoped layer | `arch.forbidden` + `pattern` |
38
- | Raw HTML/JSX element must never appear in a scoped layer | `arch.forbidden` + `jsx-element` |
39
- | Raw HTML/JSX element may only appear in one primitive file | `arch.singleton` + `jsx-element` |
40
- | A value-shaped pattern (regex) must never appear in a scoped layer | `arch.forbidden` + `pattern: "re:…"` |
41
- | No file in a layer may exceed N lines | `arch.maxLines` + `limit` |
42
- | No file may be more than N% comments | `arch.maxCommentDensity` + `limit` |
43
- | No comment block may stack more than N lines | `arch.maxCommentBlock` + `limit` |
44
-
45
- 4. **Read `klint.yaml`** — check existing `arch.layers` and rules before adding anything. Add a new named layer to `arch.layers` if the file group doesn't exist yet.
46
-
47
- 5. **Write the stanza** in `klint.yaml` under the correct `arch:` section.
48
-
49
- 6. **Verify zero new violations on the current codebase:**
50
- ```sh
51
- bun klint/cli.ts # in-repo
52
- npx klint # after npm install
53
- ```
54
- If new violations appear on existing code, fix them first or adjust the rule scope — then land the rule.
55
-
56
- 7. **Write a break test** — prove the rule fires on a deliberate violation, and doesn't fire on clean code. Run the test suite to confirm.
57
-
58
- ## Run tracking
59
-
60
- Every run is recorded in `.klint/stats.json` next to the config. The folder ignores itself, so never add it to `.gitignore`.
61
-
62
- | Command or flag | Use |
63
- |---|---|
64
- | `klint stats [--config <dir>]` | Print runs, violations reported and errors prevented, in total and per rule. Use it to answer "how much has klint caught?" |
65
- | `--hook` | Put it only in agent hook commands, e.g. `klint --json --hook`. Leave it off when you run klint yourself, so your runs count as direct. |
66
- | `KLINT_NO_STATS=1` | Turn recording off for a run, e.g. in a throwaway fixture. |
67
-
68
- Every violation a run reports counts as reported, repeats included; klint's own `klint/*` advisories do not. An error counts as prevented once, when a later run no longer reports it. Errors match by rule, file and message, so moving code to another line is not a fix. Warnings are not tracked.
69
-
70
- ## Primitive reference
71
-
72
- ### Layers
73
- ```yaml
74
- arch:
75
- layers:
76
- core: ["src/lib/**", "src/hooks/**"]
77
- ui: ["src/components/**"]
78
- targets: ["src/targets/**"]
79
- ```
80
-
81
- ### Import boundaries
82
- ```yaml
83
- arch:
84
- imports:
85
- # deny: block imports from one layer into another
86
- - from: core
87
- deny: targets
88
- message: "Core must not depend on agent-specific code"
89
- severity: error # optional — default is error; use warn to record without blocking
90
-
91
- # allow: whitelist — anything not listed is denied (npm + node: builtins always pass)
92
- - from: ["src/dao/**"]
93
- allow: ["src/dao/**", "src/prisma/**"]
94
- message: "DAO may only import from dao or prisma"
95
-
96
- # type-only: allow — import type {} and Python if TYPE_CHECKING: blocks stay permitted
97
- - from: core
98
- deny: targets
99
- type-only: allow
100
- message: "Core must not depend on agent-specific code"
101
-
102
- # deny-packages: block npm packages and node: builtins, which deny/allow cannot reach
103
- # because they never resolve to a project file. Composes with deny in the same rule.
104
- - from: ["src/dao/**"]
105
- deny-packages: ["next/headers", "node:fs"]
106
- message: "Data access must not read request state or touch the filesystem"
107
- ```
108
-
109
- `deny-packages` matches per segment: `next` covers `next/headers`, `nextra` is unaffected, and
110
- `next/headers` does not match `next/navigation`. Python uses dotted segments, so `os` covers
111
- `os.path` and blocks pip packages and stdlib modules alike. Swift uses dotted segments too, at
112
- module granularity, which is what makes system frameworks like `UIKit` reachable. Rust splits on
113
- `::`, so `tokio` covers `tokio::sync::Mutex` while `tokio_util` is unaffected. The specifier is read off the AST, so
114
- static imports, dynamic `import()`, and `export … from` re-exports all count while a comment
115
- mentioning the package does not — unlike `forbidden` + `pattern`, which is a line-based text scan.
116
-
117
- ### Singleton — one designated location
118
-
119
- Pin a `pattern` (literal string) **or** a `jsx-element` (AST-matched tag) to exactly one file. Every other in-scope occurrence is a violation.
120
-
121
- ```yaml
122
- arch:
123
- singleton:
124
- # pattern: literal string match — fires if it appears anywhere except `only`
125
- - pattern: "process.env.API_KEY"
126
- only: "src/lib/auth.ts"
127
- in: ["src/**"] # optional: limit scan scope (default: all files). string OR array
128
- message: "Use the auth module instead of reading API_KEY directly"
129
- severity: error # optional — default error; use warn to record without blocking
130
-
131
- # jsx-element: pin a raw element to its design-system primitive
132
- - jsx-element: "button" # one tag, or a list: ["button", "input"]
133
- only: "src/components/ui/button.tsx"
134
- in: ["src/**/*.tsx"]
135
- message: "Raw <button> belongs only to the Button primitive"
136
- ```
137
-
138
- Required: `only` + `message`, plus one of `pattern` / `jsx-element` (not both in the same stanza).
139
-
140
- ### Forbidden — banned pattern in scope
141
-
142
- Block a `pattern` (literal string) **or** a `jsx-element` (AST-matched tag) inside a scoped layer.
143
-
144
- ```yaml
145
- arch:
146
- forbidden:
147
- # pattern: literal substring by default; prefix with `re:` for a regex
148
- - pattern: "console.log("
149
- in: ["src/lib/**"] # string OR array; supports ! negation
150
- message: "Use the logger — console.log leaks into the agent event stream"
151
- severity: error # optional — default error; use warn to record without blocking
152
-
153
- # re: prefix — match a regular expression (common JS/RE2 subset; no lookaround/backrefs)
154
- - pattern: 're:\b(?:p|gap)-\['
155
- in: ["src/**/*.tsx"]
156
- message: "Use the spacing scale, not arbitrary bracket values like p-[18px]"
157
-
158
- # jsx-element: forbid raw HTML elements outside the design system
159
- - jsx-element: ["button", "input", "label"]
160
- in: ["src/app/**/*.tsx", "src/components/**/*.tsx", "!src/components/ui/**"]
161
- message: "Use the design-system primitives in @/components/ui/* instead of raw HTML elements"
162
- ```
163
-
164
- Required: `in` + `message`, plus one of `pattern` / `jsx-element` (not both in the same stanza). A `pattern` may be a literal substring or, with the `re:` prefix, a regex; `singleton` patterns accept `re:` too.
165
-
166
- `jsx-element` matches intrinsic element names on the AST (opening and self-closing tags), so it is robust to whitespace, attributes, and naming collisions like `<buttonGroup>` — unlike a literal `pattern: "<button"` scan. It works on `.tsx`/`.jsx` files in both the TS and Rust engines.
167
-
168
- ### Max lines — cap file length
169
-
170
- Limit how many physical lines a file in scope may have. Use a separate stanza per scope to give different ceilings to source and tests.
171
-
172
- ```yaml
173
- arch:
174
- maxLines:
175
- - limit: 300 # positive integer; required
176
- in: ["src/**"] # string OR array; supports ! negation
177
- message: "Split this module" # optional — defaults to "File exceeds the maximum of N lines"
178
- severity: error # optional — default error; use warn to record without blocking
179
-
180
- - limit: 600 # tests may run longer
181
- in: ["tests/**"]
182
- ```
183
-
184
- Required: `limit` + `in`. The count is **total physical lines** — blanks and comments included, not code lines — and a file over the limit is flagged at line `limit + 1`. Enforced identically in the TS and Rust engines, across every language klint scans.
185
-
186
- ### Comment budget — cap explanatory prose
187
-
188
- Two ceilings on how much of a file may be comments. Use them to push intent into names and small
189
- functions rather than paragraphs that drift from the code they describe.
190
-
191
- ```yaml
192
- arch:
193
- # maxCommentDensity: what share of a file may be comment lines
194
- maxCommentDensity:
195
- - limit: 5 # percent; required
196
- in: ["src/**"] # string OR array; supports ! negation
197
- countDocComments: false # optional — default false, so JSDoc/docstrings are exempt
198
- message: "Encode intent in names and small functions, not prose"
199
- severity: error # optional — default error; use warn to record without blocking
200
-
201
- # maxCommentBlock: how tall a single stack of comment lines may get
202
- maxCommentBlock:
203
- - limit: 2 # consecutive comment lines; required
204
- in: ["src/**"]
205
- ignore: ["@codegen:"] # optional — structural markers, not prose
206
- message: "Extract a well-named function instead of stacking comment lines"
207
- ```
208
-
209
- Required: `limit` + `in`. Density is a **percentage of total physical lines** (code + comments +
210
- blanks — the same denominator `maxLines` uses). Block height counts **consecutive** comment lines and
211
- is reported at the first offending line. Both exempt doc-comments by default — `/** … */` JSDoc and
212
- Python docstrings — so documentation is not penalised; ordinary `//`, `/* */`, and `#` comments always
213
- count. Set `countDocComments: true` to include them.
214
-
215
- The two are complementary: density catches a file that is 30% commented in scattered one-liners,
216
- block catches a ten-line essay above one function in an otherwise sparse file. Neither alone catches both.
217
-
218
- **`ignore` — comments that are machinery, not prose.** Some comments are load-bearing input to a
219
- build or codegen step (`// @codegen:region-start`, feature-strip markers, pragmas). They are comments to the
220
- lexer but structure to the project, and counting them makes both limits measure the wrong thing:
221
-
222
- ```yaml
223
- arch:
224
- maxCommentDensity:
225
- - limit: 5
226
- in: ["src/**"]
227
- ignore: ["@codegen:"] # literal substring, or "re:" for a regex
228
- ```
229
-
230
- Two rules govern how an ignored line behaves, and both matter:
231
-
232
- - **It stays in the density denominator.** A marker is a physical line like code, so a 20-line file
233
- with 4 markers and 1 real comment measures 1/20, not 1/16.
234
- - **It does not break a comment block.** `// a` / `// @marker` / `// b` / `// c` is a run of three
235
- counted lines, not two runs. Otherwise sprinkling a marker every other line would defeat the cap.
236
-
237
- `ignore` tests the **physical source line**, so it only ever applies to lines already identified as
238
- comments — code containing the same text is untouched. Enforced identically in both engines.
239
-
240
- ## Pitfalls
241
-
242
- - `pattern` in `singleton` and `forbidden` is a **literal substring** by default — it will not catch `process.env["KEY"]` (bracket notation). Either grep for both forms, or use a `re:` regex prefix to match them in one rule. Caveat: a literal pattern that itself begins with `re:` cannot be expressed (it is always read as a regex), and regexes must stay in the common JS/RE2 subset (no lookaround or backreferences) so both engines agree.
243
- - `maxLines.limit` counts **total physical lines** (blanks and comments included), not code lines; a trailing newline does not add a line, and both engines count identically.
244
- - `maxCommentDensity.limit` is a **percentage, not a line count** — `limit: 5` means 5% of the file, so a 40-line file is over budget at its third comment line. Measure before picking a number: land it as `severity: warn` first, read the reported densities, then tighten to `error`.
245
- - `jsx-element` matches **intrinsic** (lowercase HTML) tags only — `button`, `input`, `label`. It does not match custom React components like `<Button>`; to restrict those, use `arch.imports` against the component's module instead.
246
- - A `forbidden`/`singleton` stanza takes either `pattern` or `jsx-element`, never both — split into two stanzas if you need both.
247
- - `only` in `singleton` is relative to the project root — use forward slashes on all platforms.
248
- - `severity: warn` records the violation but does not block the session. Use `error` (the default) to block.
249
- - Path aliases (`@/*`) resolve via `tsconfig.json` `compilerOptions.paths`, including `extends` chains.
250
- - Adding a rule that fires on existing code is not a blocker — fix the violations first, then add the rule.
251
-
252
- ## Output format
253
-
254
- After adding or modifying a rule, report:
255
- - The YAML stanza added
256
- - The grep evidence that confirmed the pattern is real
257
- - The test written and its result
258
- - The output of `bun run klint` (or `npx klint`) confirming zero new violations
259
-
260
- ## Do NOT use
261
-
262
- - To fix TypeScript type errors, lint issues, or Biome violations — those are separate tools
263
- - To enforce naming conventions (hooks must start with `use`, components must be PascalCase) — use ESLint with the appropriate plugin
264
- - To write custom TypeScript AST rules in `klint.rules.ts` — that is a separate, code-level workflow
@@ -1,64 +0,0 @@
1
- import { mkdirSync, writeFileSync } from "node:fs";
2
- import { dirname, resolve } from "node:path";
3
-
4
- interface FormulaOptions {
5
- version: string;
6
- darwinArm64Sha: string;
7
- darwinX64Sha: string;
8
- }
9
-
10
- export function homebrewFormula(options: FormulaOptions): string {
11
- const version = options.version.replace(/^v/, "");
12
-
13
- return `# typed: false
14
- # frozen_string_literal: true
15
-
16
- class Klint < Formula
17
- desc "Architecture-as-Code linter for TypeScript, Python, and Swift projects"
18
- homepage "https://github.com/konvert7/klint"
19
- version "${version}"
20
- license "MIT"
21
-
22
- if Hardware::CPU.arm?
23
- url "https://github.com/konvert7/klint/releases/download/v${version}/klint-${version}-darwin-arm64.tar.gz"
24
- sha256 "${options.darwinArm64Sha}"
25
- else
26
- url "https://github.com/konvert7/klint/releases/download/v${version}/klint-${version}-darwin-x64.tar.gz"
27
- sha256 "${options.darwinX64Sha}"
28
- end
29
-
30
- def install
31
- binary = File.exist?("klint") ? "klint" : Dir["klint-*/klint"].first
32
- odie "klint binary not found" unless binary
33
- bin.install binary => "klint"
34
- end
35
-
36
- test do
37
- assert_match "klint-rs", shell_output("#{bin}/klint --version")
38
- end
39
- end
40
- `;
41
- }
42
-
43
- if (import.meta.main) {
44
- const args = process.argv.slice(2);
45
- const output = requireValue(args, "--output");
46
- const formula = homebrewFormula({
47
- version: requireValue(args, "--version"),
48
- darwinArm64Sha: requireValue(args, "--darwin-arm64-sha"),
49
- darwinX64Sha: requireValue(args, "--darwin-x64-sha"),
50
- });
51
-
52
- const path = resolve(output);
53
- mkdirSync(dirname(path), { recursive: true });
54
- writeFileSync(path, formula);
55
- }
56
-
57
- function requireValue(args: string[], name: string): string {
58
- const index = args.indexOf(name);
59
- const value = index >= 0 ? args[index + 1] : undefined;
60
- if (!value) {
61
- throw new Error(`${name} is required`);
62
- }
63
- return value;
64
- }
@@ -1,166 +0,0 @@
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
- delete packageJson.optionalDependencies;
73
-
74
- const outPath = join(options.outDir, "package.json");
75
- writeJson(outPath, packageJson);
76
- return packageJson;
77
- }
78
-
79
- function prepareNativePackage(nativePackage: ReturnType<typeof nativePackages>[number]): {
80
- binaryCopied: boolean;
81
- packageJson: PackageJson;
82
- } {
83
- const packageDirName = `${nativePackage.platform}-${nativePackage.arch}`;
84
- const sourceDir = join(root, "npm", "native", packageDirName);
85
- const outDir = join(options.outDir, "npm", "native", packageDirName);
86
- mkdirSync(outDir, { recursive: true });
87
-
88
- const packageJson = readPackageJson(join(sourceDir, "package.json"));
89
- packageJson.version = options.version;
90
- packageJson.private = false;
91
- writeJson(join(outDir, "package.json"), packageJson);
92
-
93
- const sourceBinary = join(sourceDir, nativePackage.binaryPath);
94
- const outBinary = join(outDir, nativePackage.binaryPath);
95
- const binaryCopied = existsSync(sourceBinary);
96
- if (binaryCopied) {
97
- mkdirSync(dirname(outBinary), { recursive: true });
98
- copyFileSync(sourceBinary, outBinary);
99
- }
100
-
101
- return { binaryCopied, packageJson };
102
- }
103
-
104
- function validateRootPackageJson(packageJson: PackageJson): void {
105
- const optionalDependencies = packageJson.optionalDependencies ?? {};
106
- for (const nativePackage of nativePackages()) {
107
- if (nativePackage.packageName in optionalDependencies) {
108
- fail(
109
- `Root package must not consume dark native package ${nativePackage.packageName}`
110
- );
111
- }
112
- }
113
- }
114
-
115
- function validateNativePackage(packageJson: PackageJson): void {
116
- if (packageJson.version !== options.version) {
117
- fail(
118
- `${packageJson.name} version is ${packageJson.version}, expected ${options.version}`
119
- );
120
- }
121
- if (packageJson.private !== false) {
122
- fail(`${packageJson.name} private must be false in release prep output`);
123
- }
124
- }
125
-
126
- function readPackageJson(path: string): PackageJson {
127
- try {
128
- return JSON.parse(readFileSync(path, "utf-8")) as PackageJson;
129
- } catch (error) {
130
- const message = error instanceof Error ? error.message : String(error);
131
- fail(`Failed to read package JSON at ${path}: ${message}`);
132
- }
133
- }
134
-
135
- function writeJson(path: string, value: unknown): void {
136
- writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
137
- }
138
-
139
- function parseArgs(): Options {
140
- const args = process.argv.slice(2);
141
- const version = valueAfter(args, "--version");
142
- if (!version) {
143
- fail(
144
- "Usage: bun tools/prepare-native-release.ts --version <version> [--out <dir>] [--require-binaries] [--keep]"
145
- );
146
- }
147
-
148
- return {
149
- version,
150
- outDir: resolve(
151
- valueAfter(args, "--out") ?? mkdtempSync(join(tmpdir(), "klint-native-release-"))
152
- ),
153
- requireBinaries: args.includes("--require-binaries"),
154
- keep: args.includes("--keep"),
155
- };
156
- }
157
-
158
- function valueAfter(args: string[], name: string): string | undefined {
159
- const index = args.indexOf(name);
160
- return index >= 0 ? args[index + 1] : undefined;
161
- }
162
-
163
- function fail(message: string): never {
164
- process.stderr.write(`${message}\n`);
165
- process.exit(1);
166
- }
@@ -1,62 +0,0 @@
1
- import { readFileSync, writeFileSync } from "node:fs";
2
- import { join, resolve } from "node:path";
3
- import { fileURLToPath } from "node:url";
4
-
5
- interface Options {
6
- version: string;
7
- root: string;
8
- }
9
-
10
- const repoRoot = fileURLToPath(new URL("..", import.meta.url));
11
- const options = parseArgs();
12
- const csprojPath = join(options.root, "nuget", "Klint.Tool.csproj");
13
-
14
- const csproj = readFileSync(csprojPath, "utf-8");
15
- const nextCsproj = replaceRequired(
16
- csproj,
17
- /<Version>.+<\/Version>/,
18
- `<Version>${options.version}</Version>`,
19
- csprojPath
20
- );
21
- writeFileSync(csprojPath, nextCsproj);
22
-
23
- process.stdout.write(
24
- `prepared NuGet release metadata: Konvert7.Klint@${options.version}\n`
25
- );
26
-
27
- function parseArgs(): Options {
28
- const args = process.argv.slice(2);
29
- const version = valueAfter(args, "--version");
30
- if (!version) {
31
- fail(
32
- "Usage: bun tools/prepare-nuget-release.ts --version <version> [--root <repo-root>]"
33
- );
34
- }
35
-
36
- return {
37
- version,
38
- root: resolve(valueAfter(args, "--root") ?? repoRoot),
39
- };
40
- }
41
-
42
- function replaceRequired(
43
- input: string,
44
- pattern: RegExp,
45
- replacement: string,
46
- path: string
47
- ): string {
48
- if (!pattern.test(input)) {
49
- fail(`Expected release metadata pattern ${pattern} in ${path}`);
50
- }
51
- return input.replace(pattern, replacement);
52
- }
53
-
54
- function valueAfter(args: string[], name: string): string | undefined {
55
- const index = args.indexOf(name);
56
- return index >= 0 ? args[index + 1] : undefined;
57
- }
58
-
59
- function fail(message: string): never {
60
- process.stderr.write(`${message}\n`);
61
- process.exit(1);
62
- }
@@ -1,76 +0,0 @@
1
- import { readFileSync, writeFileSync } from "node:fs";
2
- import { join, resolve } from "node:path";
3
- import { fileURLToPath } from "node:url";
4
-
5
- interface Options {
6
- name: string;
7
- root: string;
8
- version: string;
9
- }
10
-
11
- const repoRoot = fileURLToPath(new URL("..", import.meta.url));
12
- const options = parseArgs();
13
- const pythonRoot = join(options.root, "python");
14
- const pyprojectPath = join(pythonRoot, "pyproject.toml");
15
- const initPath = join(pythonRoot, "klint", "__init__.py");
16
-
17
- const pyproject = readFileSync(pyprojectPath, "utf-8");
18
- const nextPyproject = replaceRequired(
19
- replaceRequired(pyproject, /^name = ".+"$/m, `name = "${options.name}"`, pyprojectPath),
20
- /^version = ".+"$/m,
21
- `version = "${options.version}"`,
22
- pyprojectPath
23
- );
24
- writeFileSync(pyprojectPath, nextPyproject);
25
-
26
- const init = readFileSync(initPath, "utf-8");
27
- const nextInit = replaceRequired(
28
- init,
29
- /^__version__ = ".+"$/m,
30
- `__version__ = "${options.version}"`,
31
- initPath
32
- );
33
- writeFileSync(initPath, nextInit);
34
-
35
- process.stdout.write(
36
- `prepared Python release metadata: ${options.name}@${options.version}\n`
37
- );
38
-
39
- function parseArgs(): Options {
40
- const args = process.argv.slice(2);
41
- const name = valueAfter(args, "--name");
42
- const version = valueAfter(args, "--version");
43
- if (!name || !version) {
44
- fail(
45
- "Usage: bun tools/prepare-python-release.ts --name <package-name> --version <version> [--root <repo-root>]"
46
- );
47
- }
48
-
49
- return {
50
- name,
51
- version,
52
- root: resolve(valueAfter(args, "--root") ?? repoRoot),
53
- };
54
- }
55
-
56
- function replaceRequired(
57
- input: string,
58
- pattern: RegExp,
59
- replacement: string,
60
- path: string
61
- ): string {
62
- if (!pattern.test(input)) {
63
- fail(`Expected release metadata pattern ${pattern} in ${path}`);
64
- }
65
- return input.replace(pattern, replacement);
66
- }
67
-
68
- function valueAfter(args: string[], name: string): string | undefined {
69
- const index = args.indexOf(name);
70
- return index >= 0 ? args[index + 1] : undefined;
71
- }
72
-
73
- function fail(message: string): never {
74
- process.stderr.write(`${message}\n`);
75
- process.exit(1);
76
- }