@raftlabs/raftstack 1.4.2 → 1.6.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
@@ -27,6 +27,7 @@ var PACKAGE_MANAGERS = {
27
27
  exec: "npx",
28
28
  lockfile: "package-lock.json",
29
29
  installFrozen: "npm ci",
30
+ addDev: "install -D",
30
31
  needsSetupAction: false,
31
32
  cacheKey: "npm-${{ hashFiles('**/package-lock.json') }}"
32
33
  },
@@ -37,6 +38,7 @@ var PACKAGE_MANAGERS = {
37
38
  exec: "pnpm dlx",
38
39
  lockfile: "pnpm-lock.yaml",
39
40
  installFrozen: "pnpm install --frozen-lockfile",
41
+ addDev: "add -D",
40
42
  needsSetupAction: true,
41
43
  cacheKey: "pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}"
42
44
  },
@@ -47,6 +49,7 @@ var PACKAGE_MANAGERS = {
47
49
  exec: "yarn",
48
50
  lockfile: "yarn.lock",
49
51
  installFrozen: "yarn install --frozen-lockfile",
52
+ addDev: "add -D",
50
53
  needsSetupAction: false,
51
54
  cacheKey: "yarn-${{ hashFiles('**/yarn.lock') }}"
52
55
  },
@@ -57,6 +60,7 @@ var PACKAGE_MANAGERS = {
57
60
  exec: "yarn dlx",
58
61
  lockfile: "yarn.lock",
59
62
  installFrozen: "yarn install --immutable",
63
+ addDev: "add -D",
60
64
  needsSetupAction: false,
61
65
  cacheKey: "yarn-${{ hashFiles('**/yarn.lock') }}"
62
66
  }
@@ -484,35 +488,19 @@ function fileExists(filePath) {
484
488
  }
485
489
 
486
490
  // src/generators/husky.ts
