oh-my-customcode 0.10.0 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/cli/index.js +370 -19
  2. package/dist/index.js +387 -19
  3. package/package.json +1 -1
  4. package/templates/.claude/agents/mgr-gitnerd.md +1 -1
  5. package/templates/.claude/agents/mgr-sauron.md +3 -3
  6. package/templates/.claude/rules/MUST-continuous-improvement.md +1 -1
  7. package/templates/.claude/rules/MUST-intent-transparency.md +1 -1
  8. package/templates/.claude/rules/MUST-orchestrator-coordination.md +1 -1
  9. package/templates/.claude/rules/MUST-sync-verification.md +1 -1
  10. package/templates/.claude/rules/SHOULD-agent-teams.md +1 -1
  11. package/templates/.claude/rules/index.yaml +4 -4
  12. package/templates/.claude/skills/de-lead-routing/SKILL.md +13 -0
  13. package/templates/.claude/skills/dev-lead-routing/SKILL.md +14 -0
  14. package/templates/.claude/skills/qa-lead-routing/SKILL.md +13 -0
  15. package/templates/.claude/skills/sauron-watch/SKILL.md +4 -4
  16. package/templates/.claude/skills/secretary-routing/SKILL.md +14 -1
  17. package/templates/.claude/skills/springboot-best-practices/SKILL.md +7 -152
  18. package/templates/.claude/skills/springboot-best-practices/examples/config-properties-example.java +22 -0
  19. package/templates/.claude/skills/springboot-best-practices/examples/controller-example.java +28 -0
  20. package/templates/.claude/skills/springboot-best-practices/examples/controller-test-example.java +33 -0
  21. package/templates/.claude/skills/springboot-best-practices/examples/entity-example.java +22 -0
  22. package/templates/.claude/skills/springboot-best-practices/examples/exception-handler-example.java +30 -0
  23. package/templates/.claude/skills/springboot-best-practices/examples/repository-example.java +17 -0
  24. package/templates/.claude/skills/springboot-best-practices/examples/repository-test-example.java +23 -0
  25. package/templates/.claude/skills/springboot-best-practices/examples/security-config-example.java +27 -0
  26. package/templates/.claude/skills/springboot-best-practices/examples/service-example.java +33 -0
  27. package/templates/.codex/agents/mgr-gitnerd.md +1 -1
  28. package/templates/.codex/agents/mgr-sauron.md +3 -3
  29. package/templates/.codex/rules/MUST-continuous-improvement.md +1 -1
  30. package/templates/.codex/rules/MUST-intent-transparency.md +1 -1
  31. package/templates/.codex/rules/MUST-orchestrator-coordination.md +1 -1
  32. package/templates/.codex/rules/MUST-sync-verification.md +1 -1
  33. package/templates/.codex/rules/SHOULD-agent-teams.md +1 -1
  34. package/templates/.codex/rules/index.yaml +4 -4
  35. package/templates/.codex/skills/de-lead-routing/SKILL.md +13 -0
  36. package/templates/.codex/skills/dev-lead-routing/SKILL.md +14 -0
  37. package/templates/.codex/skills/qa-lead-routing/SKILL.md +13 -0
  38. package/templates/.codex/skills/sauron-watch/SKILL.md +4 -4
  39. package/templates/.codex/skills/secretary-routing/SKILL.md +14 -1
  40. package/templates/CLAUDE.md.en +8 -29
  41. package/templates/CLAUDE.md.ko +8 -29
  42. package/templates/guides/index.yaml +74 -0
package/dist/cli/index.js CHANGED
@@ -12301,8 +12301,38 @@ var $visitAsync = visit.visitAsync;
12301
12301
  import { join as join2 } from "node:path";
12302
12302
 
12303
12303
  // src/utils/fs.ts
