ts-builds 1.0.0 → 1.2.1

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 (3) hide show
  1. package/dist/cli.js +323 -33
  2. package/package.json +24 -17
  3. package/templates/npmrc +0 -7
package/dist/cli.js CHANGED
@@ -1,12 +1,72 @@
1
1
  #!/usr/bin/env node
2
- import { copyFileSync, existsSync, readFileSync, writeFileSync } from "node:fs";
3
- import { dirname, join } from "node:path";
2
+ import { spawn } from "node:child_process";
3
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
4
+ import { join } from "node:path";
4
5
  import { createInterface } from "node:readline";
5
- import { fileURLToPath } from "node:url";
6
6
 
7
7
  //#region src/cli.ts
8
- const templateDir = join(dirname(fileURLToPath(import.meta.url)), "..", "templates");
9
8
  const targetDir = process.cwd();
9
+ const defaultChains = { validate: [
10
+ "format",
11
+ "lint",
12
+ "typecheck",
13
+ "test",
14
+ "build"
15
+ ] };
16
+ function loadConfig() {
17
+ const configPath = join(targetDir, "ts-builds.config.json");
18
+ let userConfig = {};
19
+ if (existsSync(configPath)) try {
20
+ userConfig = JSON.parse(readFileSync(configPath, "utf-8"));
21
+ } catch {
22
+ console.error("Warning: Failed to parse ts-builds.config.json, using defaults");
23
+ }
24
+ const commands = {};
25
+ if (userConfig.commands) for (const [name, cmd] of Object.entries(userConfig.commands)) commands[name] = typeof cmd === "string" ? { run: cmd } : cmd;
26
+ const chains = { ...defaultChains };
27
+ if (userConfig.validateChain) chains.validate = userConfig.validateChain;
28
+ if (userConfig.chains) Object.assign(chains, userConfig.chains);
29
+ return {
30
+ srcDir: userConfig.srcDir ?? "./src",
31
+ testDir: userConfig.testDir ?? "./test",
32
+ commands,
33
+ chains
34
+ };
35
+ }
36
+ function runCommand(command$1, args, options = {}) {
37
+ const cwd = options.cwd ? join(targetDir, options.cwd) : targetDir;
38
+ return new Promise((resolve) => {
39
+ const child = spawn(command$1, args, {
40
+ cwd,
41
+ stdio: "inherit",
42
+ shell: true
43
+ });
44
+ child.on("close", (code) => {
45
+ resolve(code ?? 1);
46
+ });
47
+ child.on("error", (err) => {
48
+ console.error(`Failed to run ${command$1}: ${err.message}`);
49
+ resolve(1);
50
+ });
51
+ });
52
+ }
53
+ function runShellCommand(shellCmd, options = {}) {
54
+ const cwd = options.cwd ? join(targetDir, options.cwd) : targetDir;
55
+ return new Promise((resolve) => {
56
+ const child = spawn(shellCmd, {
57
+ cwd,
58
+ stdio: "inherit",
59
+ shell: true
60
+ });
61
+ child.on("close", (code) => {
62
+ resolve(code ?? 1);
63
+ });
64
+ child.on("error", (err) => {
65
+ console.error(`Failed to run: ${err.message}`);
66
+ resolve(1);
67
+ });
68
+ });
69
+ }
10
70
  const bundledPackages = [
11
71
  "@eslint/eslintrc",
12
72
  "@eslint/js",
@@ -26,37 +86,98 @@ const bundledPackages = [
26
86
  "typescript",
27
87
  "vitest"
28
88
  ];
29
- const files = [{
30
- src: "npmrc",
31
- dest: ".npmrc"
32
- }];
89
+ const requiredHoistPatterns = [
90
+ "public-hoist-pattern[]=*eslint*",
91
+ "public-hoist-pattern[]=*prettier*",
92
+ "public-hoist-pattern[]=*vitest*",
93
+ "public-hoist-pattern[]=typescript",
94
+ "public-hoist-pattern[]=*rimraf*",
95
+ "public-hoist-pattern[]=*cross-env*"
96
+ ];
97
+ function ensureNpmrcHoistPatterns() {
98
+ const npmrcPath = join(targetDir, ".npmrc");
99
+ const existingContent = existsSync(npmrcPath) ? readFileSync(npmrcPath, "utf-8") : "";
100
+ const missingPatterns = requiredHoistPatterns.filter((pattern) => !existingContent.includes(pattern));
101
+ if (missingPatterns.length === 0) return;
102
+ const header = "# Hoist CLI tool binaries from peer dependencies";
103
+ const hasHeader = existingContent.includes(header);
104
+ let newContent = existingContent;
105
+ if (!hasHeader && missingPatterns.length > 0) newContent = existingContent + (existingContent.length > 0 && !existingContent.endsWith("\n") ? "\n\n" : existingContent.length > 0 ? "\n" : "") + header + "\n";
106
+ for (const pattern of missingPatterns) {
107
+ if (!newContent.endsWith("\n") && newContent.length > 0) newContent += "\n";
108
+ newContent += pattern + "\n";
109
+ }
110
+ writeFileSync(npmrcPath, newContent);
111
+ console.log(`✓ Updated .npmrc with ${missingPatterns.length} missing hoist pattern(s)`);
112
+ }
33
113
  function showHelp() {
34
114
  console.log(`
35
- ts-builds - Shared TypeScript configuration files
115
+ ts-builds - Shared TypeScript build tooling
36
116
 
37
117
  USAGE:
38
118
  npx ts-builds [command]
39
119
 
40
- COMMANDS:
41
- init Initialize project with config files (default)
120
+ SETUP COMMANDS:
121
+ init Initialize project with .npmrc hoist patterns (default)
122
+ config Create ts-builds.config.json (use --force to overwrite)
42
123
  info Show bundled packages you don't need to install
43
124
  cleanup Remove redundant dependencies from package.json
44
125
  help Show this help message
45
126
 
46
- EXAMPLES:
47
- npx ts-builds # Initialize project
48
- npx ts-builds info # List bundled packages
49
- npx ts-builds cleanup # Remove redundant deps
127
+ SCRIPT COMMANDS:
128
+ validate Run full validation chain (configurable)
129
+ format Format code with Prettier (--write)
130
+ format:check Check formatting without writing
131
+ lint Lint and fix with ESLint
132
+ lint:check Check lint without fixing
133
+ typecheck Run TypeScript type checking (tsc --noEmit)
134
+ test Run tests once (vitest run)
135
+ test:watch Run tests in watch mode
136
+ test:coverage Run tests with coverage
137
+ test:ui Launch Vitest UI
138
+ build Production build (rimraf dist && tsdown)
139
+ build:watch Watch mode build
140
+ dev Alias for build:watch
50
141
 
51
- DESCRIPTION:
52
- This package bundles all ESLint, Prettier, Vitest, and TypeScript
53
- tooling as dependencies. You only need to install:
142
+ CONFIGURATION:
143
+ Create ts-builds.config.json in your project root:
54
144
 
55
- - ts-builds (this package)
56
- - tsdown (peer dependency for building)
145
+ Basic:
146
+ {
147
+ "srcDir": "./src",
148
+ "validateChain": ["format", "lint", "typecheck", "test", "build"]
149
+ }
57
150
 
58
- Run 'npx ts-builds info' to see the full list
59
- of bundled packages.
151
+ Advanced (monorepo with custom commands):
152
+ {
153
+ "srcDir": "./src",
154
+ "commands": {
155
+ "docs:validate": "pnpm docs:build && pnpm docs:check",
156
+ "landing:validate": { "run": "pnpm validate", "cwd": "./landing" }
157
+ },
158
+ "chains": {
159
+ "validate": ["validate:core", "validate:landing"],
160
+ "validate:core": ["format", "lint", "compile", "test", "docs:validate", "build"],
161
+ "validate:landing": ["landing:validate"]
162
+ }
163
+ }
164
+
165
+ USAGE IN PACKAGE.JSON:
166
+ {
167
+ "scripts": {
168
+ "validate": "ts-builds validate",
169
+ "validate:core": "ts-builds validate:core",
170
+ "format": "ts-builds format",
171
+ "lint": "ts-builds lint",
172
+ "test": "ts-builds test",
173
+ "build": "ts-builds build"
174
+ }
175
+ }
176
+
177
+ EXAMPLES:
178
+ npx ts-builds validate # Run default validation chain
179
+ npx ts-builds validate:core # Run named chain
180
+ npx ts-builds lint # Run single command
60
181
  `);
61
182
  }
62
183
  function showInfo() {
@@ -130,21 +251,136 @@ async function cleanup() {
130
251
  }
131
252
  function init() {
132
253
  console.log("Initializing ts-builds...");
133
- for (const { src: srcFile, dest: destFile } of files) {
134
- const src = join(templateDir, srcFile);
135
- const dest = join(targetDir, destFile);
136
- if (existsSync(dest)) console.log(` ⚠ ${destFile} already exists, skipping`);
137
- else {
138
- copyFileSync(src, dest);
139
- console.log(` ✓ Created ${destFile}`);
140
- }
141
- }
254
+ ensureNpmrcHoistPatterns();
142
255
  console.log("\nDone! Your project is configured to hoist CLI binaries from peer dependencies.");
143
256
  console.log("\nNext steps:");
257
+ console.log(" - Run 'npx ts-builds config' to create a config file");
144
258
  console.log(" - Run 'npx ts-builds info' to see bundled packages");
145
259
  console.log(" - Run 'npx ts-builds cleanup' to remove redundant deps");
146
260
  }
147
- switch (process.argv[2] || "init") {
261
+ function createConfig(force = false) {
262
+ const configPath = join(targetDir, "ts-builds.config.json");
263
+ if (existsSync(configPath) && !force) {
264
+ console.log("ts-builds.config.json already exists.");
265
+ console.log("Use 'ts-builds config --force' to overwrite.");
266
+ return;
267
+ }
268
+ writeFileSync(configPath, JSON.stringify({
269
+ srcDir: "./src",
270
+ validateChain: [
271
+ "format",
272
+ "lint",
273
+ "typecheck",
274
+ "test",
275
+ "build"
276
+ ]
277
+ }, null, 2) + "\n");
278
+ console.log("✓ Created ts-builds.config.json");
279
+ console.log(`
280
+ Configuration options:
281
+ srcDir Source directory for linting (default: "./src")
282
+ testDir Test directory (default: "./test")
283
+ validateChain Commands to run for validate (default shown above)
284
+ commands Custom commands: { "name": "shell command" }
285
+ chains Named chains: { "validate:fast": ["format", "lint"] }
286
+
287
+ Example with custom commands:
288
+ {
289
+ "srcDir": "./src",
290
+ "commands": {
291
+ "docs": "pnpm docs:build",
292
+ "subproject": { "run": "pnpm validate", "cwd": "./packages/sub" }
293
+ },
294
+ "chains": {
295
+ "validate": ["format", "lint", "test", "build"],
296
+ "validate:full": ["format", "lint", "typecheck", "test", "docs", "build"]
297
+ }
298
+ }
299
+ `);
300
+ }
301
+ async function runFormat(check = false) {
302
+ return runCommand("prettier", check ? ["--check", "."] : ["--write", "."]);
303
+ }
304
+ async function runLint(check = false) {
305
+ const config = loadConfig();
306
+ return runCommand("eslint", check ? [config.srcDir] : ["--fix", config.srcDir]);
307
+ }
308
+ async function runTypecheck() {
309
+ return runCommand("tsc", ["--noEmit"]);
310
+ }
311
+ async function runTest(mode = "run") {
312
+ switch (mode) {
313
+ case "watch": return runCommand("vitest", []);
314
+ case "coverage": return runCommand("vitest", ["run", "--coverage"]);
315
+ case "ui": return runCommand("vitest", ["--ui"]);
316
+ default: return runCommand("vitest", ["run"]);
317
+ }
318
+ }
319
+ async function runBuild(watch = false) {
320
+ if (watch) return runCommand("tsdown", ["--watch"]);
321
+ const cleanCode = await runCommand("rimraf", ["dist"]);
322
+ if (cleanCode !== 0) return cleanCode;
323
+ return runCommand("cross-env", ["NODE_ENV=production", "tsdown"]);
324
+ }
325
+ function getBuiltinCommands(config) {
326
+ return {
327
+ format: { run: "prettier --write ." },
328
+ "format:check": { run: "prettier --check ." },
329
+ lint: { run: `eslint --fix ${config.srcDir}` },
330
+ "lint:check": { run: `eslint ${config.srcDir}` },
331
+ typecheck: { run: "tsc --noEmit" },
332
+ "ts-types": { run: "tsc --noEmit" },
333
+ test: { run: "vitest run" },
334
+ "test:watch": { run: "vitest" },
335
+ "test:coverage": { run: "vitest run --coverage" },
336
+ "test:ui": { run: "vitest --ui" },
337
+ build: { run: "rimraf dist && cross-env NODE_ENV=production tsdown" },
338
+ "build:watch": { run: "tsdown --watch" },
339
+ dev: { run: "tsdown --watch" },
340
+ compile: { run: "tsc" }
341
+ };
342
+ }
343
+ async function runChain(chainName, config, visited = /* @__PURE__ */ new Set()) {
344
+ if (visited.has(chainName)) {
345
+ console.error(`Circular chain reference detected: ${chainName}`);
346
+ return 1;
347
+ }
348
+ visited.add(chainName);
349
+ const chain = config.chains[chainName];
350
+ if (!chain) {
351
+ console.error(`Unknown chain: ${chainName}`);
352
+ return 1;
353
+ }
354
+ const builtins = getBuiltinCommands(config);
355
+ console.log(`\n📋 Running chain: ${chainName} [${chain.join(" → ")}]`);
356
+ for (const step of chain) {
357
+ if (config.chains[step]) {
358
+ const code$1 = await runChain(step, config, visited);
359
+ if (code$1 !== 0) return code$1;
360
+ continue;
361
+ }
362
+ const cmdDef = config.commands[step] ?? builtins[step];
363
+ if (!cmdDef) {
364
+ console.error(`Unknown command or chain: ${step}`);
365
+ return 1;
366
+ }
367
+ const cwdLabel = cmdDef.cwd ? ` (in ${cmdDef.cwd})` : "";
368
+ console.log(`\n▶ Running ${step}...${cwdLabel}`);
369
+ const code = await runShellCommand(cmdDef.run, { cwd: cmdDef.cwd });
370
+ if (code !== 0) {
371
+ console.error(`\n✗ ${step} failed with exit code ${code}`);
372
+ return code;
373
+ }
374
+ console.log(`✓ ${step} complete`);
375
+ }
376
+ return 0;
377
+ }
378
+ async function runValidate(chainName = "validate") {
379
+ return runChain(chainName, loadConfig());
380
+ }
381
+ const command = process.argv[2] || "init";
382
+ const subCommand = process.argv[3];
383
+ switch (command) {
148
384
  case "help":
149
385
  case "--help":
150
386
  case "-h":
@@ -158,10 +394,64 @@ switch (process.argv[2] || "init") {
158
394
  case "--cleanup":
159
395
  await cleanup();
160
396
  break;
397
+ case "format":
398
+ process.exit(await runFormat(subCommand === "check"));
399
+ break;
400
+ case "format:check":
401
+ process.exit(await runFormat(true));
402
+ break;
403
+ case "lint":
404
+ process.exit(await runLint(subCommand === "check"));
405
+ break;
406
+ case "lint:check":
407
+ process.exit(await runLint(true));
408
+ break;
409
+ case "typecheck":
410
+ case "ts-types":
411
+ process.exit(await runTypecheck());
412
+ break;
413
+ case "test":
414
+ process.exit(await runTest(subCommand));
415
+ break;
416
+ case "test:watch":
417
+ process.exit(await runTest("watch"));
418
+ break;
419
+ case "test:coverage":
420
+ process.exit(await runTest("coverage"));
421
+ break;
422
+ case "test:ui":
423
+ process.exit(await runTest("ui"));
424
+ break;
425
+ case "build":
426
+ process.exit(await runBuild(subCommand === "watch"));
427
+ break;
428
+ case "build:watch":
429
+ case "dev":
430
+ process.exit(await runBuild(true));
431
+ break;
432
+ case "validate":
433
+ process.exit(await runValidate());
434
+ break;
161
435
  case "init":
162
- default:
163
436
  init();
164
437
  break;
438
+ case "config":
439
+ createConfig(process.argv.includes("--force") || process.argv.includes("-f"));
440
+ break;
441
+ default: {
442
+ const config = loadConfig();
443
+ if (config.chains[command]) process.exit(await runValidate(command));
444
+ else if (config.commands[command]) {
445
+ const cmdDef = config.commands[command];
446
+ const code = await runShellCommand(cmdDef.run, { cwd: cmdDef.cwd });
447
+ process.exit(code);
448
+ } else {
449
+ console.error(`Unknown command: ${command}`);
450
+ console.log("Run 'ts-builds help' for usage information.");
451
+ process.exit(1);
452
+ }
453
+ break;
454
+ }
165
455
  }
166
456
 
167
457
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-builds",
3
- "version": "1.0.0",
3
+ "version": "1.2.1",
4
4
  "description": "Shared TypeScript configuration files for library templates. Provides standardized ESLint, Prettier, Vitest, TypeScript, and build configs.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -18,11 +18,11 @@
18
18
  "homepage": "https://github.com/jordanburke/ts-builds",
19
19
  "repository": {
20
20
  "type": "git",
21
- "url": "https://github.com/jordanburke/ts-builds"
21
+ "url": "git+https://github.com/jordanburke/ts-builds.git"
22
22
  },
23
23
  "main": "./prettier-config.js",
24
24
  "bin": {
25
- "ts-builds": "./dist/cli.js"
25
+ "ts-builds": "dist/cli.js"
26
26
  },
27
27
  "exports": {
28
28
  ".": "./prettier-config.js",
@@ -36,7 +36,6 @@
36
36
  "./package.json": "./package.json"
37
37
  },
38
38
  "files": [
39
- "templates",
40
39
  "prettier-config.js",
41
40
  ".prettierignore",
42
41
  "eslint.config.base.mjs",
@@ -51,10 +50,10 @@
51
50
  "@eslint/eslintrc": "^3.3.3",
52
51
  "@eslint/js": "^9.39.1",
53
52
  "@types/node": "^22.19.1",
54
- "@typescript-eslint/eslint-plugin": "^8.48.0",
55
- "@typescript-eslint/parser": "^8.48.0",
56
- "@vitest/coverage-v8": "^4.0.14",
57
- "@vitest/ui": "^4.0.14",
53
+ "@typescript-eslint/eslint-plugin": "^8.48.1",
54
+ "@typescript-eslint/parser": "^8.48.1",
55
+ "@vitest/coverage-v8": "^4.0.15",
56
+ "@vitest/ui": "^4.0.15",
58
57
  "cross-env": "^10.1.0",
59
58
  "eslint": "^9.39.1",
60
59
  "eslint-config-prettier": "^10.1.8",
@@ -62,11 +61,11 @@
62
61
  "eslint-plugin-prettier": "^5.5.4",
63
62
  "eslint-plugin-simple-import-sort": "^12.1.1",
64
63
  "globals": "^16.5.0",
65
- "prettier": "^3.7.3",
64
+ "prettier": "^3.7.4",
66
65
  "rimraf": "^6.1.2",
67
66
  "ts-node": "^10.9.2",
68
67
  "typescript": "^5.9.3",
69
- "vitest": "^4.0.14"
68
+ "vitest": "^4.0.15"
70
69
  },
71
70
  "devDependencies": {
72
71
  "tsdown": "^0.16.8"
@@ -80,11 +79,19 @@
80
79
  }
81
80
  },
82
81
  "scripts": {
83
- "format": "prettier --write .",
84
- "format:check": "prettier --check .",
85
- "build": "rimraf dist && tsdown",
86
- "test": "vitest run",
82
+ "bootstrap": "rimraf dist && tsdown",
83
+ "validate": "node dist/cli.js validate",
84
+ "validate:bootstrap": "pnpm bootstrap && pnpm validate",
85
+ "format": "node dist/cli.js format",
86
+ "format:check": "node dist/cli.js format:check",
87
+ "lint": "node dist/cli.js lint",
88
+ "lint:check": "node dist/cli.js lint:check",
89
+ "typecheck": "node dist/cli.js typecheck",
90
+ "test": "node dist/cli.js test",
87
91
  "test:watch": "vitest",
88
- "validate": "pnpm format && pnpm build && pnpm test"
89
- }
90
- }
92
+ "build": "node dist/cli.js build",
93
+ "dev": "node dist/cli.js dev",
94
+ "prepublishOnly": "pnpm validate:bootstrap"
95
+ },
96
+ "packageManager": "pnpm@10.24.0+sha512.01ff8ae71b4419903b65c60fb2dc9d34cf8bb6e06d03bde112ef38f7a34d6904c424ba66bea5cdcf12890230bf39f9580473140ed9c946fef328b6e5238a345a"
97
+ }
package/templates/npmrc DELETED
@@ -1,7 +0,0 @@
1
- # Hoist CLI tool binaries from peer dependencies
2
- public-hoist-pattern[]=*eslint*
3
- public-hoist-pattern[]=*prettier*
4
- public-hoist-pattern[]=*vitest*
5
- public-hoist-pattern[]=typescript
6
- public-hoist-pattern[]=*rimraf*
7
- public-hoist-pattern[]=*cross-env*