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.
- package/dist/cli/index.js +370 -19
- package/dist/index.js +387 -19
- package/package.json +1 -1
- package/templates/.claude/agents/mgr-gitnerd.md +1 -1
- package/templates/.claude/agents/mgr-sauron.md +3 -3
- package/templates/.claude/rules/MUST-continuous-improvement.md +1 -1
- package/templates/.claude/rules/MUST-intent-transparency.md +1 -1
- package/templates/.claude/rules/MUST-orchestrator-coordination.md +1 -1
- package/templates/.claude/rules/MUST-sync-verification.md +1 -1
- package/templates/.claude/rules/SHOULD-agent-teams.md +1 -1
- package/templates/.claude/rules/index.yaml +4 -4
- package/templates/.claude/skills/de-lead-routing/SKILL.md +13 -0
- package/templates/.claude/skills/dev-lead-routing/SKILL.md +14 -0
- package/templates/.claude/skills/qa-lead-routing/SKILL.md +13 -0
- package/templates/.claude/skills/sauron-watch/SKILL.md +4 -4
- package/templates/.claude/skills/secretary-routing/SKILL.md +14 -1
- package/templates/.claude/skills/springboot-best-practices/SKILL.md +7 -152
- package/templates/.claude/skills/springboot-best-practices/examples/config-properties-example.java +22 -0
- package/templates/.claude/skills/springboot-best-practices/examples/controller-example.java +28 -0
- package/templates/.claude/skills/springboot-best-practices/examples/controller-test-example.java +33 -0
- package/templates/.claude/skills/springboot-best-practices/examples/entity-example.java +22 -0
- package/templates/.claude/skills/springboot-best-practices/examples/exception-handler-example.java +30 -0
- package/templates/.claude/skills/springboot-best-practices/examples/repository-example.java +17 -0
- package/templates/.claude/skills/springboot-best-practices/examples/repository-test-example.java +23 -0
- package/templates/.claude/skills/springboot-best-practices/examples/security-config-example.java +27 -0
- package/templates/.claude/skills/springboot-best-practices/examples/service-example.java +33 -0
- package/templates/.codex/agents/mgr-gitnerd.md +1 -1
- package/templates/.codex/agents/mgr-sauron.md +3 -3
- package/templates/.codex/rules/MUST-continuous-improvement.md +1 -1
- package/templates/.codex/rules/MUST-intent-transparency.md +1 -1
- package/templates/.codex/rules/MUST-orchestrator-coordination.md +1 -1
- package/templates/.codex/rules/MUST-sync-verification.md +1 -1
- package/templates/.codex/rules/SHOULD-agent-teams.md +1 -1
- package/templates/.codex/rules/index.yaml +4 -4
- package/templates/.codex/skills/de-lead-routing/SKILL.md +13 -0
- package/templates/.codex/skills/dev-lead-routing/SKILL.md +14 -0
- package/templates/.codex/skills/qa-lead-routing/SKILL.md +13 -0
- package/templates/.codex/skills/sauron-watch/SKILL.md +4 -4
- package/templates/.codex/skills/secretary-routing/SKILL.md +14 -1
- package/templates/CLAUDE.md.en +8 -29
- package/templates/CLAUDE.md.ko +8 -29
- 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:
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
14369
|
-
|
|
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 (
|
|
14372
|
-
customizations.
|
|
14373
|
-
|
|
14374
|
-
|
|
14375
|
-
|
|
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
|
-
|
|
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) {
|