lintcn 0.1.0 → 0.2.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/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## 0.2.0
2
+
3
+ 1. **Pinned tsgolint version** — each lintcn release bundles a specific tsgolint version (`v0.9.2`). Builds are now reproducible: everyone on the same lintcn version compiles against the same tsgolint API. Previously used `main` branch which was non-deterministic.
4
+
5
+ 2. **`--tsgolint-version` flag** — override the pinned version for testing unreleased tsgolint:
6
+ ```bash
7
+ npx lintcn lint --tsgolint-version v0.10.0
8
+ ```
9
+
10
+ 3. **Version pinning docs** — README now explains why you should pin lintcn in `package.json` (no `^` or `~`) and how to update safely.
11
+
1
12
  ## 0.1.0
2
13
 
3
14
  1. **Initial release** — CLI for adding type-aware TypeScript lint rules as Go files to your project:
package/README.md CHANGED
@@ -127,6 +127,30 @@ if (user instanceof Error) return user
127
127
  void getUser("id")
128
128
  ```
129
129
 
130
+ ## Version pinning
131
+
132
+ **Pin lintcn in your `package.json`** — do not use `^` or `~`:
133
+
134
+ ```json
135
+ {
136
+ "devDependencies": {
137
+ "lintcn": "0.1.0"
138
+ }
139
+ }
140
+ ```
141
+
142
+ Each lintcn release bundles a specific tsgolint version. Updating lintcn can change the underlying tsgolint API, which may cause your rules to no longer compile. Always update consciously:
143
+
144
+ 1. Check the [changelog](./CHANGELOG.md) for tsgolint version changes
145
+ 2. Run `npx lintcn build` after updating to verify your rules still compile
146
+ 3. Fix any compilation errors before committing
147
+
148
+ You can test against an unreleased tsgolint version without updating lintcn:
149
+
150
+ ```bash
151
+ npx lintcn lint --tsgolint-version v0.10.0
152
+ ```
153
+
130
154
  ## Prerequisites
131
155
 
132
156
  - **Node.js** — for the CLI
package/dist/cache.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const DEFAULT_TSGOLINT_VERSION = "main";
1
+ export declare const DEFAULT_TSGOLINT_VERSION = "v0.9.2";
2
2
  export declare function getCacheDir(): string;
3
3
  export declare function getTsgolintSourceDir(version: string): string;
4
4
  export declare function getBinDir(): string;
@@ -1 +1 @@
1
- {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,wBAAwB,SAAS,CAAA;AAE9C,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA0D3E;AAED,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAQ/D"}
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,wBAAwB,WAAW,CAAA;AAEhD,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA2D3E;AAED,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAQ/D"}
package/dist/cache.js CHANGED
@@ -6,8 +6,11 @@ import fs from 'node:fs';
6
6
  import os from 'node:os';
7
7
  import path from 'node:path';
8
8
  import { execAsync } from "./exec.js";
9
- // Default tsgolint version — pinned to a known-good commit
10
- export const DEFAULT_TSGOLINT_VERSION = 'main';
9
+ // Pinned tsgolint version — updated with each lintcn release.
10
+ // This ensures reproducible builds: every user on the same lintcn version
11
+ // compiles rules against the same tsgolint API. Changing this is a conscious
12
+ // decision — tsgolint API changes can break user rules.
13
+ export const DEFAULT_TSGOLINT_VERSION = 'v0.9.2';
11
14
  export function getCacheDir() {
12
15
  return path.join(os.homedir(), '.cache', 'lintcn');
13
16
  }
@@ -31,12 +34,13 @@ export async function ensureTsgolintSource(version) {
31
34
  }
32
35
  console.log(`Cloning tsgolint@${version}...`);
33
36
  fs.mkdirSync(sourceDir, { recursive: true });
34
- // clone with depth 1 for speed
35
- const cloneArgs = ['clone', '--depth', '1', '--recurse-submodules', '--shallow-submodules'];
36
- if (version !== 'main') {
37
- cloneArgs.push('--branch', version);
38
- }
39
- cloneArgs.push('https://github.com/oxc-project/tsgolint.git', sourceDir);
37
+ // clone with depth 1 for speed — --branch works with tags and branches
38
+ const cloneArgs = [
39
+ 'clone', '--depth', '1',
40
+ '--branch', version,
41
+ '--recurse-submodules', '--shallow-submodules',
42
+ 'https://github.com/oxc-project/tsgolint.git', sourceDir,
43
+ ];
40
44
  await execAsync('git', cloneArgs);
41
45
  // apply patches if they exist
42
46
  const patchesDir = path.join(sourceDir, 'patches');
package/dist/cli.js CHANGED
@@ -7,6 +7,7 @@ import { addRule } from "./commands/add.js";
7
7
  import { lint, buildBinary } from "./commands/lint.js";
8
8
  import { listRules } from "./commands/list.js";
9
9
  import { removeRule } from "./commands/remove.js";
10
+ import { DEFAULT_TSGOLINT_VERSION } from "./cache.js";
10
11
  const require = createRequire(import.meta.url);
11
12
  const packageJson = require('../package.json');
12
13
  const cli = goke('lintcn');
@@ -35,7 +36,9 @@ cli
35
36
  .option('--rebuild', 'Force rebuild even if cached binary exists')
36
37
  .option('--tsconfig <path>', 'Path to tsconfig.json')
37
38
  .option('--list-files', 'List matched files')
39
+ .option('--tsgolint-version [version]', 'Override the pinned tsgolint version (tag or commit). For testing unreleased tsgolint versions.')
38
40
  .action(async (options) => {
41
+ const tsgolintVersion = options.tsgolintVersion || DEFAULT_TSGOLINT_VERSION;
39
42
  const passthroughArgs = [];
40
43
  if (options.tsconfig) {
41
44
  passthroughArgs.push('--tsconfig', options.tsconfig);
@@ -50,6 +53,7 @@ cli
50
53
  }
51
54
  const exitCode = await lint({
52
55
  rebuild: !!options.rebuild,
56
+ tsgolintVersion,
53
57
  passthroughArgs,
54
58
  });
55
59
  process.exit(exitCode);
@@ -57,8 +61,10 @@ cli
57
61
  cli
58
62
  .command('build', 'Build the custom tsgolint binary without running it')
59
63
  .option('--rebuild', 'Force rebuild even if cached binary exists')
64
+ .option('--tsgolint-version [version]', 'Override the pinned tsgolint version (tag or commit). For testing unreleased tsgolint versions.')
60
65
  .action(async (options) => {
61
- const binaryPath = await buildBinary({ rebuild: !!options.rebuild });
66
+ const tsgolintVersion = options.tsgolintVersion || DEFAULT_TSGOLINT_VERSION;
67
+ const binaryPath = await buildBinary({ rebuild: !!options.rebuild, tsgolintVersion });
62
68
  console.log(binaryPath);
63
69
  });
64
70
  cli.help();
@@ -1,8 +1,10 @@
1
- export declare function buildBinary({ rebuild }: {
1
+ export declare function buildBinary({ rebuild, tsgolintVersion, }: {
2
2
  rebuild: boolean;
3
+ tsgolintVersion: string;
3
4
  }): Promise<string>;
4
- export declare function lint({ rebuild, passthroughArgs, }: {
5
+ export declare function lint({ rebuild, tsgolintVersion, passthroughArgs, }: {
5
6
  rebuild: boolean;
7
+ tsgolintVersion: string;
6
8
  passthroughArgs: string[];
7
9
  }): Promise<number>;
8
10
  //# sourceMappingURL=lint.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../src/commands/lint.ts"],"names":[],"mappings":"AAuBA,wBAAsB,WAAW,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAoDpF;AAED,wBAAsB,IAAI,CAAC,EACzB,OAAO,EACP,eAAe,GAChB,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,eAAe,EAAE,MAAM,EAAE,CAAA;CAC1B,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBlB"}
1
+ {"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../src/commands/lint.ts"],"names":[],"mappings":"AAuBA,wBAAsB,WAAW,CAAC,EAChC,OAAO,EACP,eAAe,GAChB,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,eAAe,EAAE,MAAM,CAAA;CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,CAoDlB;AAED,wBAAsB,IAAI,CAAC,EACzB,OAAO,EACP,eAAe,EACf,eAAe,GAChB,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,eAAe,EAAE,MAAM,CAAA;IACvB,eAAe,EAAE,MAAM,EAAE,CAAA;CAC1B,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBlB"}
@@ -5,7 +5,7 @@ import { spawn } from 'node:child_process';
5
5
  import { getLintcnDir } from "../paths.js";
6
6
  import { discoverRules } from "../discover.js";
7
7
  import { generateBuildWorkspace } from "../codegen.js";
8
- import { ensureTsgolintSource, DEFAULT_TSGOLINT_VERSION, cachedBinaryExists, getBinaryPath, getBuildDir, getBinDir } from "../cache.js";
8
+ import { ensureTsgolintSource, cachedBinaryExists, getBinaryPath, getBuildDir, getBinDir } from "../cache.js";
9
9
  import { computeContentHash } from "../hash.js";
10
10
  import { execAsync } from "../exec.js";
11
11
  async function checkGoInstalled() {
@@ -17,7 +17,7 @@ async function checkGoInstalled() {
17
17
  'Install from https://go.dev/dl/');
18
18
  }
19
19
  }
20
- export async function buildBinary({ rebuild }) {
20
+ export async function buildBinary({ rebuild, tsgolintVersion, }) {
21
21
  await checkGoInstalled();
22
22
  const lintcnDir = getLintcnDir();
23
23
  if (!fs.existsSync(lintcnDir)) {
@@ -27,13 +27,13 @@ export async function buildBinary({ rebuild }) {
27
27
  if (rules.length === 0) {
28
28
  throw new Error('No rules found in .lintcn/. Run `lintcn add <url>` to add rules.');
29
29
  }
30
- console.log(`Found ${rules.length} custom rule${rules.length === 1 ? '' : 's'}`);
30
+ console.log(`Found ${rules.length} custom rule${rules.length === 1 ? '' : 's'} (tsgolint ${tsgolintVersion})`);
31
31
  // ensure tsgolint source
32
- const tsgolintDir = await ensureTsgolintSource(DEFAULT_TSGOLINT_VERSION);
32
+ const tsgolintDir = await ensureTsgolintSource(tsgolintVersion);
33
33
  // compute content hash
34
34
  const contentHash = await computeContentHash({
35
35
  lintcnDir,
36
- tsgolintVersion: DEFAULT_TSGOLINT_VERSION,
36
+ tsgolintVersion,
37
37
  });
38
38
  // check cache
39
39
  if (!rebuild && cachedBinaryExists(contentHash)) {
@@ -60,8 +60,8 @@ export async function buildBinary({ rebuild }) {
60
60
  console.log('Build complete');
61
61
  return binaryPath;
62
62
  }
63
- export async function lint({ rebuild, passthroughArgs, }) {
64
- const binaryPath = await buildBinary({ rebuild });
63
+ export async function lint({ rebuild, tsgolintVersion, passthroughArgs, }) {
64
+ const binaryPath = await buildBinary({ rebuild, tsgolintVersion });
65
65
  // run the binary with passthrough args, inheriting stdio
66
66
  return new Promise((resolve) => {
67
67
  const proc = spawn(binaryPath, passthroughArgs, {
package/dist/index.d.ts CHANGED
@@ -4,4 +4,5 @@ export { addRule } from './commands/add.ts';
4
4
  export { lint, buildBinary } from './commands/lint.ts';
5
5
  export { listRules } from './commands/list.ts';
6
6
  export { removeRule } from './commands/remove.ts';
7
+ export { DEFAULT_TSGOLINT_VERSION } from './cache.ts';
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC1E,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAC3C,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC1E,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAC3C,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACjD,OAAO,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAA"}
package/dist/index.js CHANGED
@@ -3,3 +3,4 @@ export { addRule } from "./commands/add.js";
3
3
  export { lint, buildBinary } from "./commands/lint.js";
4
4
  export { listRules } from "./commands/list.js";
5
5
  export { removeRule } from "./commands/remove.js";
6
+ export { DEFAULT_TSGOLINT_VERSION } from "./cache.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lintcn",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "The shadcn for type-aware TypeScript lint rules. Browse, pick, and copy rules into your project.",
6
6
  "bin": "dist/cli.js",
package/src/cache.ts CHANGED
@@ -8,8 +8,11 @@ import os from 'node:os'
8
8
  import path from 'node:path'
9
9
  import { execAsync } from './exec.ts'
10
10
 
11
- // Default tsgolint version — pinned to a known-good commit
12
- export const DEFAULT_TSGOLINT_VERSION = 'main'
11
+ // Pinned tsgolint version — updated with each lintcn release.
12
+ // This ensures reproducible builds: every user on the same lintcn version
13
+ // compiles rules against the same tsgolint API. Changing this is a conscious
14
+ // decision — tsgolint API changes can break user rules.
15
+ export const DEFAULT_TSGOLINT_VERSION = 'v0.9.2'
13
16
 
14
17
  export function getCacheDir(): string {
15
18
  return path.join(os.homedir(), '.cache', 'lintcn')
@@ -43,12 +46,13 @@ export async function ensureTsgolintSource(version: string): Promise<string> {
43
46
 
44
47
  fs.mkdirSync(sourceDir, { recursive: true })
45
48
 
46
- // clone with depth 1 for speed
47
- const cloneArgs = ['clone', '--depth', '1', '--recurse-submodules', '--shallow-submodules']
48
- if (version !== 'main') {
49
- cloneArgs.push('--branch', version)
50
- }
51
- cloneArgs.push('https://github.com/oxc-project/tsgolint.git', sourceDir)
49
+ // clone with depth 1 for speed — --branch works with tags and branches
50
+ const cloneArgs = [
51
+ 'clone', '--depth', '1',
52
+ '--branch', version,
53
+ '--recurse-submodules', '--shallow-submodules',
54
+ 'https://github.com/oxc-project/tsgolint.git', sourceDir,
55
+ ]
52
56
 
53
57
  await execAsync('git', cloneArgs)
54
58
 
package/src/cli.ts CHANGED
@@ -9,6 +9,7 @@ import { addRule } from './commands/add.ts'
9
9
  import { lint, buildBinary } from './commands/lint.ts'
10
10
  import { listRules } from './commands/list.ts'
11
11
  import { removeRule } from './commands/remove.ts'
12
+ import { DEFAULT_TSGOLINT_VERSION } from './cache.ts'
12
13
 
13
14
  const require = createRequire(import.meta.url)
14
15
  const packageJson = require('../package.json') as { version: string }
@@ -43,7 +44,9 @@ cli
43
44
  .option('--rebuild', 'Force rebuild even if cached binary exists')
44
45
  .option('--tsconfig <path>', 'Path to tsconfig.json')
45
46
  .option('--list-files', 'List matched files')
47
+ .option('--tsgolint-version [version]', 'Override the pinned tsgolint version (tag or commit). For testing unreleased tsgolint versions.')
46
48
  .action(async (options) => {
49
+ const tsgolintVersion = (options.tsgolintVersion as string) || DEFAULT_TSGOLINT_VERSION
47
50
  const passthroughArgs: string[] = []
48
51
  if (options.tsconfig) {
49
52
  passthroughArgs.push('--tsconfig', options.tsconfig as string)
@@ -58,6 +61,7 @@ cli
58
61
  }
59
62
  const exitCode = await lint({
60
63
  rebuild: !!options.rebuild,
64
+ tsgolintVersion,
61
65
  passthroughArgs,
62
66
  })
63
67
  process.exit(exitCode)
@@ -66,8 +70,10 @@ cli
66
70
  cli
67
71
  .command('build', 'Build the custom tsgolint binary without running it')
68
72
  .option('--rebuild', 'Force rebuild even if cached binary exists')
73
+ .option('--tsgolint-version [version]', 'Override the pinned tsgolint version (tag or commit). For testing unreleased tsgolint versions.')
69
74
  .action(async (options) => {
70
- const binaryPath = await buildBinary({ rebuild: !!options.rebuild })
75
+ const tsgolintVersion = (options.tsgolintVersion as string) || DEFAULT_TSGOLINT_VERSION
76
+ const binaryPath = await buildBinary({ rebuild: !!options.rebuild, tsgolintVersion })
71
77
  console.log(binaryPath)
72
78
  })
73
79
 
@@ -21,7 +21,13 @@ async function checkGoInstalled(): Promise<void> {
21
21
  }
22
22
  }
23
23
 
24
- export async function buildBinary({ rebuild }: { rebuild: boolean }): Promise<string> {
24
+ export async function buildBinary({
25
+ rebuild,
26
+ tsgolintVersion,
27
+ }: {
28
+ rebuild: boolean
29
+ tsgolintVersion: string
30
+ }): Promise<string> {
25
31
  await checkGoInstalled()
26
32
 
27
33
  const lintcnDir = getLintcnDir()
@@ -34,15 +40,15 @@ export async function buildBinary({ rebuild }: { rebuild: boolean }): Promise<st
34
40
  throw new Error('No rules found in .lintcn/. Run `lintcn add <url>` to add rules.')
35
41
  }
36
42
 
37
- console.log(`Found ${rules.length} custom rule${rules.length === 1 ? '' : 's'}`)
43
+ console.log(`Found ${rules.length} custom rule${rules.length === 1 ? '' : 's'} (tsgolint ${tsgolintVersion})`)
38
44
 
39
45
  // ensure tsgolint source
40
- const tsgolintDir = await ensureTsgolintSource(DEFAULT_TSGOLINT_VERSION)
46
+ const tsgolintDir = await ensureTsgolintSource(tsgolintVersion)
41
47
 
42
48
  // compute content hash
43
49
  const contentHash = await computeContentHash({
44
50
  lintcnDir,
45
- tsgolintVersion: DEFAULT_TSGOLINT_VERSION,
51
+ tsgolintVersion,
46
52
  })
47
53
 
48
54
  // check cache
@@ -77,12 +83,14 @@ export async function buildBinary({ rebuild }: { rebuild: boolean }): Promise<st
77
83
 
78
84
  export async function lint({
79
85
  rebuild,
86
+ tsgolintVersion,
80
87
  passthroughArgs,
81
88
  }: {
82
89
  rebuild: boolean
90
+ tsgolintVersion: string
83
91
  passthroughArgs: string[]
84
92
  }): Promise<number> {
85
- const binaryPath = await buildBinary({ rebuild })
93
+ const binaryPath = await buildBinary({ rebuild, tsgolintVersion })
86
94
 
87
95
  // run the binary with passthrough args, inheriting stdio
88
96
  return new Promise((resolve) => {
package/src/index.ts CHANGED
@@ -4,3 +4,4 @@ export { addRule } from './commands/add.ts'
4
4
  export { lint, buildBinary } from './commands/lint.ts'
5
5
  export { listRules } from './commands/list.ts'
6
6
  export { removeRule } from './commands/remove.ts'
7
+ export { DEFAULT_TSGOLINT_VERSION } from './cache.ts'