create-einja-app 0.2.18 → 0.3.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
@@ -3,8 +3,8 @@
3
3
  // src/cli.ts
4
4
  import { Command } from "commander";
5
5
  import { readFileSync as readFileSync5 } from "fs";
6
- import { fileURLToPath as fileURLToPath4 } from "url";
7
- import { dirname as dirname7, join as join17 } from "path";
6
+ import { fileURLToPath as fileURLToPath3 } from "url";
7
+ import { dirname as dirname6, join as join8 } from "path";
8
8
 
9
9
  // src/commands/create.ts
10
10
  import { existsSync as existsSync3, readdirSync } from "fs";
@@ -167,63 +167,11 @@ import { fileURLToPath } from "url";
167
167
  // src/utils/fs.ts
168
168
  import { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync } from "fs";
169
169
  import { join, dirname } from "path";
170
- function writeWithStrategy(filePath, content, strategy) {
171
- const exists = existsSync(filePath);
172
- if (!exists) {
173
- ensureDir(dirname(filePath));
174
- writeFileSync(filePath, content, "utf-8");
175
- return true;
176
- }
177
- switch (strategy) {
178
- case "overwrite": {
179
- writeFileSync(filePath, content, "utf-8");
180
- return true;
181
- }
182
- case "merge": {
183
- const existingContent = readFileSync(filePath, "utf-8");
184
- const mergedContent = mergeContent(existingContent, content);
185
- writeFileSync(filePath, mergedContent, "utf-8");
186
- return true;
187
- }
188
- case "skip": {
189
- return false;
190
- }
191
- default: {
192
- const _exhaustiveCheck = strategy;
193
- throw new Error(`Unknown strategy: ${_exhaustiveCheck}`);
194
- }
195
- }
196
- }
197
- function mergeContent(existing, newContent) {
198
- if (existing.includes(newContent)) {
199
- return existing;
200
- }
201
- return `${existing}
202
- ${newContent}`;
203
- }
204
170
  function ensureDir(dirPath) {
205
171
  if (!existsSync(dirPath)) {
206
172
  mkdirSync(dirPath, { recursive: true });
207
173
  }
208
174
  }
209
- function appendToGitignore(targetDir, line) {
210
- const gitignorePath = join(targetDir, ".gitignore");
211
- if (!existsSync(gitignorePath)) {
212
- writeFileSync(gitignorePath, `${line}
213
- `, "utf-8");
214
- return;
215
- }
216
- const content = readFileSync(gitignorePath, "utf-8");
217
- if (content.includes(line)) {
218
- return;
219
- }
220
- appendFileSync(gitignorePath, `
221
- ${line}
222
- `, "utf-8");
223
- }
224
- function fileExists(filePath) {
225
- return existsSync(filePath);
226
- }
227
175
 
228
176
  // src/utils/logger.ts
229
177
  import chalk from "chalk";
@@ -588,625 +536,603 @@ async function createCommand(projectName, options) {
588
536
  }
589
537
  }
590
538
 
591
- // src/commands/setup.ts
592
- import { existsSync as existsSync4 } from "fs";
593
- import { join as join9 } from "path";
594
- import ora3 from "ora";
539
+ // src/commands/sync.ts
540
+ import { dirname as dirname5, join as join7 } from "path";
541
+ import { fileURLToPath as fileURLToPath2 } from "url";
542
+ import fsExtra3 from "fs-extra";
543
+ import inquirer5 from "inquirer";
595
544
 
