@raftlabs/raftstack 1.2.1 → 1.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.
package/dist/cli.js CHANGED
@@ -12,9 +12,111 @@ import * as p from "@clack/prompts";
12
12
  import pc from "picocolors";
13
13
 
14
14
  // src/utils/detect-project.ts
15
- import { existsSync } from "fs";
15
+ import { existsSync as existsSync2 } from "fs";
16
16
  import { readFile } from "fs/promises";
17
+ import { join as join2 } from "path";
18
+
19
+ // src/utils/detect-package-manager.ts
20
+ import { existsSync, readFileSync } from "fs";
17
21
  import { join } from "path";
22
+ var PACKAGE_MANAGERS = {
23
+ npm: {
24
+ name: "npm",
25
+ install: "npm install",
26
+ run: "npm run",
27
+ exec: "npx",
28
+ lockfile: "package-lock.json",
29
+ installFrozen: "npm ci",
30
+ needsSetupAction: false,
31
+ cacheKey: "npm-${{ hashFiles('**/package-lock.json') }}"
32
+ },
33
+ pnpm: {
34
+ name: "pnpm",
35
+ install: "pnpm install",
36
+ run: "pnpm",
37
+ exec: "pnpm dlx",
38
+ lockfile: "pnpm-lock.yaml",
39
+ installFrozen: "pnpm install --frozen-lockfile",
40
+ needsSetupAction: true,
41
+ cacheKey: "pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}"
42
+ },
43
+ yarn: {
44
+ name: "yarn",
45
+ install: "yarn install",
46
+ run: "yarn",
47
+ exec: "yarn",
48
+ lockfile: "yarn.lock",
49
+ installFrozen: "yarn install --frozen-lockfile",
50
+ needsSetupAction: false,
51
+ cacheKey: "yarn-${{ hashFiles('**/yarn.lock') }}"
52
+ },
53
+ "yarn-berry": {
54
+ name: "yarn-berry",
55
+ install: "yarn install",
56
+ run: "yarn",
57
+ exec: "yarn dlx",
58
+ lockfile: "yarn.lock",
59
+ installFrozen: "yarn install --immutable",
60
+ needsSetupAction: false,
61
+ cacheKey: "yarn-${{ hashFiles('**/yarn.lock') }}"
62
+ }
63
+ };
64
+ function detectYarnVersion(targetDir) {
65
+ const packageJsonPath = join(targetDir, "package.json");
66
+ if (!existsSync(packageJsonPath)) {
67
+ return null;
68
+ }
69
+ try {
70
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
71
+ const packageManager = packageJson.packageManager;
72
+ if (!packageManager) {
73
+ return null;
74
+ }
75
+ const match = packageManager.match(/^yarn@(\d+)\./);
76
+ if (match) {
77
+ const majorVersion = Number.parseInt(match[1], 10);
78
+ return majorVersion >= 2 ? "yarn-berry" : "yarn";
79
+ }
80
+ return null;
81
+ } catch {
82
+ return null;
83
+ }
84
+ }
85
+ function detectPackageManager(targetDir) {
86
+ if (existsSync(join(targetDir, "pnpm-lock.yaml"))) {
87
+ return PACKAGE_MANAGERS.pnpm;
88
+ }
89
+ if (existsSync(join(targetDir, "yarn.lock"))) {
90
+ const yarnVersion = detectYarnVersion(targetDir);
91
+ if (yarnVersion === "yarn-berry") {
92
+ return PACKAGE_MANAGERS["yarn-berry"];
93
+ }
94
+ return PACKAGE_MANAGERS.yarn;
95
+ }
96
+ if (existsSync(join(targetDir, "package-lock.json"))) {
97
+ return PACKAGE_MANAGERS.npm;
98
+ }
99
+ return null;
100
+ }
101
+ function getPackageManagerInfo(name) {
102
+ return PACKAGE_MANAGERS[name];
103
+ }
104
+ function getPackageManagerDescription(pm) {
105
+ switch (pm.name) {
106
+ case "npm":
107
+ return "npm (Node Package Manager)";
108
+ case "pnpm":
109
+ return "pnpm (Performant npm)";
110
+ case "yarn":
111
+ return "Yarn Classic (1.x)";
112
+ case "yarn-berry":
113
+ return "Yarn Berry (2+)";
114
+ default:
115
+ return pm.name;
116
+ }
117
+ }
118
+
119
+ // src/utils/detect-project.ts
18
120
  var INDICATORS = [
19
121
  { file: "nx.json", type: "nx", confidence: "high" },
20
122
  { file: "turbo.json", type: "turbo", confidence: "high" },
@@ -26,8 +128,8 @@ async function detectProjectType(targetDir = process.cwd()) {
26
128
  let detectedType = "single";
27
129
  let confidence = "low";
28
130
  for (const indicator of INDICATORS) {
29
- const filePath = join(targetDir, indicator.file);
30
- if (existsSync(filePath)) {
131
+ const filePath = join2(targetDir, indicator.file);
132
+ if (existsSync2(filePath)) {
31
133
  foundIndicators.push(indicator.file);
32
134
  if (confidence === "low" || confidence === "medium" && indicator.confidence === "high") {
33
135
  detectedType = indicator.type;
@@ -45,8 +147,8 @@ async function detectProjectType(targetDir = process.cwd()) {
45
147
  };
46
148
  }
47
149
  async function hasTypeScript(targetDir = process.cwd()) {
48
- const tsConfigPath = join(targetDir, "tsconfig.json");
49
- return existsSync(tsConfigPath);
150
+ const tsConfigPath = join2(targetDir, "tsconfig.json");
151
+ return existsSync2(tsConfigPath);
50
152
  }
51
153
  async function hasEslint(targetDir = process.cwd()) {
52
154
  const eslintFiles = [
@@ -61,13 +163,13 @@ async function hasEslint(targetDir = process.cwd()) {
61
163
  "eslint.config.cjs"
62
164
  ];
63
165
  for (const file of eslintFiles) {
64
- if (existsSync(join(targetDir, file))) {
166
+ if (existsSync2(join2(targetDir, file))) {
65
167
  return true;
66
168
  }
67
169
  }
68
170
  try {
69
- const pkgPath = join(targetDir, "package.json");
70
- if (existsSync(pkgPath)) {
171
+ const pkgPath = join2(targetDir, "package.json");
172
+ if (existsSync2(pkgPath)) {
71
173
  const content = await readFile(pkgPath, "utf-8");
72
174
  const pkg = JSON.parse(content);
73
175
  if (pkg.eslintConfig) {
@@ -92,13 +194,13 @@ async function hasPrettier(targetDir = process.cwd()) {
92
194
  "prettier.config.mjs"
93
195
  ];
94
196
  for (const file of prettierFiles) {
95
- if (existsSync(join(targetDir, file))) {
197
+ if (existsSync2(join2(targetDir, file))) {
96
198
  return true;
97
199
  }
98
200
  }
99
201
  try {
100
- const pkgPath = join(targetDir, "package.json");
101
- if (existsSync(pkgPath)) {
202
+ const pkgPath = join2(targetDir, "package.json");
203
+ if (existsSync2(pkgPath)) {
102
204
  const content = await readFile(pkgPath, "utf-8");
103
205
  const pkg = JSON.parse(content);
104
206
  if (pkg.prettier) {
@@ -241,11 +343,51 @@ async function promptCodeowners() {
241
343
  }
242
344
  return owners.split(",").map((u) => u.trim()).filter(Boolean).map((u) => u.startsWith("@") ? u : `@${u}`);
243
345
  }
346
+ async function promptPackageManager(targetDir) {
347
+ const detected = detectPackageManager(targetDir);
348
+ if (detected) {
349
+ const description = getPackageManagerDescription(detected);
350
+ p.log.info(`Detected ${pc.cyan(description)} from lockfile`);
351
+ return detected;
352
+ }
353
+ p.log.warn("No package manager lockfile detected");
354
+ const selected = await p.select({
355
+ message: "Select your package manager:",
356
+ options: [
357
+ {
358
+ value: "npm",
359
+ label: "npm",
360
+ hint: "Node Package Manager (default)"
361
+ },
362
+ {
363
+ value: "pnpm",
364
+ label: "pnpm",
365
+ hint: "Performant npm"
366
+ },
367
+ {
368
+ value: "yarn",
369
+ label: "Yarn Classic (1.x)",
370
+ hint: "Classic Yarn"
371
+ },
372
+ {
373
+ value: "yarn-berry",
374
+ label: "Yarn Berry (2+)",
375
+ hint: "Modern Yarn"
376
+ }
377
+ ]
378
+ });
379
+ if (p.isCancel(selected)) {
380
+ p.cancel("Setup cancelled.");
381
+ process.exit(0);
382
+ }
383
+ return getPackageManagerInfo(selected);
384
+ }
244
385
  async function promptConfirmation(config) {
245
386
  console.log();
246
387
  p.note(
247
388
  [
248
389
  `${pc.cyan("Project Type:")} ${getProjectTypeDescription(config.projectType)}`,
390
+ `${pc.cyan("Package Manager:")} ${getPackageManagerDescription(config.packageManager)}`,
249
391
  `${pc.cyan("TypeScript:")} ${config.usesTypeScript ? "Yes" : "No"}`,
250
392
  `${pc.cyan("ESLint:")} ${config.usesEslint ? "Yes" : "No"}`,
251
393
  `${pc.cyan("Prettier:")} ${config.usesPrettier ? "Yes" : "No"}`,
@@ -269,6 +411,7 @@ async function collectConfig(targetDir = process.cwd()) {
269
411
  showWelcome();
270
412
  const detection = await detectProjectType(targetDir);
271
413
  const projectType = await promptProjectType(detection);
414
+ const packageManager = await promptPackageManager(targetDir);
272
415
  const usesTypeScript = await hasTypeScript(targetDir);
273
416
  const usesEslint = await hasEslint(targetDir);
274
417
  const usesPrettier = await hasPrettier(targetDir);
@@ -277,6 +420,7 @@ async function collectConfig(targetDir = process.cwd()) {
277
420
  const codeowners = await promptCodeowners();
278
421
  const config = {
279
422
  projectType,
423
+ packageManager,
280
424
  asanaBaseUrl,
281
425
  aiReviewTool,
282
426
  codeowners,
@@ -293,10 +437,10 @@ async function collectConfig(targetDir = process.cwd()) {
293
437
  }
294
438
 
295
439
  // src/generators/husky.ts
296
- import { join as join3 } from "path";
440
+ import { join as join4 } from "path";
297
441
 
298
442
  // src/utils/file-system.ts
299
- import { existsSync as existsSync2 } from "fs";
443
+ import { existsSync as existsSync3 } from "fs";
300
444
  import {
301
445
  mkdir,
302
446
  readFile as readFile2,
@@ -304,14 +448,14 @@ import {
304
448
  copyFile,
305
449
  chmod
306
450
  } from "fs/promises";
307
- import { dirname, join as join2 } from "path";
451
+ import { dirname, join as join3 } from "path";
308
452
  async function ensureDir(dirPath) {
309
- if (!existsSync2(dirPath)) {
453
+ if (!existsSync3(dirPath)) {
310
454
  await mkdir(dirPath, { recursive: true });
311
455
  }
312
456
  }
313
457
  async function backupFile(filePath) {
314
- if (!existsSync2(filePath)) {
458
+ if (!existsSync3(filePath)) {
315
459
  return null;
316
460
  }
317
461
  const backupPath = `${filePath}.backup`;
@@ -320,7 +464,7 @@ async function backupFile(filePath) {
320
464
  }
321
465
  async function writeFileSafe(filePath, content, options = {}) {
322
466
  const { backup = true, overwrite = true, executable = false } = options;
323
- const exists = existsSync2(filePath);
467
+ const exists = existsSync3(filePath);
324
468
  if (exists && !overwrite) {
325
469
  return { created: false, backedUp: null };
326
470
  }
@@ -336,51 +480,51 @@ async function writeFileSafe(filePath, content, options = {}) {
336
480
  return { created: true, backedUp };
337
481
  }
338
482
  function fileExists(filePath) {
339
- return existsSync2(filePath);
483
+ return existsSync3(filePath);
340
484
  }
341
485
 
342
486
  // src/generators/husky.ts
343
- function getPreCommitHook(projectType) {
487
+ function getPreCommitHook(projectType, pm) {
344
488
  if (projectType === "nx") {
345
489
  return `#!/usr/bin/env sh
346
490
  . "$(dirname -- "$0")/_/husky.sh"
347
491
 
348
- npx lint-staged
492
+ ${pm.exec} lint-staged
349
493
  `;
350
494
  }
351
495
  return `#!/usr/bin/env sh
352
496
  . "$(dirname -- "$0")/_/husky.sh"
353
497
 
354
- npx lint-staged
498
+ ${pm.exec} lint-staged
355
499
  `;
356
500
  }
357
- function getCommitMsgHook() {
501
+ function getCommitMsgHook(pm) {
358
502
  return `#!/usr/bin/env sh
359
503
  . "$(dirname -- "$0")/_/husky.sh"
360
504
 
361
- npx --no -- commitlint --edit "$1"
505
+ ${pm.exec} --no -- commitlint --edit "$1"
362
506
  `;
363
507
  }
364
- function getPrePushHook() {
508
+ function getPrePushHook(pm) {
365
509
  return `#!/usr/bin/env sh
366
510
  . "$(dirname -- "$0")/_/husky.sh"
367
511
 
368
- npx validate-branch-name
512
+ ${pm.exec} validate-branch-name
369
513
  `;
370
514
  }
371
- async function generateHuskyHooks(targetDir, projectType) {
515
+ async function generateHuskyHooks(targetDir, projectType, pm) {
372
516
  const result = {
373
517
  created: [],
374
518
  modified: [],
375
519
  skipped: [],
376
520
  backedUp: []
377
521
  };
378
- const huskyDir = join3(targetDir, ".husky");
522
+ const huskyDir = join4(targetDir, ".husky");
379
523
  await ensureDir(huskyDir);
380
- const preCommitPath = join3(huskyDir, "pre-commit");
524
+ const preCommitPath = join4(huskyDir, "pre-commit");
381
525
  const preCommitResult = await writeFileSafe(
382
526
  preCommitPath,
383
- getPreCommitHook(projectType),
527
+ getPreCommitHook(projectType, pm),
384
528
  { executable: true, backup: true }
385
529
  );
386
530
  if (preCommitResult.created) {
@@ -389,10 +533,10 @@ async function generateHuskyHooks(targetDir, projectType) {
389
533
  result.backedUp.push(preCommitResult.backedUp);
390
534
  }
391
535
  }
392
- const commitMsgPath = join3(huskyDir, "commit-msg");
536
+ const commitMsgPath = join4(huskyDir, "commit-msg");
393
537
  const commitMsgResult = await writeFileSafe(
394
538
  commitMsgPath,
395
- getCommitMsgHook(),
539
+ getCommitMsgHook(pm),
396
540
  { executable: true, backup: true }
397
541
  );
398
542
  if (commitMsgResult.created) {
@@ -401,8 +545,8 @@ async function generateHuskyHooks(targetDir, projectType) {
401
545
  result.backedUp.push(commitMsgResult.backedUp);
402
546
  }
403
547
  }
404
- const prePushPath = join3(huskyDir, "pre-push");
405
- const prePushResult = await writeFileSafe(prePushPath, getPrePushHook(), {
548
+ const prePushPath = join4(huskyDir, "pre-push");
549
+ const prePushResult = await writeFileSafe(prePushPath, getPrePushHook(pm), {
406
550
  executable: true,
407
551
  backup: true
408
552
  });
@@ -416,7 +560,7 @@ async function generateHuskyHooks(targetDir, projectType) {
416
560
  }
417
561
 
418
562
  // src/generators/commitlint.ts
419
- import { join as join4 } from "path";
563
+ import { join as join5 } from "path";
420
564
  function getCommitlintConfig(asanaBaseUrl) {
421
565
  const baseConfig = `// @ts-check
422
566
 
@@ -492,7 +636,7 @@ async function generateCommitlint(targetDir, asanaBaseUrl) {
492
636
  skipped: [],
493
637
  backedUp: []
494
638
  };
495
- const configPath = join4(targetDir, "commitlint.config.js");
639
+ const configPath = join5(targetDir, "commitlint.config.js");
496
640
  const writeResult = await writeFileSafe(
497
641
  configPath,
498
642
  getCommitlintConfig(asanaBaseUrl),
@@ -508,7 +652,7 @@ async function generateCommitlint(targetDir, asanaBaseUrl) {
508
652
  }
509
653
 
510
654
  // src/generators/cz-git.ts
511
- import { join as join5 } from "path";
655
+ import { join as join6 } from "path";
512
656
  function getCzGitConfig(asanaBaseUrl) {
513
657
  const asanaSection = asanaBaseUrl ? `
514
658
  // Asana task reference settings
@@ -591,7 +735,7 @@ async function generateCzGit(targetDir, asanaBaseUrl) {
591
735
  skipped: [],
592
736
  backedUp: []
593
737
  };
594
- const configPath = join5(targetDir, ".czrc");
738
+ const configPath = join6(targetDir, ".czrc");
595
739
  const writeResult = await writeFileSafe(
596
740
  configPath,
597
741
  JSON.stringify({ path: "node_modules/cz-git" }, null, 2) + "\n",
@@ -603,7 +747,7 @@ async function generateCzGit(targetDir, asanaBaseUrl) {
603
747
  result.backedUp.push(writeResult.backedUp);
604
748
  }
605
749
  }
606
- const czConfigPath = join5(targetDir, "cz.config.js");
750
+ const czConfigPath = join6(targetDir, "cz.config.js");
607
751
  const czConfigResult = await writeFileSafe(
608
752
  czConfigPath,
609
753
  getCzGitConfig(asanaBaseUrl),
@@ -619,7 +763,7 @@ async function generateCzGit(targetDir, asanaBaseUrl) {
619
763
  }
620
764
 
621
765
  // src/generators/lint-staged.ts
622
- import { join as join6 } from "path";
766
+ import { join as join7 } from "path";
623
767
  function getLintStagedConfig(projectType, usesEslint, usesPrettier, usesTypeScript) {
624
768
  const rules = {};
625
769
  if (usesTypeScript) {
@@ -684,7 +828,7 @@ async function generateLintStaged(targetDir, projectType, usesEslint, usesPretti
684
828
  skipped: [],
685
829
  backedUp: []
686
830
  };
687
- const configPath = join6(targetDir, ".lintstagedrc.js");
831
+ const configPath = join7(targetDir, ".lintstagedrc.js");
688
832
  const writeResult = await writeFileSafe(
689
833
  configPath,
690
834
  getLintStagedConfig(projectType, usesEslint, usesPrettier, usesTypeScript),
@@ -701,18 +845,18 @@ async function generateLintStaged(targetDir, projectType, usesEslint, usesPretti
701
845
 
702
846
  // src/utils/package-json.ts
703
847
  import { readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
704
- import { existsSync as existsSync3 } from "fs";
705
- import { join as join7 } from "path";
848
+ import { existsSync as existsSync4 } from "fs";
849
+ import { join as join8 } from "path";
706
850
  async function readPackageJson(targetDir = process.cwd()) {
707
- const pkgPath = join7(targetDir, "package.json");
708
- if (!existsSync3(pkgPath)) {
851
+ const pkgPath = join8(targetDir, "package.json");
852
+ if (!existsSync4(pkgPath)) {
709
853
  throw new Error(`No package.json found in ${targetDir}`);
710
854
  }
711
855
  const content = await readFile3(pkgPath, "utf-8");
712
856
  return JSON.parse(content);
713
857
  }
714
858
  async function writePackageJson(pkg, targetDir = process.cwd()) {
715
- const pkgPath = join7(targetDir, "package.json");
859
+ const pkgPath = join8(targetDir, "package.json");
716
860
  const content = JSON.stringify(pkg, null, 2) + "\n";
717
861
  await writeFile2(pkgPath, content, "utf-8");
718
862
  }
@@ -795,7 +939,7 @@ async function generateBranchValidation(targetDir) {
795
939
  }
796
940
 
797
941
  // src/generators/pr-template.ts
798
- import { join as join8 } from "path";
942
+ import { join as join9 } from "path";
799
943
  function getPRTemplate(hasAsana) {
800
944
  const asanaSection = hasAsana ? `## Asana Task
801
945
  <!-- Link to the Asana task -->
@@ -847,9 +991,9 @@ async function generatePRTemplate(targetDir, hasAsana) {
847
991
  skipped: [],
848
992
  backedUp: []
849
993
  };
850
- const githubDir = join8(targetDir, ".github");
994
+ const githubDir = join9(targetDir, ".github");
851
995
  await ensureDir(githubDir);
852
- const templatePath = join8(githubDir, "PULL_REQUEST_TEMPLATE.md");
996
+ const templatePath = join9(githubDir, "PULL_REQUEST_TEMPLATE.md");
853
997
  const writeResult = await writeFileSafe(
854
998
  templatePath,
855
999
  getPRTemplate(hasAsana),
@@ -865,8 +1009,8 @@ async function generatePRTemplate(targetDir, hasAsana) {
865
1009
  }
866
1010
 
867
1011
  // src/generators/github-workflows.ts
868
- import { join as join9 } from "path";
869
- function getPRChecksWorkflow(projectType, usesTypeScript, usesEslint) {
1012
+ import { join as join10 } from "path";
1013
+ function getPRChecksWorkflow(projectType, usesTypeScript, usesEslint, pm) {
870
1014
  const steps = [];
871
1015
  steps.push(` - name: Checkout
872
1016
  uses: actions/checkout@v4`);
@@ -875,49 +1019,51 @@ function getPRChecksWorkflow(projectType, usesTypeScript, usesEslint) {
875
1019
  uses: actions/setup-node@v4
876
1020
  with:
877
1021
  node-version: '20'`);
878
- steps.push(`
1022
+ if (pm.needsSetupAction) {
1023
+ steps.push(`
879
1024
  - name: Setup pnpm
880
1025
  uses: pnpm/action-setup@v3
881
1026
  with:
882
1027
  version: 9`);
1028
+ }
883
1029
  steps.push(`
884
1030
  - name: Install dependencies
885
- run: pnpm install --frozen-lockfile`);
1031
+ run: ${pm.installFrozen}`);
886
1032
  if (usesTypeScript) {
887
1033
  steps.push(`
888
1034
  - name: Type check
889
- run: pnpm typecheck`);
1035
+ run: ${pm.run} typecheck`);
890
1036
  }
891
1037
  if (usesEslint) {
892
1038
  steps.push(`
893
1039
  - name: Lint
894
- run: pnpm lint`);
1040
+ run: ${pm.run} lint`);
895
1041
  }
896
1042
  if (projectType === "nx") {
897
1043
  steps.push(`
898
1044
  - name: Build
899
- run: pnpm nx affected --target=build --parallel=3`);
1045
+ run: ${pm.run} nx affected --target=build --parallel=3`);
900
1046
  } else if (projectType === "turbo") {
901
1047
  steps.push(`
902
1048
  - name: Build
903
- run: pnpm turbo build`);
1049
+ run: ${pm.run} turbo build`);
904
1050
  } else {
905
1051
  steps.push(`
906
1052
  - name: Build
907
- run: pnpm build`);
1053
+ run: ${pm.run} build`);
908
1054
  }
909
1055
  if (projectType === "nx") {
910
1056
  steps.push(`
911
1057
  - name: Test
912
- run: pnpm nx affected --target=test --parallel=3`);
1058
+ run: ${pm.run} nx affected --target=test --parallel=3`);
913
1059
  } else if (projectType === "turbo") {
914
1060
  steps.push(`
915
1061
  - name: Test
916
- run: pnpm turbo test`);
1062
+ run: ${pm.run} turbo test`);
917
1063
  } else {
918
1064
  steps.push(`
919
1065
  - name: Test
920
- run: pnpm test`);
1066
+ run: ${pm.run} test`);
921
1067
  }
922
1068
  return `name: PR Checks
923
1069
 
@@ -937,19 +1083,19 @@ jobs:
937
1083
  ${steps.join("\n")}
938
1084
  `;
939
1085
  }
940
- async function generateGitHubWorkflows(targetDir, projectType, usesTypeScript, usesEslint) {
1086
+ async function generateGitHubWorkflows(targetDir, projectType, usesTypeScript, usesEslint, pm) {
941
1087
  const result = {
942
1088
  created: [],
943
1089
  modified: [],
944
1090
  skipped: [],
945
1091
  backedUp: []
946
1092
  };
947
- const workflowsDir = join9(targetDir, ".github", "workflows");
1093
+ const workflowsDir = join10(targetDir, ".github", "workflows");
948
1094
  await ensureDir(workflowsDir);
949
- const prChecksPath = join9(workflowsDir, "pr-checks.yml");
1095
+ const prChecksPath = join10(workflowsDir, "pr-checks.yml");
950
1096
  const writeResult = await writeFileSafe(
951
1097
  prChecksPath,
952
- getPRChecksWorkflow(projectType, usesTypeScript, usesEslint),
1098
+ getPRChecksWorkflow(projectType, usesTypeScript, usesEslint, pm),
953
1099
  { backup: true }
954
1100
  );
955
1101
  if (writeResult.created) {
@@ -962,7 +1108,7 @@ async function generateGitHubWorkflows(targetDir, projectType, usesTypeScript, u
962
1108
  }
963
1109
 
964
1110
  // src/generators/codeowners.ts
965
- import { join as join10 } from "path";
1111
+ import { join as join11 } from "path";
966
1112
  function getCodeownersContent(owners) {
967
1113
  if (owners.length === 0) {
968
1114
  return `# CODEOWNERS file
@@ -994,9 +1140,9 @@ async function generateCodeowners(targetDir, owners) {
994
1140
  skipped: [],
995
1141
  backedUp: []
996
1142
  };
997
- const githubDir = join10(targetDir, ".github");
1143
+ const githubDir = join11(targetDir, ".github");
998
1144
  await ensureDir(githubDir);
999
- const codeownersPath = join10(githubDir, "CODEOWNERS");
1145
+ const codeownersPath = join11(githubDir, "CODEOWNERS");
1000
1146
  const writeResult = await writeFileSafe(
1001
1147
  codeownersPath,
1002
1148
  getCodeownersContent(owners),
@@ -1012,7 +1158,7 @@ async function generateCodeowners(targetDir, owners) {
1012
1158
  }
1013
1159
 
1014
1160
  // src/generators/ai-review.ts
1015
- import { join as join11 } from "path";
1161
+ import { join as join12 } from "path";
1016
1162
  function getCodeRabbitConfig() {
1017
1163
  return `# CodeRabbit Configuration
1018
1164
  # Learn more: https://docs.coderabbit.ai/guides/configure-coderabbit
@@ -1076,7 +1222,7 @@ async function generateAIReview(targetDir, tool) {
1076
1222
  return result;
1077
1223
  }
1078
1224
  if (tool === "coderabbit") {
1079
- const configPath = join11(targetDir, ".coderabbit.yaml");
1225
+ const configPath = join12(targetDir, ".coderabbit.yaml");
1080
1226
  const writeResult = await writeFileSafe(configPath, getCodeRabbitConfig(), {
1081
1227
  backup: true
1082
1228
  });
@@ -1088,9 +1234,9 @@ async function generateAIReview(targetDir, tool) {
1088
1234
  }
1089
1235
  }
1090
1236
  if (tool === "copilot") {
1091
- const workflowsDir = join11(targetDir, ".github", "workflows");
1237
+ const workflowsDir = join12(targetDir, ".github", "workflows");
1092
1238
  await ensureDir(workflowsDir);
1093
- const workflowPath = join11(workflowsDir, "copilot-review.yml");
1239
+ const workflowPath = join12(workflowsDir, "copilot-review.yml");
1094
1240
  const writeResult = await writeFileSafe(workflowPath, getCopilotWorkflow(), {
1095
1241
  backup: true
1096
1242
  });
@@ -1105,7 +1251,7 @@ async function generateAIReview(targetDir, tool) {
1105
1251
  }
1106
1252
 
1107
1253
  // src/generators/branch-protection.ts
1108
- import { join as join12 } from "path";
1254
+ import { join as join13 } from "path";
1109
1255
  function getBranchProtectionDocs() {
1110
1256
  return `# Branch Protection Setup Guide
1111
1257
 
@@ -1222,9 +1368,9 @@ async function generateBranchProtectionDocs(targetDir) {
1222
1368
  skipped: [],
1223
1369
  backedUp: []
1224
1370
  };
1225
- const docsDir = join12(targetDir, ".github");
1371
+ const docsDir = join13(targetDir, ".github");
1226
1372
  await ensureDir(docsDir);
1227
- const docsPath = join12(docsDir, "BRANCH_PROTECTION_SETUP.md");
1373
+ const docsPath = join13(docsDir, "BRANCH_PROTECTION_SETUP.md");
1228
1374
  const writeResult = await writeFileSafe(docsPath, getBranchProtectionDocs(), {
1229
1375
  backup: true
1230
1376
  });
@@ -1238,8 +1384,8 @@ async function generateBranchProtectionDocs(targetDir) {
1238
1384
  }
1239
1385
 
1240
1386
  // src/generators/contributing.ts
1241
- import { join as join13 } from "path";
1242
- function getContributingContent(hasAsana) {
1387
+ import { join as join14 } from "path";
1388
+ function getContributingContent(hasAsana, pm) {
1243
1389
  const asanaSection = hasAsana ? `
1244
1390
  ## Linking to Asana
1245
1391
 
@@ -1264,7 +1410,7 @@ Thank you for your interest in contributing! This document outlines our developm
1264
1410
  ## Getting Started
1265
1411
 
1266
1412
  1. Clone the repository
1267
- 2. Install dependencies: \`pnpm install\`
1413
+ 2. Install dependencies: \`${pm.install}\`
1268
1414
  3. Create a new branch following our naming convention
1269
1415
 
1270
1416
  ## Branch Naming Convention
@@ -1289,7 +1435,7 @@ We use structured branch names to keep our repository organized:
1289
1435
  We follow [Conventional Commits](https://www.conventionalcommits.org/). Use the interactive commit tool:
1290
1436
 
1291
1437
  \`\`\`bash
1292
- pnpm commit
1438
+ ${pm.run} commit
1293
1439
  \`\`\`
1294
1440
 
1295
1441
  ### Commit Types
@@ -1370,17 +1516,17 @@ Before committing, the following checks run automatically:
1370
1516
  If you have questions, please open an issue or reach out to the maintainers.
1371
1517
  `;
1372
1518
  }
1373
- async function generateContributing(targetDir, hasAsana) {
1519
+ async function generateContributing(targetDir, hasAsana, pm) {
1374
1520
  const result = {
1375
1521
  created: [],
1376
1522
  modified: [],
1377
1523
  skipped: [],
1378
1524
  backedUp: []
1379
1525
  };
1380
- const contributingPath = join13(targetDir, "CONTRIBUTING.md");
1526
+ const contributingPath = join14(targetDir, "CONTRIBUTING.md");
1381
1527
  const writeResult = await writeFileSafe(
1382
1528
  contributingPath,
1383
- getContributingContent(hasAsana),
1529
+ getContributingContent(hasAsana, pm),
1384
1530
  { backup: true }
1385
1531
  );
1386
1532
  if (writeResult.created) {
@@ -1393,7 +1539,7 @@ async function generateContributing(targetDir, hasAsana) {
1393
1539
  }
1394
1540
 
1395
1541
  // src/generators/prettier.ts
1396
- import { join as join14 } from "path";
1542
+ import { join as join15 } from "path";
1397
1543
  function getPrettierConfig() {
1398
1544
  return JSON.stringify(
1399
1545
  {
@@ -1451,7 +1597,7 @@ function hasPrettierConfig(targetDir) {
1451
1597
  "prettier.config.cjs",
1452
1598
  "prettier.config.mjs"
1453
1599
  ];
1454
- return prettierFiles.some((file) => fileExists(join14(targetDir, file)));
1600
+ return prettierFiles.some((file) => fileExists(join15(targetDir, file)));
1455
1601
  }
1456
1602
  async function generatePrettier(targetDir) {
1457
1603
  const result = {
@@ -1464,7 +1610,7 @@ async function generatePrettier(targetDir) {
1464
1610
  result.skipped.push(".prettierrc (already exists)");
1465
1611
  return result;
1466
1612
  }
1467
- const configPath = join14(targetDir, ".prettierrc");
1613
+ const configPath = join15(targetDir, ".prettierrc");
1468
1614
  const configResult = await writeFileSafe(configPath, getPrettierConfig(), {
1469
1615
  backup: true
1470
1616
  });
@@ -1474,7 +1620,7 @@ async function generatePrettier(targetDir) {
1474
1620
  result.backedUp.push(configResult.backedUp);
1475
1621
  }
1476
1622
  }
1477
- const ignorePath = join14(targetDir, ".prettierignore");
1623
+ const ignorePath = join15(targetDir, ".prettierignore");
1478
1624
  const ignoreResult = await writeFileSafe(ignorePath, getPrettierIgnore(), {
1479
1625
  backup: true,
1480
1626
  overwrite: false
@@ -1492,26 +1638,26 @@ async function generatePrettier(targetDir) {
1492
1638
  }
1493
1639
 
1494
1640
  // src/generators/claude-skills.ts
1495
- import { existsSync as existsSync4 } from "fs";
1641
+ import { existsSync as existsSync5 } from "fs";
1496
1642
  import { readdir, copyFile as copyFile2 } from "fs/promises";
1497
- import { join as join15, dirname as dirname2 } from "path";
1643
+ import { join as join16, dirname as dirname2 } from "path";
1498
1644
  import { fileURLToPath } from "url";
1499
1645
  function getPackageSkillsDir() {
1500
1646
  const currentFilePath = fileURLToPath(import.meta.url);
1501
- const packageRoot = join15(dirname2(currentFilePath), "..");
1502
- return join15(packageRoot, ".claude", "skills");
1647
+ const packageRoot = join16(dirname2(currentFilePath), "..");
1648
+ return join16(packageRoot, ".claude", "skills");
1503
1649
  }
1504
1650
  async function copyDirectory(srcDir, destDir, result, baseDir) {
1505
1651
  await ensureDir(destDir);
1506
1652
  const entries = await readdir(srcDir, { withFileTypes: true });
1507
1653
  for (const entry of entries) {
1508
- const srcPath = join15(srcDir, entry.name);
1509
- const destPath = join15(destDir, entry.name);
1654
+ const srcPath = join16(srcDir, entry.name);
1655
+ const destPath = join16(destDir, entry.name);
1510
1656
  const relativePath = destPath.replace(baseDir + "/", "");
1511
1657
  if (entry.isDirectory()) {
1512
1658
  await copyDirectory(srcPath, destPath, result, baseDir);
1513
1659
  } else {
1514
- if (existsSync4(destPath)) {
1660
+ if (existsSync5(destPath)) {
1515
1661
  const backupPath = await backupFile(destPath);
1516
1662
  if (backupPath) {
1517
1663
  result.backedUp.push(relativePath);
@@ -1530,33 +1676,33 @@ async function generateClaudeSkills(targetDir) {
1530
1676
  backedUp: []
1531
1677
  };
1532
1678
  const packageSkillsDir = getPackageSkillsDir();
1533
- const targetSkillsDir = join15(targetDir, ".claude", "skills");
1534
- if (!existsSync4(packageSkillsDir)) {
1679
+ const targetSkillsDir = join16(targetDir, ".claude", "skills");
1680
+ if (!existsSync5(packageSkillsDir)) {
1535
1681
  console.warn(
1536
1682
  "Warning: Skills directory not found in package. Skipping skills generation."
1537
1683
  );
1538
1684
  return result;
1539
1685
  }
1540
- await ensureDir(join15(targetDir, ".claude"));
1686
+ await ensureDir(join16(targetDir, ".claude"));
1541
1687
  await copyDirectory(packageSkillsDir, targetSkillsDir, result, targetDir);
1542
1688
  return result;
1543
1689
  }
1544
1690
 
1545
1691
  // src/generators/eslint.ts
1546
- import { existsSync as existsSync5 } from "fs";
1692
+ import { existsSync as existsSync6 } from "fs";
1547
1693
  import { readFile as readFile4 } from "fs/promises";
1548
- import { join as join16 } from "path";
1694
+ import { join as join17 } from "path";
1549
1695
 
1550
1696
  // src/generators/quick-reference.ts
1551
- import { join as join17 } from "path";
1552
- async function generateQuickReference(targetDir) {
1697
+ import { join as join18 } from "path";
1698
+ async function generateQuickReference(targetDir, pm) {
1553
1699
  const result = {
1554
1700
  created: [],
1555
1701
  modified: [],
1556
1702
  skipped: [],
1557
1703
  backedUp: []
1558
1704
  };
1559
- const quickRefPath = join17(targetDir, ".github", "QUICK_REFERENCE.md");
1705
+ const quickRefPath = join18(targetDir, ".github", "QUICK_REFERENCE.md");
1560
1706
  const content = `# RaftStack Quick Reference
1561
1707
 
1562
1708
  > One-page guide for the RaftStack Git workflow
@@ -1578,7 +1724,7 @@ git checkout -b hotfix/payment-timeout
1578
1724
 
1579
1725
  \`\`\`bash
1580
1726
  # Use the interactive commit tool
1581
- pnpm commit
1727
+ ${pm.run} commit
1582
1728
  \`\`\`
1583
1729
 
1584
1730
  This will prompt you for:
@@ -1641,16 +1787,16 @@ Before submitting:
1641
1787
 
1642
1788
  \`\`\`bash
1643
1789
  # Interactive commit
1644
- pnpm commit
1790
+ ${pm.run} commit
1645
1791
 
1646
1792
  # Check compliance metrics
1647
- pnpm dlx @raftlabs/raftstack metrics
1793
+ ${pm.exec} @raftlabs/raftstack metrics
1648
1794
 
1649
1795
  # Run linting
1650
- pnpm lint
1796
+ ${pm.run} lint
1651
1797
 
1652
1798
  # Run tests
1653
- pnpm test
1799
+ ${pm.run} test
1654
1800
  \`\`\`
1655
1801
 
1656
1802
  ---
@@ -1691,10 +1837,10 @@ pnpm test
1691
1837
 
1692
1838
  // src/utils/git.ts
1693
1839
  import { execa } from "execa";
1694
- import { existsSync as existsSync6 } from "fs";
1695
- import { join as join18 } from "path";
1840
+ import { existsSync as existsSync7 } from "fs";
1841
+ import { join as join19 } from "path";
1696
1842
  async function isGitRepo(targetDir = process.cwd()) {
1697
- if (existsSync6(join18(targetDir, ".git"))) {
1843
+ if (existsSync7(join19(targetDir, ".git"))) {
1698
1844
  return true;
1699
1845
  }
1700
1846
  try {
@@ -1786,7 +1932,9 @@ async function runInit(targetDir = process.cwd()) {
1786
1932
  spinner4.start("Generating configuration files...");
1787
1933
  const results = [];
1788
1934
  try {
1789
- results.push(await generateHuskyHooks(targetDir, config.projectType));
1935
+ results.push(
1936
+ await generateHuskyHooks(targetDir, config.projectType, config.packageManager)
1937
+ );
1790
1938
  results.push(await generateCommitlint(targetDir, config.asanaBaseUrl));
1791
1939
  results.push(await generateCzGit(targetDir, config.asanaBaseUrl));
1792
1940
  results.push(
@@ -1808,14 +1956,17 @@ async function runInit(targetDir = process.cwd()) {
1808
1956
  targetDir,
1809
1957
  config.projectType,
1810
1958
  config.usesTypeScript,
1811
- config.usesEslint
1959
+ config.usesEslint,
1960
+ config.packageManager
1812
1961
  )
1813
1962
  );
1814
1963
  results.push(await generateCodeowners(targetDir, config.codeowners));
1815
1964
  results.push(await generateAIReview(targetDir, config.aiReviewTool));
1816
1965
  results.push(await generateBranchProtectionDocs(targetDir));
1817
- results.push(await generateContributing(targetDir, !!config.asanaBaseUrl));
1818
- results.push(await generateQuickReference(targetDir));
1966
+ results.push(
1967
+ await generateContributing(targetDir, !!config.asanaBaseUrl, config.packageManager)
1968
+ );
1969
+ results.push(await generateQuickReference(targetDir, config.packageManager));
1819
1970
  results.push(await generateClaudeSkills(targetDir));
1820
1971
  results.push(await updateProjectPackageJson(targetDir, config));
1821
1972
  spinner4.stop("Configuration files generated!");
@@ -1860,9 +2011,9 @@ async function runInit(targetDir = process.cwd()) {
1860
2011
  console.log();
1861
2012
  p2.note(
1862
2013
  [
1863
- `${pc2.cyan("1.")} Run ${pc2.yellow("pnpm install")} to install dependencies`,
2014
+ `${pc2.cyan("1.")} Run ${pc2.yellow(config.packageManager.install)} to install dependencies`,
1864
2015
  `${pc2.cyan("2.")} Review the generated configuration files`,
1865
- `${pc2.cyan("3.")} Use ${pc2.yellow("pnpm commit")} for interactive commits`,
2016
+ `${pc2.cyan("3.")} Use ${pc2.yellow(`${config.packageManager.run} commit`)} for interactive commits`,
1866
2017
  `${pc2.cyan("4.")} Set up branch protection rules (see .github/BRANCH_PROTECTION_SETUP.md)`
1867
2018
  ].join("\n"),
1868
2019
  "Next Steps"
@@ -2272,11 +2423,90 @@ ${pc4.bold("Branches")}
2272
2423
  }
2273
2424
  }
2274
2425
 
2426
+ // package.json
2427
+ var package_default = {
2428
+ name: "@raftlabs/raftstack",
2429
+ version: "1.4.0",
2430
+ description: "CLI tool for setting up Git hooks, commit conventions, and GitHub integration",
2431
+ type: "module",
2432
+ main: "./dist/index.js",
2433
+ types: "./dist/index.d.ts",
2434
+ bin: {
2435
+ raftstack: "./dist/cli.js"
2436
+ },
2437
+ exports: {
2438
+ ".": {
2439
+ types: "./dist/index.d.ts",
2440
+ import: "./dist/index.js"
2441
+ }
2442
+ },
2443
+ files: [
2444
+ "dist",
2445
+ "templates",
2446
+ ".claude/skills"
2447
+ ],
2448
+ scripts: {
2449
+ build: "tsup",
2450
+ dev: "tsup --watch",
2451
+ typecheck: "tsc --noEmit",
2452
+ test: "vitest",
2453
+ "test:run": "vitest run",
2454
+ prepublishOnly: "pnpm build",
2455
+ prepublish: "pnpm test:run && pnpm build",
2456
+ "pack:test": "pnpm pack --dry-run",
2457
+ "publish:test": "pnpm pack && tar -xvzf raftlabs-raftstack-*.tgz && rm -rf package raftlabs-raftstack-*.tgz",
2458
+ release: "standard-version",
2459
+ "release:patch": "standard-version --release-as patch",
2460
+ "release:minor": "standard-version --release-as minor",
2461
+ "release:major": "standard-version --release-as major",
2462
+ "release:first": "standard-version --first-release"
2463
+ },
2464
+ keywords: [
2465
+ "cli",
2466
+ "git-hooks",
2467
+ "husky",
2468
+ "commitlint",
2469
+ "lint-staged",
2470
+ "developer-experience"
2471
+ ],
2472
+ author: "Aravind Jaimon <dev@aravindjaimon.com>",
2473
+ license: "MIT",
2474
+ repository: {
2475
+ type: "git",
2476
+ url: "git+https://github.com/Raft-Labs/raftstack.git"
2477
+ },
2478
+ homepage: "https://github.com/Raft-Labs/raftstack#readme",
2479
+ bugs: {
2480
+ url: "https://github.com/Raft-Labs/raftstack/issues"
2481
+ },
2482
+ publishConfig: {
2483
+ access: "public",
2484
+ registry: "https://registry.npmjs.org/"
2485
+ },
2486
+ packageManager: "pnpm@10.23.0",
2487
+ engines: {
2488
+ node: ">=18"
2489
+ },
2490
+ devDependencies: {
2491
+ "@types/node": "^20.10.0",
2492
+ "standard-version": "^9.5.0",
2493
+ tsup: "^8.0.0",
2494
+ typescript: "^5.3.0",
2495
+ vitest: "^1.0.0"
2496
+ },
2497
+ dependencies: {
2498
+ "@clack/prompts": "^0.7.0",
2499
+ commander: "^12.0.0",
2500
+ execa: "^8.0.0",
2501
+ picocolors: "^1.0.0"
2502
+ }
2503
+ };
2504
+
2275
2505
  // src/cli.ts
2276
2506
  var program = new Command();
2277
2507
  program.name("raftstack").description(
2278
2508
  "CLI tool for setting up Git hooks, commit conventions, and GitHub integration"
2279
- ).version("1.1.0");
2509
+ ).version(package_default.version);
2280
2510
  program.command("init").description("Initialize RaftStack configuration in your project").action(async () => {
2281
2511
  await runInit(process.cwd());
2282
2512
  });