12304
- import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
12304
+ import { dirname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
12305
12305
  import { fileURLToPath } from "node:url";
12306
+ function validatePreserveFilePath(filePath, projectRoot) {
12307
+ if (!filePath || filePath.trim() === "") {
12308
+ return {
12309
+ valid: false,
12310
+ reason: "Path cannot be empty"
12311
+ };
12312
+ }
12313
+ if (isAbsolute(filePath)) {
12314
+ return {
12315
+ valid: false,
12316
+ reason: "Absolute paths are not allowed"
12317
+ };
12318
+ }
12319
+ const normalizedPath = normalize(filePath);
12320
+ if (normalizedPath.startsWith("..")) {
12321
+ return {
12322
+ valid: false,
12323
+ reason: "Path cannot traverse outside project root"
12324
+ };
12325
+ }
12326
+ const resolvedPath = resolve(projectRoot, normalizedPath);
12327
+ const relativePath = relative(projectRoot, resolvedPath);
12328
+ if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
12329
+ return {
12330
+ valid: false,
12331
+ reason: "Resolved path escapes project root"
12332
+ };
12333
+ }
12334
+ return { valid: true };
12335
+ }
12306
12336
  async function fileExists(path) {
12307
12337
  const fs = await import("node:fs/promises");
12308
12338
  try {
@@ -12691,7 +12721,7 @@ async function loadConfig(targetDir) {
12691
12721
  if (await fileExists(configPath)) {
12692
12722
  try {
12693
12723
  const config = await readJsonFile(configPath);
12694
- const merged = mergeConfig(getDefaultConfig(), config);
12724
+ const merged = mergeConfig(getDefaultConfig(), config, targetDir);
12695
12725
  if (merged.configVersion < CURRENT_CONFIG_VERSION) {
12696
12726
  const migrated = migrateConfig(merged);
12697
12727
  await saveConfig(targetDir, migrated);
@@ -12720,7 +12750,30 @@ function deduplicateCustomComponents(components) {
12720
12750
  }
12721
12751
  return [...seen.values()];
12722
12752
  }
12723
- function mergeConfig(defaults, overrides) {
12753
+ function mergeConfig(defaults, overrides, targetDir) {
12754
+ let mergedPreserveFiles;
12755
+ if (overrides.preserveFiles) {
12756
+ const allFiles = [...new Set([...defaults.preserveFiles || [], ...overrides.preserveFiles])];
12757
+ if (targetDir) {
12758
+ const validatedFiles = [];
12759
+ for (const filePath of allFiles) {
12760
+ const validation = validatePreserveFilePath(filePath, targetDir);
12761
+ if (validation.valid) {
12762
+ validatedFiles.push(filePath);
12763
+ } else {
12764
+ warn("config.invalid_preserve_path", {
12765
+ path: filePath,
12766
+ reason: validation.reason ?? "Invalid path"
12767
+ });
12768
+ }
12769
+ }
12770
+ mergedPreserveFiles = validatedFiles;
12771
+ } else {
12772
+ mergedPreserveFiles = allFiles;
12773
+ }
12774
+ } else {
12775
+ mergedPreserveFiles = defaults.preserveFiles;
12776
+ }
12724
12777
  return {
12725
12778
  ...defaults,
12726
12779
  ...overrides,
@@ -12734,7 +12787,7 @@ function mergeConfig(defaults, overrides) {
12734
12787
  ...defaults.agents,
12735
12788
  ...overrides.agents
12736
12789
  },
12737
- preserveFiles: overrides.preserveFiles ? [...new Set([...defaults.preserveFiles || [], ...overrides.preserveFiles])] : defaults.preserveFiles,
12790
+ preserveFiles: mergedPreserveFiles,
12738
12791
  customComponents: overrides.customComponents ? deduplicateCustomComponents([
12739
12792
  ...defaults.customComponents || [],
12740
12793
  ...overrides.customComponents
@@ -13465,8 +13518,260 @@ async function doctorCommand(options = {}) {
13465
13518
  import { join as join5 } from "node:path";
13466
13519
 
13467
13520
  // src/core/installer.ts
13468
- import { copyFile as fsCopyFile, rename } from "node:fs/promises";
13521
+ import { readFile as fsReadFile, writeFile as fsWriteFile, rename } from "node:fs/promises";
13469
13522
  import { basename, join as join4 } from "node:path";
13523
+
13524
+ // src/core/git-workflow.ts
13525
+ import { execFileSync } from "node:child_process";
13526
+ function execGit(args, cwd) {
13527
+ try {
13528
+ return execFileSync("git", args, {
13529
+ cwd,
13530
+ encoding: "utf-8",
13531
+ stdio: ["pipe", "pipe", "pipe"],
13532
+ env: { ...process.env, GIT_DIR: undefined, GIT_WORK_TREE: undefined }
13533
+ }).trim();
13534
+ } catch {
13535
+ return "";
13536
+ }
13537
+ }
13538
+ function isGitRepo(cwd) {
13539
+ return execGit(["rev-parse", "--is-inside-work-tree"], cwd) === "true";
13540
+ }
13541
+ function detectDefaultBranch(cwd) {
13542
+ const remoteHead = execGit(["symbolic-ref", "refs/remotes/origin/HEAD"], cwd);
13543
+ if (remoteHead) {
13544
+ return remoteHead.replace("refs/remotes/origin/", "");
13545
+ }
13546
+ const branches = getLocalBranches(cwd);
13547
+ for (const candidate of ["main", "master", "develop"]) {
13548
+ if (branches.includes(candidate)) {
13549
+ return candidate;
13550
+ }
13551
+ }
13552
+ const head = execGit(["symbolic-ref", "--short", "HEAD"], cwd);
13553
+ if (head) {
13554
+ return head;
13555
+ }
13556
+ return "main";
13557
+ }
13558
+ function getLocalBranches(cwd) {
13559
+ const output = execGit(["branch", "--format=%(refname:short)"], cwd);
13560
+ if (!output)
13561
+ return [];
13562
+ return output.split(`
13563
+ `).filter(Boolean);
13564
+ }
13565
+ function getRemoteBranches(cwd) {
13566
+ const output = execGit(["branch", "-r", "--format=%(refname:short)"], cwd);
13567
+ if (!output)
13568
+ return [];
13569
+ return output.split(`
13570
+ `).filter(Boolean).map((b) => b.replace(/^origin\//, "")).filter((b) => b !== "HEAD");
13571
+ }
13572
+ function detectBranchPatterns(branches) {
13573
+ const prefixes = new Set;
13574
+ const knownPrefixes = ["feature", "release", "hotfix", "bugfix", "fix", "chore", "docs"];
13575
+ for (const branch of branches) {
13576
+ const slashIdx = branch.indexOf("/");
13577
+ if (slashIdx > 0) {
13578
+ const prefix = branch.substring(0, slashIdx);
13579
+ if (knownPrefixes.includes(prefix)) {
13580
+ prefixes.add(`${prefix}/*`);
13581
+ }
13582
+ }
13583
+ }
13584
+ return [...prefixes].sort();
13585
+ }
13586
+ function determineWorkflowType(hasDevelop, branchPatterns, allBranches) {
13587
+ const hasFlowPatterns = branchPatterns.some((p) => p === "feature/*" || p === "release/*" || p === "hotfix/*");
13588
+ if (hasDevelop && hasFlowPatterns) {
13589
+ return "git-flow";
13590
+ }
13591
+ const hasFeatureBranches = allBranches.some((b) => b.includes("/"));
13592
+ if (!hasDevelop && hasFeatureBranches) {
13593
+ return "github-flow";
13594
+ }
13595
+ if (!hasDevelop && !hasFeatureBranches) {
13596
+ return "trunk-based";
13597
+ }
13598
+ if (hasDevelop) {
13599
+ return "git-flow";
13600
+ }
13601
+ return "github-flow";
13602
+ }
13603
+ function detectGitWorkflow(cwd) {
13604
+ if (!isGitRepo(cwd)) {
13605
+ return null;
13606
+ }
13607
+ const localBranches = getLocalBranches(cwd);
13608
+ const remoteBranches = getRemoteBranches(cwd);
13609
+ const allBranches = [...new Set([...localBranches, ...remoteBranches])];
13610
+ const defaultBranch = detectDefaultBranch(cwd);
13611
+ const hasDevelop = localBranches.includes("develop") || remoteBranches.includes("develop");
13612
+ const branchPatterns = detectBranchPatterns(allBranches);
13613
+ const type = determineWorkflowType(hasDevelop, branchPatterns, allBranches);
13614
+ return {
13615
+ type,
13616
+ defaultBranch,
13617
+ hasDevelop,
13618
+ branchPatterns
13619
+ };
13620
+ }
13621
+ function renderGitWorkflowEN(result) {
13622
+ switch (result.type) {
13623
+ case "git-flow":
13624
+ return renderGitFlowEN(result);
13625
+ case "github-flow":
13626
+ return renderGithubFlowEN(result);
13627
+ case "trunk-based":
13628
+ return renderTrunkBasedEN(result);
13629
+ }
13630
+ }
13631
+ function renderGitWorkflowKO(result) {
13632
+ switch (result.type) {
13633
+ case "git-flow":
13634
+ return renderGitFlowKO(result);
13635
+ case "github-flow":
13636
+ return renderGithubFlowKO(result);
13637
+ case "trunk-based":
13638
+ return renderTrunkBasedKO(result);
13639
+ }
13640
+ }
13641
+ function renderGitFlowEN(r) {
13642
+ const lines = [
13643
+ "## Git Workflow (MUST follow)",
13644
+ "",
13645
+ "| Branch | Purpose |",
13646
+ "|--------|---------|",
13647
+ `| \`${r.defaultBranch}\` | Main development branch (default) |`
13648
+ ];
13649
+ if (r.branchPatterns.includes("feature/*")) {
13650
+ lines.push(`| \`feature/*\` | New features -> PR to ${r.defaultBranch} |`);
13651
+ }
13652
+ if (r.branchPatterns.includes("release/*")) {
13653
+ lines.push("| `release/*` | Release preparation -> **npm publish here only** |");
13654
+ }
13655
+ if (r.branchPatterns.includes("hotfix/*")) {
13656
+ lines.push(`| \`hotfix/*\` | Critical fixes -> tag -> publish -> merge to ${r.defaultBranch} |`);
13657
+ }
13658
+ if (r.branchPatterns.includes("bugfix/*")) {
13659
+ lines.push(`| \`bugfix/*\` | Bug fixes -> PR to ${r.defaultBranch} |`);
13660
+ }
13661
+ lines.push("");
13662
+ lines.push("**Key rules:**");
13663
+ lines.push(`- Create feature branches from \`${r.defaultBranch}\``);
13664
+ lines.push("- Use conventional commits: `feat:`, `fix:`, `docs:`, `chore:`");
13665
+ lines.push('- Include "Closes #N" in commit message to auto-close issues');
13666
+ return lines.join(`
13667
+ `);
13668
+ }
13669
+ function renderGithubFlowEN(r) {
13670
+ const lines = [
13671
+ "## Git Workflow (MUST follow)",
13672
+ "",
13673
+ "| Branch | Purpose |",
13674
+ "|--------|---------|",
13675
+ `| \`${r.defaultBranch}\` | Production-ready code (default) |`,
13676
+ `| \`feature/*\` | New features -> PR to ${r.defaultBranch} |`,
13677
+ "",
13678
+ "**Key rules:**",
13679
+ `- Create feature branches from \`${r.defaultBranch}\``,
13680
+ `- All changes go through PR to \`${r.defaultBranch}\``,
13681
+ "- Use conventional commits: `feat:`, `fix:`, `docs:`, `chore:`",
13682
+ '- Include "Closes #N" in commit message to auto-close issues'
13683
+ ];
13684
+ return lines.join(`
13685
+ `);
13686
+ }
13687
+ function renderTrunkBasedEN(r) {
13688
+ const lines = [
13689
+ "## Git Workflow (MUST follow)",
13690
+ "",
13691
+ "| Branch | Purpose |",
13692
+ "|--------|---------|",
13693
+ `| \`${r.defaultBranch}\` | Main trunk (default) |`,
13694
+ "",
13695
+ "**Key rules:**",
13696
+ `- Commit directly to \`${r.defaultBranch}\` or use short-lived branches`,
13697
+ "- Keep branches short-lived (merge within 1-2 days)",
13698
+ "- Use conventional commits: `feat:`, `fix:`, `docs:`, `chore:`"
13699
+ ];
13700
+ return lines.join(`
13701
+ `);
13702
+ }
13703
+ function renderGitFlowKO(r) {
13704
+ const lines = [
13705
+ "## Git 워크플로우 (반드시 준수)",
13706
+ "",
13707
+ "| 브랜치 | 용도 |",
13708
+ "|--------|------|",
13709
+ `| \`${r.defaultBranch}\` | 메인 개발 브랜치 (기본) |`
13710
+ ];
13711
+ if (r.branchPatterns.includes("feature/*")) {
13712
+ lines.push(`| \`feature/*\` | 새 기능 -> ${r.defaultBranch}으로 PR |`);
13713
+ }
13714
+ if (r.branchPatterns.includes("release/*")) {
13715
+ lines.push("| `release/*` | 릴리스 준비 -> **npm 배포는 여기서만** |");
13716
+ }
13717
+ if (r.branchPatterns.includes("hotfix/*")) {
13718
+ lines.push(`| \`hotfix/*\` | 긴급 수정 -> 태그 -> 배포 -> ${r.defaultBranch} 머지 |`);
13719
+ }
13720
+ if (r.branchPatterns.includes("bugfix/*")) {
13721
+ lines.push(`| \`bugfix/*\` | 버그 수정 -> ${r.defaultBranch}으로 PR |`);
13722
+ }
13723
+ lines.push("");
13724
+ lines.push("**핵심 규칙:**");
13725
+ lines.push(`- \`${r.defaultBranch}\`에서 feature 브랜치 생성`);
13726
+ lines.push("- Conventional commits 사용: `feat:`, `fix:`, `docs:`, `chore:`");
13727
+ lines.push('- 커밋 메시지에 "Closes #N" 포함시 이슈 자동 종료');
13728
+ return lines.join(`
13729
+ `);
13730
+ }
13731
+ function renderGithubFlowKO(r) {
13732
+ const lines = [
13733
+ "## Git 워크플로우 (반드시 준수)",
13734
+ "",
13735
+ "| 브랜치 | 용도 |",
13736
+ "|--------|------|",
13737
+ `| \`${r.defaultBranch}\` | 프로덕션 준비 코드 (기본) |`,
13738
+ `| \`feature/*\` | 새 기능 -> ${r.defaultBranch}으로 PR |`,
13739
+ "",
13740
+ "**핵심 규칙:**",
13741
+ `- \`${r.defaultBranch}\`에서 feature 브랜치 생성`,
13742
+ `- 모든 변경은 \`${r.defaultBranch}\`으로 PR을 통해 진행`,
13743
+ "- Conventional commits 사용: `feat:`, `fix:`, `docs:`, `chore:`",
13744
+ '- 커밋 메시지에 "Closes #N" 포함시 이슈 자동 종료'
13745
+ ];
13746
+ return lines.join(`
13747
+ `);
13748
+ }
13749
+ function renderTrunkBasedKO(r) {
13750
+ const lines = [
13751
+ "## Git 워크플로우 (반드시 준수)",
13752
+ "",
13753
+ "| 브랜치 | 용도 |",
13754
+ "|--------|------|",
13755
+ `| \`${r.defaultBranch}\` | 메인 트렁크 (기본) |`,
13756
+ "",
13757
+ "**핵심 규칙:**",
13758
+ `- \`${r.defaultBranch}\`에 직접 커밋하거나 단기 브랜치 사용`,
13759
+ "- 브랜치는 단기 유지 (1-2일 내 머지)",
13760
+ "- Conventional commits 사용: `feat:`, `fix:`, `docs:`, `chore:`"
13761
+ ];
13762
+ return lines.join(`
13763
+ `);
13764
+ }
13765
+ function getDefaultWorkflow() {
13766
+ return {
13767
+ type: "github-flow",
13768
+ defaultBranch: "main",
13769
+ hasDevelop: false,
13770
+ branchPatterns: ["feature/*"]
13771
+ };
13772
+ }
13773
+
13774
+ // src/core/installer.ts
13470
13775
  var DEFAULT_LANGUAGE2 = "en";
13471
13776
  function getTemplateDir() {
13472
13777
  const packageRoot = getPackageRoot();
@@ -13601,6 +13906,11 @@ async function installComponent(targetDir, provider, component, options) {
13601
13906
  debug("install.component_installed", { component });
13602
13907
  return true;
13603
13908
  }
13909
+ var GIT_WORKFLOW_PLACEHOLDER = "<!-- omcustom:git-workflow -->";
13910
+ function renderGitWorkflowSection(targetDir, language) {
13911
+ const result = detectGitWorkflow(targetDir) ?? getDefaultWorkflow();
13912
+ return language === "ko" ? renderGitWorkflowKO(result) : renderGitWorkflowEN(result);
13913
+ }
13604
13914
  async function installEntryDoc(targetDir, provider, language, overwrite = false) {
13605
13915
  const layout = getProviderLayout(provider);
13606
13916
  const templateFile = getEntryTemplateName(provider, language);
@@ -13615,7 +13925,12 @@ async function installEntryDoc(targetDir, provider, language, overwrite = false)
13615
13925
  debug("install.entry_md_skipped", { reason: "exists", language, entry: layout.entryFile });
13616
13926
  return false;
13617
13927
  }
13618
- await fsCopyFile(srcPath, destPath);
13928
+ let content = await fsReadFile(srcPath, "utf-8");
13929
+ if (content.includes(GIT_WORKFLOW_PLACEHOLDER)) {
13930
+ const workflowSection = renderGitWorkflowSection(targetDir, language);
13931
+ content = content.replace(GIT_WORKFLOW_PLACEHOLDER, workflowSection);
13932
+ }
13933
+ await fsWriteFile(destPath, content, "utf-8");
13619
13934
  debug("install.entry_md_installed", { language, entry: layout.entryFile });
13620
13935
  return true;
13621
13936
  }
@@ -14362,27 +14677,63 @@ function resolveConfigPreserveFiles(options, config) {
14362
14677
  if (options.forceOverwriteAll) {
14363
14678
  return [];
14364
14679
  }
14365
- return config.preserveFiles || [];
14680
+ const preserveFiles = config.preserveFiles || [];
14681
+ const validatedPaths = [];
14682
+ for (const filePath of preserveFiles) {
14683
+ const validation = validatePreserveFilePath(filePath, options.targetDir);
14684
+ if (validation.valid) {
14685
+ validatedPaths.push(filePath);
14686
+ } else {
14687
+ warn("preserve_files.invalid_path", {
14688
+ path: filePath,
14689
+ reason: validation.reason ?? "Invalid path"
14690
+ });
14691
+ }
14692
+ }
14693
+ return validatedPaths;
14366
14694
  }
14367
- function resolveCustomizations(customizations, configPreserveFiles) {
14368
- if (!customizations && configPreserveFiles.length === 0) {
14369
- return null;
14695
+ function resolveCustomizations(customizations, configPreserveFiles, targetDir) {
14696
+ const validatedManifestFiles = [];
14697
+ if (customizations && customizations.preserveFiles.length > 0) {
14698
+ for (const filePath of customizations.preserveFiles) {
14699
+ const validation = validatePreserveFilePath(filePath, targetDir);
14700
+ if (validation.valid) {
14701
+ validatedManifestFiles.push(filePath);
14702
+ } else {
14703
+ warn("preserve_files.invalid_path", {
14704
+ path: filePath,
14705
+ reason: validation.reason ?? "Invalid path",
14706
+ source: "manifest"
14707
+ });
14708
+ }
14709
+ }
14370
14710
  }
14371
- if (customizations && configPreserveFiles.length > 0) {
14372
- customizations.preserveFiles = [
14373
- ...new Set([...customizations.preserveFiles, ...configPreserveFiles])
14374
- ];
14375
- return customizations;
14711
+ if (validatedManifestFiles.length === 0 && configPreserveFiles.length === 0) {
14712
+ return customizations && customizations.modifiedFiles.length > 0 ? customizations : null;
14713
+ }
14714
+ if (validatedManifestFiles.length > 0 && configPreserveFiles.length > 0) {
14715
+ const merged = customizations || {
14716
+ modifiedFiles: [],
14717
+ preserveFiles: [],
14718
+ customComponents: [],
14719
+ lastUpdated: new Date().toISOString()
14720
+ };
14721
+ merged.preserveFiles = [...new Set([...validatedManifestFiles, ...configPreserveFiles])];
14722
+ return merged;
14376
14723
  }
14377
14724
  if (configPreserveFiles.length > 0) {
14378
14725
  return {
14379
- modifiedFiles: [],
14726
+ modifiedFiles: customizations?.modifiedFiles || [],
14380
14727
  preserveFiles: configPreserveFiles,
14381
- customComponents: [],
14728
+ customComponents: customizations?.customComponents || [],
14382
14729
  lastUpdated: new Date().toISOString()
14383
14730
  };
14384
14731
  }
14385
- return customizations;
14732
+ if (customizations) {
14733
+ customizations.preserveFiles = validatedManifestFiles;
14734
+ return customizations;
14735
+ }
14736
+ return null;
14386
14737
  }
14387
14738
  async function updateEntryDoc(targetDir, provider, config, options) {
14388
14739
  const layout = getProviderLayout(provider);
@@ -14437,7 +14788,7 @@ async function update(options) {
14437
14788
  await handleBackupIfRequested(options.targetDir, provider, !!options.backup, result);
14438
14789
  const manifestCustomizations = await resolveManifestCustomizations(options, options.targetDir);
14439
14790
  const configPreserveFiles = resolveConfigPreserveFiles(options, config);
14440
- const customizations = resolveCustomizations(manifestCustomizations, configPreserveFiles);
14791
+ const customizations = resolveCustomizations(manifestCustomizations, configPreserveFiles, options.targetDir);
14441
14792
  const components = options.components || getAllUpdateComponents();
14442
14793
  await updateAllComponents(options.targetDir, provider, components, updateCheck, customizations, options, result, config);
14443
14794
  if (!options.components || options.components.length === 0) {