596
- // src/prompts/setup.ts
597
- import inquirer3 from "inquirer";
598
- async function promptSetupConfig() {
599
- const answers = await inquirer3.prompt([
600
- {
601
- type: "checkbox",
602
- name: "tools",
603
- message: "\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u3059\u308B\u30C4\u30FC\u30EB\u3092\u9078\u629E\uFF08\u8907\u6570\u9078\u629E\u53EF\uFF09:",
604
- choices: [
605
- {
606
- name: "direnv\uFF08\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3054\u3068\u306E\u74B0\u5883\u5909\u6570\u7BA1\u7406\uFF09",
607
- value: "direnv",
608
- checked: true
609
- },
610
- {
611
- name: "dotenvx\uFF08.env\u6697\u53F7\u5316\uFF09",
612
- value: "dotenvx",
613
- checked: true
614
- },
615
- {
616
- name: "Volta\uFF08Node.js\u30D0\u30FC\u30B8\u30E7\u30F3\u7BA1\u7406\uFF09",
617
- value: "volta",
618
- checked: true
619
- },
620
- {
621
- name: "Biome\uFF08Linter / Formatter\uFF09",
622
- value: "biome",
623
- checked: false
624
- },
625
- {
626
- name: "Husky + lint-staged\uFF08Git hooks\uFF09",
627
- value: "husky",
628
- checked: false
629
- }
630
- ]
631
- },
632
- {
633
- type: "list",
634
- name: "conflictStrategy",
635
- message: "\u65E2\u5B58\u30D5\u30A1\u30A4\u30EB\u304C\u3042\u308B\u5834\u5408\u306E\u52D5\u4F5C:",
636
- choices: [
637
- {
638
- name: "\u30DE\u30FC\u30B8\uFF08\u65E2\u5B58\u8A2D\u5B9A\u3092\u4FDD\u6301\u3057\u3064\u3064\u8FFD\u52A0\uFF09",
639
- value: "merge"
640
- },
641
- {
642
- name: "\u4E0A\u66F8\u304D",
643
- value: "overwrite"
644
- },
645
- {
646
- name: "\u30B9\u30AD\u30C3\u30D7",
647
- value: "skip"
648
- }
649
- ],
650
- default: "merge"
545
+ // src/generators/sync.ts
546
+ import { glob as glob2 } from "glob";
547
+ var CATEGORY_PATTERNS = {
548
+ env: [".env*", ".envrc", ".volta", ".node-version"],
549
+ tools: ["biome.json", ".prettierrc*", ".editorconfig", ".vscode/**"],
550
+ git: [".gitignore", ".gitattributes"],
551
+ "git-hooks": [".husky/**"],
552
+ github: [".github/workflows/**", ".github/actions/**", ".github/dependabot.yml"],
553
+ docker: ["Dockerfile*", "docker-compose*.yml", ".dockerignore"],
554
+ monorepo: ["turbo.json", "pnpm-workspace.yaml"],
555
+ "root-config": ["package.json", "tsconfig.json"],
556
+ scripts: ["scripts/**"],
557
+ apps: ["apps/**"],
558
+ packages: ["packages/**"],
559
+ docs: ["README.md", "docs/**"]
560
+ };
561
+ var ENV_FILE_PROTECTION = {
562
+ // 同期から保護するファイル(秘密鍵・暗号化済み値を含む)
563
+ protected: [
564
+ ".env.keys",
565
+ ".env.personal",
566
+ ".env.develop",
567
+ ".env.local",
568
+ ".env.production",
569
+ ".env.staging",
570
+ ".env.preview"
571
+ ],
572
+ // 同期を許可するファイル(テンプレート・例示ファイル)
573
+ allowed: [
574
+ ".env.example",
575
+ ".env.personal.example",
576
+ ".envrc"
577
+ ]
578
+ };
579
+ var SYNC_EXCLUDE_PATTERNS = [
580
+ "**/prisma/schema.prisma",
581
+ // DBモデルはユーザー固有
582
+ "**/prisma/migrations/**",
583
+ // マイグレーション履歴
584
+ "pnpm-lock.yaml",
585
+ // lockfile は sync すべきでない
586
+ "**/node_modules/**",
587
+ // 依存関係
588
+ "**/.git/**"
589
+ // Git内部
590
+ ];
591
+ function isProtectedEnvFile(filePath) {
592
+ const fileName = filePath.split("/").pop() ?? "";
593
+ if (ENV_FILE_PROTECTION.allowed.some((pattern) => fileName === pattern)) {
594
+ return false;
595
+ }
596
+ if (ENV_FILE_PROTECTION.protected.some((pattern) => fileName === pattern)) {
597
+ return true;
598
+ }
599
+ if (fileName.startsWith(".env")) {
600
+ return true;
601
+ }
602
+ return false;
603
+ }
604
+ function isExcludedFile(filePath) {
605
+ for (const pattern of SYNC_EXCLUDE_PATTERNS) {
606
+ const regexPattern = pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\./g, "\\.");
607
+ const regex = new RegExp(`^${regexPattern}$`);
608
+ if (regex.test(filePath)) {
609
+ return true;
651
610
  }
652
- ]);
653
- const toolsArray = answers.tools;
654
- const tools = {
655
- direnv: toolsArray.includes("direnv"),
656
- dotenvx: toolsArray.includes("dotenvx"),
657
- volta: toolsArray.includes("volta"),
658
- biome: toolsArray.includes("biome"),
659
- husky: toolsArray.includes("husky")
660
- };
661
- return {
662
- tools,
663
- conflictStrategy: answers.conflictStrategy
664
- };
611
+ }
612
+ return false;
665
613
  }
666
-
667
- // src/generators/tools/direnv.ts
668
- import { join as join3 } from "path";
669
- import { execSync } from "child_process";
670
- import inquirer4 from "inquirer";
671
- var ENVRC_CONTENT = `# direnv configuration
672
- # Load .env if it exists
673
- dotenv_if_exists
674
-
675
- # Allow local overrides
676
- dotenv_if_exists .env.local
677
- `;
678
- var ENVRC_EXAMPLE_CONTENT = `# Example direnv configuration
679
- # Copy this file to .envrc and run 'direnv allow'
680
-
681
- # Load environment variables from .env
682
- dotenv_if_exists
683
-
684
- # Load local overrides
685
- dotenv_if_exists .env.local
686
- `;
687
- function setupDirenv(options) {
688
- const { targetDir, conflictStrategy } = options;
689
- const envrcPath = join3(targetDir, ".envrc");
690
- const envrcExamplePath = join3(targetDir, ".envrc.example");
691
- writeWithStrategy(envrcPath, ENVRC_CONTENT, conflictStrategy);
692
- writeWithStrategy(envrcExamplePath, ENVRC_EXAMPLE_CONTENT, conflictStrategy);
693
- appendToGitignore(targetDir, ".envrc");
614
+ function extractPatternsFromCategories(categories, appsDetail, packagesDetail) {
615
+ const patterns = [];
616
+ for (const category of categories) {
617
+ const categoryPatterns = CATEGORY_PATTERNS[category];
618
+ if (!categoryPatterns) {
619
+ warn(`\u4E0D\u660E\u306A\u30AB\u30C6\u30B4\u30EA: ${category}`);
620
+ continue;
621
+ }
622
+ if (category === "apps" && appsDetail && appsDetail.length > 0) {
623
+ patterns.push(...appsDetail.map((app) => `apps/${app}/**`));
624
+ } else if (category === "packages" && packagesDetail && packagesDetail.length > 0) {
625
+ patterns.push(...packagesDetail.map((pkg) => `packages/${pkg}/**`));
626
+ } else {
627
+ patterns.push(...categoryPatterns);
628
+ }
629
+ }
630
+ return patterns;
694
631
  }
695
- async function promptDirenvAllow(targetDir) {
632
+ async function collectSyncFiles(templateDir, categories, appsDetail, packagesDetail) {
696
633
  try {
697
- const { shouldAllow } = await inquirer4.prompt([
698
- {
699
- type: "confirm",
700
- name: "shouldAllow",
701
- message: "direnv allow \u3092\u5B9F\u884C\u3057\u307E\u3059\u304B\uFF1F\uFF08\u74B0\u5883\u5909\u6570\u3092\u6709\u52B9\u5316\u3057\u307E\u3059\uFF09",
702
- default: true
703
- }
704
- ]);
705
- if (shouldAllow) {
634
+ info("\u540C\u671F\u5BFE\u8C61\u30D5\u30A1\u30A4\u30EB\u3092\u53CE\u96C6\u4E2D...");
635
+ const patterns = extractPatternsFromCategories(
636
+ categories,
637
+ appsDetail,
638
+ packagesDetail
639
+ );
640
+ if (patterns.length === 0) {
641
+ warn("\u540C\u671F\u5BFE\u8C61\u306E\u30D1\u30BF\u30FC\u30F3\u304C\u3042\u308A\u307E\u305B\u3093");
642
+ return [];
643
+ }
644
+ const fileSet = /* @__PURE__ */ new Set();
645
+ for (const pattern of patterns) {
706
646
  try {
707
- execSync("direnv allow", { cwd: targetDir, stdio: "inherit" });
708
- success("direnv allow \u3092\u5B9F\u884C\u3057\u307E\u3057\u305F");
647
+ const files = await glob2(pattern, {
648
+ cwd: templateDir,
649
+ dot: true,
650
+ // .で始まるファイルも含める
651
+ nodir: true
652
+ // ディレクトリは除外
653
+ });
654
+ for (const file of files) {
655
+ fileSet.add(file);
656
+ }
709
657
  } catch (error2) {
710
- warn("direnv allow \u306E\u5B9F\u884C\u306B\u5931\u6557\u3057\u307E\u3057\u305F");
711
- info("\u5F8C\u3067\u624B\u52D5\u3067 'direnv allow' \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044");
658
+ warn(`\u30D1\u30BF\u30FC\u30F3 ${pattern} \u306E\u51E6\u7406\u4E2D\u306B\u30A8\u30E9\u30FC: ${error2}`);
712
659
  }
713
- } else {
714
- info("direnv allow \u3092\u30B9\u30AD\u30C3\u30D7\u3057\u307E\u3057\u305F");
715
- info("\u5F8C\u3067\u624B\u52D5\u3067 'direnv allow' \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044");
716
660
  }
661
+ const allFiles = Array.from(fileSet);
662
+ const filteredFiles = allFiles.filter((file) => {
663
+ if (isProtectedEnvFile(file)) {
664
+ info(`\u4FDD\u8B77\u5BFE\u8C61\u30D5\u30A1\u30A4\u30EB\u3092\u9664\u5916: ${file}`);
665
+ return false;
666
+ }
667
+ if (isExcludedFile(file)) {
668
+ info(`\u9664\u5916\u30D1\u30BF\u30FC\u30F3\u306B\u30DE\u30C3\u30C1: ${file}`);
669
+ return false;
670
+ }
671
+ return true;
672
+ });
673
+ success(`${filteredFiles.length}\u500B\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u53CE\u96C6\u3057\u307E\u3057\u305F`);
674
+ return filteredFiles.sort();
717
675
  } catch (error2) {
718
- info("direnv allow \u3092\u30B9\u30AD\u30C3\u30D7\u3057\u307E\u3057\u305F");
719
- }
720
- }
721
-
722
- // src/generators/tools/dotenvx.ts
723
- import { join as join5 } from "path";
724
-
725
- // src/utils/package-json.ts
726
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
727
- import { join as join4 } from "path";
728
- function readPackageJson(targetDir) {
729
- const packageJsonPath2 = join4(targetDir, "package.json");
730
- if (!fileExists(packageJsonPath2)) {
731
- return {};
732
- }
733
- const content = readFileSync3(packageJsonPath2, "utf-8");
734
- return JSON.parse(content);
735
- }
736
- function writePackageJson(targetDir, data) {
737
- const packageJsonPath2 = join4(targetDir, "package.json");
738
- const content = JSON.stringify(data, null, 2);
739
- writeFileSync3(packageJsonPath2, `${content}
740
- `, "utf-8");
741
- }
742
- function addScripts(targetDir, scripts) {
743
- const pkg = readPackageJson(targetDir);
744
- pkg.scripts = { ...pkg.scripts, ...scripts };
745
- writePackageJson(targetDir, pkg);
746
- }
747
- function addDependencies(targetDir, dependencies, dev = false) {
748
- const pkg = readPackageJson(targetDir);
749
- if (dev) {
750
- pkg.devDependencies = { ...pkg.devDependencies, ...dependencies };
751
- } else {
752
- pkg.dependencies = { ...pkg.dependencies, ...dependencies };
676
+ error(`\u30D5\u30A1\u30A4\u30EB\u53CE\u96C6\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F: ${error2}`);
677
+ throw error2;
753
678
  }
754
- writePackageJson(targetDir, pkg);
755
- }
756
- function addVoltaField(targetDir, nodeVersion, pnpmVersion) {
757
- const pkg = readPackageJson(targetDir);
758
- pkg.volta = {
759
- node: nodeVersion,
760
- pnpm: pnpmVersion
761
- };
762
- writePackageJson(targetDir, pkg);
763
- }
764
- function addLintStaged(targetDir, config) {
765
- const pkg = readPackageJson(targetDir);
766
- pkg["lint-staged"] = { ...pkg["lint-staged"], ...config };
767
- writePackageJson(targetDir, pkg);
768
- }
769
-
770
- // src/generators/tools/dotenvx.ts
771
- var ENV_EXAMPLE_CONTENT = `# Environment variables template
772
- # Copy this file to .env and fill in the values
773
-
774
- # Database
775
- DATABASE_URL="postgresql://user:password@localhost:25432/dbname"
776
-
777
- # NextAuth
778
- NEXTAUTH_URL="http://localhost:3000"
779
- NEXTAUTH_SECRET="your-secret-here"
780
-
781
- # OAuth (if using)
782
- # GOOGLE_CLIENT_ID=""
783
- # GOOGLE_CLIENT_SECRET=""
784
- # GITHUB_CLIENT_ID=""
785
- # GITHUB_CLIENT_SECRET=""
786
- `;
787
- function setupDotenvx(options) {
788
- const { targetDir, conflictStrategy } = options;
789
- addDependencies(targetDir, {
790
- "@dotenvx/dotenvx": "^1.29.0"
791
- });
792
- addScripts(targetDir, {
793
- "env:encrypt": "dotenvx encrypt",
794
- "env:decrypt": "dotenvx decrypt"
795
- });
796
- const envExamplePath = join5(targetDir, ".env.example");
797
- writeWithStrategy(envExamplePath, ENV_EXAMPLE_CONTENT, conflictStrategy);
798
- }
799
-
800
- // src/generators/tools/volta.ts
801
- import { join as join6 } from "path";
802
- var NODE_VERSION = "22.16.0";
803
- var PNPM_VERSION = "9.15.0";
804
- var NODE_VERSION_CONTENT = `${NODE_VERSION}
805
- `;
806
- function setupVolta(options) {
807
- const { targetDir, conflictStrategy } = options;
808
- addVoltaField(targetDir, NODE_VERSION, PNPM_VERSION);
809
- const nodeVersionPath = join6(targetDir, ".node-version");
810
- writeWithStrategy(nodeVersionPath, NODE_VERSION_CONTENT, conflictStrategy);
811
679
  }
812
680
 
813
- // src/generators/tools/biome.ts
814
- import { join as join7 } from "path";
815
- var BIOME_CONFIG = `{
816
- "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
817
- "vcs": {
818
- "enabled": true,
819
- "clientKind": "git",
820
- "useIgnoreFile": true
681
+ // src/prompts/sync.ts
682
+ import inquirer3 from "inquirer";
683
+ import * as fs from "fs";
684
+ import * as path from "path";
685
+ var CATEGORY_CONFIGS = {
686
+ env: {
687
+ name: "\u74B0\u5883\u8A2D\u5B9A",
688
+ description: ".env*, .envrc, .volta, .node-version",
689
+ patterns: [".env*", ".envrc", ".volta", ".node-version"],
690
+ defaultChecked: true,
691
+ firstRunDefault: true
821
692
  },
822
- "files": {
823
- "ignoreUnknown": false,
824
- "ignore": ["node_modules", "dist", ".next", "out", "build", "coverage"]
693
+ tools: {
694
+ name: "\u958B\u767A\u30C4\u30FC\u30EB",
695
+ description: "biome.json, .prettierrc, .editorconfig, .vscode/",
696
+ patterns: ["biome.json", ".prettierrc*", ".editorconfig", ".vscode/"],
697
+ defaultChecked: true,
698
+ firstRunDefault: true
825
699
  },
826
- "formatter": {
827
- "enabled": true,
828
- "indentStyle": "space",
829
- "indentWidth": 2,
830
- "lineEnding": "lf",
831
- "lineWidth": 100
700
+ git: {
701
+ name: "Git\u8A2D\u5B9A",
702
+ description: ".gitignore, .gitattributes",
703
+ patterns: [".gitignore", ".gitattributes"],
704
+ defaultChecked: false,
705
+ firstRunDefault: true
832
706
  },
833
- "organizeImports": {
834
- "enabled": true
707
+ "git-hooks": {
708
+ name: "Git Hooks",
709
+ description: ".husky/",
710
+ patterns: [".husky/"],
711
+ defaultChecked: false,
712
+ firstRunDefault: true
835
713
  },
836
- "linter": {
837
- "enabled": true,
838
- "rules": {
839
- "recommended": true
840
- }
714
+ github: {
715
+ name: "CI/CD",
716
+ description: ".github/workflows/, .github/actions/",
717
+ patterns: [".github/workflows/", ".github/actions/", ".github/dependabot.yml"],
718
+ defaultChecked: false
841
719
  },
842
- "javascript": {
843
- "formatter": {
844
- "quoteStyle": "double",
845
- "trailingCommas": "es5",
846
- "semicolons": "always",
847
- "arrowParentheses": "always"
848
- }
849
- }
850
- }
851
- `;
852
- var VSCODE_SETTINGS = `{
853
- "editor.defaultFormatter": "biomejs.biome",
854
- "editor.formatOnSave": true,
855
- "editor.codeActionsOnSave": {
856
- "quickfix.biome": "explicit",
857
- "source.organizeImports.biome": "explicit"
720
+ docker: {
721
+ name: "\u30B3\u30F3\u30C6\u30CA",
722
+ description: "Dockerfile*, docker-compose.yml, .dockerignore",
723
+ patterns: ["Dockerfile*", "docker-compose*.yml", ".dockerignore"],
724
+ defaultChecked: false
858
725
  },
859
- "[javascript]": {
860
- "editor.defaultFormatter": "biomejs.biome"
726
+ monorepo: {
727
+ name: "\u30E2\u30CE\u30EC\u30DD\u69CB\u6210",
728
+ description: "turbo.json, pnpm-workspace.yaml",
729
+ patterns: ["turbo.json", "pnpm-workspace.yaml"],
730
+ defaultChecked: false,
731
+ firstRunDefault: true
861
732
  },
862
- "[typescript]": {
863
- "editor.defaultFormatter": "biomejs.biome"
733
+ "root-config": {
734
+ name: "\u30EB\u30FC\u30C8\u8A2D\u5B9A",
735
+ description: "package.json, tsconfig.json",
736
+ patterns: ["package.json", "tsconfig.json"],
737
+ defaultChecked: false,
738
+ firstRunDefault: true
864
739
  },
865
- "[json]": {
866
- "editor.defaultFormatter": "biomejs.biome"
867
- }
868
- }
869
- `;
870
- function setupBiome(options) {
871
- const { targetDir, conflictStrategy } = options;
872
- const biomeConfigPath = join7(targetDir, "biome.json");
873
- writeWithStrategy(biomeConfigPath, BIOME_CONFIG, conflictStrategy);
874
- addDependencies(
875
- targetDir,
876
- {
877
- "@biomejs/biome": "^1.9.4"
878
- },
879
- true
880
- );
881
- addScripts(targetDir, {
882
- lint: "biome lint .",
883
- "lint:fix": "biome lint --write .",
884
- format: "biome format .",
885
- "format:fix": "biome format --write ."
886
- });
887
- const vscodeDir = join7(targetDir, ".vscode");
888
- ensureDir(vscodeDir);
889
- const vscodeSettingsPath = join7(vscodeDir, "settings.json");
890
- writeWithStrategy(vscodeSettingsPath, VSCODE_SETTINGS, conflictStrategy);
891
- }
892
-
893
- // src/generators/tools/husky.ts
894
- import { join as join8 } from "path";
895
- import { writeFileSync as writeFileSync4 } from "fs";
896
- var PRE_COMMIT_HOOK = `#!/usr/bin/env sh
897
- . "$(dirname -- "$0")/_/husky.sh"
898
-
899
- pnpm lint-staged
900
- `;
901
- function setupHusky(options) {
902
- const { targetDir } = options;
903
- addDependencies(
904
- targetDir,
905
- {
906
- husky: "^9.1.7",
907
- "lint-staged": "^15.2.11"
908
- },
909
- true
910
- );
911
- addScripts(targetDir, {
912
- prepare: "husky"
913
- });
914
- addLintStaged(targetDir, {
915
- "*.{js,jsx,ts,tsx}": ["biome format --write", "biome lint --write"],
916
- "*.{json,md,yml,yaml}": ["biome format --write"]
917
- });
918
- const huskyDir = join8(targetDir, ".husky");
919
- ensureDir(huskyDir);
920
- const preCommitPath = join8(huskyDir, "pre-commit");
921
- writeFileSync4(preCommitPath, PRE_COMMIT_HOOK, { mode: 493 });
922
- }
923
-
924
- // src/commands/setup.ts
925
- async function setupCommand() {
926
- const targetDir = process.cwd();
927
- const packageJsonPath2 = join9(targetDir, "package.json");
928
- if (!existsSync4(packageJsonPath2)) {
929
- error("\u30A8\u30E9\u30FC: package.json\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093");
930
- info("\u3053\u306E\u30B3\u30DE\u30F3\u30C9\u306F\u65E2\u5B58\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3067\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044");
931
- process.exit(1);
932
- }
933
- info("\u65E2\u5B58\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3078\u306E\u30C4\u30FC\u30EB\u8FFD\u52A0\u3092\u958B\u59CB\u3057\u307E\u3059");
934
- info("");
935
- const config = await promptSetupConfig();
936
- info("");
937
- info("\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u3092\u958B\u59CB\u3057\u307E\u3059...");
938
- info("");
939
- const options = {
940
- targetDir,
941
- conflictStrategy: config.conflictStrategy
942
- };
943
- let setupCount = 0;
944
- if (config.tools.direnv) {
945
- const spin = ora3("direnv \u3092\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u3057\u3066\u3044\u307E\u3059...").start();
946
- try {
947
- setupDirenv(options);
948
- spin.succeed("direnv \u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u5B8C\u4E86");
949
- setupCount++;
950
- } catch (error2) {
951
- spin.fail("direnv \u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u5931\u6557");
952
- error(
953
- error2 instanceof Error ? error2.message : "\u4E88\u671F\u3057\u306A\u3044\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
954
- );
955
- }
956
- }
957
- if (config.tools.dotenvx) {
958
- const spin = ora3("dotenvx \u3092\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u3057\u3066\u3044\u307E\u3059...").start();
959
- try {
960
- setupDotenvx(options);
961
- spin.succeed("dotenvx \u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u5B8C\u4E86");
962
- setupCount++;
963
- } catch (error2) {
964
- spin.fail("dotenvx \u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u5931\u6557");
965
- error(
966
- error2 instanceof Error ? error2.message : "\u4E88\u671F\u3057\u306A\u3044\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
967
- );
968
- }
969
- }
970
- if (config.tools.volta) {
971
- const spin = ora3("Volta \u3092\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u3057\u3066\u3044\u307E\u3059...").start();
972
- try {
973
- setupVolta(options);
974
- spin.succeed("Volta \u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u5B8C\u4E86");
975
- setupCount++;
976
- } catch (error2) {
977
- spin.fail("Volta \u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u5931\u6557");
978
- error(
979
- error2 instanceof Error ? error2.message : "\u4E88\u671F\u3057\u306A\u3044\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
980
- );
981
- }
740
+ scripts: {
741
+ name: "\u30B9\u30AF\u30EA\u30D7\u30C8",
742
+ description: "scripts/ \u914D\u4E0B",
743
+ patterns: ["scripts/**"],
744
+ defaultChecked: false
745
+ },
746
+ apps: {
747
+ name: "\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3",
748
+ description: "apps/ \u914D\u4E0B\uFF08\u6B21\u306E\u753B\u9762\u3067\u500B\u5225\u9078\u629E\uFF09",
749
+ patterns: ["apps/**"],
750
+ defaultChecked: false,
751
+ firstRunDefault: true,
752
+ requiresDetailSelection: true
753
+ },
754
+ packages: {
755
+ name: "\u5171\u901A\u30D1\u30C3\u30B1\u30FC\u30B8",
756
+ description: "packages/ \u914D\u4E0B\uFF08\u6B21\u306E\u753B\u9762\u3067\u500B\u5225\u9078\u629E\uFF09",
757
+ patterns: ["packages/**"],
758
+ defaultChecked: false,
759
+ firstRunDefault: true,
760
+ requiresDetailSelection: true
761
+ },
762
+ docs: {
763
+ name: "\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8",
764
+ description: "README.md, docs/",
765
+ patterns: ["README.md", "docs/**"],
766
+ defaultChecked: false
982
767
  }
983
- if (config.tools.biome) {
984
- const spin = ora3("Biome \u3092\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u3057\u3066\u3044\u307E\u3059...").start();
985
- try {
986
- setupBiome(options);
987
- spin.succeed("Biome \u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u5B8C\u4E86");
988
- setupCount++;
989
- } catch (error2) {
990
- spin.fail("Biome \u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u5931\u6557");
991
- error(
992
- error2 instanceof Error ? error2.message : "\u4E88\u671F\u3057\u306A\u3044\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
993
- );
768
+ };
769
+ function getAvailableApps(templateDir) {
770
+ const appsDir = path.join(templateDir, "apps");
771
+ try {
772
+ if (!fs.existsSync(appsDir)) {
773
+ return [];
994
774
  }
775
+ return fs.readdirSync(appsDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
776
+ } catch (error2) {
777
+ console.error(`Error reading apps directory: ${error2}`);
778
+ return [];
995
779
  }
996
- if (config.tools.husky) {
997
- const spin = ora3("Husky \u3092\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u3057\u3066\u3044\u307E\u3059...").start();
998
- try {
999
- setupHusky(options);
1000
- spin.succeed("Husky \u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u5B8C\u4E86");
1001
- setupCount++;
1002
- } catch (error2) {
1003
- spin.fail("Husky \u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u5931\u6557");
1004
- error(
1005
- error2 instanceof Error ? error2.message : "\u4E88\u671F\u3057\u306A\u3044\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
1006
- );
780
+ }
781
+ function getAvailablePackages(templateDir) {
782
+ const packagesDir = path.join(templateDir, "packages");
783
+ try {
784
+ if (!fs.existsSync(packagesDir)) {
785
+ return [];
1007
786
  }
787
+ return fs.readdirSync(packagesDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
788
+ } catch (error2) {
789
+ console.error(`Error reading packages directory: ${error2}`);
790
+ return [];
1008
791
  }
1009
- info("");
1010
- if (config.tools.direnv) {
1011
- await promptDirenvAllow(targetDir);
1012
- info("");
1013
- }
1014
- success(`\u2705 \u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u304C\u5B8C\u4E86\u3057\u307E\u3057\u305F\uFF01\uFF08${setupCount}\u500B\u306E\u30C4\u30FC\u30EB\uFF09`);
1015
- info("");
1016
- info("\u6B21\u306E\u30B9\u30C6\u30C3\u30D7:");
1017
- if (config.tools.direnv) {
1018
- info(" 1. .envrc \u3092\u7DE8\u96C6\u3057\u3066\u74B0\u5883\u5909\u6570\u3092\u8A2D\u5B9A");
1019
- info(" 2. direnv allow \u3092\u5B9F\u884C\uFF08\u307E\u3060\u306E\u5834\u5408\uFF09");
1020
- }
1021
- if (config.tools.dotenvx) {
1022
- info(" - .env.example \u3092\u30B3\u30D4\u30FC\u3057\u3066 .env \u3092\u4F5C\u6210");
1023
- info(" - \u5FC5\u8981\u306B\u5FDC\u3058\u3066 pnpm env:encrypt \u3067\u6697\u53F7\u5316");
1024
- }
1025
- if (config.tools.biome) {
1026
- info(" - pnpm lint \u3067\u30B3\u30FC\u30C9\u3092\u30C1\u30A7\u30C3\u30AF");
1027
- info(" - pnpm format:fix \u3067\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8");
1028
- }
1029
- if (config.tools.husky) {
1030
- info(" - pnpm install \u3067Husky\u30D5\u30C3\u30AF\u3092\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB");
1031
- }
1032
- info("");
1033
- success("\u958B\u767A\u3092\u958B\u59CB\u3067\u304D\u307E\u3059\uFF01");
1034
- }
1035
-
1036
- // src/commands/add.ts
1037
- import { existsSync as existsSync6 } from "fs";
1038
- import { readFile as readFile2 } from "fs/promises";
1039
- import { dirname as dirname4, join as join13 } from "path";
1040
- import { fileURLToPath as fileURLToPath2 } from "url";
1041
- import ora4 from "ora";
1042
-
1043
- // src/prompts/add.ts
1044
- import inquirer5 from "inquirer";
1045
- function getDefaultAddConfig() {
1046
- return {
1047
- components: {
1048
- packages: true,
1049
- apps: true,
1050
- config: true
1051
- },
1052
- packageComponents: ["front-core", "server-core", "config", "ui"],
1053
- appComponents: ["web"],
1054
- dryRun: false
1055
- };
1056
792
  }
1057
- async function promptAddConfig(dryRun) {
1058
- const componentAnswers = await inquirer5.prompt([
793
+ async function promptSyncCategories(templateDir, isFirstRun = false) {
794
+ const categoryAnswers = await inquirer3.prompt([
1059
795
  {
1060
796
  type: "checkbox",
1061
- name: "components",
1062
- message: "\u8FFD\u52A0\u3059\u308B\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8\u3092\u9078\u629E\uFF08Space\u3067\u9078\u629E\u3001Enter\u3067\u78BA\u5B9A\uFF09:",
1063
- choices: [
1064
- {
1065
- name: "packages/ - \u5171\u901A\u30D1\u30C3\u30B1\u30FC\u30B8\uFF08front-core, server-core, config, ui\uFF09",
1066
- value: "packages",
1067
- checked: true
1068
- },
1069
- {
1070
- name: "apps/ - \u30A2\u30D7\u30EA\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8",
1071
- value: "apps",
1072
- checked: true
1073
- },
797
+ name: "categories",
798
+ message: "\u540C\u671F\u3059\u308B\u9805\u76EE\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044\uFF08Space\u3067\u9078\u629E\u3001Enter\u3067\u78BA\u5B9A\uFF09:",
799
+ choices: Object.entries(CATEGORY_CONFIGS).map(([key, config]) => ({
800
+ name: `${config.name} - ${config.description}`,
801
+ value: key,
802
+ checked: isFirstRun ? config.firstRunDefault ?? config.defaultChecked ?? false : config.defaultChecked ?? false
803
+ }))
804
+ }
805
+ ]);
806
+ const selectedCategories = categoryAnswers.categories;
807
+ const hasApps = selectedCategories.includes("apps");
808
+ const hasPackages = selectedCategories.includes("packages");
809
+ const hasRootConfig = selectedCategories.includes("root-config");
810
+ let appsDetail;
811
+ let packagesDetail;
812
+ let packageJsonSections;
813
+ let conflictStrategy = "merge";
814
+ if (hasApps) {
815
+ const availableApps = getAvailableApps(templateDir);
816
+ if (availableApps.length > 0) {
817
+ const appsAnswers = await inquirer3.prompt([
1074
818
  {
1075
- name: "\u76F4\u4E0B\u8A2D\u5B9A\u30D5\u30A1\u30A4\u30EB - turbo.json, pnpm-workspace.yaml \u7B49",
1076
- value: "config",
1077
- checked: true
819
+ type: "checkbox",
820
+ name: "apps",
821
+ message: "\u540C\u671F\u3059\u308B\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u3092\u9078\u629E:",
822
+ choices: availableApps.map((app) => ({
823
+ name: app,
824
+ value: app,
825
+ checked: true
826
+ })),
827
+ validate: (input) => {
828
+ if (input.length === 0) {
829
+ return "\u5C11\u306A\u304F\u3068\u30821\u3064\u306E\u30A2\u30D7\u30EA\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044";
830
+ }
831
+ return true;
832
+ }
1078
833
  }
1079
- ]
834
+ ]);
835
+ appsDetail = appsAnswers.apps;
836
+ } else {
837
+ console.warn("\u8B66\u544A: apps/ \u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u304C\u898B\u3064\u304B\u3089\u306A\u3044\u304B\u3001\u30A2\u30D7\u30EA\u304C\u5B58\u5728\u3057\u307E\u305B\u3093");
838
+ appsDetail = [];
1080
839
  }
1081
- ]);
1082
- const selectedComponents = componentAnswers.components;
1083
- const hasPackages = selectedComponents.includes("packages");
1084
- const hasApps = selectedComponents.includes("apps");
1085
- const hasConfig = selectedComponents.includes("config");
1086
- let packageComponents = [];
840
+ }
1087
841
  if (hasPackages) {
1088
- const packageAnswers = await inquirer5.prompt([
1089
- {
1090
- type: "checkbox",
1091
- name: "packages",
1092
- message: "\u8FFD\u52A0\u3059\u308B\u30D1\u30C3\u30B1\u30FC\u30B8\u3092\u9078\u629E:",
1093
- choices: [
1094
- {
1095
- name: "front-core - \u30D5\u30ED\u30F3\u30C8\u30A8\u30F3\u30C9\u5171\u901A\u5C64\uFF08\u8A8D\u8A3C\u8A2D\u5B9A\u3001hooks\u3001utils\uFF09",
1096
- value: "front-core",
1097
- checked: true
1098
- },
1099
- {
1100
- name: "server-core - \u30D0\u30C3\u30AF\u30A8\u30F3\u30C9\u5171\u901A\u5C64\uFF08Prisma\u3001\u30C9\u30E1\u30A4\u30F3\u30ED\u30B8\u30C3\u30AF\uFF09",
1101
- value: "server-core",
1102
- checked: true
1103
- },
1104
- {
1105
- name: "config - \u5171\u901A\u8A2D\u5B9A\uFF08Biome, TypeScript, Panda CSS\uFF09",
1106
- value: "config",
1107
- checked: true
1108
- },
1109
- {
1110
- name: "ui - \u5171\u901AUI\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8\uFF08shadcn/ui\uFF09",
1111
- value: "ui",
842
+ const availablePackages = getAvailablePackages(templateDir);
843
+ if (availablePackages.length > 0) {
844
+ const packagesAnswers = await inquirer3.prompt([
845
+ {
846
+ type: "checkbox",
847
+ name: "packages",
848
+ message: "\u540C\u671F\u3059\u308B\u30D1\u30C3\u30B1\u30FC\u30B8\u3092\u9078\u629E:",
849
+ choices: availablePackages.map((pkg) => ({
850
+ name: pkg,
851
+ value: pkg,
1112
852
  checked: true
853
+ })),
854
+ validate: (input) => {
855
+ if (input.length === 0) {
856
+ return "\u5C11\u306A\u304F\u3068\u30821\u3064\u306E\u30D1\u30C3\u30B1\u30FC\u30B8\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044";
857
+ }
858
+ return true;
1113
859
  }
1114
- ],
1115
- validate: (input) => {
1116
- if (input.length === 0) {
1117
- return "\u5C11\u306A\u304F\u3068\u30821\u3064\u306E\u30D1\u30C3\u30B1\u30FC\u30B8\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044";
1118
- }
1119
- return true;
1120
860
  }
1121
- }
1122
- ]);
1123
- packageComponents = packageAnswers.packages;
861
+ ]);
862
+ packagesDetail = packagesAnswers.packages;
863
+ } else {
864
+ console.warn("\u8B66\u544A: packages/ \u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u304C\u898B\u3064\u304B\u3089\u306A\u3044\u304B\u3001\u30D1\u30C3\u30B1\u30FC\u30B8\u304C\u5B58\u5728\u3057\u307E\u305B\u3093");
865
+ packagesDetail = [];
866
+ }
1124
867
  }
1125
- let appComponents = [];
1126
- if (hasApps) {
1127
- const appAnswers = await inquirer5.prompt([
868
+ if (hasRootConfig) {
869
+ const packageJsonAnswers = await inquirer3.prompt([
1128
870
  {
1129
871
  type: "checkbox",
1130
- name: "apps",
1131
- message: "\u8FFD\u52A0\u3059\u308B\u30A2\u30D7\u30EA\u3092\u9078\u629E:",
872
+ name: "sections",
873
+ message: "package.json\u306E\u540C\u671F\u30BB\u30AF\u30B7\u30E7\u30F3\u3092\u9078\u629E:",
1132
874
  choices: [
1133
- {
1134
- name: "web - \u30E1\u30A4\u30F3\u7BA1\u7406\u753B\u9762\u30A2\u30D7\u30EA\uFF08Next.js + App Router\uFF09",
1135
- value: "web",
1136
- checked: true
1137
- }
1138
- ],
1139
- validate: (input) => {
1140
- if (input.length === 0) {
1141
- return "\u5C11\u306A\u304F\u3068\u30821\u3064\u306E\u30A2\u30D7\u30EA\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044";
1142
- }
1143
- return true;
1144
- }
875
+ { name: "scripts\uFF08\u63A8\u5968\uFF09", value: "scripts", checked: true },
876
+ { name: "engines\uFF08\u63A8\u5968\uFF09", value: "engines", checked: true },
877
+ { name: "dependencies", value: "dependencies", checked: false },
878
+ { name: "devDependencies", value: "devDependencies", checked: false },
879
+ { name: "peerDependencies", value: "peerDependencies", checked: false }
880
+ ]
1145
881
  }
1146
882
  ]);
1147
- appComponents = appAnswers.apps;
883
+ packageJsonSections = packageJsonAnswers.sections;
1148
884
  }
885
+ const strategyAnswers = await inquirer3.prompt([
886
+ {
887
+ type: "list",
888
+ name: "conflictStrategy",
889
+ message: "\u7AF6\u5408\u89E3\u6C7A\u6226\u7565\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044:",
890
+ choices: [
891
+ { name: "\u30DE\u30FC\u30AB\u30FC\u30D9\u30FC\u30B9\u30DE\u30FC\u30B8\uFF08\u63A8\u5968\uFF09", value: "merge" },
892
+ { name: "\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u3067\u4E0A\u66F8\u304D", value: "overwrite" },
893
+ { name: "\u65E2\u5B58\u30D5\u30A1\u30A4\u30EB\u512A\u5148", value: "skip" }
894
+ ],
895
+ default: "merge"
896
+ }
897
+ ]);
898
+ conflictStrategy = strategyAnswers.conflictStrategy;
1149
899
  return {
1150
- components: {
1151
- packages: hasPackages,
1152
- apps: hasApps,
1153
- config: hasConfig
1154
- },
1155
- packageComponents,
1156
- appComponents,
1157
- dryRun
900
+ categories: selectedCategories,
901
+ appsDetail,
902
+ packagesDetail,
903
+ packageJsonSections,
904
+ conflictStrategy
1158
905
  };
1159
906
  }
1160
907
 
1161
- // src/generators/partials/packages.ts
1162
- import { readdir } from "fs/promises";
1163
- import { join as join10 } from "path";
1164
-
1165
- // src/utils/merger.ts
1166
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync5, existsSync as existsSync5 } from "fs";
1167
- import { dirname as dirname3, basename } from "path";
1168
-
1169
- // src/utils/package-json-merger.ts
1170
- import inquirer6 from "inquirer";
1171
- function hasVersionConflict(existingVersion, templateVersion) {
1172
- if (existingVersion === templateVersion) {
1173
- return false;
1174
- }
1175
- const normalize = (v) => v.replace(/^[\^~]/, "");
1176
- return normalize(existingVersion) !== normalize(templateVersion);
908
+ // src/utils/backup.ts
909
+ import fsExtra2 from "fs-extra";
910
+ import { join as join4, dirname as dirname3, relative as relative2 } from "path";
911
+ var { copy, ensureDir: ensureDir2, readdir, remove, pathExists } = fsExtra2;
912
+ function getTimestamp() {
913
+ const now = /* @__PURE__ */ new Date();
914
+ const year = now.getFullYear();
915
+ const month = String(now.getMonth() + 1).padStart(2, "0");
916
+ const day = String(now.getDate()).padStart(2, "0");
917
+ const hours = String(now.getHours()).padStart(2, "0");
918
+ const minutes = String(now.getMinutes()).padStart(2, "0");
919
+ const seconds = String(now.getSeconds()).padStart(2, "0");
920
+ return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}`;
1177
921
  }
1178
- async function mergeDependenciesWithConflictDetection(existing, template) {
1179
- const merged = { ...existing };
1180
- const conflicts = [];
1181
- for (const [packageName, templateVersion] of Object.entries(template)) {
1182
- if (packageName in existing) {
1183
- const existingVersion = existing[packageName];
1184
- if (existingVersion && hasVersionConflict(existingVersion, templateVersion)) {
1185
- conflicts.push({
1186
- packageName,
1187
- existingVersion,
1188
- templateVersion
1189
- });
1190
- } else {
1191
- merged[packageName] = templateVersion;
1192
- }
1193
- } else {
1194
- merged[packageName] = templateVersion;
1195
- }
922
+ function parseBackupTimestamp(dirName) {
923
+ const match = dirName.match(/^\.einja-sync-backup-(\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2})$/);
924
+ if (!match || !match[1]) {
925
+ return null;
1196
926
  }
1197
- return { merged, conflicts };
1198
- }
1199
- async function resolveVersionConflicts(conflicts) {
1200
- const resolutions = /* @__PURE__ */ new Map();
1201
- warn(`
1202
- \u26A0\uFE0F ${conflicts.length}\u500B\u306E\u30D1\u30C3\u30B1\u30FC\u30B8\u3067\u30D0\u30FC\u30B8\u30E7\u30F3\u7AF6\u5408\u304C\u691C\u51FA\u3055\u308C\u307E\u3057\u305F:
1203
- `);
927
+ const timestampStr = match[1];
928
+ const parts = timestampStr.split("_");
929
+ if (parts.length !== 2) {
930
+ return null;
931
+ }
932
+ const [datePart, timePart] = parts;
933
+ if (!datePart || !timePart) {
934
+ return null;
935
+ }
936
+ const dateParts = datePart.split("-").map(Number);
937
+ const timeParts = timePart.split("-").map(Number);
938
+ if (dateParts.length !== 3 || timeParts.length !== 3) {
939
+ return null;
940
+ }
941
+ const [year, month, day] = dateParts;
942
+ const [hours, minutes, seconds] = timeParts;
943
+ if (year === void 0 || month === void 0 || day === void 0 || hours === void 0 || minutes === void 0 || seconds === void 0) {
944
+ return null;
945
+ }
946
+ return new Date(year, month - 1, day, hours, minutes, seconds);
947
+ }
948
+ async function createBackup(targetDir, filesToBackup) {
949
+ const timestamp = getTimestamp();
950
+ const backupDirName = `.einja-sync-backup-${timestamp}`;
951
+ const backupDir = join4(targetDir, backupDirName);
952
+ try {
953
+ await ensureDir2(backupDir);
954
+ for (const file of filesToBackup) {
955
+ const sourcePath = join4(targetDir, file);
956
+ const destPath = join4(backupDir, file);
957
+ if (!await pathExists(sourcePath)) {
958
+ continue;
959
+ }
960
+ await ensureDir2(dirname3(destPath));
961
+ await copy(sourcePath, destPath);
962
+ }
963
+ return backupDir;
964
+ } catch (error2) {
965
+ error(`\u30D0\u30C3\u30AF\u30A2\u30C3\u30D7\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${error2 instanceof Error ? error2.message : String(error2)}`);
966
+ throw error2;
967
+ }
968
+ }
969
+ async function restoreFromBackup(backupDir, targetDir) {
970
+ try {
971
+ if (!await pathExists(backupDir)) {
972
+ error(`\u30D0\u30C3\u30AF\u30A2\u30C3\u30D7\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${backupDir}`);
973
+ return false;
974
+ }
975
+ const files = await getAllFiles(backupDir);
976
+ for (const file of files) {
977
+ const sourcePath = join4(backupDir, file);
978
+ const destPath = join4(targetDir, file);
979
+ await ensureDir2(dirname3(destPath));
980
+ await copy(sourcePath, destPath, { overwrite: true });
981
+ }
982
+ return true;
983
+ } catch (error2) {
984
+ error(`\u30D0\u30C3\u30AF\u30A2\u30C3\u30D7\u304B\u3089\u306E\u5FA9\u5143\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${error2 instanceof Error ? error2.message : String(error2)}`);
985
+ return false;
986
+ }
987
+ }
988
+ async function getAllFiles(dirPath, baseDir) {
989
+ const base = baseDir ?? dirPath;
990
+ const entries = await readdir(dirPath, { withFileTypes: true });
991
+ const files = [];
992
+ for (const entry of entries) {
993
+ const fullPath = join4(dirPath, entry.name);
994
+ if (entry.isDirectory()) {
995
+ const subFiles = await getAllFiles(fullPath, base);
996
+ files.push(...subFiles);
997
+ } else {
998
+ files.push(relative2(base, fullPath));
999
+ }
1000
+ }
1001
+ return files;
1002
+ }
1003
+ async function listBackups(targetDir) {
1004
+ try {
1005
+ if (!await pathExists(targetDir)) {
1006
+ return [];
1007
+ }
1008
+ const entries = await readdir(targetDir, { withFileTypes: true });
1009
+ const backups = [];
1010
+ for (const entry of entries) {
1011
+ if (!entry.isDirectory()) {
1012
+ continue;
1013
+ }
1014
+ const timestamp = parseBackupTimestamp(entry.name);
1015
+ if (!timestamp) {
1016
+ continue;
1017
+ }
1018
+ backups.push({
1019
+ path: join4(targetDir, entry.name),
1020
+ name: entry.name,
1021
+ timestamp
1022
+ });
1023
+ }
1024
+ backups.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
1025
+ return backups;
1026
+ } catch (error2) {
1027
+ error(`\u30D0\u30C3\u30AF\u30A2\u30C3\u30D7\u4E00\u89A7\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${error2 instanceof Error ? error2.message : String(error2)}`);
1028
+ return [];
1029
+ }
1030
+ }
1031
+ async function getLatestBackup(targetDir) {
1032
+ const backups = await listBackups(targetDir);
1033
+ return backups.length > 0 ? backups[0] ?? null : null;
1034
+ }
1035
+
1036
+ // src/utils/git.ts
1037
+ import { execSync } from "child_process";
1038
+ function isGitRepository(targetDir) {
1039
+ try {
1040
+ const cwd = targetDir || process.cwd();
1041
+ execSync("git rev-parse --is-inside-work-tree", {
1042
+ cwd,
1043
+ stdio: "ignore"
1044
+ });
1045
+ return true;
1046
+ } catch {
1047
+ return false;
1048
+ }
1049
+ }
1050
+ function hasUncommittedChanges(targetDir) {
1051
+ if (!isGitRepository(targetDir)) {
1052
+ warn("\u26A0\uFE0F \u3053\u306E\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306FGit\u30EA\u30DD\u30B8\u30C8\u30EA\u3067\u306F\u3042\u308A\u307E\u305B\u3093");
1053
+ return false;
1054
+ }
1055
+ try {
1056
+ const cwd = targetDir || process.cwd();
1057
+ const output = execSync("git status --porcelain", {
1058
+ cwd,
1059
+ encoding: "utf-8"
1060
+ });
1061
+ return output.trim().length > 0;
1062
+ } catch (error2) {
1063
+ warn(`\u26A0\uFE0F Git\u30B9\u30C6\u30FC\u30BF\u30B9\u78BA\u8A8D\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F: ${error2}`);
1064
+ return false;
1065
+ }
1066
+ }
1067
+ function checkGitStatusForSync(force, targetDir) {
1068
+ if (!isGitRepository(targetDir)) {
1069
+ warn("\u26A0\uFE0F \u3053\u306E\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306FGit\u30EA\u30DD\u30B8\u30C8\u30EA\u3067\u306F\u3042\u308A\u307E\u305B\u3093");
1070
+ warn(
1071
+ " sync\u5B9F\u884C\u5F8C\u306E\u5DEE\u5206\u78BA\u8A8D\u306F `git diff` \u3067\u884C\u3046\u3053\u3068\u3092\u63A8\u5968\u3057\u307E\u3059"
1072
+ );
1073
+ return true;
1074
+ }
1075
+ if (hasUncommittedChanges(targetDir)) {
1076
+ if (!force) {
1077
+ error("\u274C \u672A\u30B3\u30DF\u30C3\u30C8\u306E\u5909\u66F4\u304C\u3042\u308A\u307E\u3059");
1078
+ error(
1079
+ " \u5909\u66F4\u3092\u30B3\u30DF\u30C3\u30C8\u3057\u3066\u304B\u3089\u5B9F\u884C\u3059\u308B\u304B\u3001--force \u30D5\u30E9\u30B0\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044"
1080
+ );
1081
+ process.exit(1);
1082
+ }
1083
+ warn("\u26A0\uFE0F \u672A\u30B3\u30DF\u30C3\u30C8\u306E\u5909\u66F4\u304C\u3042\u308A\u307E\u3059\u304C\u3001--force \u306B\u3088\u308A\u7D9A\u884C\u3057\u307E\u3059");
1084
+ warn(
1085
+ " \u554F\u984C\u304C\u767A\u751F\u3057\u305F\u5834\u5408\u306F `git checkout .` \u3067\u5FA9\u5143\u3067\u304D\u307E\u3059"
1086
+ );
1087
+ }
1088
+ return true;
1089
+ }
1090
+
1091
+ // src/utils/merger.ts
1092
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync5 } from "fs";
1093
+ import { dirname as dirname4, basename } from "path";
1094
+
1095
+ // src/utils/package-json-merger.ts
1096
+ import inquirer4 from "inquirer";
1097
+ function hasVersionConflict(existingVersion, templateVersion) {
1098
+ if (existingVersion === templateVersion) {
1099
+ return false;
1100
+ }
1101
+ const normalize = (v) => v.replace(/^[\^~]/, "");
1102
+ return normalize(existingVersion) !== normalize(templateVersion);
1103
+ }
1104
+ async function mergeDependenciesWithConflictDetection(existing, template) {
1105
+ const merged = { ...existing };
1106
+ const conflicts = [];
1107
+ for (const [packageName, templateVersion] of Object.entries(template)) {
1108
+ if (packageName in existing) {
1109
+ const existingVersion = existing[packageName];
1110
+ if (existingVersion && hasVersionConflict(existingVersion, templateVersion)) {
1111
+ conflicts.push({
1112
+ packageName,
1113
+ existingVersion,
1114
+ templateVersion
1115
+ });
1116
+ } else {
1117
+ merged[packageName] = templateVersion;
1118
+ }
1119
+ } else {
1120
+ merged[packageName] = templateVersion;
1121
+ }
1122
+ }
1123
+ return { merged, conflicts };
1124
+ }
1125
+ async function resolveVersionConflicts(conflicts) {
1126
+ const resolutions = /* @__PURE__ */ new Map();
1127
+ warn(`
1128
+ \u26A0\uFE0F ${conflicts.length}\u500B\u306E\u30D1\u30C3\u30B1\u30FC\u30B8\u3067\u30D0\u30FC\u30B8\u30E7\u30F3\u7AF6\u5408\u304C\u691C\u51FA\u3055\u308C\u307E\u3057\u305F:
1129
+ `);
1204
1130
  for (const conflict of conflicts) {
1205
1131
  warn(`\u{1F4E6} ${conflict.packageName}`);
1206
1132
  warn(` \u65E2\u5B58: ${conflict.existingVersion}`);
1207
1133
  warn(` \u30C6\u30F3\u30D7\u30EC\u30FC\u30C8: ${conflict.templateVersion}
1208
1134
  `);
1209
- const answer = await inquirer6.prompt([
1135
+ const answer = await inquirer4.prompt([
1210
1136
  {
1211
1137
  type: "list",
1212
1138
  name: "version",
@@ -1378,23 +1304,6 @@ function deepClone(value) {
1378
1304
  }
1379
1305
  return JSON.parse(JSON.stringify(value));
1380
1306
  }
1381
- async function loadSyncMetadata(targetDir) {
1382
- const metadataPath = `${targetDir}/.einja-sync.json`;
1383
- if (!existsSync5(metadataPath)) {
1384
- return null;
1385
- }
1386
- try {
1387
- const content = readFileSync4(metadataPath, "utf-8");
1388
- return JSON.parse(content);
1389
- } catch {
1390
- return null;
1391
- }
1392
- }
1393
- async function saveSyncMetadata(targetDir, metadata) {
1394
- const metadataPath = `${targetDir}/.einja-sync.json`;
1395
- ensureDir(dirname3(metadataPath));
1396
- writeFileSync5(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
1397
- }
1398
1307
  async function mergePackageJson(existingContent, templateContent, packageJsonSections) {
1399
1308
  const existingPkg = JSON.parse(existingContent);
1400
1309
  const templatePkg = JSON.parse(templateContent);
@@ -1430,10 +1339,21 @@ async function mergePackageJson(existingContent, templateContent, packageJsonSec
1430
1339
  return `${JSON.stringify(result, null, 2)}
1431
1340
  `;
1432
1341
  }
1433
- async function mergeAndWriteFile(templatePath, targetPath, syncMetadata, packageJsonSections) {
1434
- const templateContent = readFileSync4(templatePath, "utf-8");
1342
+ async function mergeAndWriteFile(templatePath, targetPath, syncMetadata, packageJsonSections, conflictStrategy = "merge", templateVariables) {
1343
+ let templateContent = readFileSync3(templatePath, "utf-8");
1344
+ if (templateVariables) {
1345
+ templateContent = replacePlaceholders(templateContent, templateVariables);
1346
+ }
1435
1347
  const targetExists = existsSync5(targetPath);
1436
- const existingContent = targetExists ? readFileSync4(targetPath, "utf-8") : null;
1348
+ const existingContent = targetExists ? readFileSync3(targetPath, "utf-8") : null;
1349
+ if (targetExists && conflictStrategy === "skip") {
1350
+ return { action: "skipped", path: targetPath };
1351
+ }
1352
+ if (targetExists && conflictStrategy === "overwrite") {
1353
+ ensureDir(dirname4(targetPath));
1354
+ writeFileSync3(targetPath, templateContent, "utf-8");
1355
+ return { action: "overwritten", path: targetPath };
1356
+ }
1437
1357
  const isJsonFile = targetPath.endsWith(".json");
1438
1358
  const isPackageJson = basename(targetPath) === "package.json";
1439
1359
  let mergedContent;
@@ -1449,1028 +1369,272 @@ async function mergeAndWriteFile(templatePath, targetPath, syncMetadata, package
1449
1369
  mergedContent = templateContent;
1450
1370
  action = "overwritten";
1451
1371
  }
1452
- } else if (isJsonFile) {
1453
- try {
1454
- const templateJson = JSON.parse(templateContent);
1455
- const existingJson = existingContent ? JSON.parse(existingContent) : null;
1456
- const jsonPaths = syncMetadata.jsonPaths || { managed: {}, seed: {} };
1457
- const fileName = targetPath.split("/").pop() || "package.json";
1458
- const mergedJson = mergeJson(templateJson, existingJson, jsonPaths, fileName);
1459
- mergedContent = JSON.stringify(mergedJson, null, 2);
1460
- action = "merged";
1461
- } catch {
1462
- mergedContent = templateContent;
1463
- action = "overwritten";
1464
- }
1465
- } else {
1466
- mergedContent = mergeTextWithMarkers(templateContent, existingContent);
1467
- if (mergedContent === existingContent) {
1468
- action = "skipped";
1469
- } else {
1470
- action = "merged";
1471
- }
1472
- }
1473
- if (action !== "skipped") {
1474
- ensureDir(dirname3(targetPath));
1475
- writeFileSync5(targetPath, mergedContent, "utf-8");
1476
- }
1477
- return { action, path: targetPath };
1478
- }
1479
- function parseMarkers(content) {
1480
- const lines = content.split("\n");
1481
- const sections = [];
1482
- let currentType = "unmanaged";
1483
- let currentStartLine = 1;
1484
- let currentContent = [];
1485
- let currentId;
1486
- for (let i = 0; i < lines.length; i++) {
1487
- const line = lines[i];
1488
- const lineNumber = i + 1;
1489
- const startMarker = parseStartMarker(line);
1490
- if (startMarker) {
1491
- if (currentType !== "unmanaged") {
1492
- currentContent.push(line);
1493
- continue;
1494
- }
1495
- if (currentContent.length > 0 || sections.length === 0) {
1496
- sections.push({
1497
- type: "unmanaged",
1498
- startLine: currentStartLine,
1499
- endLine: lineNumber - 1,
1500
- content: currentContent.join("\n")
1501
- });
1502
- }
1503
- currentType = startMarker.type;
1504
- currentId = startMarker.id;
1505
- currentStartLine = lineNumber;
1506
- currentContent = [line];
1507
- } else if (parseEndMarker(line)) {
1508
- if (currentType === "unmanaged") {
1509
- currentContent.push(line);
1510
- continue;
1511
- }
1512
- currentContent.push(line);
1513
- sections.push({
1514
- type: currentType,
1515
- startLine: currentStartLine,
1516
- endLine: lineNumber,
1517
- content: currentContent.join("\n"),
1518
- id: currentId
1519
- });
1520
- currentType = "unmanaged";
1521
- currentId = void 0;
1522
- currentStartLine = lineNumber + 1;
1523
- currentContent = [];
1524
- } else {
1525
- currentContent.push(line);
1526
- }
1527
- }
1528
- if (currentContent.length > 0 || sections.length === 0) {
1529
- sections.push({
1530
- type: currentType,
1531
- startLine: currentStartLine,
1532
- endLine: lines.length,
1533
- content: currentContent.join("\n"),
1534
- id: currentId
1535
- });
1536
- }
1537
- return sections;
1538
- }
1539
- function parseStartMarker(line) {
1540
- const markdownManagedPattern = /^<!--\s*@einja:managed:start(?:\s+id="([^"]+)")?\s*-->$/;
1541
- let match = line.match(markdownManagedPattern);
1542
- if (match) {
1543
- return { type: "managed", id: match[1] || void 0 };
1544
- }
1545
- const markdownSeedPattern = /^<!--\s*@einja:seed:start(?:\s+id="([^"]+)")?\s*-->$/;
1546
- match = line.match(markdownSeedPattern);
1547
- if (match) {
1548
- return { type: "seed", id: match[1] || void 0 };
1549
- }
1550
- const yamlManagedPattern = /^\s*#\s*@einja:managed:start(?:\s+id="([^"]+)")?\s*$/;
1551
- match = line.match(yamlManagedPattern);
1552
- if (match) {
1553
- return { type: "managed", id: match[1] || void 0 };
1554
- }
1555
- const yamlSeedPattern = /^\s*#\s*@einja:seed:start(?:\s+id="([^"]+)")?\s*$/;
1556
- match = line.match(yamlSeedPattern);
1557
- if (match) {
1558
- return { type: "seed", id: match[1] || void 0 };
1559
- }
1560
- return null;
1561
- }
1562
- function parseEndMarker(line) {
1563
- if (/^<!--\s*@einja:managed:end\s*-->$/.test(line)) {
1564
- return "managed";
1565
- }
1566
- if (/^<!--\s*@einja:seed:end\s*-->$/.test(line)) {
1567
- return "seed";
1568
- }
1569
- if (/^\s*#\s*@einja:managed:end\s*$/.test(line)) {
1570
- return "managed";
1571
- }
1572
- if (/^\s*#\s*@einja:seed:end\s*$/.test(line)) {
1573
- return "seed";
1574
- }
1575
- return null;
1576
- }
1577
- function isPathManaged(filePath, keyPath, jsonPaths) {
1578
- const managedPaths = jsonPaths.managed[filePath] || [];
1579
- return managedPaths.some(
1580
- (p) => keyPath === p || keyPath.startsWith(`${p}.`)
1581
- );
1582
- }
1583
- function isPathSeed(filePath, keyPath, jsonPaths) {
1584
- const seedPaths = jsonPaths.seed[filePath] || [];
1585
- return seedPaths.some((p) => keyPath === p || keyPath.startsWith(`${p}.`));
1586
- }
1587
-
1588
- // src/generators/partials/packages.ts
1589
- async function addPackages(options, components, syncMetadata) {
1590
- const added = [];
1591
- const skipped = [];
1592
- const merged = [];
1593
- const { targetDir, templateDir, config } = options;
1594
- for (const component of components) {
1595
- const componentName = component === "front-core" ? "front-core" : component === "server-core" ? "server-core" : component === "config" ? "config" : "ui";
1596
- const srcDir = join10(templateDir, "packages", componentName);
1597
- const destDir = join10(targetDir, "packages", componentName);
1598
- info(`Adding package component: ${componentName}`);
1599
- await copyDirectory(
1600
- srcDir,
1601
- destDir,
1602
- { added, skipped, merged },
1603
- config.dryRun,
1604
- syncMetadata
1605
- );
1606
- }
1607
- return { added, skipped, merged };
1608
- }
1609
- async function copyDirectory(srcDir, destDir, result, dryRun, syncMetadata) {
1610
- const entries = await readdir(srcDir, { withFileTypes: true });
1611
- for (const entry of entries) {
1612
- const srcPath = join10(srcDir, entry.name);
1613
- const destPath = join10(destDir, entry.name);
1614
- if (entry.isDirectory()) {
1615
- await copyDirectory(srcPath, destPath, result, dryRun, syncMetadata);
1616
- } else {
1617
- if (!dryRun) {
1618
- const mergeResult = await mergeAndWriteFile(
1619
- srcPath,
1620
- destPath,
1621
- syncMetadata
1622
- );
1623
- if (mergeResult.action === "created") {
1624
- result.added.push(destPath);
1625
- } else if (mergeResult.action === "skipped") {
1626
- result.skipped.push(destPath);
1627
- } else if (mergeResult.action === "merged") {
1628
- result.merged.push(destPath);
1629
- }
1630
- } else {
1631
- result.skipped.push(destPath);
1632
- }
1633
- }
1634
- }
1635
- }
1636
-
1637
- // src/generators/partials/apps.ts
1638
- import { readdir as readdir2 } from "fs/promises";
1639
- import { join as join11 } from "path";
1640
- async function addApps(options, components, syncMetadata) {
1641
- const added = [];
1642
- const skipped = [];
1643
- const merged = [];
1644
- const { targetDir, templateDir, config } = options;
1645
- for (const component of components) {
1646
- const componentName = component === "web" ? "web" : component;
1647
- const srcDir = join11(templateDir, "apps", componentName);
1648
- const destDir = join11(targetDir, "apps", componentName);
1649
- info(`Adding app component: ${componentName}`);
1650
- await copyDirectory2(
1651
- srcDir,
1652
- destDir,
1653
- { added, skipped, merged },
1654
- config.dryRun,
1655
- syncMetadata
1656
- );
1657
- }
1658
- return { added, skipped, merged };
1659
- }
1660
- async function copyDirectory2(srcDir, destDir, result, dryRun, syncMetadata) {
1661
- const entries = await readdir2(srcDir, { withFileTypes: true });
1662
- for (const entry of entries) {
1663
- const srcPath = join11(srcDir, entry.name);
1664
- const destPath = join11(destDir, entry.name);
1665
- if (entry.isDirectory()) {
1666
- await copyDirectory2(srcPath, destPath, result, dryRun, syncMetadata);
1667
- } else {
1668
- if (!dryRun) {
1669
- const mergeResult = await mergeAndWriteFile(
1670
- srcPath,
1671
- destPath,
1672
- syncMetadata
1673
- );
1674
- if (mergeResult.action === "created") {
1675
- result.added.push(destPath);
1676
- } else if (mergeResult.action === "skipped") {
1677
- result.skipped.push(destPath);
1678
- } else if (mergeResult.action === "merged") {
1679
- result.merged.push(destPath);
1680
- }
1681
- } else {
1682
- result.skipped.push(destPath);
1683
- }
1684
- }
1685
- }
1686
- }
1687
-
1688
- // src/generators/partials/config.ts
1689
- import { readdir as readdir3, readFile } from "fs/promises";
1690
- import { join as join12, relative as relative2, sep } from "path";
1691
- async function addConfigFiles(options, syncMetadata) {
1692
- const added = [];
1693
- const skipped = [];
1694
- const merged = [];
1695
- const { targetDir, templateDir, config } = options;
1696
- info("Adding config files from template root");
1697
- const excludedPaths = /* @__PURE__ */ new Set([
1698
- ".claude",
1699
- "docs/einja",
1700
- "CLAUDE.md",
1701
- ".mcp.json",
1702
- "node_modules",
1703
- ".turbo",
1704
- "next-env.d.ts",
1705
- "styled-system",
1706
- "pnpm-lock.yaml",
1707
- "package-lock.json",
1708
- "packages",
1709
- "apps"
1710
- ]);
1711
- const gitignorePatterns = await loadGitignorePatterns(templateDir);
1712
- await copyConfigDirectory(
1713
- templateDir,
1714
- targetDir,
1715
- templateDir,
1716
- // rootDir として templateDir を渡す
1717
- { added, skipped, merged },
1718
- config.dryRun,
1719
- excludedPaths,
1720
- gitignorePatterns,
1721
- syncMetadata
1722
- );
1723
- return { added, skipped, merged };
1724
- }
1725
- async function loadGitignorePatterns(templateDir) {
1726
- const patterns = /* @__PURE__ */ new Set();
1727
- const gitignorePath = join12(templateDir, ".gitignore");
1728
- try {
1729
- const content = await readFile(gitignorePath, "utf-8");
1730
- const lines = content.split("\n");
1731
- for (const line of lines) {
1732
- const trimmed = line.trim();
1733
- if (trimmed && !trimmed.startsWith("#")) {
1734
- const pattern = trimmed.startsWith("/") ? trimmed.slice(1) : trimmed;
1735
- patterns.add(pattern);
1736
- }
1737
- }
1738
- } catch {
1739
- }
1740
- return patterns;
1741
- }
1742
- async function copyConfigDirectory(srcDir, destDir, rootDir, result, dryRun, excludedPaths, gitignorePatterns, syncMetadata) {
1743
- const entries = await readdir3(srcDir, { withFileTypes: true });
1744
- for (const entry of entries) {
1745
- const srcPath = join12(srcDir, entry.name);
1746
- const destPath = join12(destDir, entry.name);
1747
- const rawRelativePath = relative2(rootDir, srcPath);
1748
- const relativePath = rawRelativePath.split(sep).join("/");
1749
- if (shouldExclude(relativePath, excludedPaths, gitignorePatterns)) {
1750
- continue;
1751
- }
1752
- if (entry.isDirectory()) {
1753
- await copyConfigDirectory(
1754
- srcPath,
1755
- destPath,
1756
- rootDir,
1757
- // rootDir を引き継ぐ
1758
- result,
1759
- dryRun,
1760
- excludedPaths,
1761
- gitignorePatterns,
1762
- syncMetadata
1763
- );
1764
- } else {
1765
- if (!dryRun) {
1766
- const mergeResult = await mergeAndWriteFile(
1767
- srcPath,
1768
- destPath,
1769
- syncMetadata
1770
- );
1771
- if (mergeResult.action === "created") {
1772
- result.added.push(destPath);
1773
- } else if (mergeResult.action === "skipped") {
1774
- result.skipped.push(destPath);
1775
- } else if (mergeResult.action === "merged") {
1776
- result.merged.push(destPath);
1777
- }
1778
- } else {
1779
- result.skipped.push(destPath);
1780
- }
1781
- }
1782
- }
1783
- }
1784
- function shouldExclude(relativePath, excludedPaths, gitignorePatterns) {
1785
- for (const excluded of excludedPaths) {
1786
- if (relativePath === excluded || relativePath.startsWith(`${excluded}/`)) {
1787
- return true;
1788
- }
1789
- }
1790
- for (const pattern of gitignorePatterns) {
1791
- if (matchPattern(relativePath, pattern)) {
1792
- return true;
1793
- }
1794
- }
1795
- return false;
1796
- }
1797
- function matchPattern(path2, pattern) {
1798
- if (pattern.endsWith("/")) {
1799
- const dirPattern = pattern.slice(0, -1);
1800
- return path2 === dirPattern || path2.startsWith(`${dirPattern}/`);
1801
- }
1802
- if (pattern.includes("*")) {
1803
- const regexPattern = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*");
1804
- return new RegExp(`^${regexPattern}$`).test(path2);
1805
- }
1806
- return path2 === pattern || path2.startsWith(`${pattern}/`);
1807
- }
1808
-
1809
- // src/commands/add.ts
1810
- function getTemplatePath2(templateName) {
1811
- const __filename3 = fileURLToPath2(import.meta.url);
1812
- const __dirname3 = dirname4(__filename3);
1813
- const distPath = join13(__dirname3, "../templates", templateName);
1814
- const srcPath = join13(__dirname3, "../../templates", templateName);
1815
- if (existsSync6(distPath)) {
1816
- return distPath;
1817
- }
1818
- if (existsSync6(srcPath)) {
1819
- return srcPath;
1820
- }
1821
- return distPath;
1822
- }
1823
- async function loadTemplateSyncMetadata(templateDir) {
1824
- const syncFilePath = join13(templateDir, ".einja-sync.json");
1825
- try {
1826
- const content = await readFile2(syncFilePath, "utf-8");
1827
- return JSON.parse(content);
1828
- } catch {
1829
- return null;
1830
- }
1831
- }
1832
- function mergeSyncMetadata(template, existing) {
1833
- const now = (/* @__PURE__ */ new Date()).toISOString();
1834
- const jsonPaths = template?.jsonPaths ?? existing?.jsonPaths ?? { managed: {}, seed: {} };
1835
- return {
1836
- version: template?.version ?? existing?.version ?? "1.0.0",
1837
- lastSync: now,
1838
- templateVersion: template?.templateVersion ?? "1.0.0",
1839
- files: { ...existing?.files ?? {}, ...template?.files ?? {} },
1840
- jsonPaths
1841
- };
1842
- }
1843
- async function addCommand(options) {
1844
- try {
1845
- const targetDir = process.cwd();
1846
- let config;
1847
- if (options.skipPrompts) {
1848
- config = getDefaultAddConfig();
1849
- info("\u30C7\u30D5\u30A9\u30EB\u30C8\u8A2D\u5B9A\u3092\u4F7F\u7528\u3057\u307E\u3059\uFF08\u3059\u3079\u3066\u306E\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8\u3092\u9078\u629E\uFF09");
1850
- } else {
1851
- config = await promptAddConfig(options.dryRun);
1852
- }
1853
- config.dryRun = options.dryRun;
1854
- if (config.dryRun) {
1855
- warn("dry-run\u30E2\u30FC\u30C9: \u5B9F\u969B\u306E\u30D5\u30A1\u30A4\u30EB\u64CD\u4F5C\u306F\u884C\u3044\u307E\u305B\u3093");
1856
- info("\n--- \u8FFD\u52A0\u4E88\u5B9A\u306E\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8 ---");
1857
- if (config.components.packages) {
1858
- info(
1859
- `- packages/: ${config.packageComponents.join(", ")}`
1860
- );
1861
- }
1862
- if (config.components.apps) {
1863
- info(`- apps/: ${config.appComponents.join(", ")}`);
1864
- }
1865
- if (config.components.config) {
1866
- info("- \u76F4\u4E0B\u8A2D\u5B9A\u30D5\u30A1\u30A4\u30EB: turbo.json, pnpm-workspace.yaml \u7B49");
1867
- }
1868
- info("---\n");
1869
- }
1870
- const templateDir = getTemplatePath2("default");
1871
- if (!existsSync6(templateDir)) {
1872
- throw new Error("\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: default");
1873
- }
1874
- const templateMetadata = await loadTemplateSyncMetadata(templateDir);
1875
- const existingMetadata = await loadSyncMetadata(targetDir);
1876
- const syncMetadata = mergeSyncMetadata(templateMetadata, existingMetadata);
1877
- const addOptions = {
1878
- targetDir,
1879
- templateDir,
1880
- config
1881
- };
1882
- let totalAdded = 0;
1883
- let totalMerged = 0;
1884
- let totalSkipped = 0;
1885
- if (config.components.packages && config.packageComponents.length > 0) {
1886
- const spinner = ora4("\u30D1\u30C3\u30B1\u30FC\u30B8\u3092\u8FFD\u52A0\u4E2D...").start();
1887
- try {
1888
- const result = await addPackages(
1889
- addOptions,
1890
- config.packageComponents,
1891
- syncMetadata
1892
- );
1893
- totalAdded += result.added.length;
1894
- totalMerged += result.merged.length;
1895
- totalSkipped += result.skipped.length;
1896
- spinner.succeed(
1897
- `\u30D1\u30C3\u30B1\u30FC\u30B8\u3092\u8FFD\u52A0\u3057\u307E\u3057\u305F\uFF08\u8FFD\u52A0: ${result.added.length}, \u30DE\u30FC\u30B8: ${result.merged.length}, \u30B9\u30AD\u30C3\u30D7: ${result.skipped.length}\uFF09`
1898
- );
1899
- } catch (error2) {
1900
- spinner.fail("\u30D1\u30C3\u30B1\u30FC\u30B8\u306E\u8FFD\u52A0\u306B\u5931\u6557\u3057\u307E\u3057\u305F");
1901
- throw error2;
1902
- }
1903
- }
1904
- if (config.components.apps && config.appComponents.length > 0) {
1905
- const spinner = ora4("\u30A2\u30D7\u30EA\u3092\u8FFD\u52A0\u4E2D...").start();
1906
- try {
1907
- const result = await addApps(
1908
- addOptions,
1909
- config.appComponents,
1910
- syncMetadata
1911
- );
1912
- totalAdded += result.added.length;
1913
- totalMerged += result.merged.length;
1914
- totalSkipped += result.skipped.length;
1915
- spinner.succeed(
1916
- `\u30A2\u30D7\u30EA\u3092\u8FFD\u52A0\u3057\u307E\u3057\u305F\uFF08\u8FFD\u52A0: ${result.added.length}, \u30DE\u30FC\u30B8: ${result.merged.length}, \u30B9\u30AD\u30C3\u30D7: ${result.skipped.length}\uFF09`
1917
- );
1918
- } catch (error2) {
1919
- spinner.fail("\u30A2\u30D7\u30EA\u306E\u8FFD\u52A0\u306B\u5931\u6557\u3057\u307E\u3057\u305F");
1920
- throw error2;
1921
- }
1922
- }
1923
- if (config.components.config) {
1924
- const spinner = ora4("\u8A2D\u5B9A\u30D5\u30A1\u30A4\u30EB\u3092\u8FFD\u52A0\u4E2D...").start();
1925
- try {
1926
- const result = await addConfigFiles(addOptions, syncMetadata);
1927
- totalAdded += result.added.length;
1928
- totalMerged += result.merged.length;
1929
- totalSkipped += result.skipped.length;
1930
- spinner.succeed(
1931
- `\u8A2D\u5B9A\u30D5\u30A1\u30A4\u30EB\u3092\u8FFD\u52A0\u3057\u307E\u3057\u305F\uFF08\u8FFD\u52A0: ${result.added.length}, \u30DE\u30FC\u30B8: ${result.merged.length}, \u30B9\u30AD\u30C3\u30D7: ${result.skipped.length}\uFF09`
1932
- );
1933
- } catch (error2) {
1934
- spinner.fail("\u8A2D\u5B9A\u30D5\u30A1\u30A4\u30EB\u306E\u8FFD\u52A0\u306B\u5931\u6557\u3057\u307E\u3057\u305F");
1935
- throw error2;
1936
- }
1937
- }
1938
- if (!config.dryRun) {
1939
- syncMetadata.lastSync = (/* @__PURE__ */ new Date()).toISOString();
1940
- await saveSyncMetadata(targetDir, syncMetadata);
1941
- }
1942
- success("\n\u2713 \u8FFD\u52A0\u5B8C\u4E86\uFF01\n");
1943
- if (config.dryRun) {
1944
- info("\uFF08dry-run\u30E2\u30FC\u30C9\u306E\u305F\u3081\u3001\u5B9F\u969B\u306E\u5909\u66F4\u306F\u884C\u308F\u308C\u3066\u3044\u307E\u305B\u3093\uFF09\n");
1945
- }
1946
- info(`\u8FFD\u52A0\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB: ${totalAdded}\u500B`);
1947
- info(`\u30DE\u30FC\u30B8\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB: ${totalMerged}\u500B`);
1948
- info(`\u30B9\u30AD\u30C3\u30D7\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB: ${totalSkipped}\u500B
1949
- `);
1950
- const packageJsonPath2 = join13(targetDir, "package.json");
1951
- if (existsSync6(packageJsonPath2)) {
1952
- const packageJson2 = await import(packageJsonPath2);
1953
- const hasEinjaCli = packageJson2.default?.devDependencies?.["@einja/dev-cli"] || packageJson2.default?.dependencies?.["@einja/dev-cli"];
1954
- if (!hasEinjaCli) {
1955
- info("\u6B21\u306E\u30B9\u30C6\u30C3\u30D7:");
1956
- info("1. pnpm install");
1957
- info("2. pnpm dev:setup");
1958
- info("\n\u63A8\u5968:");
1959
- info(" einja\u958B\u767A\u652F\u63F4CLI (@einja/dev-cli) \u306E\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB:");
1960
- info(" pnpm add -D @einja/dev-cli\n");
1961
- } else {
1962
- info("\u6B21\u306E\u30B9\u30C6\u30C3\u30D7:");
1963
- info("1. pnpm install");
1964
- info("2. pnpm dev:setup\n");
1965
- }
1966
- } else {
1967
- info("\u6B21\u306E\u30B9\u30C6\u30C3\u30D7:");
1968
- info("1. pnpm install");
1969
- info("2. pnpm dev:setup\n");
1970
- }
1971
- } catch (error2) {
1972
- error("\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F:");
1973
- if (error2 instanceof Error) {
1974
- error(error2.message);
1975
- } else {
1976
- error(String(error2));
1977
- }
1978
- process.exit(1);
1979
- }
1980
- }
1981
-
1982
- // src/commands/sync.ts
1983
- import { dirname as dirname6, join as join16 } from "path";
1984
- import { fileURLToPath as fileURLToPath3 } from "url";
1985
- import fsExtra3 from "fs-extra";
1986
- import inquirer8 from "inquirer";
1987
-
1988
- // src/generators/sync.ts
1989
- import { glob as glob2 } from "glob";
1990
- var CATEGORY_PATTERNS = {
1991
- env: [".env*", ".envrc", ".volta", ".node-version"],
1992
- tools: ["biome.json", ".prettierrc*", ".editorconfig", ".vscode/**"],
1993
- git: [".gitignore", ".gitattributes"],
1994
- "git-hooks": [".husky/**"],
1995
- github: [".github/workflows/**", ".github/actions/**", ".github/dependabot.yml"],
1996
- docker: ["Dockerfile*", "docker-compose*.yml", ".dockerignore"],
1997
- monorepo: ["turbo.json", "pnpm-workspace.yaml"],
1998
- "root-config": ["package.json", "tsconfig.json"],
1999
- scripts: ["scripts/**"],
2000
- apps: ["apps/**"],
2001
- packages: ["packages/**"],
2002
- docs: ["README.md", "docs/**"]
2003
- };
2004
- var ENV_FILE_PROTECTION = {
2005
- protected: [".env.keys", ".env.personal"]
2006
- };
2007
- function isProtectedEnvFile(filePath) {
2008
- return ENV_FILE_PROTECTION.protected.some(
2009
- (pattern) => filePath.endsWith(pattern)
2010
- );
2011
- }
2012
- function extractPatternsFromCategories(categories, appsDetail, packagesDetail) {
2013
- const patterns = [];
2014
- for (const category of categories) {
2015
- const categoryPatterns = CATEGORY_PATTERNS[category];
2016
- if (!categoryPatterns) {
2017
- warn(`\u4E0D\u660E\u306A\u30AB\u30C6\u30B4\u30EA: ${category}`);
2018
- continue;
2019
- }
2020
- if (category === "apps" && appsDetail && appsDetail.length > 0) {
2021
- patterns.push(...appsDetail.map((app) => `apps/${app}/**`));
2022
- } else if (category === "packages" && packagesDetail && packagesDetail.length > 0) {
2023
- patterns.push(...packagesDetail.map((pkg) => `packages/${pkg}/**`));
2024
- } else {
2025
- patterns.push(...categoryPatterns);
2026
- }
2027
- }
2028
- return patterns;
2029
- }
2030
- async function collectSyncFiles(templateDir, categories, appsDetail, packagesDetail) {
2031
- try {
2032
- info("\u540C\u671F\u5BFE\u8C61\u30D5\u30A1\u30A4\u30EB\u3092\u53CE\u96C6\u4E2D...");
2033
- const patterns = extractPatternsFromCategories(
2034
- categories,
2035
- appsDetail,
2036
- packagesDetail
2037
- );
2038
- if (patterns.length === 0) {
2039
- warn("\u540C\u671F\u5BFE\u8C61\u306E\u30D1\u30BF\u30FC\u30F3\u304C\u3042\u308A\u307E\u305B\u3093");
2040
- return [];
2041
- }
2042
- const fileSet = /* @__PURE__ */ new Set();
2043
- for (const pattern of patterns) {
2044
- try {
2045
- const files = await glob2(pattern, {
2046
- cwd: templateDir,
2047
- dot: true,
2048
- // .で始まるファイルも含める
2049
- nodir: true
2050
- // ディレクトリは除外
2051
- });
2052
- for (const file of files) {
2053
- fileSet.add(file);
2054
- }
2055
- } catch (error2) {
2056
- warn(`\u30D1\u30BF\u30FC\u30F3 ${pattern} \u306E\u51E6\u7406\u4E2D\u306B\u30A8\u30E9\u30FC: ${error2}`);
2057
- }
2058
- }
2059
- const allFiles = Array.from(fileSet);
2060
- const filteredFiles = allFiles.filter((file) => {
2061
- if (isProtectedEnvFile(file)) {
2062
- info(`\u4FDD\u8B77\u5BFE\u8C61\u30D5\u30A1\u30A4\u30EB\u3092\u9664\u5916: ${file}`);
2063
- return false;
2064
- }
2065
- return true;
2066
- });
2067
- success(`${filteredFiles.length}\u500B\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u53CE\u96C6\u3057\u307E\u3057\u305F`);
2068
- return filteredFiles.sort();
2069
- } catch (error2) {
2070
- error(`\u30D5\u30A1\u30A4\u30EB\u53CE\u96C6\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F: ${error2}`);
2071
- throw error2;
2072
- }
2073
- }
2074
-
2075
- // src/prompts/sync.ts
2076
- import inquirer7 from "inquirer";
2077
- import * as fs from "fs";
2078
- import * as path from "path";
2079
- var CATEGORY_CONFIGS = {
2080
- env: {
2081
- name: "\u74B0\u5883\u8A2D\u5B9A",
2082
- description: ".env*, .envrc, .volta, .node-version",
2083
- patterns: [".env*", ".envrc", ".volta", ".node-version"],
2084
- defaultChecked: true
2085
- },
2086
- tools: {
2087
- name: "\u958B\u767A\u30C4\u30FC\u30EB",
2088
- description: "biome.json, .prettierrc, .editorconfig, .vscode/",
2089
- patterns: ["biome.json", ".prettierrc*", ".editorconfig", ".vscode/"],
2090
- defaultChecked: true
2091
- },
2092
- git: {
2093
- name: "Git\u8A2D\u5B9A",
2094
- description: ".gitignore, .gitattributes",
2095
- patterns: [".gitignore", ".gitattributes"],
2096
- defaultChecked: false
2097
- },
2098
- "git-hooks": {
2099
- name: "Git Hooks",
2100
- description: ".husky/",
2101
- patterns: [".husky/"],
2102
- defaultChecked: false
2103
- },
2104
- github: {
2105
- name: "CI/CD",
2106
- description: ".github/workflows/, .github/actions/",
2107
- patterns: [".github/workflows/", ".github/actions/", ".github/dependabot.yml"],
2108
- defaultChecked: false
2109
- },
2110
- docker: {
2111
- name: "\u30B3\u30F3\u30C6\u30CA",
2112
- description: "Dockerfile*, docker-compose.yml, .dockerignore",
2113
- patterns: ["Dockerfile*", "docker-compose*.yml", ".dockerignore"],
2114
- defaultChecked: false
2115
- },
2116
- monorepo: {
2117
- name: "\u30E2\u30CE\u30EC\u30DD\u69CB\u6210",
2118
- description: "turbo.json, pnpm-workspace.yaml",
2119
- patterns: ["turbo.json", "pnpm-workspace.yaml"],
2120
- defaultChecked: false
2121
- },
2122
- "root-config": {
2123
- name: "\u30EB\u30FC\u30C8\u8A2D\u5B9A",
2124
- description: "package.json, tsconfig.json",
2125
- patterns: ["package.json", "tsconfig.json"],
2126
- defaultChecked: false
2127
- },
2128
- scripts: {
2129
- name: "\u30B9\u30AF\u30EA\u30D7\u30C8",
2130
- description: "scripts/ \u914D\u4E0B",
2131
- patterns: ["scripts/**"],
2132
- defaultChecked: false
2133
- },
2134
- apps: {
2135
- name: "\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3",
2136
- description: "apps/ \u914D\u4E0B\uFF08\u6B21\u306E\u753B\u9762\u3067\u500B\u5225\u9078\u629E\uFF09",
2137
- patterns: ["apps/**"],
2138
- defaultChecked: false,
2139
- requiresDetailSelection: true
2140
- },
2141
- packages: {
2142
- name: "\u5171\u901A\u30D1\u30C3\u30B1\u30FC\u30B8",
2143
- description: "packages/ \u914D\u4E0B\uFF08\u6B21\u306E\u753B\u9762\u3067\u500B\u5225\u9078\u629E\uFF09",
2144
- patterns: ["packages/**"],
2145
- defaultChecked: false,
2146
- requiresDetailSelection: true
2147
- },
2148
- docs: {
2149
- name: "\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8",
2150
- description: "README.md, docs/",
2151
- patterns: ["README.md", "docs/**"],
2152
- defaultChecked: false
2153
- }
2154
- };
2155
- function getAvailableApps(templateDir) {
2156
- const appsDir = path.join(templateDir, "apps");
2157
- try {
2158
- if (!fs.existsSync(appsDir)) {
2159
- return [];
2160
- }
2161
- return fs.readdirSync(appsDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
2162
- } catch (error2) {
2163
- console.error(`Error reading apps directory: ${error2}`);
2164
- return [];
2165
- }
2166
- }
2167
- function getAvailablePackages(templateDir) {
2168
- const packagesDir = path.join(templateDir, "packages");
2169
- try {
2170
- if (!fs.existsSync(packagesDir)) {
2171
- return [];
2172
- }
2173
- return fs.readdirSync(packagesDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
2174
- } catch (error2) {
2175
- console.error(`Error reading packages directory: ${error2}`);
2176
- return [];
2177
- }
2178
- }
2179
- async function promptSyncCategories(templateDir) {
2180
- const categoryAnswers = await inquirer7.prompt([
2181
- {
2182
- type: "checkbox",
2183
- name: "categories",
2184
- message: "\u540C\u671F\u3059\u308B\u9805\u76EE\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044\uFF08Space\u3067\u9078\u629E\u3001Enter\u3067\u78BA\u5B9A\uFF09:",
2185
- choices: Object.entries(CATEGORY_CONFIGS).map(([key, config]) => ({
2186
- name: `${config.name} - ${config.description}`,
2187
- value: key,
2188
- checked: config.defaultChecked ?? false
2189
- }))
2190
- }
2191
- ]);
2192
- const selectedCategories = categoryAnswers.categories;
2193
- const hasApps = selectedCategories.includes("apps");
2194
- const hasPackages = selectedCategories.includes("packages");
2195
- const hasRootConfig = selectedCategories.includes("root-config");
2196
- let appsDetail;
2197
- let packagesDetail;
2198
- let packageJsonSections;
2199
- let conflictStrategy = "merge";
2200
- if (hasApps) {
2201
- const availableApps = getAvailableApps(templateDir);
2202
- if (availableApps.length > 0) {
2203
- const appsAnswers = await inquirer7.prompt([
2204
- {
2205
- type: "checkbox",
2206
- name: "apps",
2207
- message: "\u540C\u671F\u3059\u308B\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u3092\u9078\u629E:",
2208
- choices: availableApps.map((app) => ({
2209
- name: app,
2210
- value: app,
2211
- checked: true
2212
- })),
2213
- validate: (input) => {
2214
- if (input.length === 0) {
2215
- return "\u5C11\u306A\u304F\u3068\u30821\u3064\u306E\u30A2\u30D7\u30EA\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044";
2216
- }
2217
- return true;
2218
- }
2219
- }
2220
- ]);
2221
- appsDetail = appsAnswers.apps;
2222
- } else {
2223
- console.warn("\u8B66\u544A: apps/ \u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u304C\u898B\u3064\u304B\u3089\u306A\u3044\u304B\u3001\u30A2\u30D7\u30EA\u304C\u5B58\u5728\u3057\u307E\u305B\u3093");
2224
- appsDetail = [];
2225
- }
2226
- }
2227
- if (hasPackages) {
2228
- const availablePackages = getAvailablePackages(templateDir);
2229
- if (availablePackages.length > 0) {
2230
- const packagesAnswers = await inquirer7.prompt([
2231
- {
2232
- type: "checkbox",
2233
- name: "packages",
2234
- message: "\u540C\u671F\u3059\u308B\u30D1\u30C3\u30B1\u30FC\u30B8\u3092\u9078\u629E:",
2235
- choices: availablePackages.map((pkg) => ({
2236
- name: pkg,
2237
- value: pkg,
2238
- checked: true
2239
- })),
2240
- validate: (input) => {
2241
- if (input.length === 0) {
2242
- return "\u5C11\u306A\u304F\u3068\u30821\u3064\u306E\u30D1\u30C3\u30B1\u30FC\u30B8\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044";
2243
- }
2244
- return true;
2245
- }
2246
- }
2247
- ]);
2248
- packagesDetail = packagesAnswers.packages;
2249
- } else {
2250
- console.warn("\u8B66\u544A: packages/ \u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u304C\u898B\u3064\u304B\u3089\u306A\u3044\u304B\u3001\u30D1\u30C3\u30B1\u30FC\u30B8\u304C\u5B58\u5728\u3057\u307E\u305B\u3093");
2251
- packagesDetail = [];
2252
- }
2253
- }
2254
- if (hasRootConfig) {
2255
- const packageJsonAnswers = await inquirer7.prompt([
2256
- {
2257
- type: "checkbox",
2258
- name: "sections",
2259
- message: "package.json\u306E\u540C\u671F\u30BB\u30AF\u30B7\u30E7\u30F3\u3092\u9078\u629E:",
2260
- choices: [
2261
- { name: "scripts\uFF08\u63A8\u5968\uFF09", value: "scripts", checked: true },
2262
- { name: "engines\uFF08\u63A8\u5968\uFF09", value: "engines", checked: true },
2263
- { name: "dependencies", value: "dependencies", checked: false },
2264
- { name: "devDependencies", value: "devDependencies", checked: false }
2265
- ]
2266
- }
2267
- ]);
2268
- packageJsonSections = packageJsonAnswers.sections;
2269
- }
2270
- const strategyAnswers = await inquirer7.prompt([
2271
- {
2272
- type: "list",
2273
- name: "conflictStrategy",
2274
- message: "\u7AF6\u5408\u89E3\u6C7A\u6226\u7565\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044:",
2275
- choices: [
2276
- { name: "\u30DE\u30FC\u30AB\u30FC\u30D9\u30FC\u30B9\u30DE\u30FC\u30B8\uFF08\u63A8\u5968\uFF09", value: "merge" },
2277
- { name: "\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u3067\u4E0A\u66F8\u304D", value: "overwrite" },
2278
- { name: "\u65E2\u5B58\u30D5\u30A1\u30A4\u30EB\u512A\u5148", value: "skip" }
2279
- ],
2280
- default: "merge"
2281
- }
2282
- ]);
2283
- conflictStrategy = strategyAnswers.conflictStrategy;
2284
- return {
2285
- categories: selectedCategories,
2286
- appsDetail,
2287
- packagesDetail,
2288
- packageJsonSections,
2289
- conflictStrategy
2290
- };
2291
- }
2292
-
2293
- // src/utils/backup.ts
2294
- import fsExtra2 from "fs-extra";
2295
- import { join as join15, dirname as dirname5, relative as relative3 } from "path";
2296
- var { copy, ensureDir: ensureDir2, readdir: readdir4, remove, pathExists } = fsExtra2;
2297
- function getTimestamp() {
2298
- const now = /* @__PURE__ */ new Date();
2299
- const year = now.getFullYear();
2300
- const month = String(now.getMonth() + 1).padStart(2, "0");
2301
- const day = String(now.getDate()).padStart(2, "0");
2302
- const hours = String(now.getHours()).padStart(2, "0");
2303
- const minutes = String(now.getMinutes()).padStart(2, "0");
2304
- const seconds = String(now.getSeconds()).padStart(2, "0");
2305
- return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}`;
2306
- }
2307
- function parseBackupTimestamp(dirName) {
2308
- const match = dirName.match(/^\.einja-sync-backup-(\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2})$/);
2309
- if (!match || !match[1]) {
2310
- return null;
2311
- }
2312
- const timestampStr = match[1];
2313
- const parts = timestampStr.split("_");
2314
- if (parts.length !== 2) {
2315
- return null;
2316
- }
2317
- const [datePart, timePart] = parts;
2318
- if (!datePart || !timePart) {
2319
- return null;
2320
- }
2321
- const dateParts = datePart.split("-").map(Number);
2322
- const timeParts = timePart.split("-").map(Number);
2323
- if (dateParts.length !== 3 || timeParts.length !== 3) {
2324
- return null;
2325
- }
2326
- const [year, month, day] = dateParts;
2327
- const [hours, minutes, seconds] = timeParts;
2328
- if (year === void 0 || month === void 0 || day === void 0 || hours === void 0 || minutes === void 0 || seconds === void 0) {
2329
- return null;
2330
- }
2331
- return new Date(year, month - 1, day, hours, minutes, seconds);
2332
- }
2333
- async function createBackup(targetDir, filesToBackup) {
2334
- const timestamp = getTimestamp();
2335
- const backupDirName = `.einja-sync-backup-${timestamp}`;
2336
- const backupDir = join15(targetDir, backupDirName);
2337
- try {
2338
- await ensureDir2(backupDir);
2339
- for (const file of filesToBackup) {
2340
- const sourcePath = join15(targetDir, file);
2341
- const destPath = join15(backupDir, file);
2342
- if (!await pathExists(sourcePath)) {
2343
- continue;
2344
- }
2345
- await ensureDir2(dirname5(destPath));
2346
- await copy(sourcePath, destPath);
2347
- }
2348
- return backupDir;
2349
- } catch (error2) {
2350
- error(`\u30D0\u30C3\u30AF\u30A2\u30C3\u30D7\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${error2 instanceof Error ? error2.message : String(error2)}`);
2351
- throw error2;
2352
- }
2353
- }
2354
- async function restoreFromBackup(backupDir, targetDir) {
2355
- try {
2356
- if (!await pathExists(backupDir)) {
2357
- error(`\u30D0\u30C3\u30AF\u30A2\u30C3\u30D7\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${backupDir}`);
2358
- return false;
2359
- }
2360
- const files = await getAllFiles(backupDir);
2361
- for (const file of files) {
2362
- const sourcePath = join15(backupDir, file);
2363
- const destPath = join15(targetDir, file);
2364
- await ensureDir2(dirname5(destPath));
2365
- await copy(sourcePath, destPath, { overwrite: true });
1372
+ } else if (isJsonFile) {
1373
+ try {
1374
+ const templateJson = JSON.parse(templateContent);
1375
+ const existingJson = existingContent ? JSON.parse(existingContent) : null;
1376
+ const jsonPaths = syncMetadata.jsonPaths || { managed: {}, seed: {} };
1377
+ const fileName = targetPath.split("/").pop() || "package.json";
1378
+ const mergedJson = mergeJson(templateJson, existingJson, jsonPaths, fileName);
1379
+ mergedContent = JSON.stringify(mergedJson, null, 2);
1380
+ action = "merged";
1381
+ } catch {
1382
+ mergedContent = templateContent;
1383
+ action = "overwritten";
2366
1384
  }
2367
- return true;
2368
- } catch (error2) {
2369
- error(`\u30D0\u30C3\u30AF\u30A2\u30C3\u30D7\u304B\u3089\u306E\u5FA9\u5143\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${error2 instanceof Error ? error2.message : String(error2)}`);
2370
- return false;
2371
- }
2372
- }
2373
- async function getAllFiles(dirPath, baseDir) {
2374
- const base = baseDir ?? dirPath;
2375
- const entries = await readdir4(dirPath, { withFileTypes: true });
2376
- const files = [];
2377
- for (const entry of entries) {
2378
- const fullPath = join15(dirPath, entry.name);
2379
- if (entry.isDirectory()) {
2380
- const subFiles = await getAllFiles(fullPath, base);
2381
- files.push(...subFiles);
1385
+ } else {
1386
+ mergedContent = mergeTextWithMarkers(templateContent, existingContent);
1387
+ if (mergedContent === existingContent) {
1388
+ action = "skipped";
2382
1389
  } else {
2383
- files.push(relative3(base, fullPath));
1390
+ action = "merged";
2384
1391
  }
2385
1392
  }
2386
- return files;
1393
+ if (action !== "skipped") {
1394
+ ensureDir(dirname4(targetPath));
1395
+ writeFileSync3(targetPath, mergedContent, "utf-8");
1396
+ }
1397
+ return { action, path: targetPath };
2387
1398
  }
2388
- async function listBackups(targetDir) {
2389
- try {
2390
- if (!await pathExists(targetDir)) {
2391
- return [];
2392
- }
2393
- const entries = await readdir4(targetDir, { withFileTypes: true });
2394
- const backups = [];
2395
- for (const entry of entries) {
2396
- if (!entry.isDirectory()) {
1399
+ function parseMarkers(content) {
1400
+ const lines = content.split("\n");
1401
+ const sections = [];
1402
+ let currentType = "unmanaged";
1403
+ let currentStartLine = 1;
1404
+ let currentContent = [];
1405
+ let currentId;
1406
+ for (let i = 0; i < lines.length; i++) {
1407
+ const line = lines[i];
1408
+ const lineNumber = i + 1;
1409
+ const startMarker = parseStartMarker(line);
1410
+ if (startMarker) {
1411
+ if (currentType !== "unmanaged") {
1412
+ currentContent.push(line);
2397
1413
  continue;
2398
1414
  }
2399
- const timestamp = parseBackupTimestamp(entry.name);
2400
- if (!timestamp) {
1415
+ if (currentContent.length > 0 || sections.length === 0) {
1416
+ sections.push({
1417
+ type: "unmanaged",
1418
+ startLine: currentStartLine,
1419
+ endLine: lineNumber - 1,
1420
+ content: currentContent.join("\n")
1421
+ });
1422
+ }
1423
+ currentType = startMarker.type;
1424
+ currentId = startMarker.id;
1425
+ currentStartLine = lineNumber;
1426
+ currentContent = [line];
1427
+ } else if (parseEndMarker(line)) {
1428
+ if (currentType === "unmanaged") {
1429
+ currentContent.push(line);
2401
1430
  continue;
2402
1431
  }
2403
- backups.push({
2404
- path: join15(targetDir, entry.name),
2405
- name: entry.name,
2406
- timestamp
1432
+ currentContent.push(line);
1433
+ sections.push({
1434
+ type: currentType,
1435
+ startLine: currentStartLine,
1436
+ endLine: lineNumber,
1437
+ content: currentContent.join("\n"),
1438
+ id: currentId
2407
1439
  });
1440
+ currentType = "unmanaged";
1441
+ currentId = void 0;
1442
+ currentStartLine = lineNumber + 1;
1443
+ currentContent = [];
1444
+ } else {
1445
+ currentContent.push(line);
2408
1446
  }
2409
- backups.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
2410
- return backups;
2411
- } catch (error2) {
2412
- error(`\u30D0\u30C3\u30AF\u30A2\u30C3\u30D7\u4E00\u89A7\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${error2 instanceof Error ? error2.message : String(error2)}`);
2413
- return [];
2414
1447
  }
2415
- }
2416
- async function getLatestBackup(targetDir) {
2417
- const backups = await listBackups(targetDir);
2418
- return backups.length > 0 ? backups[0] ?? null : null;
2419
- }
2420
-
2421
- // src/utils/git.ts
2422
- import { execSync as execSync2 } from "child_process";
2423
- function isGitRepository(targetDir) {
2424
- try {
2425
- const cwd = targetDir || process.cwd();
2426
- execSync2("git rev-parse --is-inside-work-tree", {
2427
- cwd,
2428
- stdio: "ignore"
1448
+ if (currentContent.length > 0 || sections.length === 0) {
1449
+ sections.push({
1450
+ type: currentType,
1451
+ startLine: currentStartLine,
1452
+ endLine: lines.length,
1453
+ content: currentContent.join("\n"),
1454
+ id: currentId
2429
1455
  });
2430
- return true;
2431
- } catch {
2432
- return false;
2433
1456
  }
1457
+ return sections;
2434
1458
  }
2435
- function hasUncommittedChanges(targetDir) {
2436
- if (!isGitRepository(targetDir)) {
2437
- warn("\u26A0\uFE0F \u3053\u306E\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306FGit\u30EA\u30DD\u30B8\u30C8\u30EA\u3067\u306F\u3042\u308A\u307E\u305B\u3093");
2438
- return false;
1459
+ function parseStartMarker(line) {
1460
+ const markdownManagedPattern = /^<!--\s*@einja:managed:start(?:\s+id="([^"]+)")?\s*-->$/;
1461
+ let match = line.match(markdownManagedPattern);
1462
+ if (match) {
1463
+ return { type: "managed", id: match[1] || void 0 };
2439
1464
  }
2440
- try {
2441
- const cwd = targetDir || process.cwd();
2442
- const output = execSync2("git status --porcelain", {
2443
- cwd,
2444
- encoding: "utf-8"
2445
- });
2446
- return output.trim().length > 0;
2447
- } catch (error2) {
2448
- warn(`\u26A0\uFE0F Git\u30B9\u30C6\u30FC\u30BF\u30B9\u78BA\u8A8D\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F: ${error2}`);
2449
- return false;
1465
+ const markdownSeedPattern = /^<!--\s*@einja:seed:start(?:\s+id="([^"]+)")?\s*-->$/;
1466
+ match = line.match(markdownSeedPattern);
1467
+ if (match) {
1468
+ return { type: "seed", id: match[1] || void 0 };
1469
+ }
1470
+ const yamlManagedPattern = /^\s*#\s*@einja:managed:start(?:\s+id="([^"]+)")?\s*$/;
1471
+ match = line.match(yamlManagedPattern);
1472
+ if (match) {
1473
+ return { type: "managed", id: match[1] || void 0 };
1474
+ }
1475
+ const yamlSeedPattern = /^\s*#\s*@einja:seed:start(?:\s+id="([^"]+)")?\s*$/;
1476
+ match = line.match(yamlSeedPattern);
1477
+ if (match) {
1478
+ return { type: "seed", id: match[1] || void 0 };
2450
1479
  }
1480
+ return null;
2451
1481
  }
2452
- function checkGitStatusForSync(force, targetDir) {
2453
- if (!isGitRepository(targetDir)) {
2454
- warn("\u26A0\uFE0F \u3053\u306E\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306FGit\u30EA\u30DD\u30B8\u30C8\u30EA\u3067\u306F\u3042\u308A\u307E\u305B\u3093");
2455
- warn(
2456
- " sync\u5B9F\u884C\u5F8C\u306E\u5DEE\u5206\u78BA\u8A8D\u306F `git diff` \u3067\u884C\u3046\u3053\u3068\u3092\u63A8\u5968\u3057\u307E\u3059"
2457
- );
2458
- return true;
1482
+ function parseEndMarker(line) {
1483
+ if (/^<!--\s*@einja:managed:end\s*-->$/.test(line)) {
1484
+ return "managed";
2459
1485
  }
2460
- if (hasUncommittedChanges(targetDir)) {
2461
- if (!force) {
2462
- error("\u274C \u672A\u30B3\u30DF\u30C3\u30C8\u306E\u5909\u66F4\u304C\u3042\u308A\u307E\u3059");
2463
- error(
2464
- " \u5909\u66F4\u3092\u30B3\u30DF\u30C3\u30C8\u3057\u3066\u304B\u3089\u5B9F\u884C\u3059\u308B\u304B\u3001--force \u30D5\u30E9\u30B0\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044"
1486
+ if (/^<!--\s*@einja:seed:end\s*-->$/.test(line)) {
1487
+ return "seed";
1488
+ }
1489
+ if (/^\s*#\s*@einja:managed:end\s*$/.test(line)) {
1490
+ return "managed";
1491
+ }
1492
+ if (/^\s*#\s*@einja:seed:end\s*$/.test(line)) {
1493
+ return "seed";
1494
+ }
1495
+ return null;
1496
+ }
1497
+ function isPathManaged(filePath, keyPath, jsonPaths) {
1498
+ const managedPaths = jsonPaths.managed[filePath] || [];
1499
+ return managedPaths.some(
1500
+ (p) => keyPath === p || keyPath.startsWith(`${p}.`)
1501
+ );
1502
+ }
1503
+ function isPathSeed(filePath, keyPath, jsonPaths) {
1504
+ const seedPaths = jsonPaths.seed[filePath] || [];
1505
+ return seedPaths.some((p) => keyPath === p || keyPath.startsWith(`${p}.`));
1506
+ }
1507
+
1508
+ // src/utils/placeholder-validator.ts
1509
+ import { readFile } from "fs/promises";
1510
+ import { join as join5 } from "path";
1511
+ var PLACEHOLDER_PATTERNS = [
1512
+ "@repo/",
1513
+ "{{packageName}}",
1514
+ "{{projectName}}",
1515
+ "{{description}}"
1516
+ ];
1517
+ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
1518
+ ".png",
1519
+ ".jpg",
1520
+ ".jpeg",
1521
+ ".gif",
1522
+ ".ico",
1523
+ ".svg",
1524
+ ".woff",
1525
+ ".woff2",
1526
+ ".ttf",
1527
+ ".eot",
1528
+ ".zip",
1529
+ ".tar",
1530
+ ".gz",
1531
+ ".pdf",
1532
+ ".mp4",
1533
+ ".mp3"
1534
+ ]);
1535
+ var EXCLUDED_DIRS = ["node_modules/", ".git/", "dist/", ".next/"];
1536
+ function isBinaryFile(filePath) {
1537
+ const lastDot = filePath.lastIndexOf(".");
1538
+ if (lastDot === -1) return false;
1539
+ const ext = filePath.substring(lastDot).toLowerCase();
1540
+ return BINARY_EXTENSIONS.has(ext);
1541
+ }
1542
+ function isExcludedDir(filePath) {
1543
+ return EXCLUDED_DIRS.some((dir) => filePath.includes(dir));
1544
+ }
1545
+ async function validatePlaceholders(targetDir, filePaths) {
1546
+ const violations = [];
1547
+ for (const filePath of filePaths) {
1548
+ if (isBinaryFile(filePath) || isExcludedDir(filePath)) {
1549
+ continue;
1550
+ }
1551
+ try {
1552
+ const fullPath = join5(targetDir, filePath);
1553
+ const content = await readFile(fullPath, "utf-8");
1554
+ const lines = content.split("\n");
1555
+ for (let i = 0; i < lines.length; i++) {
1556
+ const line = lines[i] ?? "";
1557
+ for (const pattern of PLACEHOLDER_PATTERNS) {
1558
+ if (line.includes(pattern)) {
1559
+ violations.push({
1560
+ filePath,
1561
+ line: i + 1,
1562
+ placeholder: pattern,
1563
+ context: line.trim()
1564
+ });
1565
+ }
1566
+ }
1567
+ }
1568
+ } catch {
1569
+ }
1570
+ }
1571
+ return {
1572
+ isValid: violations.length === 0,
1573
+ violations
1574
+ };
1575
+ }
1576
+
1577
+ // src/utils/project-detector.ts
1578
+ import { readFileSync as readFileSync4, existsSync as existsSync6, readdirSync as readdirSync3, statSync } from "fs";
1579
+ import { join as join6 } from "path";
1580
+ async function detectProjectConfig(targetDir) {
1581
+ let projectName = null;
1582
+ let packageScope = null;
1583
+ const rootPackageJsonPath = join6(targetDir, "package.json");
1584
+ if (existsSync6(rootPackageJsonPath)) {
1585
+ try {
1586
+ const rootPkg = JSON.parse(
1587
+ readFileSync4(rootPackageJsonPath, "utf-8")
2465
1588
  );
2466
- process.exit(1);
1589
+ if (rootPkg.name) {
1590
+ projectName = rootPkg.name;
1591
+ }
1592
+ } catch {
2467
1593
  }
2468
- warn("\u26A0\uFE0F \u672A\u30B3\u30DF\u30C3\u30C8\u306E\u5909\u66F4\u304C\u3042\u308A\u307E\u3059\u304C\u3001--force \u306B\u3088\u308A\u7D9A\u884C\u3057\u307E\u3059");
2469
- warn(
2470
- " \u554F\u984C\u304C\u767A\u751F\u3057\u305F\u5834\u5408\u306F `git checkout .` \u3067\u5FA9\u5143\u3067\u304D\u307E\u3059"
2471
- );
2472
1594
  }
2473
- return true;
1595
+ const candidateDirs = [join6(targetDir, "apps"), join6(targetDir, "packages")];
1596
+ for (const dir of candidateDirs) {
1597
+ if (!existsSync6(dir)) {
1598
+ continue;
1599
+ }
1600
+ try {
1601
+ const entries = readdirSync3(dir);
1602
+ for (const entry of entries) {
1603
+ const entryPath = join6(dir, entry);
1604
+ if (!statSync(entryPath).isDirectory()) {
1605
+ continue;
1606
+ }
1607
+ const pkgJsonPath = join6(entryPath, "package.json");
1608
+ if (!existsSync6(pkgJsonPath)) {
1609
+ continue;
1610
+ }
1611
+ try {
1612
+ const pkg = JSON.parse(
1613
+ readFileSync4(pkgJsonPath, "utf-8")
1614
+ );
1615
+ if (pkg.name) {
1616
+ const match = pkg.name.match(/^(@[^/]+)\//);
1617
+ if (match?.[1]) {
1618
+ const detectedScope = match[1];
1619
+ if (packageScope && packageScope !== detectedScope) {
1620
+ console.warn(
1621
+ `\u8B66\u544A: \u7570\u306A\u308B\u30B9\u30B3\u30FC\u30D7\u304C\u691C\u51FA\u3055\u308C\u307E\u3057\u305F\uFF08${packageScope} vs ${detectedScope}\uFF09\u3002\u6700\u521D\u306B\u898B\u3064\u304B\u3063\u305F ${packageScope} \u3092\u4F7F\u7528\u3057\u307E\u3059\u3002`
1622
+ );
1623
+ } else if (!packageScope) {
1624
+ packageScope = detectedScope;
1625
+ }
1626
+ }
1627
+ }
1628
+ } catch {
1629
+ }
1630
+ }
1631
+ } catch {
1632
+ }
1633
+ }
1634
+ if (projectName && packageScope) {
1635
+ return { projectName, packageScope };
1636
+ }
1637
+ return null;
2474
1638
  }
2475
1639
 
2476
1640
  // src/commands/sync.ts
@@ -2486,7 +1650,7 @@ async function handleInterrupt() {
2486
1650
  info("\u30D0\u30C3\u30AF\u30A2\u30C3\u30D7\u304C\u4F5C\u6210\u3055\u308C\u3066\u3044\u306A\u3044\u305F\u3081\u3001\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u306F\u4E0D\u8981\u3067\u3059");
2487
1651
  process.exit(0);
2488
1652
  }
2489
- const answer = await inquirer8.prompt([
1653
+ const answer = await inquirer5.prompt([
2490
1654
  {
2491
1655
  type: "confirm",
2492
1656
  name: "rollback",
@@ -2507,22 +1671,22 @@ async function handleInterrupt() {
2507
1671
  }
2508
1672
  process.exit(0);
2509
1673
  }
2510
- function getTemplatePath3() {
2511
- const __filename3 = fileURLToPath3(import.meta.url);
2512
- const __dirname3 = dirname6(__filename3);
2513
- const { existsSync: existsSync8 } = fsExtra3;
2514
- const distPath = join16(__dirname3, "../templates/default");
2515
- const srcPath = join16(__dirname3, "../../templates/default");
2516
- if (existsSync8(distPath)) {
1674
+ function getTemplatePath2() {
1675
+ const __filename3 = fileURLToPath2(import.meta.url);
1676
+ const __dirname3 = dirname5(__filename3);
1677
+ const { existsSync: existsSync7 } = fsExtra3;
1678
+ const distPath = join7(__dirname3, "../templates/default");
1679
+ const srcPath = join7(__dirname3, "../../templates/default");
1680
+ if (existsSync7(distPath)) {
2517
1681
  return distPath;
2518
1682
  }
2519
- if (existsSync8(srcPath)) {
1683
+ if (existsSync7(srcPath)) {
2520
1684
  return srcPath;
2521
1685
  }
2522
1686
  throw new Error("\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093");
2523
1687
  }
2524
1688
  async function syncCommand(options) {
2525
- const { existsSync: existsSync8 } = fsExtra3;
1689
+ const { existsSync: existsSync7 } = fsExtra3;
2526
1690
  const sigintHandler = () => {
2527
1691
  handleInterrupt().catch((error2) => {
2528
1692
  error(`\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u4E2D\u306B\u30A8\u30E9\u30FC: ${error2}`);
@@ -2554,7 +1718,7 @@ async function syncCommand(options) {
2554
1718
  }
2555
1719
  const targetDir = process.cwd();
2556
1720
  checkGitStatusForSync(options.force || false, targetDir);
2557
- const templatePath = getTemplatePath3();
1721
+ const templatePath = getTemplatePath2();
2558
1722
  let categories;
2559
1723
  let appsDetail;
2560
1724
  let packagesDetail;
@@ -2599,6 +1763,52 @@ async function syncCommand(options) {
2599
1763
  return;
2600
1764
  }
2601
1765
  info(`\u540C\u671F\u5BFE\u8C61: ${filesToSync.length}\u500B\u306E\u30D5\u30A1\u30A4\u30EB`);
1766
+ let templateVariables;
1767
+ info("\u{1F50D} \u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u8A2D\u5B9A\u3092\u691C\u51FA\u4E2D...");
1768
+ const detectedConfig = await detectProjectConfig(targetDir);
1769
+ if (detectedConfig) {
1770
+ templateVariables = {
1771
+ projectName: detectedConfig.projectName,
1772
+ packageName: detectedConfig.packageScope,
1773
+ description: `${detectedConfig.projectName} - Einja Management Template`
1774
+ };
1775
+ info(` \u2713 \u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u540D: ${detectedConfig.projectName}`);
1776
+ info(` \u2713 \u30D1\u30C3\u30B1\u30FC\u30B8\u30B9\u30B3\u30FC\u30D7: ${detectedConfig.packageScope}`);
1777
+ } else {
1778
+ warn("\u26A0\uFE0F \u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u8A2D\u5B9A\u3092\u81EA\u52D5\u691C\u51FA\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F");
1779
+ const inputAnswers = await inquirer5.prompt([
1780
+ {
1781
+ type: "input",
1782
+ name: "projectName",
1783
+ message: "\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u540D\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044:",
1784
+ validate: (input) => {
1785
+ if (!input.trim()) {
1786
+ return "\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u540D\u306F\u5FC5\u9808\u3067\u3059";
1787
+ }
1788
+ return true;
1789
+ }
1790
+ },
1791
+ {
1792
+ type: "input",
1793
+ name: "packageScope",
1794
+ message: "\u30D1\u30C3\u30B1\u30FC\u30B8\u30B9\u30B3\u30FC\u30D7\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u4F8B: @mycompany\uFF09:",
1795
+ validate: (input) => {
1796
+ if (!input.trim()) {
1797
+ return "\u30D1\u30C3\u30B1\u30FC\u30B8\u30B9\u30B3\u30FC\u30D7\u306F\u5FC5\u9808\u3067\u3059";
1798
+ }
1799
+ if (!input.startsWith("@")) {
1800
+ return "\u30D1\u30C3\u30B1\u30FC\u30B8\u30B9\u30B3\u30FC\u30D7\u306F @ \u3067\u59CB\u307E\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\uFF08\u4F8B: @mycompany\uFF09";
1801
+ }
1802
+ return true;
1803
+ }
1804
+ }
1805
+ ]);
1806
+ templateVariables = {
1807
+ projectName: inputAnswers.projectName,
1808
+ packageName: inputAnswers.packageScope,
1809
+ description: `${inputAnswers.projectName} - Einja Management Template`
1810
+ };
1811
+ }
2602
1812
  if (options.dryRun) {
2603
1813
  info("\n\u{1F4CB} \u540C\u671F\u30D7\u30EC\u30D3\u30E5\u30FC (--dry-run)\n");
2604
1814
  for (const file of filesToSync) {
@@ -2612,7 +1822,7 @@ async function syncCommand(options) {
2612
1822
  let backupDir;
2613
1823
  if (options.backup !== false) {
2614
1824
  info("\u{1F4BE} \u30D0\u30C3\u30AF\u30A2\u30C3\u30D7\u4F5C\u6210\u4E2D...");
2615
- const existingFiles = filesToSync.filter((file) => existsSync8(join16(targetDir, file)));
1825
+ const existingFiles = filesToSync.filter((file) => existsSync7(join7(targetDir, file)));
2616
1826
  if (existingFiles.length > 0) {
2617
1827
  backupDir = await createBackup(targetDir, existingFiles);
2618
1828
  currentBackupDir = backupDir;
@@ -2641,9 +1851,9 @@ async function syncCommand(options) {
2641
1851
  };
2642
1852
  for (const file of filesToSync) {
2643
1853
  try {
2644
- const sourcePath = join16(templatePath, file);
2645
- const targetPath = join16(targetDir, file);
2646
- if (!existsSync8(sourcePath)) {
1854
+ const sourcePath = join7(templatePath, file);
1855
+ const targetPath = join7(targetDir, file);
1856
+ if (!existsSync7(sourcePath)) {
2647
1857
  warn(`\u30B9\u30AD\u30C3\u30D7: ${file} (\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u30D5\u30A1\u30A4\u30EB\u304C\u5B58\u5728\u3057\u307E\u305B\u3093)`);
2648
1858
  result.skipped++;
2649
1859
  result.files.push({
@@ -2657,7 +1867,9 @@ async function syncCommand(options) {
2657
1867
  sourcePath,
2658
1868
  targetPath,
2659
1869
  syncMetadata,
2660
- packageJsonSections
1870
+ packageJsonSections,
1871
+ conflictStrategy,
1872
+ templateVariables
2661
1873
  );
2662
1874
  const mappedAction = mergeResult.action === "created" || mergeResult.action === "overwritten" ? "copied" : mergeResult.action;
2663
1875
  result.success++;
@@ -2676,6 +1888,16 @@ async function syncCommand(options) {
2676
1888
  error(` \u2717 ${file}: ${error2}`);
2677
1889
  }
2678
1890
  }
1891
+ const syncedFiles = result.files.filter((f) => f.action === "copied" || f.action === "merged").map((f) => f.path);
1892
+ if (syncedFiles.length > 0) {
1893
+ const validation = await validatePlaceholders(targetDir, syncedFiles);
1894
+ if (!validation.isValid) {
1895
+ warn("\n\u26A0 \u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u5909\u6570\u306E\u7F6E\u63DB\u6F0F\u308C\u3092\u691C\u51FA:");
1896
+ for (const v of validation.violations) {
1897
+ warn(` ${v.filePath}:${v.line} \u2014 ${v.placeholder}`);
1898
+ }
1899
+ }
1900
+ }
2679
1901
  info("\n\u{1F4CA} \u540C\u671F\u7D50\u679C:");
2680
1902
  info(` \u6210\u529F: ${result.success}\u30D5\u30A1\u30A4\u30EB`);
2681
1903
  if (result.skipped > 0) {
@@ -2706,9 +1928,9 @@ async function syncCommand(options) {
2706
1928
  }
2707
1929
 
2708
1930
  // src/cli.ts
2709
- var __filename2 = fileURLToPath4(import.meta.url);
2710
- var __dirname2 = dirname7(__filename2);
2711
- var packageJsonPath = join17(__dirname2, "../package.json");
1931
+ var __filename2 = fileURLToPath3(import.meta.url);
1932
+ var __dirname2 = dirname6(__filename2);
1933
+ var packageJsonPath = join8(__dirname2, "../package.json");
2712
1934
  var packageJson = JSON.parse(readFileSync5(packageJsonPath, "utf-8"));
2713
1935
  var program = new Command();
2714
1936
  program.name("create-einja-app").description("CLI tool to create new projects with Einja Management Template").version(packageJson.version);
@@ -2717,17 +1939,6 @@ program.argument("[project-name]", "Project name").option("--skip-git", "Skip gi
2717
1939
  await createCommand(projectName, options);
2718
1940
  }
2719
1941
  );
2720
- program.command("setup").description("Setup tools for existing project").action(async () => {
2721
- await setupCommand();
2722
- });
2723
- program.command("add").description("Add einja components to existing monorepo").option("--all", "Select all components").option("--dry-run", "Preview changes without making them").action(
2724
- async (options) => {
2725
- await addCommand({
2726
- skipPrompts: options.all || false,
2727
- dryRun: options.dryRun || false
2728
- });
2729
- }
2730
- );
2731
1942
  program.command("sync").description("Sync template files to existing project").option("--categories <categories>", "Comma-separated list of categories to sync").option("--all", "Sync all categories").option("--dry-run", "Preview changes without making them").option("--backup", "Create backup before syncing (default: true)", true).option("--rollback", "Rollback to previous backup").option("--force", "Force sync even with uncommitted changes").action(
2732
1943
  async (options) => {
2733
1944
  await syncCommand({