@raftlabs/raftstack 1.4.2 → 1.5.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
@@ -484,35 +484,19 @@ function fileExists(filePath) {
484
484
  }
485
485
 
486
486
  // 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
487
+ function getPreCommitHook(_projectType) {
488
+ return `lint-staged
499
489
  `;
500
490
  }
501
- function getCommitMsgHook(pm) {
502
- return `#!/usr/bin/env sh
503
- . "$(dirname -- "$0")/_/husky.sh"
504
-
505
- ${pm.exec} --no -- commitlint --edit "$1"
491
+ function getCommitMsgHook() {
492
+ return `commitlint --edit "$1"
506
493
  `;
507
494
  }
508
- function getPrePushHook(pm) {
509
- return `#!/usr/bin/env sh
510
- . "$(dirname -- "$0")/_/husky.sh"
511
-
512
- ${pm.exec} validate-branch-name
495
+ function getPrePushHook() {
496
+ return `validate-branch-name
513
497
  `;
514
498
  }
515
- async function generateHuskyHooks(targetDir, projectType, pm) {
499
+ async function generateHuskyHooks(targetDir, projectType, _pm) {
516
500
  const result = {
517
501
  created: [],
518
502
  modified: [],
@@ -524,7 +508,7 @@ async function generateHuskyHooks(targetDir, projectType, pm) {
524
508
  const preCommitPath = join4(huskyDir, "pre-commit");
525
509
  const preCommitResult = await writeFileSafe(
526
510
  preCommitPath,
527
- getPreCommitHook(projectType, pm),
511
+ getPreCommitHook(projectType),
528
512
  { executable: true, backup: true }
529
513
  );
530
514
  if (preCommitResult.created) {
@@ -536,7 +520,7 @@ async function generateHuskyHooks(targetDir, projectType, pm) {
536
520
  const commitMsgPath = join4(huskyDir, "commit-msg");
537
521
  const commitMsgResult = await writeFileSafe(
538
522
  commitMsgPath,
539
- getCommitMsgHook(pm),
523
+ getCommitMsgHook(),
540
524
  { executable: true, backup: true }
541
525
  );
542
526
  if (commitMsgResult.created) {
@@ -546,7 +530,7 @@ async function generateHuskyHooks(targetDir, projectType, pm) {
546
530
  }
547
531
  }
548
532
  const prePushPath = join4(huskyDir, "pre-push");
549
- const prePushResult = await writeFileSafe(prePushPath, getPrePushHook(pm), {
533
+ const prePushResult = await writeFileSafe(prePushPath, getPrePushHook(), {
550
534
  executable: true,
551
535
  backup: true
552
536
  });
@@ -562,10 +546,8 @@ async function generateHuskyHooks(targetDir, projectType, pm) {
562
546
  // src/generators/commitlint.ts
563
547
  import { join as join5 } from "path";
564
548
  function getCommitlintConfig(asanaBaseUrl) {
565
- const baseConfig = `// @ts-check
566
-
567
- /** @type {import('@commitlint/types').UserConfig} */
568
- const config = {
549
+ const baseConfig = `/** @type {import('@commitlint/types').UserConfig} */
550
+ export default {
569
551
  extends: ['@commitlint/config-conventional'],
570
552
  rules: {
571
553
  // Type must be one of the conventional types
@@ -593,10 +575,12 @@ const config = {
593
575
  // Subject should be lowercase
594
576
  'subject-case': [2, 'always', 'lower-case'],
595
577
  // Header max length
596
- 'header-max-length': [2, 'always', 100],
597
- },`;
578
+ 'header-max-length': [2, 'always', 100],`;
598
579
  if (asanaBaseUrl) {
599
580
  return `${baseConfig}
581
+ // Asana task link (warning only - won't block commits)
582
+ 'asana-task-link': [1, 'always'],
583
+ },
600
584
  plugins: [
601
585
  {
602
586
  rules: {
@@ -615,18 +599,11 @@ const config = {
615
599
  },
616
600
  ],
617
601
  };
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
602
  `;
625
603
  }
626
604
  return `${baseConfig}
605
+ },
627
606
  };
628
-
629
- module.exports = config;
630
607
  `;
631
608
  }
632
609
  async function generateCommitlint(targetDir, asanaBaseUrl) {
@@ -763,92 +740,36 @@ async function generateCzGit(targetDir, asanaBaseUrl) {
763
740
  }
764
741
 
765
742
  // src/generators/lint-staged.ts
766
- import { join as join7 } from "path";
767
- function getLintStagedConfig(projectType, usesEslint, usesPrettier, usesTypeScript) {
768
- const rules = {};
743
+ function getLintStagedConfig(usesEslint, usesPrettier, usesTypeScript) {
744
+ const config = {};
745
+ const codePatterns = [];
769
746
  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
- }
747
+ codePatterns.push("ts", "mts", "cts", "tsx");
780
748
  }
781
- const jsCommands = [];
749
+ codePatterns.push("js", "mjs", "cjs", "jsx");
750
+ const codeCommands = [];
782
751
  if (usesEslint) {
783
- jsCommands.push("eslint --fix");
752
+ codeCommands.push("eslint --fix");
784
753
  }
785
754
  if (usesPrettier) {
786
- jsCommands.push("prettier --write");
755
+ codeCommands.push("prettier --write");
787
756
  }
788
- if (jsCommands.length > 0) {
789
- rules["*.{js,jsx,mjs,cjs}"] = jsCommands;
790
- }
791
- if (usesPrettier) {
792
- rules["*.{json,md,yaml,yml}"] = "prettier --write";
757
+ if (codeCommands.length > 0) {
758
+ const pattern = `*.{${codePatterns.join(",")}}`;
759
+ config[pattern] = codeCommands;
793
760
  }
794
761
  if (usesPrettier) {
795
- rules["*.{css,scss,less}"] = "prettier --write";
762
+ config["*.{json,css,md}"] = ["prettier --write"];
796
763
  }
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
- }
842
- }
843
- return result;
764
+ return config;
844
765
  }
845
766
 
846
767
  // src/utils/package-json.ts
847
768
  import { readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
848
769
  import { existsSync as existsSync4 } from "fs";
849
- import { join as join8 } from "path";
770
+ import { join as join7 } from "path";
850
771
  async function readPackageJson(targetDir = process.cwd()) {
851
- const pkgPath = join8(targetDir, "package.json");
772
+ const pkgPath = join7(targetDir, "package.json");
852
773
  if (!existsSync4(pkgPath)) {
853
774
  throw new Error(`No package.json found in ${targetDir}`);
854
775
  }
@@ -856,7 +777,7 @@ async function readPackageJson(targetDir = process.cwd()) {
856
777
  return JSON.parse(content);
857
778
  }
858
779
  async function writePackageJson(pkg, targetDir = process.cwd()) {
859
- const pkgPath = join8(targetDir, "package.json");
780
+ const pkgPath = join7(targetDir, "package.json");
860
781
  const content = JSON.stringify(pkg, null, 2) + "\n";
861
782
  await writeFile2(pkgPath, content, "utf-8");
862
783
  }
@@ -893,13 +814,25 @@ function addPackageJsonConfig(pkg, key, config, overwrite = false) {
893
814
  };
894
815
  }
895
816
  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",
817
+ // Commit tooling
818
+ "@commitlint/cli": "^19.0.0",
819
+ "@commitlint/config-conventional": "^19.0.0",
820
+ "cz-git": "^1.11.0",
821
+ czg: "^1.11.0",
900
822
  husky: "^9.0.0",
901
- "lint-staged": "^15.0.0",
902
- "validate-branch-name": "^1.3.0"
823
+ "lint-staged": "^16.0.0",
824
+ "validate-branch-name": "^1.3.0",
825
+ // Linting & formatting
826
+ eslint: "^9.0.0",
827
+ "@eslint/js": "^9.0.0",
828
+ "typescript-eslint": "^8.0.0",
829
+ "eslint-config-prettier": "^10.0.0",
830
+ prettier: "^3.0.0",
831
+ globals: "^15.0.0"
832
+ };
833
+ var REACT_ESLINT_DEPS = {
834
+ "eslint-plugin-react": "^7.35.0",
835
+ "eslint-plugin-react-hooks": "^5.0.0"
903
836
  };
904
837
 
905
838
  // src/generators/branch-validation.ts
@@ -939,7 +872,7 @@ async function generateBranchValidation(targetDir) {
939
872
  }
940
873
 
941
874
  // src/generators/pr-template.ts
942
- import { join as join9 } from "path";
875
+ import { join as join8 } from "path";
943
876
  function getPRTemplate(hasAsana) {
944
877
  const asanaSection = hasAsana ? `## Asana Task
945
878
  <!-- Link to the Asana task -->
@@ -991,9 +924,9 @@ async function generatePRTemplate(targetDir, hasAsana) {
991
924
  skipped: [],
992
925
  backedUp: []
993
926
  };
994
- const githubDir = join9(targetDir, ".github");
927
+ const githubDir = join8(targetDir, ".github");
995
928
  await ensureDir(githubDir);
996
- const templatePath = join9(githubDir, "PULL_REQUEST_TEMPLATE.md");
929
+ const templatePath = join8(githubDir, "PULL_REQUEST_TEMPLATE.md");
997
930
  const writeResult = await writeFileSafe(
998
931
  templatePath,
999
932
  getPRTemplate(hasAsana),
@@ -1009,7 +942,7 @@ async function generatePRTemplate(targetDir, hasAsana) {
1009
942
  }
1010
943
 
1011
944
  // src/generators/github-workflows.ts
1012
- import { join as join10 } from "path";
945
+ import { join as join9 } from "path";
1013
946
  function getPRChecksWorkflow(projectType, usesTypeScript, usesEslint, pm) {
1014
947
  const steps = [];
1015
948
  steps.push(` - name: Checkout
@@ -1090,9 +1023,9 @@ async function generateGitHubWorkflows(targetDir, projectType, usesTypeScript, u
1090
1023
  skipped: [],
1091
1024
  backedUp: []
1092
1025
  };
1093
- const workflowsDir = join10(targetDir, ".github", "workflows");
1026
+ const workflowsDir = join9(targetDir, ".github", "workflows");
1094
1027
  await ensureDir(workflowsDir);
1095
- const prChecksPath = join10(workflowsDir, "pr-checks.yml");
1028
+ const prChecksPath = join9(workflowsDir, "pr-checks.yml");
1096
1029
  const writeResult = await writeFileSafe(
1097
1030
  prChecksPath,
1098
1031
  getPRChecksWorkflow(projectType, usesTypeScript, usesEslint, pm),
@@ -1108,7 +1041,7 @@ async function generateGitHubWorkflows(targetDir, projectType, usesTypeScript, u
1108
1041
  }
1109
1042
 
1110
1043
  // src/generators/codeowners.ts
1111
- import { join as join11 } from "path";
1044
+ import { join as join10 } from "path";
1112
1045
  function getCodeownersContent(owners) {
1113
1046
  if (owners.length === 0) {
1114
1047
  return `# CODEOWNERS file
@@ -1140,9 +1073,9 @@ async function generateCodeowners(targetDir, owners) {
1140
1073
  skipped: [],
1141
1074
  backedUp: []
1142
1075
  };
1143
- const githubDir = join11(targetDir, ".github");
1076
+ const githubDir = join10(targetDir, ".github");
1144
1077
  await ensureDir(githubDir);
1145
- const codeownersPath = join11(githubDir, "CODEOWNERS");
1078
+ const codeownersPath = join10(githubDir, "CODEOWNERS");
1146
1079
  const writeResult = await writeFileSafe(
1147
1080
  codeownersPath,
1148
1081
  getCodeownersContent(owners),
@@ -1158,7 +1091,7 @@ async function generateCodeowners(targetDir, owners) {
1158
1091
  }
1159
1092
 
1160
1093
  // src/generators/ai-review.ts
1161
- import { join as join12 } from "path";
1094
+ import { join as join11 } from "path";
1162
1095
  function getCodeRabbitConfig() {
1163
1096
  return `# CodeRabbit Configuration
1164
1097
  # Learn more: https://docs.coderabbit.ai/guides/configure-coderabbit
@@ -1222,7 +1155,7 @@ async function generateAIReview(targetDir, tool) {
1222
1155
  return result;
1223
1156
  }
1224
1157
  if (tool === "coderabbit") {
1225
- const configPath = join12(targetDir, ".coderabbit.yaml");
1158
+ const configPath = join11(targetDir, ".coderabbit.yaml");
1226
1159
  const writeResult = await writeFileSafe(configPath, getCodeRabbitConfig(), {
1227
1160
  backup: true
1228
1161
  });
@@ -1234,9 +1167,9 @@ async function generateAIReview(targetDir, tool) {
1234
1167
  }
1235
1168
  }
1236
1169
  if (tool === "copilot") {
1237
- const workflowsDir = join12(targetDir, ".github", "workflows");
1170
+ const workflowsDir = join11(targetDir, ".github", "workflows");
1238
1171
  await ensureDir(workflowsDir);
1239
- const workflowPath = join12(workflowsDir, "copilot-review.yml");
1172
+ const workflowPath = join11(workflowsDir, "copilot-review.yml");
1240
1173
  const writeResult = await writeFileSafe(workflowPath, getCopilotWorkflow(), {
1241
1174
  backup: true
1242
1175
  });
@@ -1251,7 +1184,7 @@ async function generateAIReview(targetDir, tool) {
1251
1184
  }
1252
1185
 
1253
1186
  // src/generators/branch-protection.ts
1254
- import { join as join13 } from "path";
1187
+ import { join as join12 } from "path";
1255
1188
  function getBranchProtectionDocs() {
1256
1189
  return `# Branch Protection Setup Guide
1257
1190
 
@@ -1368,9 +1301,9 @@ async function generateBranchProtectionDocs(targetDir) {
1368
1301
  skipped: [],
1369
1302
  backedUp: []
1370
1303
  };
1371
- const docsDir = join13(targetDir, ".github");
1304
+ const docsDir = join12(targetDir, ".github");
1372
1305
  await ensureDir(docsDir);
1373
- const docsPath = join13(docsDir, "BRANCH_PROTECTION_SETUP.md");
1306
+ const docsPath = join12(docsDir, "BRANCH_PROTECTION_SETUP.md");
1374
1307
  const writeResult = await writeFileSafe(docsPath, getBranchProtectionDocs(), {
1375
1308
  backup: true
1376
1309
  });
@@ -1384,7 +1317,7 @@ async function generateBranchProtectionDocs(targetDir) {
1384
1317
  }
1385
1318
 
1386
1319
  // src/generators/contributing.ts
1387
- import { join as join14 } from "path";
1320
+ import { join as join13 } from "path";
1388
1321
  function getContributingContent(hasAsana, pm) {
1389
1322
  const asanaSection = hasAsana ? `
1390
1323
  ## Linking to Asana
@@ -1523,7 +1456,7 @@ async function generateContributing(targetDir, hasAsana, pm) {
1523
1456
  skipped: [],
1524
1457
  backedUp: []
1525
1458
  };
1526
- const contributingPath = join14(targetDir, "CONTRIBUTING.md");
1459
+ const contributingPath = join13(targetDir, "CONTRIBUTING.md");
1527
1460
  const writeResult = await writeFileSafe(
1528
1461
  contributingPath,
1529
1462
  getContributingContent(hasAsana, pm),
@@ -1539,16 +1472,16 @@ async function generateContributing(targetDir, hasAsana, pm) {
1539
1472
  }
1540
1473
 
1541
1474
  // src/generators/prettier.ts
1542
- import { join as join15 } from "path";
1475
+ import { join as join14 } from "path";
1543
1476
  function getPrettierConfig() {
1544
1477
  return JSON.stringify(
1545
1478
  {
1546
1479
  semi: true,
1547
- singleQuote: true,
1548
- tabWidth: 2,
1549
1480
  trailingComma: "es5",
1550
- printWidth: 100,
1551
- bracketSpacing: true,
1481
+ singleQuote: false,
1482
+ printWidth: 80,
1483
+ tabWidth: 2,
1484
+ useTabs: false,
1552
1485
  arrowParens: "always",
1553
1486
  endOfLine: "lf"
1554
1487
  },
@@ -1557,31 +1490,14 @@ function getPrettierConfig() {
1557
1490
  ) + "\n";
1558
1491
  }
1559
1492
  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
1493
+ return `node_modules
1494
+ dist
1495
+ build
1496
+ .turbo
1497
+ .next
1498
+ *.lock
1580
1499
  pnpm-lock.yaml
1581
- yarn.lock
1582
-
1583
- # Other
1584
- .git/
1500
+ coverage
1585
1501
  `;
1586
1502
  }
1587
1503
  function hasPrettierConfig(targetDir) {
@@ -1597,7 +1513,7 @@ function hasPrettierConfig(targetDir) {
1597
1513
  "prettier.config.cjs",
1598
1514
  "prettier.config.mjs"
1599
1515
  ];
1600
- return prettierFiles.some((file) => fileExists(join15(targetDir, file)));
1516
+ return prettierFiles.some((file) => fileExists(join14(targetDir, file)));
1601
1517
  }
1602
1518
  async function generatePrettier(targetDir) {
1603
1519
  const result = {
@@ -1610,7 +1526,7 @@ async function generatePrettier(targetDir) {
1610
1526
  result.skipped.push(".prettierrc (already exists)");
1611
1527
  return result;
1612
1528
  }
1613
- const configPath = join15(targetDir, ".prettierrc");
1529
+ const configPath = join14(targetDir, ".prettierrc");
1614
1530
  const configResult = await writeFileSafe(configPath, getPrettierConfig(), {
1615
1531
  backup: true
1616
1532
  });
@@ -1620,7 +1536,7 @@ async function generatePrettier(targetDir) {
1620
1536
  result.backedUp.push(configResult.backedUp);
1621
1537
  }
1622
1538
  }
1623
- const ignorePath = join15(targetDir, ".prettierignore");
1539
+ const ignorePath = join14(targetDir, ".prettierignore");
1624
1540
  const ignoreResult = await writeFileSafe(ignorePath, getPrettierIgnore(), {
1625
1541
  backup: true,
1626
1542
  overwrite: false
@@ -1640,19 +1556,19 @@ async function generatePrettier(targetDir) {
1640
1556
  // src/generators/claude-skills.ts
1641
1557
  import { existsSync as existsSync5 } from "fs";
1642
1558
  import { readdir, copyFile as copyFile2 } from "fs/promises";
1643
- import { join as join16, dirname as dirname2 } from "path";
1559
+ import { join as join15, dirname as dirname2 } from "path";
1644
1560
  import { fileURLToPath } from "url";
1645
1561
  function getPackageSkillsDir() {
1646
1562
  const currentFilePath = fileURLToPath(import.meta.url);
1647
- const packageRoot = join16(dirname2(currentFilePath), "..");
1648
- return join16(packageRoot, ".claude", "skills");
1563
+ const packageRoot = join15(dirname2(currentFilePath), "..");
1564
+ return join15(packageRoot, ".claude", "skills");
1649
1565
  }
1650
1566
  async function copyDirectory(srcDir, destDir, result, baseDir) {
1651
1567
  await ensureDir(destDir);
1652
1568
  const entries = await readdir(srcDir, { withFileTypes: true });
1653
1569
  for (const entry of entries) {
1654
- const srcPath = join16(srcDir, entry.name);
1655
- const destPath = join16(destDir, entry.name);
1570
+ const srcPath = join15(srcDir, entry.name);
1571
+ const destPath = join15(destDir, entry.name);
1656
1572
  const relativePath = destPath.replace(baseDir + "/", "");
1657
1573
  if (entry.isDirectory()) {
1658
1574
  await copyDirectory(srcPath, destPath, result, baseDir);
@@ -1676,14 +1592,14 @@ async function generateClaudeSkills(targetDir) {
1676
1592
  backedUp: []
1677
1593
  };
1678
1594
  const packageSkillsDir = getPackageSkillsDir();
1679
- const targetSkillsDir = join16(targetDir, ".claude", "skills");
1595
+ const targetSkillsDir = join15(targetDir, ".claude", "skills");
1680
1596
  if (!existsSync5(packageSkillsDir)) {
1681
1597
  console.warn(
1682
1598
  "Warning: Skills directory not found in package. Skipping skills generation."
1683
1599
  );
1684
1600
  return result;
1685
1601
  }
1686
- await ensureDir(join16(targetDir, ".claude"));
1602
+ await ensureDir(join15(targetDir, ".claude"));
1687
1603
  await copyDirectory(packageSkillsDir, targetSkillsDir, result, targetDir);
1688
1604
  return result;
1689
1605
  }
@@ -1691,10 +1607,229 @@ async function generateClaudeSkills(targetDir) {
1691
1607
  // src/generators/eslint.ts
1692
1608
  import { existsSync as existsSync6 } from "fs";
1693
1609
  import { readFile as readFile4 } from "fs/promises";
1694
- import { join as join17 } from "path";
1610
+ import { join as join16 } from "path";
1611
+ async function hasReact(targetDir) {
1612
+ try {
1613
+ const pkgPath = join16(targetDir, "package.json");
1614
+ if (existsSync6(pkgPath)) {
1615
+ const content = await readFile4(pkgPath, "utf-8");
1616
+ const pkg = JSON.parse(content);
1617
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
1618
+ return "react" in deps || "react-dom" in deps;
1619
+ }
1620
+ } catch {
1621
+ }
1622
+ return false;
1623
+ }
1624
+ function generateTsConfig(hasReactDep) {
1625
+ if (hasReactDep) {
1626
+ return `import eslint from "@eslint/js";
1627
+ import tseslint from "typescript-eslint";
1628
+ import eslintConfigPrettier from "eslint-config-prettier";
1629
+ import reactPlugin from "eslint-plugin-react";
1630
+ import reactHooksPlugin from "eslint-plugin-react-hooks";
1631
+ import globals from "globals";
1632
+
1633
+ export default tseslint.config(
1634
+ eslint.configs.recommended,
1635
+ ...tseslint.configs.strict,
1636
+ eslintConfigPrettier,
1637
+ {
1638
+ files: ["**/*.{ts,tsx}"],
1639
+ languageOptions: {
1640
+ parserOptions: {
1641
+ ecmaFeatures: {
1642
+ jsx: true,
1643
+ },
1644
+ },
1645
+ globals: {
1646
+ ...globals.browser,
1647
+ ...globals.node,
1648
+ },
1649
+ },
1650
+ plugins: {
1651
+ react: reactPlugin,
1652
+ "react-hooks": reactHooksPlugin,
1653
+ },
1654
+ rules: {
1655
+ // TypeScript rules
1656
+ "@typescript-eslint/no-unused-vars": [
1657
+ "error",
1658
+ { argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
1659
+ ],
1660
+ "@typescript-eslint/no-explicit-any": "warn",
1661
+ "@typescript-eslint/consistent-type-imports": [
1662
+ "warn",
1663
+ { prefer: "type-imports" },
1664
+ ],
1665
+
1666
+ // React rules
1667
+ "react/react-in-jsx-scope": "off",
1668
+ "react/prop-types": "off",
1669
+ "react-hooks/rules-of-hooks": "error",
1670
+ "react-hooks/exhaustive-deps": "warn",
1671
+
1672
+ // General rules
1673
+ "no-console": ["warn", { allow: ["warn", "error"] }],
1674
+ },
1675
+ settings: {
1676
+ react: {
1677
+ version: "detect",
1678
+ },
1679
+ },
1680
+ },
1681
+ {
1682
+ ignores: ["node_modules/", "dist/", "build/", ".next/", "coverage/", ".turbo/"],
1683
+ }
1684
+ );
1685
+ `;
1686
+ }
1687
+ return `import eslint from "@eslint/js";
1688
+ import tseslint from "typescript-eslint";
1689
+ import eslintConfigPrettier from "eslint-config-prettier";
1690
+ import globals from "globals";
1691
+
1692
+ export default tseslint.config(
1693
+ eslint.configs.recommended,
1694
+ ...tseslint.configs.strict,
1695
+ eslintConfigPrettier,
1696
+ {
1697
+ files: ["**/*.{ts,tsx}"],
1698
+ languageOptions: {
1699
+ globals: {
1700
+ ...globals.node,
1701
+ },
1702
+ },
1703
+ rules: {
1704
+ // TypeScript rules
1705
+ "@typescript-eslint/no-unused-vars": [
1706
+ "error",
1707
+ { argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
1708
+ ],
1709
+ "@typescript-eslint/no-explicit-any": "warn",
1710
+ "@typescript-eslint/consistent-type-imports": [
1711
+ "warn",
1712
+ { prefer: "type-imports" },
1713
+ ],
1714
+
1715
+ // General rules
1716
+ "no-console": ["warn", { allow: ["warn", "error"] }],
1717
+ },
1718
+ },
1719
+ {
1720
+ ignores: ["node_modules/", "dist/", "build/", "coverage/", ".turbo/"],
1721
+ }
1722
+ );
1723
+ `;
1724
+ }
1725
+ function generateJsConfig(hasReactDep) {
1726
+ if (hasReactDep) {
1727
+ return `import eslint from "@eslint/js";
1728
+ import eslintConfigPrettier from "eslint-config-prettier";
1729
+ import reactPlugin from "eslint-plugin-react";
1730
+ import reactHooksPlugin from "eslint-plugin-react-hooks";
1731
+ import globals from "globals";
1732
+
1733
+ export default [
1734
+ eslint.configs.recommended,
1735
+ eslintConfigPrettier,
1736
+ {
1737
+ files: ["**/*.{js,jsx}"],
1738
+ languageOptions: {
1739
+ ecmaVersion: "latest",
1740
+ sourceType: "module",
1741
+ parserOptions: {
1742
+ ecmaFeatures: {
1743
+ jsx: true,
1744
+ },
1745
+ },
1746
+ globals: {
1747
+ ...globals.browser,
1748
+ ...globals.node,
1749
+ },
1750
+ },
1751
+ plugins: {
1752
+ react: reactPlugin,
1753
+ "react-hooks": reactHooksPlugin,
1754
+ },
1755
+ rules: {
1756
+ // React rules
1757
+ "react/react-in-jsx-scope": "off",
1758
+ "react/prop-types": "warn",
1759
+ "react-hooks/rules-of-hooks": "error",
1760
+ "react-hooks/exhaustive-deps": "warn",
1761
+
1762
+ // General rules
1763
+ "no-console": ["warn", { allow: ["warn", "error"] }],
1764
+ "no-unused-vars": ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],
1765
+ },
1766
+ settings: {
1767
+ react: {
1768
+ version: "detect",
1769
+ },
1770
+ },
1771
+ },
1772
+ {
1773
+ ignores: ["node_modules/", "dist/", "build/", ".next/", "coverage/", ".turbo/"],
1774
+ },
1775
+ ];
1776
+ `;
1777
+ }
1778
+ return `import eslint from "@eslint/js";
1779
+ import eslintConfigPrettier from "eslint-config-prettier";
1780
+ import globals from "globals";
1781
+
1782
+ export default [
1783
+ eslint.configs.recommended,
1784
+ eslintConfigPrettier,
1785
+ {
1786
+ files: ["**/*.js"],
1787
+ languageOptions: {
1788
+ ecmaVersion: "latest",
1789
+ sourceType: "module",
1790
+ globals: {
1791
+ ...globals.node,
1792
+ },
1793
+ },
1794
+ rules: {
1795
+ // General rules
1796
+ "no-console": ["warn", { allow: ["warn", "error"] }],
1797
+ "no-unused-vars": ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],
1798
+ },
1799
+ },
1800
+ {
1801
+ ignores: ["node_modules/", "dist/", "build/", "coverage/", ".turbo/"],
1802
+ },
1803
+ ];
1804
+ `;
1805
+ }
1806
+ async function detectReact(targetDir) {
1807
+ return hasReact(targetDir);
1808
+ }
1809
+ async function generateEslint(targetDir, usesTypeScript, force = false) {
1810
+ const result = {
1811
+ created: [],
1812
+ modified: [],
1813
+ skipped: [],
1814
+ backedUp: []
1815
+ };
1816
+ if (!force && await hasEslint(targetDir)) {
1817
+ result.skipped.push("eslint.config.js (ESLint already configured)");
1818
+ return result;
1819
+ }
1820
+ const usesReact = await hasReact(targetDir);
1821
+ const config = usesTypeScript ? generateTsConfig(usesReact) : generateJsConfig(usesReact);
1822
+ const configPath = join16(targetDir, "eslint.config.js");
1823
+ const writeResult = await writeFileSafe(configPath, config);
1824
+ if (writeResult.backedUp) {
1825
+ result.backedUp.push("eslint.config.js");
1826
+ }
1827
+ result.created.push("eslint.config.js");
1828
+ return result;
1829
+ }
1695
1830
 
1696
1831
  // src/generators/quick-reference.ts
1697
- import { join as join18 } from "path";
1832
+ import { join as join17 } from "path";
1698
1833
  async function generateQuickReference(targetDir, pm) {
1699
1834
  const result = {
1700
1835
  created: [],
@@ -1702,7 +1837,7 @@ async function generateQuickReference(targetDir, pm) {
1702
1837
  skipped: [],
1703
1838
  backedUp: []
1704
1839
  };
1705
- const quickRefPath = join18(targetDir, ".github", "QUICK_REFERENCE.md");
1840
+ const quickRefPath = join17(targetDir, ".github", "QUICK_REFERENCE.md");
1706
1841
  const content = `# RaftStack Quick Reference
1707
1842
 
1708
1843
  > One-page guide for the RaftStack Git workflow
@@ -1838,9 +1973,9 @@ ${pm.run} test
1838
1973
  // src/utils/git.ts
1839
1974
  import { execa } from "execa";
1840
1975
  import { existsSync as existsSync7 } from "fs";
1841
- import { join as join19 } from "path";
1976
+ import { join as join18 } from "path";
1842
1977
  async function isGitRepo(targetDir = process.cwd()) {
1843
- if (existsSync7(join19(targetDir, ".git"))) {
1978
+ if (existsSync7(join18(targetDir, ".git"))) {
1844
1979
  return true;
1845
1980
  }
1846
1981
  try {
@@ -1885,7 +2020,7 @@ function mergeResults(results) {
1885
2020
  { created: [], modified: [], skipped: [], backedUp: [] }
1886
2021
  );
1887
2022
  }
1888
- async function updateProjectPackageJson(targetDir, _config) {
2023
+ async function updateProjectPackageJson(targetDir, config, usesReact) {
1889
2024
  const result = {
1890
2025
  created: [],
1891
2026
  modified: [],
@@ -1900,6 +2035,17 @@ async function updateProjectPackageJson(targetDir, _config) {
1900
2035
  };
1901
2036
  pkg = mergeScripts(pkg, scripts, false);
1902
2037
  pkg = mergeDevDependencies(pkg, RAFTSTACK_DEV_DEPENDENCIES);
2038
+ if (usesReact) {
2039
+ pkg = mergeDevDependencies(pkg, REACT_ESLINT_DEPS);
2040
+ }
2041
+ const lintStagedConfig = getLintStagedConfig(
2042
+ true,
2043
+ // usesEslint - always true now since we install it
2044
+ true,
2045
+ // usesPrettier - always true now since we install it
2046
+ config.usesTypeScript
2047
+ );
2048
+ pkg = addPackageJsonConfig(pkg, "lint-staged", lintStagedConfig, true);
1903
2049
  await writePackageJson(pkg, targetDir);
1904
2050
  result.modified.push("package.json");
1905
2051
  } catch (error) {
@@ -1932,31 +2078,23 @@ async function runInit(targetDir = process.cwd()) {
1932
2078
  spinner4.start("Generating configuration files...");
1933
2079
  const results = [];
1934
2080
  try {
2081
+ const usesReact = await detectReact(targetDir);
1935
2082
  results.push(
1936
2083
  await generateHuskyHooks(targetDir, config.projectType, config.packageManager)
1937
2084
  );
1938
2085
  results.push(await generateCommitlint(targetDir, config.asanaBaseUrl));
1939
2086
  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
2087
  results.push(await generateBranchValidation(targetDir));
1950
- if (!config.usesPrettier) {
1951
- results.push(await generatePrettier(targetDir));
1952
- }
2088
+ results.push(await generateEslint(targetDir, config.usesTypeScript, false));
2089
+ results.push(await generatePrettier(targetDir));
1953
2090
  results.push(await generatePRTemplate(targetDir, !!config.asanaBaseUrl));
1954
2091
  results.push(
1955
2092
  await generateGitHubWorkflows(
1956
2093
  targetDir,
1957
2094
  config.projectType,
1958
2095
  config.usesTypeScript,
1959
- config.usesEslint,
2096
+ true,
2097
+ // usesEslint - always true now
1960
2098
  config.packageManager
1961
2099
  )
1962
2100
  );
@@ -1968,7 +2106,7 @@ async function runInit(targetDir = process.cwd()) {
1968
2106
  );
1969
2107
  results.push(await generateQuickReference(targetDir, config.packageManager));
1970
2108
  results.push(await generateClaudeSkills(targetDir));
1971
- results.push(await updateProjectPackageJson(targetDir, config));
2109
+ results.push(await updateProjectPackageJson(targetDir, config, usesReact));
1972
2110
  spinner4.stop("Configuration files generated!");
1973
2111
  } catch (error) {
1974
2112
  spinner4.stop("Error generating files");
@@ -2426,7 +2564,7 @@ ${pc4.bold("Branches")}
2426
2564
  // package.json
2427
2565
  var package_default = {
2428
2566
  name: "@raftlabs/raftstack",
2429
- version: "1.4.2",
2567
+ version: "1.5.0",
2430
2568
  description: "CLI tool for setting up Git hooks, commit conventions, and GitHub integration",
2431
2569
  type: "module",
2432
2570
  main: "./dist/index.js",