lintcn 0.1.0 → 0.3.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 +32 -0
- package/README.md +24 -0
- package/dist/cache.d.ts +1 -1
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +86 -41
- package/dist/cli.js +7 -1
- package/dist/codegen.d.ts +7 -1
- package/dist/codegen.d.ts.map +1 -1
- package/dist/codegen.js +64 -498
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +29 -16
- package/dist/commands/lint.d.ts +4 -2
- package/dist/commands/lint.d.ts.map +1 -1
- package/dist/commands/lint.js +9 -12
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +3 -4
- package/dist/commands/remove.d.ts.map +1 -1
- package/dist/commands/remove.js +2 -5
- package/dist/discover.d.ts.map +1 -1
- package/dist/discover.js +8 -1
- package/dist/hash.d.ts.map +1 -1
- package/dist/hash.js +6 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/paths.d.ts +6 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +23 -1
- package/package.json +2 -1
- package/src/cache.ts +97 -46
- package/src/cli.ts +7 -1
- package/src/codegen.ts +74 -498
- package/src/commands/add.ts +34 -19
- package/src/commands/lint.ts +15 -10
- package/src/commands/list.ts +3 -4
- package/src/commands/remove.ts +2 -6
- package/src/discover.ts +10 -1
- package/src/hash.ts +7 -2
- package/src/index.ts +2 -0
- package/src/paths.ts +25 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
## 0.3.0
|
|
2
|
+
|
|
3
|
+
1. **Only custom rules run by default** — previously the binary included all 44 built-in tsgolint rules, producing thousands of noisy errors. Now only your `.lintcn/` rules run. True shadcn model: explicitly add each rule you want.
|
|
4
|
+
|
|
5
|
+
Before (0.2.0): `Found 2315 errors (linted 193 files with 45 rules)`
|
|
6
|
+
After (0.3.0): `Found 8 errors (linted 193 files with 1 rule)`
|
|
7
|
+
|
|
8
|
+
2. **Run `lintcn lint` from any subdirectory** — uses `find-up` to walk parent directories for `.lintcn/`. You no longer need to be at the project root:
|
|
9
|
+
```bash
|
|
10
|
+
cd packages/my-app
|
|
11
|
+
lintcn lint # finds .lintcn/ in parent
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
3. **No git required** — tsgolint source is now downloaded as a tarball from GitHub instead of cloned. Patches applied with `patch -p1`. Faster first setup, works without git installed.
|
|
15
|
+
|
|
16
|
+
4. **Fixed stale binary cache** — added `CACHE_SCHEMA_VERSION` to the content hash. Upgrading lintcn now correctly invalidates cached binaries built by older versions.
|
|
17
|
+
|
|
18
|
+
5. **Fixed partial download corruption** — if the tsgolint download fails midway, the partial directory is cleaned up so the next run starts fresh instead of failing repeatedly.
|
|
19
|
+
|
|
20
|
+
6. **Fixed GitHub URLs with `/` in branch names** — `lintcn add` now correctly handles branch names like `feature/my-branch` in GitHub blob URLs.
|
|
21
|
+
|
|
22
|
+
## 0.2.0
|
|
23
|
+
|
|
24
|
+
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.
|
|
25
|
+
|
|
26
|
+
2. **`--tsgolint-version` flag** — override the pinned version for testing unreleased tsgolint:
|
|
27
|
+
```bash
|
|
28
|
+
npx lintcn lint --tsgolint-version v0.10.0
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
3. **Version pinning docs** — README now explains why you should pin lintcn in `package.json` (no `^` or `~`) and how to update safely.
|
|
32
|
+
|
|
1
33
|
## 0.1.0
|
|
2
34
|
|
|
3
35
|
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 = "
|
|
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;
|
package/dist/cache.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAmBA,eAAO,MAAM,wBAAwB,WAAW,CAAA;AAOhD,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;AAuCD,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
|
@@ -1,13 +1,24 @@
|
|
|
1
|
-
// Manage cached tsgolint source
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// Manage cached tsgolint source and compiled binaries.
|
|
2
|
+
// Downloads tsgolint + typescript-go as tarballs from GitHub (no git required),
|
|
3
|
+
// applies patches with `patch -p1`, and copies internal/collections.
|
|
4
|
+
//
|
|
5
|
+
// Cache layout:
|
|
6
|
+
// ~/.cache/lintcn/tsgolint/<version>/ — extracted source (read-only)
|
|
7
|
+
// ~/.cache/lintcn/bin/<content-hash> — compiled binaries
|
|
5
8
|
import fs from 'node:fs';
|
|
6
9
|
import os from 'node:os';
|
|
7
10
|
import path from 'node:path';
|
|
11
|
+
import { pipeline } from 'node:stream/promises';
|
|
8
12
|
import { execAsync } from "./exec.js";
|
|
9
|
-
//
|
|
10
|
-
|
|
13
|
+
// Pinned tsgolint version — updated with each lintcn release.
|
|
14
|
+
// This ensures reproducible builds: every user on the same lintcn version
|
|
15
|
+
// compiles rules against the same tsgolint API. Changing this is a conscious
|
|
16
|
+
// decision — tsgolint API changes can break user rules.
|
|
17
|
+
export const DEFAULT_TSGOLINT_VERSION = 'v0.9.2';
|
|
18
|
+
// Pinned typescript-go commit that tsgolint v0.9.2 depends on.
|
|
19
|
+
// Found via `git ls-tree HEAD typescript-go` in the tsgolint repo.
|
|
20
|
+
// Must be updated when DEFAULT_TSGOLINT_VERSION changes.
|
|
21
|
+
const TYPESCRIPT_GO_COMMIT = '2437fa43e85103d2a18e8e41e1a2a994d0708ccf';
|
|
11
22
|
export function getCacheDir() {
|
|
12
23
|
return path.join(os.homedir(), '.cache', 'lintcn');
|
|
13
24
|
}
|
|
@@ -23,53 +34,87 @@ export function getBinaryPath(contentHash) {
|
|
|
23
34
|
export function getBuildDir() {
|
|
24
35
|
return path.join(getCacheDir(), 'build');
|
|
25
36
|
}
|
|
37
|
+
/** Download a tarball from URL and extract it to targetDir.
|
|
38
|
+
* GitHub tarballs have a top-level directory like `repo-ref/`,
|
|
39
|
+
* so we strip the first path component during extraction. */
|
|
40
|
+
async function downloadAndExtract(url, targetDir) {
|
|
41
|
+
const response = await fetch(url);
|
|
42
|
+
if (!response.ok || !response.body) {
|
|
43
|
+
throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`);
|
|
44
|
+
}
|
|
45
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
46
|
+
// pipe through gunzip, then extract with tar (strip top-level directory)
|
|
47
|
+
const tmpTarGz = path.join(os.tmpdir(), `lintcn-${Date.now()}.tar.gz`);
|
|
48
|
+
const fileStream = fs.createWriteStream(tmpTarGz);
|
|
49
|
+
// @ts-ignore ReadableStream vs NodeJS.ReadableStream mismatch
|
|
50
|
+
await pipeline(response.body, fileStream);
|
|
51
|
+
await execAsync('tar', ['xzf', tmpTarGz, '--strip-components=1', '-C', targetDir]);
|
|
52
|
+
fs.rmSync(tmpTarGz, { force: true });
|
|
53
|
+
}
|
|
54
|
+
/** Apply git-format patches using `patch -p1` (no git required).
|
|
55
|
+
* Patches are standard unified diff format, `patch` ignores the git metadata. */
|
|
56
|
+
async function applyPatches(patchesDir, targetDir) {
|
|
57
|
+
const patches = fs.readdirSync(patchesDir)
|
|
58
|
+
.filter((f) => { return f.endsWith('.patch'); })
|
|
59
|
+
.sort();
|
|
60
|
+
for (const patchFile of patches) {
|
|
61
|
+
const patchPath = path.join(patchesDir, patchFile);
|
|
62
|
+
// --batch silences interactive prompts, -f forces application
|
|
63
|
+
await execAsync('patch', ['-p1', '--batch', '-i', patchPath], { cwd: targetDir });
|
|
64
|
+
}
|
|
65
|
+
return patches.length;
|
|
66
|
+
}
|
|
26
67
|
export async function ensureTsgolintSource(version) {
|
|
27
68
|
const sourceDir = getTsgolintSourceDir(version);
|
|
28
69
|
const readyMarker = path.join(sourceDir, '.lintcn-ready');
|
|
29
70
|
if (fs.existsSync(readyMarker)) {
|
|
30
71
|
return sourceDir;
|
|
31
72
|
}
|
|
32
|
-
|
|
33
|
-
fs.
|
|
34
|
-
|
|
35
|
-
const cloneArgs = ['clone', '--depth', '1', '--recurse-submodules', '--shallow-submodules'];
|
|
36
|
-
if (version !== 'main') {
|
|
37
|
-
cloneArgs.push('--branch', version);
|
|
73
|
+
// clean up any partial previous attempt so we start fresh
|
|
74
|
+
if (fs.existsSync(sourceDir)) {
|
|
75
|
+
fs.rmSync(sourceDir, { recursive: true });
|
|
38
76
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
77
|
+
try {
|
|
78
|
+
// download tsgolint source tarball
|
|
79
|
+
console.log(`Downloading tsgolint@${version}...`);
|
|
80
|
+
const tsgolintUrl = `https://github.com/oxc-project/tsgolint/archive/refs/tags/${version}.tar.gz`;
|
|
81
|
+
await downloadAndExtract(tsgolintUrl, sourceDir);
|
|
82
|
+
// download typescript-go source tarball into tsgolint/typescript-go/
|
|
83
|
+
const tsGoDir = path.join(sourceDir, 'typescript-go');
|
|
84
|
+
console.log('Downloading typescript-go...');
|
|
85
|
+
const tsGoUrl = `https://github.com/microsoft/typescript-go/archive/${TYPESCRIPT_GO_COMMIT}.tar.gz`;
|
|
86
|
+
await downloadAndExtract(tsGoUrl, tsGoDir);
|
|
87
|
+
// apply patches to typescript-go
|
|
88
|
+
const patchesDir = path.join(sourceDir, 'patches');
|
|
89
|
+
if (fs.existsSync(patchesDir)) {
|
|
90
|
+
const count = await applyPatches(patchesDir, tsGoDir);
|
|
91
|
+
if (count > 0) {
|
|
92
|
+
console.log(`Applied ${count} patches`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// copy internal/collections from typescript-go (required by tsgolint, done by `just init`)
|
|
96
|
+
const collectionsDir = path.join(sourceDir, 'internal', 'collections');
|
|
97
|
+
const tsGoCollections = path.join(tsGoDir, 'internal', 'collections');
|
|
98
|
+
if (fs.existsSync(tsGoCollections)) {
|
|
99
|
+
fs.mkdirSync(collectionsDir, { recursive: true });
|
|
100
|
+
const files = fs.readdirSync(tsGoCollections).filter((f) => {
|
|
101
|
+
return f.endsWith('.go') && !f.endsWith('_test.go');
|
|
54
102
|
});
|
|
103
|
+
for (const file of files) {
|
|
104
|
+
fs.copyFileSync(path.join(tsGoCollections, file), path.join(collectionsDir, file));
|
|
105
|
+
}
|
|
55
106
|
}
|
|
107
|
+
// write ready marker
|
|
108
|
+
fs.writeFileSync(readyMarker, new Date().toISOString());
|
|
109
|
+
console.log('tsgolint source ready');
|
|
56
110
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
fs.mkdirSync(collectionsDir, { recursive: true });
|
|
62
|
-
const files = fs.readdirSync(tsGoCollections).filter((f) => {
|
|
63
|
-
return f.endsWith('.go') && !f.endsWith('_test.go');
|
|
64
|
-
});
|
|
65
|
-
for (const file of files) {
|
|
66
|
-
fs.copyFileSync(path.join(tsGoCollections, file), path.join(collectionsDir, file));
|
|
111
|
+
catch (err) {
|
|
112
|
+
// clean up partial download so next run starts fresh
|
|
113
|
+
if (fs.existsSync(sourceDir)) {
|
|
114
|
+
fs.rmSync(sourceDir, { recursive: true });
|
|
67
115
|
}
|
|
68
|
-
|
|
116
|
+
throw err;
|
|
69
117
|
}
|
|
70
|
-
// write ready marker
|
|
71
|
-
fs.writeFileSync(readyMarker, new Date().toISOString());
|
|
72
|
-
console.log('tsgolint source ready');
|
|
73
118
|
return sourceDir;
|
|
74
119
|
}
|
|
75
120
|
export function cachedBinaryExists(contentHash) {
|
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
|
|
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();
|
package/dist/codegen.d.ts
CHANGED
|
@@ -8,11 +8,17 @@ import type { RuleMetadata } from './discover.ts';
|
|
|
8
8
|
* tsgolint's own go.work (which does this) is ignored by the outer workspace.
|
|
9
9
|
* - go.mod should be minimal (no requires) — the workspace resolves everything. */
|
|
10
10
|
export declare function generateEditorGoFiles(lintcnDir: string): void;
|
|
11
|
-
/** Generate build workspace in cache dir for compiling the custom binary
|
|
11
|
+
/** Generate build workspace in cache dir for compiling the custom binary.
|
|
12
|
+
* Instead of hardcoding the built-in rule list, we copy tsgolint's actual
|
|
13
|
+
* main.go and inject custom rule imports + entries. This way the generated
|
|
14
|
+
* code always matches the pinned tsgolint version. */
|
|
12
15
|
export declare function generateBuildWorkspace({ buildDir, tsgolintDir, lintcnDir, rules, }: {
|
|
13
16
|
buildDir: string;
|
|
14
17
|
tsgolintDir: string;
|
|
15
18
|
lintcnDir: string;
|
|
16
19
|
rules: RuleMetadata[];
|
|
17
20
|
}): void;
|
|
21
|
+
/** Copy all supporting .go files from cmd/tsgolint/ into the wrapper dir.
|
|
22
|
+
* main.go is generated separately with custom rules injected. */
|
|
23
|
+
export declare function copyTsgolintCmdFiles(tsgolintDir: string, wrapperDir: string): void;
|
|
18
24
|
//# sourceMappingURL=codegen.d.ts.map
|
package/dist/codegen.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../src/codegen.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../src/codegen.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AA2BjD;;;;;;;oFAOoF;AACpF,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAiC7D;AAED;;;uDAGuD;AACvD,wBAAgB,sBAAsB,CAAC,EACrC,QAAQ,EACR,WAAW,EACX,SAAS,EACT,KAAK,GACN,EAAE;IACD,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,YAAY,EAAE,CAAA;CACtB,GAAG,IAAI,CAiDP;AA8DD;kEACkE;AAClE,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAQlF"}
|