487
- function getPreCommitHook(projectType, pm) {
488
- if (projectType === "nx") {
489
- return `#!/usr/bin/env sh
490
- . "$(dirname -- "$0")/_/husky.sh"
491
-
492
- ${pm.exec} lint-staged
493
- `;
494
- }
495
- return `#!/usr/bin/env sh
496
- . "$(dirname -- "$0")/_/husky.sh"
497
-
498
- ${pm.exec} lint-staged
491
+ function getPreCommitHook(_projectType) {
492
+ return `lint-staged
499
493
  `;
500
494
  }
501
- function getCommitMsgHook(pm) {
502
- return `#!/usr/bin/env sh
503
- . "$(dirname -- "$0")/_/husky.sh"
504
-
505
- ${pm.exec} --no -- commitlint --edit "$1"
495
+ function getCommitMsgHook() {
496
+ return `commitlint --edit "$1"
506
497
  `;
507
498
  }
508
- function getPrePushHook(pm) {
509
- return `#!/usr/bin/env sh
510
- . "$(dirname -- "$0")/_/husky.sh"
511
-
512
- ${pm.exec} validate-branch-name
499
+ function getPrePushHook() {
500
+ return `validate-branch-name
513
501
  `;
514
502
  }
515
- async function generateHuskyHooks(targetDir, projectType, pm) {
503
+ async function generateHuskyHooks(targetDir, projectType, _pm) {
516
504
  const result = {
517
505
  created: [],
518
506
  modified: [],
@@ -524,7 +512,7 @@ async function generateHuskyHooks(targetDir, projectType, pm) {
524
512
  const preCommitPath = join4(huskyDir, "pre-commit");
525
513
  const preCommitResult = await writeFileSafe(
526
514
  preCommitPath,
527
- getPreCommitHook(projectType, pm),
515
+ getPreCommitHook(projectType),
528
516
  { executable: true, backup: true }
529
517
  );
530
518
  if (preCommitResult.created) {
@@ -536,7 +524,7 @@ async function generateHuskyHooks(targetDir, projectType, pm) {
536
524
  const commitMsgPath = join4(huskyDir, "commit-msg");
537
525
  const commitMsgResult = await writeFileSafe(
538
526
  commitMsgPath,
539
- getCommitMsgHook(pm),
527
+ getCommitMsgHook(),
540
528
  { executable: true, backup: true }
541
529
  );
542
530
  if (commitMsgResult.created) {
@@ -546,7 +534,7 @@ async function generateHuskyHooks(targetDir, projectType, pm) {
546
534
  }
547
535
  }
548
536
  const prePushPath = join4(huskyDir, "pre-push");
549
- const prePushResult = await writeFileSafe(prePushPath, getPrePushHook(pm), {
537
+ const prePushResult = await writeFileSafe(prePushPath, getPrePushHook(), {
550
538
  executable: true,
551
539
  backup: true
552
540
  });
@@ -562,10 +550,8 @@ async function generateHuskyHooks(targetDir, projectType, pm) {
562
550
  // src/generators/commitlint.ts
563
551
  import { join as join5 } from "path";
564
552
  function getCommitlintConfig(asanaBaseUrl) {
565
- const baseConfig = `// @ts-check
566
-
567
- /** @type {import('@commitlint/types').UserConfig} */
568
- const config = {
553
+ const baseConfig = `/** @type {import('@commitlint/types').UserConfig} */
554
+ export default {
569
555
  extends: ['@commitlint/config-conventional'],
570
556
  rules: {
571
557
  // Type must be one of the conventional types
@@ -593,10 +579,12 @@ const config = {
593
579
  // Subject should be lowercase
594
580
  'subject-case': [2, 'always', 'lower-case'],
595
581
  // Header max length
596
- 'header-max-length': [2, 'always', 100],
597
- },`;
582
+ 'header-max-length': [2, 'always', 100],`;
598
583
  if (asanaBaseUrl) {
599
584
  return `${baseConfig}
585
+ // Asana task link (warning only - won't block commits)
586
+ 'asana-task-link': [1, 'always'],
587
+ },
600
588
  plugins: [
601
589
  {
602
590
  rules: {
@@ -615,18 +603,11 @@ const config = {
615
603
  },
616
604
  ],
617
605
  };
618
-
619
- // Enable the Asana task link rule as a WARNING (level 1)
620
- // Change to level 2 if you want to BLOCK commits without Asana links
621
- config.rules['asana-task-link'] = [1, 'always'];
622
-
623
- module.exports = config;
624
606
  `;
625
607
  }
626
608
  return `${baseConfig}
609
+ },
627
610
  };
628
-
629
- module.exports = config;
630
611
  `;
631
612
  }
632
613
  async function generateCommitlint(targetDir, asanaBaseUrl) {
@@ -763,92 +744,37 @@ async function generateCzGit(targetDir, asanaBaseUrl) {
763
744
  }
764
745
 
765
746
  // src/generators/lint-staged.ts
766
- import { join as join7 } from "path";
767
- function getLintStagedConfig(projectType, usesEslint, usesPrettier, usesTypeScript) {
768
- const rules = {};
747
+ function getLintStagedConfig(usesEslint, usesPrettier, usesTypeScript) {
748
+ const config = {};
749
+ const codePatterns = [];
769
750
  if (usesTypeScript) {
770
- const tsCommands = [];
771
- if (usesEslint) {
772
- tsCommands.push("eslint --fix");
773
- }
774
- if (usesPrettier) {
775
- tsCommands.push("prettier --write");
776
- }
777
- if (tsCommands.length > 0) {
778
- rules["*.{ts,tsx}"] = tsCommands;
779
- }
751
+ codePatterns.push("ts", "mts", "cts", "tsx");
780
752
  }
781
- const jsCommands = [];
753
+ codePatterns.push("js", "mjs", "cjs", "jsx");
754
+ const codeCommands = [];
782
755
  if (usesEslint) {
783
- jsCommands.push("eslint --fix");
756
+ codeCommands.push("eslint --fix");
784
757
  }
785
758
  if (usesPrettier) {
786
- jsCommands.push("prettier --write");
759
+ codeCommands.push("prettier --write");
787
760
  }
788
- if (jsCommands.length > 0) {
789
- rules["*.{js,jsx,mjs,cjs}"] = jsCommands;
761
+ if (codeCommands.length > 0) {
762
+ const pattern = `*.{${codePatterns.join(",")}}`;
763
+ config[pattern] = codeCommands;
790
764
  }
791
765
  if (usesPrettier) {
792
- rules["*.{json,md,yaml,yml}"] = "prettier --write";
793
- }
794
- if (usesPrettier) {
795
- rules["*.{css,scss,less}"] = "prettier --write";
796
- }
797
- if (projectType === "nx") {
798
- return `// @ts-check
799
-
800
- /**
801
- * @type {import('lint-staged').Config}
802
- */
803
- module.exports = {
804
- ${Object.entries(rules).map(([pattern, commands]) => {
805
- const cmdStr = Array.isArray(commands) ? JSON.stringify(commands) : JSON.stringify([commands]);
806
- return ` '${pattern}': ${cmdStr},`;
807
- }).join("\n")}
808
- };
809
- `;
810
- }
811
- return `// @ts-check
812
-
813
- /**
814
- * @type {import('lint-staged').Config}
815
- */
816
- module.exports = {
817
- ${Object.entries(rules).map(([pattern, commands]) => {
818
- const cmdStr = Array.isArray(commands) ? JSON.stringify(commands) : JSON.stringify([commands]);
819
- return ` '${pattern}': ${cmdStr},`;
820
- }).join("\n")}
821
- };
822
- `;
823
- }
824
- async function generateLintStaged(targetDir, projectType, usesEslint, usesPrettier, usesTypeScript) {
825
- const result = {
826
- created: [],
827
- modified: [],
828
- skipped: [],
829
- backedUp: []
830
- };
831
- const configPath = join7(targetDir, ".lintstagedrc.js");
832
- const writeResult = await writeFileSafe(
833
- configPath,
834
- getLintStagedConfig(projectType, usesEslint, usesPrettier, usesTypeScript),
835
- { backup: true }
836
- );
837
- if (writeResult.created) {
838
- result.created.push(".lintstagedrc.js");
839
- if (writeResult.backedUp) {
840
- result.backedUp.push(writeResult.backedUp);
841
- }
766
+ config["*.{json,css,md}"] = ["prettier --write"];
842
767
  }
843
- return result;
768
+ return config;
844
769
  }
845
770
 
846
771
  // src/utils/package-json.ts
847
772
  import { readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
848
773
  import { existsSync as existsSync4 } from "fs";
849
- import { join as join8 } from "path";
774
+ import { join as join7 } from "path";
775
+ import { spawn } from "child_process";
850
776
  async function readPackageJson(targetDir = process.cwd()) {
851
- const pkgPath = join8(targetDir, "package.json");
777
+ const pkgPath = join7(targetDir, "package.json");
852
778
  if (!existsSync4(pkgPath)) {
853
779
  throw new Error(`No package.json found in ${targetDir}`);
854
780
  }
@@ -856,7 +782,7 @@ async function readPackageJson(targetDir = process.cwd()) {
856
782
  return JSON.parse(content);
857
783
  }
858
784
  async function writePackageJson(pkg, targetDir = process.cwd()) {
859
- const pkgPath = join8(targetDir, "package.json");
785
+ const pkgPath = join7(targetDir, "package.json");
860
786
  const content = JSON.stringify(pkg, null, 2) + "\n";
861
787
  await writeFile2(pkgPath, content, "utf-8");
862
788
  }
@@ -873,16 +799,6 @@ function mergeScripts(pkg, scripts, overwrite = false) {
873
799
  scripts: newScripts
874
800
  };
875
801
  }
876
- function mergeDevDependencies(pkg, deps) {
877
- const existingDeps = pkg.devDependencies || {};
878
- return {
879
- ...pkg,
880
- devDependencies: {
881
- ...existingDeps,
882
- ...deps
883
- }
884
- };
885
- }
886
802
  function addPackageJsonConfig(pkg, key, config, overwrite = false) {
887
803
  if (!overwrite && pkg[key]) {
888
804
  return pkg;
@@ -892,15 +808,62 @@ function addPackageJsonConfig(pkg, key, config, overwrite = false) {
892
808
  [key]: config
893
809
  };
894
810
  }
895
- var RAFTSTACK_DEV_DEPENDENCIES = {
896
- "@commitlint/cli": "^18.0.0",
897
- "@commitlint/config-conventional": "^18.0.0",
898
- "cz-git": "^1.8.0",
899
- czg: "^1.8.0",
900
- husky: "^9.0.0",
901
- "lint-staged": "^15.0.0",
902
- "validate-branch-name": "^1.3.0"
903
- };
811
+ var RAFTSTACK_PACKAGES = [
812
+ // Commit tooling
813
+ "@commitlint/cli",
814
+ "@commitlint/config-conventional",
815
+ "cz-git",
816
+ "czg",
817
+ "husky",
818
+ "lint-staged",
819
+ "validate-branch-name",
820
+ // Linting & formatting
821
+ "eslint",
822
+ "@eslint/js",
823
+ "typescript-eslint",
824
+ "eslint-config-prettier",
825
+ "prettier",
826
+ "globals"
827
+ ];
828
+ var REACT_ESLINT_PACKAGES = [
829
+ "eslint-plugin-react",
830
+ "eslint-plugin-react-hooks"
831
+ ];
832
+ async function installPackages(pm, packages, targetDir) {
833
+ if (packages.length === 0) {
834
+ return { success: true };
835
+ }
836
+ return new Promise((resolve) => {
837
+ const addDevArgs = pm.addDev.split(" ");
838
+ const pmCommand = pm.name === "npm" ? "npm" : pm.name.replace("-berry", "");
839
+ const args = [...addDevArgs, ...packages];
840
+ const child = spawn(pmCommand, args, {
841
+ cwd: targetDir,
842
+ stdio: ["ignore", "pipe", "pipe"],
843
+ shell: process.platform === "win32"
844
+ });
845
+ let stderr = "";
846
+ child.stderr?.on("data", (data) => {
847
+ stderr += data.toString();
848
+ });
849
+ child.on("close", (code) => {
850
+ if (code === 0) {
851
+ resolve({ success: true });
852
+ } else {
853
+ resolve({
854
+ success: false,
855
+ error: stderr || `Installation failed with exit code ${code}`
856
+ });
857
+ }
858
+ });
859
+ child.on("error", (err) => {
860
+ resolve({
861
+ success: false,
862
+ error: err.message
863
+ });
864
+ });
865
+ });
866
+ }
904
867
 
905
868
  // src/generators/branch-validation.ts
906
869
  function getBranchValidationConfig() {
@@ -939,7 +902,7 @@ async function generateBranchValidation(targetDir) {
939
902
  }
940
903
 
941
904
  // src/generators/pr-template.ts
942
- import { join as join9 } from "path";
905
+ import { join as join8 } from "path";
943
906
  function getPRTemplate(hasAsana) {
944
907
  const asanaSection = hasAsana ? `## Asana Task
945
908
  <!-- Link to the Asana task -->
@@ -991,9 +954,9 @@ async function generatePRTemplate(targetDir, hasAsana) {
991
954
  skipped: [],
992
955
  backedUp: []
993
956
  };
994
- const githubDir = join9(targetDir, ".github");
957
+ const githubDir = join8(targetDir, ".github");
995
958
  await ensureDir(githubDir);
996
- const templatePath = join9(githubDir, "PULL_REQUEST_TEMPLATE.md");
959
+ const templatePath = join8(githubDir, "PULL_REQUEST_TEMPLATE.md");
997
960
  const writeResult = await writeFileSafe(
998
961
  templatePath,
999
962
  getPRTemplate(hasAsana),
@@ -1009,7 +972,7 @@ async function generatePRTemplate(targetDir, hasAsana) {
1009
972
  }
1010
973
 
1011
974
  // src/generators/github-workflows.ts
1012
- import { join as join10 } from "path";
975
+ import { join as join9 } from "path";
1013
976
  function getPRChecksWorkflow(projectType, usesTypeScript, usesEslint, pm) {
1014
977
  const steps = [];
1015
978
  steps.push(` - name: Checkout
@@ -1090,9 +1053,9 @@ async function generateGitHubWorkflows(targetDir, projectType, usesTypeScript, u
1090
1053
  skipped: [],
1091
1054
  backedUp: []
1092
1055
  };
1093
- const workflowsDir = join10(targetDir, ".github", "workflows");
1056
+ const workflowsDir = join9(targetDir, ".github", "workflows");
1094
1057
  await ensureDir(workflowsDir);
1095
- const prChecksPath = join10(workflowsDir, "pr-checks.yml");
1058
+ const prChecksPath = join9(workflowsDir, "pr-checks.yml");
1096
1059
  const writeResult = await writeFileSafe(
1097
1060
  prChecksPath,
1098
1061
  getPRChecksWorkflow(projectType, usesTypeScript, usesEslint, pm),
@@ -1108,7 +1071,7 @@ async function generateGitHubWorkflows(targetDir, projectType, usesTypeScript, u
1108
1071
  }
1109
1072
 
1110
1073
  // src/generators/codeowners.ts
1111
- import { join as join11 } from "path";
1074
+ import { join as join10 } from "path";
1112
1075
  function getCodeownersContent(owners) {
1113
1076
  if (owners.length === 0) {
1114
1077
  return `# CODEOWNERS file
@@ -1140,9 +1103,9 @@ async function generateCodeowners(targetDir, owners) {
1140
1103
  skipped: [],
1141
1104
  backedUp: []
1142
1105
  };
1143
- const githubDir = join11(targetDir, ".github");
1106
+ const githubDir = join10(targetDir, ".github");
1144
1107
  await ensureDir(githubDir);
1145
- const codeownersPath = join11(githubDir, "CODEOWNERS");
1108
+ const codeownersPath = join10(githubDir, "CODEOWNERS");
1146
1109
  const writeResult = await writeFileSafe(
1147
1110
  codeownersPath,
1148
1111
  getCodeownersContent(owners),
@@ -1158,7 +1121,7 @@ async function generateCodeowners(targetDir, owners) {
1158
1121
  }
1159
1122
 
1160
1123
  // src/generators/ai-review.ts
1161
- import { join as join12 } from "path";
1124
+ import { join as join11 } from "path";
1162
1125
  function getCodeRabbitConfig() {
1163
1126
  return `# CodeRabbit Configuration
1164
1127
  # Learn more: https://docs.coderabbit.ai/guides/configure-coderabbit
@@ -1222,7 +1185,7 @@ async function generateAIReview(targetDir, tool) {
1222
1185
  return result;
1223
1186
  }
1224
1187
  if (tool === "coderabbit") {
1225
- const configPath = join12(targetDir, ".coderabbit.yaml");
1188
+ const configPath = join11(targetDir, ".coderabbit.yaml");
1226
1189
  const writeResult = await writeFileSafe(configPath, getCodeRabbitConfig(), {
1227
1190
  backup: true
1228
1191
  });
@@ -1234,9 +1197,9 @@ async function generateAIReview(targetDir, tool) {
1234
1197
  }
1235
1198
  }
1236
1199
  if (tool === "copilot") {
1237
- const workflowsDir = join12(targetDir, ".github", "workflows");
1200
+ const workflowsDir = join11(targetDir, ".github", "workflows");
1238
1201
  await ensureDir(workflowsDir);
1239
- const workflowPath = join12(workflowsDir, "copilot-review.yml");
1202
+ const workflowPath = join11(workflowsDir, "copilot-review.yml");
1240
1203
  const writeResult = await writeFileSafe(workflowPath, getCopilotWorkflow(), {
1241
1204
  backup: true
1242
1205
  });
@@ -1251,7 +1214,7 @@ async function generateAIReview(targetDir, tool) {
1251
1214
  }
1252
1215
 
1253
1216
  // src/generators/branch-protection.ts
1254
- import { join as join13 } from "path";
1217
+ import { join as join12 } from "path";
1255
1218
  function getBranchProtectionDocs() {
1256
1219
  return `# Branch Protection Setup Guide
1257
1220
 
@@ -1368,9 +1331,9 @@ async function generateBranchProtectionDocs(targetDir) {
1368
1331
  skipped: [],
1369
1332
  backedUp: []
1370
1333
  };
1371
- const docsDir = join13(targetDir, ".github");
1334
+ const docsDir = join12(targetDir, ".github");
1372
1335
  await ensureDir(docsDir);
1373
- const docsPath = join13(docsDir, "BRANCH_PROTECTION_SETUP.md");
1336
+ const docsPath = join12(docsDir, "BRANCH_PROTECTION_SETUP.md");
1374
1337
  const writeResult = await writeFileSafe(docsPath, getBranchProtectionDocs(), {
1375
1338
  backup: true
1376
1339
  });
@@ -1384,7 +1347,7 @@ async function generateBranchProtectionDocs(targetDir) {
1384
1347
  }
1385
1348
 
1386
1349
  // src/generators/contributing.ts
1387
- import { join as join14 } from "path";
1350
+ import { join as join13 } from "path";
1388
1351
  function getContributingContent(hasAsana, pm) {
1389
1352
  const asanaSection = hasAsana ? `
1390
1353
  ## Linking to Asana
@@ -1523,7 +1486,7 @@ async function generateContributing(targetDir, hasAsana, pm) {
1523
1486
  skipped: [],
1524
1487
  backedUp: []
1525
1488
  };
1526
- const contributingPath = join14(targetDir, "CONTRIBUTING.md");
1489
+ const contributingPath = join13(targetDir, "CONTRIBUTING.md");
1527
1490
  const writeResult = await writeFileSafe(
1528
1491
  contributingPath,
1529
1492
  getContributingContent(hasAsana, pm),
@@ -1539,16 +1502,16 @@ async function generateContributing(targetDir, hasAsana, pm) {
1539
1502
  }
1540
1503
 
1541
1504
  // src/generators/prettier.ts
1542
- import { join as join15 } from "path";
1505
+ import { join as join14 } from "path";
1543
1506
  function getPrettierConfig() {
1544
1507
  return JSON.stringify(
1545
1508
  {
1546
1509
  semi: true,
1547
- singleQuote: true,
1548
- tabWidth: 2,
1549
1510
  trailingComma: "es5",
1550
- printWidth: 100,
1551
- bracketSpacing: true,
1511
+ singleQuote: false,
1512
+ printWidth: 80,
1513
+ tabWidth: 2,
1514
+ useTabs: false,
1552
1515
  arrowParens: "always",
1553
1516
  endOfLine: "lf"
1554
1517
  },
@@ -1557,31 +1520,14 @@ function getPrettierConfig() {
1557
1520
  ) + "\n";
1558
1521
  }
1559
1522
  function getPrettierIgnore() {
1560
- return `# Dependencies
1561
- node_modules/
1562
-
1563
- # Build output
1564
- dist/
1565
- build/
1566
- .next/
1567
- out/
1568
-
1569
- # Coverage
1570
- coverage/
1571
-
1572
- # IDE
1573
- .idea/
1574
- .vscode/
1575
-
1576
- # Generated files
1577
- *.min.js
1578
- *.min.css
1579
- package-lock.json
1523
+ return `node_modules
1524
+ dist
1525
+ build
1526
+ .turbo
1527
+ .next
1528
+ *.lock
1580
1529
  pnpm-lock.yaml
1581
- yarn.lock
1582
-
1583
- # Other
1584
- .git/
1530
+ coverage
1585
1531
  `;
1586
1532
  }
1587
1533
  function hasPrettierConfig(targetDir) {
@@ -1597,7 +1543,7 @@ function hasPrettierConfig(targetDir) {
1597
1543
  "prettier.config.cjs",
1598
1544
  "prettier.config.mjs"
1599
1545
  ];
1600
- return prettierFiles.some((file) => fileExists(join15(targetDir, file)));
1546
+ return prettierFiles.some((file) => fileExists(join14(targetDir, file)));
1601
1547
  }
1602
1548
  async function generatePrettier(targetDir) {
1603
1549
  const result = {
@@ -1610,7 +1556,7 @@ async function generatePrettier(targetDir) {
1610
1556
  result.skipped.push(".prettierrc (already exists)");
1611
1557
  return result;
1612
1558
  }
1613
- const configPath = join15(targetDir, ".prettierrc");
1559
+ const configPath = join14(targetDir, ".prettierrc");
1614
1560
  const configResult = await writeFileSafe(configPath, getPrettierConfig(), {
1615
1561
  backup: true
1616
1562
  });
@@ -1620,7 +1566,7 @@ async function generatePrettier(targetDir) {
1620
1566
  result.backedUp.push(configResult.backedUp);
1621
1567
  }
1622
1568
  }
1623
- const ignorePath = join15(targetDir, ".prettierignore");
1569
+ const ignorePath = join14(targetDir, ".prettierignore");
1624
1570
  const ignoreResult = await writeFileSafe(ignorePath, getPrettierIgnore(), {
1625
1571
  backup: true,
1626
1572
  overwrite: false
@@ -1640,19 +1586,19 @@ async function generatePrettier(targetDir) {
1640
1586
  // src/generators/claude-skills.ts
1641
1587
  import { existsSync as existsSync5 } from "fs";
1642
1588
  import { readdir, copyFile as copyFile2 } from "fs/promises";
1643
- import { join as join16, dirname as dirname2 } from "path";
1589
+ import { join as join15, dirname as dirname2 } from "path";
1644
1590
  import { fileURLToPath } from "url";
1645
1591
  function getPackageSkillsDir() {
1646
1592
  const currentFilePath = fileURLToPath(import.meta.url);
1647
- const packageRoot = join16(dirname2(currentFilePath), "..");
1648
- return join16(packageRoot, ".claude", "skills");
1593
+ const packageRoot = join15(dirname2(currentFilePath), "..");
1594
+ return join15(packageRoot, ".claude", "skills");
1649
1595
  }
1650
1596
  async function copyDirectory(srcDir, destDir, result, baseDir) {
1651
1597
  await ensureDir(destDir);
1652
1598
  const entries = await readdir(srcDir, { withFileTypes: true });
1653
1599
  for (const entry of entries) {
1654
- const srcPath = join16(srcDir, entry.name);
1655
- const destPath = join16(destDir, entry.name);
1600
+ const srcPath = join15(srcDir, entry.name);
1601
+ const destPath = join15(destDir, entry.name);
1656
1602
  const relativePath = destPath.replace(baseDir + "/", "");
1657
1603
  if (entry.isDirectory()) {
1658
1604
  await copyDirectory(srcPath, destPath, result, baseDir);
@@ -1676,14 +1622,14 @@ async function generateClaudeSkills(targetDir) {
1676
1622
  backedUp: []
1677
1623
  };
1678
1624
  const packageSkillsDir = getPackageSkillsDir();
1679
- const targetSkillsDir = join16(targetDir, ".claude", "skills");
1625
+ const targetSkillsDir = join15(targetDir, ".claude", "skills");
1680
1626
  if (!existsSync5(packageSkillsDir)) {
1681
1627
  console.warn(
1682
1628
  "Warning: Skills directory not found in package. Skipping skills generation."
1683
1629
  );
1684
1630
  return result;
1685
1631
  }
1686
- await ensureDir(join16(targetDir, ".claude"));
1632
+ await ensureDir(join15(targetDir, ".claude"));
1687
1633
  await copyDirectory(packageSkillsDir, targetSkillsDir, result, targetDir);
1688
1634
  return result;
1689
1635
  }
@@ -1691,10 +1637,229 @@ async function generateClaudeSkills(targetDir) {
1691
1637
  // src/generators/eslint.ts
1692
1638
  import { existsSync as existsSync6 } from "fs";
1693
1639
  import { readFile as readFile4 } from "fs/promises";
1694
- import { join as join17 } from "path";
1640
+ import { join as join16 } from "path";
1641
+ async function hasReact(targetDir) {
1642
+ try {
1643
+ const pkgPath = join16(targetDir, "package.json");
1644
+ if (existsSync6(pkgPath)) {
1645
+ const content = await readFile4(pkgPath, "utf-8");
1646
+ const pkg = JSON.parse(content);
1647
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
1648
+ return "react" in deps || "react-dom" in deps;
1649
+ }
1650
+ } catch {
1651
+ }
1652
+ return false;
1653
+ }
1654
+ function generateTsConfig(hasReactDep) {
1655
+ if (hasReactDep) {
1656
+ return `import eslint from "@eslint/js";
1657
+ import tseslint from "typescript-eslint";
1658
+ import eslintConfigPrettier from "eslint-config-prettier";
1659
+ import reactPlugin from "eslint-plugin-react";
1660
+ import reactHooksPlugin from "eslint-plugin-react-hooks";
1661
+ import globals from "globals";
1662
+
1663
+ export default tseslint.config(
1664
+ eslint.configs.recommended,
1665
+ ...tseslint.configs.strict,
1666
+ eslintConfigPrettier,
1667
+ {
1668
+ files: ["**/*.{ts,tsx}"],
1669
+ languageOptions: {
1670
+ parserOptions: {
1671
+ ecmaFeatures: {
1672
+ jsx: true,
1673
+ },
1674
+ },
1675
+ globals: {
1676
+ ...globals.browser,
1677
+ ...globals.node,
1678
+ },
1679
+ },
1680
+ plugins: {
1681
+ react: reactPlugin,
1682
+ "react-hooks": reactHooksPlugin,
1683
+ },
1684
+ rules: {
1685
+ // TypeScript rules
1686
+ "@typescript-eslint/no-unused-vars": [
1687
+ "error",
1688
+ { argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
1689
+ ],
1690
+ "@typescript-eslint/no-explicit-any": "warn",
1691
+ "@typescript-eslint/consistent-type-imports": [
1692
+ "warn",
1693
+ { prefer: "type-imports" },
1694
+ ],
1695
+
1696
+ // React rules
1697
+ "react/react-in-jsx-scope": "off",
1698
+ "react/prop-types": "off",
1699
+ "react-hooks/rules-of-hooks": "error",
1700
+ "react-hooks/exhaustive-deps": "warn",
1701
+
1702
+ // General rules
1703
+ "no-console": ["warn", { allow: ["warn", "error"] }],
1704
+ },
1705
+ settings: {
1706
+ react: {
1707
+ version: "detect",
1708
+ },
1709
+ },
1710
+ },
1711
+ {
1712
+ ignores: ["node_modules/", "dist/", "build/", ".next/", "coverage/", ".turbo/"],
1713
+ }
1714
+ );
1715
+ `;
1716
+ }
1717
+ return `import eslint from "@eslint/js";
1718
+ import tseslint from "typescript-eslint";
1719
+ import eslintConfigPrettier from "eslint-config-prettier";
1720
+ import globals from "globals";
1721
+
1722
+ export default tseslint.config(
1723
+ eslint.configs.recommended,
1724
+ ...tseslint.configs.strict,
1725
+ eslintConfigPrettier,
1726
+ {
1727
+ files: ["**/*.{ts,tsx}"],
1728
+ languageOptions: {
1729
+ globals: {
1730
+ ...globals.node,
1731
+ },
1732
+ },
1733
+ rules: {
1734
+ // TypeScript rules
1735
+ "@typescript-eslint/no-unused-vars": [
1736
+ "error",
1737
+ { argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
1738
+ ],
1739
+ "@typescript-eslint/no-explicit-any": "warn",
1740
+ "@typescript-eslint/consistent-type-imports": [
1741
+ "warn",
1742
+ { prefer: "type-imports" },
1743
+ ],
1744
+
1745
+ // General rules
1746
+ "no-console": ["warn", { allow: ["warn", "error"] }],
1747
+ },
1748
+ },
1749
+ {
1750
+ ignores: ["node_modules/", "dist/", "build/", "coverage/", ".turbo/"],
1751
+ }
1752
+ );
1753
+ `;
1754
+ }
1755
+ function generateJsConfig(hasReactDep) {
1756
+ if (hasReactDep) {
1757
+ return `import eslint from "@eslint/js";
1758
+ import eslintConfigPrettier from "eslint-config-prettier";
1759
+ import reactPlugin from "eslint-plugin-react";
1760
+ import reactHooksPlugin from "eslint-plugin-react-hooks";
1761
+ import globals from "globals";
1762
+
1763
+ export default [
1764
+ eslint.configs.recommended,
1765
+ eslintConfigPrettier,
1766
+ {
1767
+ files: ["**/*.{js,jsx}"],
1768
+ languageOptions: {
1769
+ ecmaVersion: "latest",
1770
+ sourceType: "module",
1771
+ parserOptions: {
1772
+ ecmaFeatures: {
1773
+ jsx: true,
1774
+ },
1775
+ },
1776
+ globals: {
1777
+ ...globals.browser,
1778
+ ...globals.node,
1779
+ },
1780
+ },
1781
+ plugins: {
1782
+ react: reactPlugin,
1783
+ "react-hooks": reactHooksPlugin,
1784
+ },
1785
+ rules: {
1786
+ // React rules
1787
+ "react/react-in-jsx-scope": "off",
1788
+ "react/prop-types": "warn",
1789
+ "react-hooks/rules-of-hooks": "error",
1790
+ "react-hooks/exhaustive-deps": "warn",
1791
+
1792
+ // General rules
1793
+ "no-console": ["warn", { allow: ["warn", "error"] }],
1794
+ "no-unused-vars": ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],
1795
+ },
1796
+ settings: {
1797
+ react: {
1798
+ version: "detect",
1799
+ },
1800
+ },
1801
+ },
1802
+ {
1803
+ ignores: ["node_modules/", "dist/", "build/", ".next/", "coverage/", ".turbo/"],
1804
+ },
1805
+ ];
1806
+ `;
1807
+ }
1808
+ return `import eslint from "@eslint/js";
1809
+ import eslintConfigPrettier from "eslint-config-prettier";
1810
+ import globals from "globals";
1811
+
1812
+ export default [
1813
+ eslint.configs.recommended,
1814
+ eslintConfigPrettier,
1815
+ {
1816
+ files: ["**/*.js"],
1817
+ languageOptions: {
1818
+ ecmaVersion: "latest",
1819
+ sourceType: "module",
1820
+ globals: {
1821
+ ...globals.node,
1822
+ },
1823
+ },
1824
+ rules: {
1825
+ // General rules
1826
+ "no-console": ["warn", { allow: ["warn", "error"] }],
1827
+ "no-unused-vars": ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],
1828
+ },
1829
+ },
1830
+ {
1831
+ ignores: ["node_modules/", "dist/", "build/", "coverage/", ".turbo/"],
1832
+ },
1833
+ ];
1834
+ `;
1835
+ }
1836
+ async function detectReact(targetDir) {
1837
+ return hasReact(targetDir);
1838
+ }
1839
+ async function generateEslint(targetDir, usesTypeScript, force = false) {
1840
+ const result = {
1841
+ created: [],
1842
+ modified: [],
1843
+ skipped: [],
1844
+ backedUp: []
1845
+ };
1846
+ if (!force && await hasEslint(targetDir)) {
1847
+ result.skipped.push("eslint.config.js (ESLint already configured)");
1848
+ return result;
1849
+ }
1850
+ const usesReact = await hasReact(targetDir);
1851
+ const config = usesTypeScript ? generateTsConfig(usesReact) : generateJsConfig(usesReact);
1852
+ const configPath = join16(targetDir, "eslint.config.js");
1853
+ const writeResult = await writeFileSafe(configPath, config);
1854
+ if (writeResult.backedUp) {
1855
+ result.backedUp.push("eslint.config.js");
1856
+ }
1857
+ result.created.push("eslint.config.js");
1858
+ return result;
1859
+ }
1695
1860
 
1696
1861
  // src/generators/quick-reference.ts
1697
- import { join as join18 } from "path";
1862
+ import { join as join17 } from "path";
1698
1863
  async function generateQuickReference(targetDir, pm) {
1699
1864
  const result = {
1700
1865
  created: [],
@@ -1702,7 +1867,7 @@ async function generateQuickReference(targetDir, pm) {
1702
1867
  skipped: [],
1703
1868
  backedUp: []
1704
1869
  };
1705
- const quickRefPath = join18(targetDir, ".github", "QUICK_REFERENCE.md");
1870
+ const quickRefPath = join17(targetDir, ".github", "QUICK_REFERENCE.md");
1706
1871
  const content = `# RaftStack Quick Reference
1707
1872
 
1708
1873
  > One-page guide for the RaftStack Git workflow
@@ -1838,9 +2003,9 @@ ${pm.run} test
1838
2003
  // src/utils/git.ts
1839
2004
  import { execa } from "execa";
1840
2005
  import { existsSync as existsSync7 } from "fs";
1841
- import { join as join19 } from "path";
2006
+ import { join as join18 } from "path";
1842
2007
  async function isGitRepo(targetDir = process.cwd()) {
1843
- if (existsSync7(join19(targetDir, ".git"))) {
2008
+ if (existsSync7(join18(targetDir, ".git"))) {
1844
2009
  return true;
1845
2010
  }
1846
2011
  try {
@@ -1885,7 +2050,7 @@ function mergeResults(results) {
1885
2050
  { created: [], modified: [], skipped: [], backedUp: [] }
1886
2051
  );
1887
2052
  }
1888
- async function updateProjectPackageJson(targetDir, _config) {
2053
+ async function updateProjectPackageJson(targetDir, config) {
1889
2054
  const result = {
1890
2055
  created: [],
1891
2056
  modified: [],
@@ -1899,7 +2064,14 @@ async function updateProjectPackageJson(targetDir, _config) {
1899
2064
  commit: "czg"
1900
2065
  };
1901
2066
  pkg = mergeScripts(pkg, scripts, false);
1902
- pkg = mergeDevDependencies(pkg, RAFTSTACK_DEV_DEPENDENCIES);
2067
+ const lintStagedConfig = getLintStagedConfig(
2068
+ true,
2069
+ // usesEslint - always true now since we install it
2070
+ true,
2071
+ // usesPrettier - always true now since we install it
2072
+ config.usesTypeScript
2073
+ );
2074
+ pkg = addPackageJsonConfig(pkg, "lint-staged", lintStagedConfig, true);
1903
2075
  await writePackageJson(pkg, targetDir);
1904
2076
  result.modified.push("package.json");
1905
2077
  } catch (error) {
@@ -1928,6 +2100,32 @@ async function runInit(targetDir = process.cwd()) {
1928
2100
  if (!config) {
1929
2101
  return;
1930
2102
  }
2103
+ const usesReact = await detectReact(targetDir);
2104
+ const installSpinner = p2.spinner();
2105
+ const packagesToInstall = usesReact ? [...RAFTSTACK_PACKAGES, ...REACT_ESLINT_PACKAGES] : RAFTSTACK_PACKAGES;
2106
+ installSpinner.start("Installing dependencies...");
2107
+ const installResult = await installPackages(
2108
+ config.packageManager,
2109
+ packagesToInstall,
2110
+ targetDir
2111
+ );
2112
+ let installFailed = false;
2113
+ if (installResult.success) {
2114
+ installSpinner.stop("Dependencies installed!");
2115
+ } else {
2116
+ installSpinner.stop("Failed to install dependencies");
2117
+ p2.log.warn(
2118
+ pc2.yellow(
2119
+ `Could not install dependencies automatically: ${installResult.error || "Unknown error"}`
2120
+ )
2121
+ );
2122
+ p2.log.info(
2123
+ pc2.dim(
2124
+ `You can install them manually with: ${config.packageManager.name} ${config.packageManager.addDev} ${packagesToInstall.join(" ")}`
2125
+ )
2126
+ );
2127
+ installFailed = true;
2128
+ }
1931
2129
  const spinner4 = p2.spinner();
1932
2130
  spinner4.start("Generating configuration files...");
1933
2131
  const results = [];
@@ -1937,26 +2135,17 @@ async function runInit(targetDir = process.cwd()) {
1937
2135
  );
1938
2136
  results.push(await generateCommitlint(targetDir, config.asanaBaseUrl));
1939
2137
  results.push(await generateCzGit(targetDir, config.asanaBaseUrl));
1940
- results.push(
1941
- await generateLintStaged(
1942
- targetDir,
1943
- config.projectType,
1944
- config.usesEslint,
1945
- config.usesPrettier,
1946
- config.usesTypeScript
1947
- )
1948
- );
1949
2138
  results.push(await generateBranchValidation(targetDir));
1950
- if (!config.usesPrettier) {
1951
- results.push(await generatePrettier(targetDir));
1952
- }
2139
+ results.push(await generateEslint(targetDir, config.usesTypeScript, false));
2140
+ results.push(await generatePrettier(targetDir));
1953
2141
  results.push(await generatePRTemplate(targetDir, !!config.asanaBaseUrl));
1954
2142
  results.push(
1955
2143
  await generateGitHubWorkflows(
1956
2144
  targetDir,
1957
2145
  config.projectType,
1958
2146
  config.usesTypeScript,
1959
- config.usesEslint,
2147
+ true,
2148
+ // usesEslint - always true now
1960
2149
  config.packageManager
1961
2150
  )
1962
2151
  );
@@ -2009,15 +2198,17 @@ async function runInit(targetDir = process.cwd()) {
2009
2198
  }
2010
2199
  }
2011
2200
  console.log();
2012
- p2.note(
2013
- [
2014
- `${pc2.cyan("1.")} Run ${pc2.yellow(config.packageManager.install)} to install dependencies`,
2015
- `${pc2.cyan("2.")} Review the generated configuration files`,
2016
- `${pc2.cyan("3.")} Use ${pc2.yellow(`${config.packageManager.run} commit`)} for interactive commits`,
2017
- `${pc2.cyan("4.")} Set up branch protection rules (see .github/BRANCH_PROTECTION_SETUP.md)`
2018
- ].join("\n"),
2019
- "Next Steps"
2020
- );
2201
+ const nextSteps = installFailed ? [
2202
+ `${pc2.cyan("1.")} Run ${pc2.yellow(config.packageManager.install)} to install dependencies`,
2203
+ `${pc2.cyan("2.")} Review the generated configuration files`,
2204
+ `${pc2.cyan("3.")} Use ${pc2.yellow(`${config.packageManager.run} commit`)} for interactive commits`,
2205
+ `${pc2.cyan("4.")} Set up branch protection rules (see .github/BRANCH_PROTECTION_SETUP.md)`
2206
+ ] : [
2207
+ `${pc2.cyan("1.")} Review the generated configuration files`,
2208
+ `${pc2.cyan("2.")} Use ${pc2.yellow(`${config.packageManager.run} commit`)} for interactive commits`,
2209
+ `${pc2.cyan("3.")} Set up branch protection rules (see .github/BRANCH_PROTECTION_SETUP.md)`
2210
+ ];
2211
+ p2.note(nextSteps.join("\n"), "Next Steps");
2021
2212
  p2.outro(pc2.green("RaftStack setup complete! Happy coding! \u{1F680}"));
2022
2213
  }
2023
2214
 
@@ -2426,7 +2617,7 @@ ${pc4.bold("Branches")}
2426
2617
  // package.json
2427
2618
  var package_default = {
2428
2619
  name: "@raftlabs/raftstack",
2429
- version: "1.4.2",
2620
+ version: "1.6.0",
2430
2621
  description: "CLI tool for setting up Git hooks, commit conventions, and GitHub integration",
2431
2622
  type: "module",
2432
2623
  main: "./dist/index.js",