lintcn 0.2.0 → 0.4.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.
@@ -2,7 +2,7 @@
2
2
  // Handles Go workspace generation, compilation with caching, and execution.
3
3
  import fs from 'node:fs';
4
4
  import { spawn } from 'node:child_process';
5
- import { getLintcnDir } from "../paths.js";
5
+ import { requireLintcnDir } from "../paths.js";
6
6
  import { discoverRules } from "../discover.js";
7
7
  import { generateBuildWorkspace } from "../codegen.js";
8
8
  import { ensureTsgolintSource, cachedBinaryExists, getBinaryPath, getBuildDir, getBinDir } from "../cache.js";
@@ -19,10 +19,7 @@ async function checkGoInstalled() {
19
19
  }
20
20
  export async function buildBinary({ rebuild, tsgolintVersion, }) {
21
21
  await checkGoInstalled();
22
- const lintcnDir = getLintcnDir();
23
- if (!fs.existsSync(lintcnDir)) {
24
- throw new Error('No .lintcn/ directory found. Run `lintcn add <url>` first.');
25
- }
22
+ const lintcnDir = requireLintcnDir();
26
23
  const rules = discoverRules(lintcnDir);
27
24
  if (rules.length === 0) {
28
25
  throw new Error('No rules found in .lintcn/. Run `lintcn add <url>` to add rules.');
@@ -1 +1 @@
1
- {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"AAMA,wBAAgB,SAAS,IAAI,IAAI,CA0BhC"}
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"AAKA,wBAAgB,SAAS,IAAI,IAAI,CA0BhC"}
@@ -1,10 +1,9 @@
1
1
  // lintcn list — list installed rules with metadata from .lintcn/
2
- import fs from 'node:fs';
3
- import { getLintcnDir } from "../paths.js";
2
+ import { findLintcnDir } from "../paths.js";
4
3
  import { discoverRules } from "../discover.js";
5
4
  export function listRules() {
6
- const lintcnDir = getLintcnDir();
7
- if (!fs.existsSync(lintcnDir)) {
5
+ const lintcnDir = findLintcnDir();
6
+ if (!lintcnDir) {
8
7
  console.log('No .lintcn/ directory found. Run `lintcn add <url>` to add rules.');
9
8
  return;
10
9
  }
@@ -1 +1 @@
1
- {"version":3,"file":"remove.d.ts","sourceRoot":"","sources":["../../src/commands/remove.ts"],"names":[],"mappings":"AAOA,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAiC7C"}
1
+ {"version":3,"file":"remove.d.ts","sourceRoot":"","sources":["../../src/commands/remove.ts"],"names":[],"mappings":"AAOA,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CA6B7C"}
@@ -1,13 +1,10 @@
1
1
  // lintcn remove <name> — delete a rule and its test file from .lintcn/
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
- import { getLintcnDir } from "../paths.js";
4
+ import { requireLintcnDir } from "../paths.js";
5
5
  import { discoverRules } from "../discover.js";
6
6
  export function removeRule(name) {
7
- const lintcnDir = getLintcnDir();
8
- if (!fs.existsSync(lintcnDir)) {
9
- throw new Error('No .lintcn/ directory found.');
10
- }
7
+ const lintcnDir = requireLintcnDir();
11
8
  // match by lintcn:name metadata or by filename
12
9
  const rules = discoverRules(lintcnDir);
13
10
  const normalizedName = name.replace(/-/g, '_');
@@ -1 +1 @@
1
- {"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,YAAY;IAC3B,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAA;IACZ,sDAAsD;IACtD,WAAW,EAAE,MAAM,CAAA;IACnB,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAA;IACd,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAA;IACf,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAA;CACjB;AAKD,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMrE;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGhE;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,EAAE,CAiC/D"}
1
+ {"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,YAAY;IAC3B,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAA;IACZ,sDAAsD;IACtD,WAAW,EAAE,MAAM,CAAA;IACnB,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAA;IACd,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAA;IACf,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAA;CACjB;AAOD,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMrE;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGhE;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,EAAE,CAwC/D"}
package/dist/discover.js CHANGED
@@ -2,7 +2,9 @@
2
2
  // Returns structured info about each discovered rule for codegen and list display.
3
3
  import fs from 'node:fs';
4
4
  import path from 'node:path';
5
- const RULE_VAR_RE = /^var\s+(\w+)\s*=\s*rule\.Rule\s*\{/m;
5
+ // Matches `var XxxRule = rule.Rule{` with optional leading whitespace
6
+ // and optional import alias (e.g. `r.Rule{` if imported as `r "...rule"`)
7
+ const RULE_VAR_RE = /^\s*var\s+(\w+)\s*=\s*\w*\.?Rule\s*\{/m;
6
8
  const METADATA_RE = /^\/\/\s*lintcn:(\w+)\s+(.+)$/gm;
7
9
  export function parseMetadata(content) {
8
10
  const meta = {};
@@ -28,6 +30,11 @@ export function discoverRules(lintcnDir) {
28
30
  const content = fs.readFileSync(filePath, 'utf-8');
29
31
  const varName = parseRuleVar(content);
30
32
  if (!varName) {
33
+ // warn if file contains rule.Rule but we couldn't parse the var name
34
+ if (content.includes('rule.Rule')) {
35
+ console.warn(`Warning: ${fileName} contains rule.Rule but no exported var was found. ` +
36
+ `Expected pattern: var XxxRule = rule.Rule{`);
37
+ }
31
38
  continue;
32
39
  }
33
40
  const meta = parseMetadata(content);
@@ -1 +1 @@
1
- {"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AASA,wBAAsB,kBAAkB,CAAC,EACvC,SAAS,EACT,eAAe,GAChB,EAAE;IACD,SAAS,EAAE,MAAM,CAAA;IACjB,eAAe,EAAE,MAAM,CAAA;CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,CA6BlB"}
1
+ {"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAaA,wBAAsB,kBAAkB,CAAC,EACvC,SAAS,EACT,eAAe,GAChB,EAAE;IACD,SAAS,EAAE,MAAM,CAAA;IACjB,eAAe,EAAE,MAAM,CAAA;CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,CA8BlB"}
package/dist/hash.js CHANGED
@@ -1,12 +1,16 @@
1
1
  // Content hash for binary caching.
2
- // Combines tsgolint version, rule file contents, Go version, and platform
3
- // into a single SHA-256 hash used as the cached binary filename.
2
+ // Combines cache schema version, tsgolint version, rule file contents,
3
+ // Go version, and platform into a single SHA-256 hash.
4
+ // Bump CACHE_SCHEMA_VERSION when codegen logic changes to invalidate
5
+ // stale binaries built by older lintcn versions.
4
6
  import crypto from 'node:crypto';
5
7
  import fs from 'node:fs';
6
8
  import path from 'node:path';
7
9
  import { execAsync } from "./exec.js";
10
+ const CACHE_SCHEMA_VERSION = '2';
8
11
  export async function computeContentHash({ lintcnDir, tsgolintVersion, }) {
9
12
  const hash = crypto.createHash('sha256');
13
+ hash.update(`cache-schema:${CACHE_SCHEMA_VERSION}\n`);
10
14
  hash.update(`tsgolint:${tsgolintVersion}\n`);
11
15
  hash.update(`platform:${process.platform}-${process.arch}\n`);
12
16
  // add Go version
package/dist/index.d.ts CHANGED
@@ -5,4 +5,5 @@ export { lint, buildBinary } from './commands/lint.ts';
5
5
  export { listRules } from './commands/list.ts';
6
6
  export { removeRule } from './commands/remove.ts';
7
7
  export { DEFAULT_TSGOLINT_VERSION } from './cache.ts';
8
+ export { findLintcnDir, getLintcnDir, requireLintcnDir } from './paths.ts';
8
9
  //# 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;AACjD,OAAO,EAAE,wBAAwB,EAAE,MAAM,YAAY,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;AACrD,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA"}
package/dist/index.js CHANGED
@@ -4,3 +4,4 @@ export { lint, buildBinary } from "./commands/lint.js";
4
4
  export { listRules } from "./commands/list.js";
5
5
  export { removeRule } from "./commands/remove.js";
6
6
  export { DEFAULT_TSGOLINT_VERSION } from "./cache.js";
7
+ export { findLintcnDir, getLintcnDir, requireLintcnDir } from "./paths.js";
package/dist/paths.d.ts CHANGED
@@ -1,2 +1,8 @@
1
+ /** Find the nearest .lintcn/ directory by walking up from cwd.
2
+ * Returns the absolute path to the directory, or null if not found. */
3
+ export declare function findLintcnDir(): string | null;
4
+ /** Find .lintcn/ or throw with a helpful error. */
1
5
  export declare function getLintcnDir(): string;
6
+ /** Find .lintcn/ or throw — for commands that require it to exist. */
7
+ export declare function requireLintcnDir(): string;
2
8
  //# sourceMappingURL=paths.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAIA,wBAAgB,YAAY,IAAI,MAAM,CAErC"}
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAMA;wEACwE;AACxE,wBAAgB,aAAa,IAAI,MAAM,GAAG,IAAI,CAG7C;AAED,mDAAmD;AACnD,wBAAgB,YAAY,IAAI,MAAM,CAOrC;AAED,sEAAsE;AACtE,wBAAgB,gBAAgB,IAAI,MAAM,CAMzC"}
package/dist/paths.js CHANGED
@@ -1,5 +1,27 @@
1
- // Resolve the .lintcn/ directory path relative to cwd.
1
+ // Resolve the .lintcn/ directory by walking up from cwd.
2
+ // This lets users run `lintcn lint` from any subdirectory of their project.
3
+ import { findUpSync } from 'find-up';
2
4
  import path from 'node:path';
5
+ /** Find the nearest .lintcn/ directory by walking up from cwd.
6
+ * Returns the absolute path to the directory, or null if not found. */
7
+ export function findLintcnDir() {
8
+ const found = findUpSync('.lintcn', { type: 'directory' });
9
+ return found ?? null;
10
+ }
11
+ /** Find .lintcn/ or throw with a helpful error. */
3
12
  export function getLintcnDir() {
13
+ const dir = findLintcnDir();
14
+ if (dir) {
15
+ return dir;
16
+ }
17
+ // fall back to cwd/.lintcn for `lintcn add` (creates the directory)
4
18
  return path.resolve(process.cwd(), '.lintcn');
5
19
  }
20
+ /** Find .lintcn/ or throw — for commands that require it to exist. */
21
+ export function requireLintcnDir() {
22
+ const dir = findLintcnDir();
23
+ if (!dir) {
24
+ throw new Error('No .lintcn/ directory found in current or parent directories. Run `lintcn add <url>` first.');
25
+ }
26
+ return dir;
27
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lintcn",
3
- "version": "0.2.0",
3
+ "version": "0.4.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",
@@ -54,6 +54,7 @@
54
54
  "typescript": "5.8.2"
55
55
  },
56
56
  "dependencies": {
57
+ "find-up": "^8.0.0",
57
58
  "goke": "^6.3.0"
58
59
  },
59
60
  "scripts": {
package/src/cache.ts CHANGED
@@ -1,18 +1,27 @@
1
- // Manage cached tsgolint source clone and compiled binaries.
2
- // Cache lives in ~/.cache/lintcn/ with structure:
3
- // tsgolint/<version>/ — cloned tsgolint source (read-only)
4
- // bin/<content-hash> — compiled binaries
1
+ // Manage cached tsgolint source and compiled binaries.
2
+ // Downloads tsgolint fork + typescript-go as tarballs from GitHub,
3
+ // applies tsgolint's patches to typescript-go, and copies collections.
4
+ //
5
+ // Cache layout:
6
+ // ~/.cache/lintcn/tsgolint/<version>/ — extracted source (read-only)
7
+ // ~/.cache/lintcn/bin/<content-hash> — compiled binaries
5
8
 
6
9
  import fs from 'node:fs'
7
10
  import os from 'node:os'
8
11
  import path from 'node:path'
12
+ import { pipeline } from 'node:stream/promises'
9
13
  import { execAsync } from './exec.ts'
10
14
 
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'
15
+ // Pinned tsgolint fork commit — updated with each lintcn release.
16
+ // Uses remorses/tsgolint fork which exposes pkg/runner.Run() and
17
+ // moves internal/ packages to pkg/ for clean external imports.
18
+ export const DEFAULT_TSGOLINT_VERSION = 'a93604379da2631b70332a65bc47eb5ced689a3b'
19
+
20
+ // Pinned typescript-go base commit from microsoft/typescript-go (before patches).
21
+ // Patches from tsgolint/patches/ are applied on top during setup.
22
+ // This is the upstream commit the tsgolint submodule was forked from.
23
+ // Must be updated when DEFAULT_TSGOLINT_VERSION changes.
24
+ const TYPESCRIPT_GO_COMMIT = '1b7eabe122e1575a0df9c77eccdf4e063c623224'
16
25
 
17
26
  export function getCacheDir(): string {
18
27
  return path.join(os.homedir(), '.cache', 'lintcn')
@@ -34,6 +43,41 @@ export function getBuildDir(): string {
34
43
  return path.join(getCacheDir(), 'build')
35
44
  }
36
45
 
46
+ /** Download a tarball from URL and extract it to targetDir.
47
+ * GitHub tarballs have a top-level directory like `repo-ref/`,
48
+ * so we strip the first path component during extraction. */
49
+ async function downloadAndExtract(url: string, targetDir: string): Promise<void> {
50
+ const response = await fetch(url)
51
+ if (!response.ok || !response.body) {
52
+ throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`)
53
+ }
54
+
55
+ fs.mkdirSync(targetDir, { recursive: true })
56
+
57
+ const tmpTarGz = path.join(os.tmpdir(), `lintcn-${Date.now()}.tar.gz`)
58
+ const fileStream = fs.createWriteStream(tmpTarGz)
59
+ // @ts-ignore ReadableStream vs NodeJS.ReadableStream mismatch
60
+ await pipeline(response.body, fileStream)
61
+
62
+ await execAsync('tar', ['xzf', tmpTarGz, '--strip-components=1', '-C', targetDir])
63
+ fs.rmSync(tmpTarGz, { force: true })
64
+ }
65
+
66
+ /** Apply git-format patches using `patch -p1` (no git required).
67
+ * Patches are standard unified diff format, `patch` ignores the git metadata. */
68
+ async function applyPatches(patchesDir: string, targetDir: string): Promise<number> {
69
+ const patches = fs.readdirSync(patchesDir)
70
+ .filter((f) => { return f.endsWith('.patch') })
71
+ .sort()
72
+
73
+ for (const patchFile of patches) {
74
+ const patchPath = path.join(patchesDir, patchFile)
75
+ await execAsync('patch', ['-p1', '--batch', '-i', patchPath], { cwd: targetDir })
76
+ }
77
+
78
+ return patches.length
79
+ }
80
+
37
81
  export async function ensureTsgolintSource(version: string): Promise<string> {
38
82
  const sourceDir = getTsgolintSourceDir(version)
39
83
  const readyMarker = path.join(sourceDir, '.lintcn-ready')
@@ -42,56 +86,56 @@ export async function ensureTsgolintSource(version: string): Promise<string> {
42
86
  return sourceDir
43
87
  }
44
88
 
45
- console.log(`Cloning tsgolint@${version}...`)
46
-
47
- fs.mkdirSync(sourceDir, { recursive: true })
48
-
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
- ]
56
-
57
- await execAsync('git', cloneArgs)
89
+ // clean up any partial previous attempt so we start fresh
90
+ if (fs.existsSync(sourceDir)) {
91
+ fs.rmSync(sourceDir, { recursive: true })
92
+ }
58
93
 
59
- // apply patches if they exist
60
- const patchesDir = path.join(sourceDir, 'patches')
61
- if (fs.existsSync(patchesDir)) {
62
- const patches = fs.readdirSync(patchesDir).filter((f) => {
63
- return f.endsWith('.patch')
64
- }).sort()
94
+ try {
95
+ // download tsgolint fork tarball
96
+ console.log(`Downloading tsgolint@${version.slice(0, 8)}...`)
97
+ const tsgolintUrl = `https://github.com/remorses/tsgolint/archive/${version}.tar.gz`
98
+ await downloadAndExtract(tsgolintUrl, sourceDir)
99
+
100
+ // download typescript-go from microsoft (base commit before patches)
101
+ const tsGoDir = path.join(sourceDir, 'typescript-go')
102
+ console.log('Downloading typescript-go...')
103
+ const tsGoUrl = `https://github.com/microsoft/typescript-go/archive/${TYPESCRIPT_GO_COMMIT}.tar.gz`
104
+ await downloadAndExtract(tsGoUrl, tsGoDir)
105
+
106
+ // apply tsgolint's patches to typescript-go
107
+ const patchesDir = path.join(sourceDir, 'patches')
108
+ if (fs.existsSync(patchesDir)) {
109
+ const count = await applyPatches(patchesDir, tsGoDir)
110
+ if (count > 0) {
111
+ console.log(`Applied ${count} patches`)
112
+ }
113
+ }
65
114
 
66
- if (patches.length > 0) {
67
- console.log(`Applying ${patches.length} patches...`)
68
- const patchPaths = patches.map((p) => {
69
- return path.join('..', 'patches', p)
70
- })
71
- await execAsync('git', ['am', '--3way', '--no-gpg-sign', ...patchPaths], {
72
- cwd: path.join(sourceDir, 'typescript-go'),
115
+ // copy internal/collections from typescript-go (required by tsgolint, done by `just init`)
116
+ const collectionsDir = path.join(sourceDir, 'internal', 'collections')
117
+ const tsGoCollections = path.join(tsGoDir, 'internal', 'collections')
118
+ if (fs.existsSync(tsGoCollections)) {
119
+ fs.mkdirSync(collectionsDir, { recursive: true })
120
+ const files = fs.readdirSync(tsGoCollections).filter((f) => {
121
+ return f.endsWith('.go') && !f.endsWith('_test.go')
73
122
  })
123
+ for (const file of files) {
124
+ fs.copyFileSync(path.join(tsGoCollections, file), path.join(collectionsDir, file))
125
+ }
74
126
  }
75
- }
76
127
 
77
- // copy internal/collections from typescript-go (required by tsgolint, done by `just init`)
78
- const collectionsDir = path.join(sourceDir, 'internal', 'collections')
79
- const tsGoCollections = path.join(sourceDir, 'typescript-go', 'internal', 'collections')
80
- if (!fs.existsSync(collectionsDir) && fs.existsSync(tsGoCollections)) {
81
- fs.mkdirSync(collectionsDir, { recursive: true })
82
- const files = fs.readdirSync(tsGoCollections).filter((f) => {
83
- return f.endsWith('.go') && !f.endsWith('_test.go')
84
- })
85
- for (const file of files) {
86
- fs.copyFileSync(path.join(tsGoCollections, file), path.join(collectionsDir, file))
128
+ // write ready marker
129
+ fs.writeFileSync(readyMarker, new Date().toISOString())
130
+ console.log('tsgolint source ready')
131
+ } catch (err) {
132
+ // clean up partial download so next run starts fresh
133
+ if (fs.existsSync(sourceDir)) {
134
+ fs.rmSync(sourceDir, { recursive: true })
87
135
  }
88
- console.log(`Copied ${files.length} collection files`)
136
+ throw err
89
137
  }
90
138
 
91
- // write ready marker
92
- fs.writeFileSync(readyMarker, new Date().toISOString())
93
- console.log('tsgolint source ready')
94
-
95
139
  return sourceDir
96
140
  }
97
141