heymark 1.1.0 → 1.1.1
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/README.md +189 -226
- package/package.json +57 -57
- package/scripts/lib/config.js +73 -73
- package/scripts/lib/parser.js +82 -82
- package/scripts/lib/repo.js +80 -80
- package/scripts/sync.js +239 -239
- package/scripts/tools/antigravity.js +41 -0
- package/scripts/tools/claude.js +42 -42
- package/scripts/tools/codex.js +41 -41
- package/scripts/tools/copilot.js +41 -41
- package/scripts/tools/cursor.js +41 -41
package/scripts/lib/config.js
CHANGED
|
@@ -1,73 +1,73 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const fs = require("fs");
|
|
4
|
-
const path = require("path");
|
|
5
|
-
|
|
6
|
-
const CONFIG_DIR = ".heymark";
|
|
7
|
-
const CONFIG_FILENAME = "config.json";
|
|
8
|
-
/** 프로젝트 루트 기준 설정 파일 경로 (표시용) */
|
|
9
|
-
const CONFIG_RELATIVE = path.join(CONFIG_DIR, CONFIG_FILENAME);
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* rulesSource: GitHub 저장소 URL (https://github.com/org/repo 또는 git@github.com:org/repo.git)
|
|
13
|
-
* branch: 브랜치 (기본 main)
|
|
14
|
-
* rulesSourceDir: 저장소 내부에서 .md가 있는 하위 디렉터리 (기본 "" = 루트)
|
|
15
|
-
* @typedef {{ rulesSource: string, branch?: string, rulesSourceDir?: string }} RuleBookConfig
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* 프로젝트 루트에서 .heymark/config.json을 읽습니다.
|
|
20
|
-
* @param {string} projectRoot - 프로젝트 루트 (보통 process.cwd())
|
|
21
|
-
* @returns {RuleBookConfig | null}
|
|
22
|
-
*/
|
|
23
|
-
function loadConfig(projectRoot) {
|
|
24
|
-
const configPath = path.join(projectRoot, CONFIG_DIR, CONFIG_FILENAME);
|
|
25
|
-
if (!fs.existsSync(configPath)) return null;
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
const raw = fs.readFileSync(configPath, "utf8");
|
|
29
|
-
const data = JSON.parse(raw);
|
|
30
|
-
if (!data || typeof data.rulesSource !== "string" || !data.rulesSource.trim()) {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
return {
|
|
34
|
-
rulesSource: data.rulesSource.trim(),
|
|
35
|
-
branch:
|
|
36
|
-
typeof data.branch === "string" && data.branch.trim() ? data.branch.trim() : "main",
|
|
37
|
-
rulesSourceDir:
|
|
38
|
-
typeof data.rulesSourceDir === "string" ? data.rulesSourceDir.trim() : "",
|
|
39
|
-
};
|
|
40
|
-
} catch {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* 초기 설정 파일을 생성합니다. (원격 GitHub 저장소 URL 사용) .heymark/config.json에 저장합니다.
|
|
47
|
-
* @param {string} projectRoot
|
|
48
|
-
* @param {RuleBookConfig} config - { rulesSource: repoUrl, branch?, rulesSourceDir? }
|
|
49
|
-
*/
|
|
50
|
-
function writeConfig(projectRoot, config) {
|
|
51
|
-
const configDir = path.join(projectRoot, CONFIG_DIR);
|
|
52
|
-
if (!fs.existsSync(configDir)) {
|
|
53
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
54
|
-
}
|
|
55
|
-
const configPath = path.join(configDir, CONFIG_FILENAME);
|
|
56
|
-
const toWrite = {
|
|
57
|
-
rulesSource: config.rulesSource,
|
|
58
|
-
branch: config.branch || "main",
|
|
59
|
-
};
|
|
60
|
-
if (config.rulesSourceDir) {
|
|
61
|
-
toWrite.rulesSourceDir = config.rulesSourceDir;
|
|
62
|
-
}
|
|
63
|
-
fs.writeFileSync(configPath, JSON.stringify(toWrite, null, 2), "utf8");
|
|
64
|
-
return configPath;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
module.exports = {
|
|
68
|
-
CONFIG_DIR,
|
|
69
|
-
CONFIG_FILENAME,
|
|
70
|
-
CONFIG_RELATIVE,
|
|
71
|
-
loadConfig,
|
|
72
|
-
writeConfig,
|
|
73
|
-
};
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
const CONFIG_DIR = ".heymark";
|
|
7
|
+
const CONFIG_FILENAME = "config.json";
|
|
8
|
+
/** 프로젝트 루트 기준 설정 파일 경로 (표시용) */
|
|
9
|
+
const CONFIG_RELATIVE = path.join(CONFIG_DIR, CONFIG_FILENAME);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* rulesSource: GitHub 저장소 URL (https://github.com/org/repo 또는 git@github.com:org/repo.git)
|
|
13
|
+
* branch: 브랜치 (기본 main)
|
|
14
|
+
* rulesSourceDir: 저장소 내부에서 .md가 있는 하위 디렉터리 (기본 "" = 루트)
|
|
15
|
+
* @typedef {{ rulesSource: string, branch?: string, rulesSourceDir?: string }} RuleBookConfig
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 프로젝트 루트에서 .heymark/config.json을 읽습니다.
|
|
20
|
+
* @param {string} projectRoot - 프로젝트 루트 (보통 process.cwd())
|
|
21
|
+
* @returns {RuleBookConfig | null}
|
|
22
|
+
*/
|
|
23
|
+
function loadConfig(projectRoot) {
|
|
24
|
+
const configPath = path.join(projectRoot, CONFIG_DIR, CONFIG_FILENAME);
|
|
25
|
+
if (!fs.existsSync(configPath)) return null;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const raw = fs.readFileSync(configPath, "utf8");
|
|
29
|
+
const data = JSON.parse(raw);
|
|
30
|
+
if (!data || typeof data.rulesSource !== "string" || !data.rulesSource.trim()) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
rulesSource: data.rulesSource.trim(),
|
|
35
|
+
branch:
|
|
36
|
+
typeof data.branch === "string" && data.branch.trim() ? data.branch.trim() : "main",
|
|
37
|
+
rulesSourceDir:
|
|
38
|
+
typeof data.rulesSourceDir === "string" ? data.rulesSourceDir.trim() : "",
|
|
39
|
+
};
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 초기 설정 파일을 생성합니다. (원격 GitHub 저장소 URL 사용) .heymark/config.json에 저장합니다.
|
|
47
|
+
* @param {string} projectRoot
|
|
48
|
+
* @param {RuleBookConfig} config - { rulesSource: repoUrl, branch?, rulesSourceDir? }
|
|
49
|
+
*/
|
|
50
|
+
function writeConfig(projectRoot, config) {
|
|
51
|
+
const configDir = path.join(projectRoot, CONFIG_DIR);
|
|
52
|
+
if (!fs.existsSync(configDir)) {
|
|
53
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
const configPath = path.join(configDir, CONFIG_FILENAME);
|
|
56
|
+
const toWrite = {
|
|
57
|
+
rulesSource: config.rulesSource,
|
|
58
|
+
branch: config.branch || "main",
|
|
59
|
+
};
|
|
60
|
+
if (config.rulesSourceDir) {
|
|
61
|
+
toWrite.rulesSourceDir = config.rulesSourceDir;
|
|
62
|
+
}
|
|
63
|
+
fs.writeFileSync(configPath, JSON.stringify(toWrite, null, 2), "utf8");
|
|
64
|
+
return configPath;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = {
|
|
68
|
+
CONFIG_DIR,
|
|
69
|
+
CONFIG_FILENAME,
|
|
70
|
+
CONFIG_RELATIVE,
|
|
71
|
+
loadConfig,
|
|
72
|
+
writeConfig,
|
|
73
|
+
};
|
package/scripts/lib/parser.js
CHANGED
|
@@ -1,82 +1,82 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const fs = require("fs");
|
|
4
|
-
const path = require("path");
|
|
5
|
-
|
|
6
|
-
const GENERATED_MARKER = "<!-- @generated by sync-rules - DO NOT EDIT -->";
|
|
7
|
-
|
|
8
|
-
function parseFrontmatter(content) {
|
|
9
|
-
const match = content.match(/^---\r?\n([\s\S]+?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
10
|
-
if (!match) return { metadata: {}, body: content.trim() };
|
|
11
|
-
|
|
12
|
-
const metadata = {};
|
|
13
|
-
match[1].split(/\r?\n/).forEach((line) => {
|
|
14
|
-
const idx = line.indexOf(":");
|
|
15
|
-
if (idx === -1) return;
|
|
16
|
-
const key = line.slice(0, idx).trim();
|
|
17
|
-
let value = line.slice(idx + 1).trim();
|
|
18
|
-
if (
|
|
19
|
-
(value.startsWith('"') && value.endsWith('"')) ||
|
|
20
|
-
(value.startsWith("'") && value.endsWith("'"))
|
|
21
|
-
) {
|
|
22
|
-
value = value.slice(1, -1);
|
|
23
|
-
}
|
|
24
|
-
if (value === "true") value = true;
|
|
25
|
-
else if (value === "false") value = false;
|
|
26
|
-
metadata[key] = value;
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
return { metadata, body: match[2].trim() };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function loadRules(rulesDir) {
|
|
33
|
-
if (!fs.existsSync(rulesDir)) {
|
|
34
|
-
console.error(`[Error] Rules directory not found: ${rulesDir}`);
|
|
35
|
-
console.error(" Ensure 'rules/' exists relative to the script location.");
|
|
36
|
-
process.exit(1);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const files = fs
|
|
40
|
-
.readdirSync(rulesDir)
|
|
41
|
-
.filter((f) => f.endsWith(".md"))
|
|
42
|
-
.sort();
|
|
43
|
-
|
|
44
|
-
if (files.length === 0) {
|
|
45
|
-
console.error(`[Error] No .md files found in: ${rulesDir}`);
|
|
46
|
-
process.exit(1);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return files.map((file) => {
|
|
50
|
-
const raw = fs.readFileSync(path.join(rulesDir, file), "utf8");
|
|
51
|
-
const { metadata, body } = parseFrontmatter(raw);
|
|
52
|
-
const baseName = path.basename(file, ".md");
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
fileName: file,
|
|
56
|
-
name: metadata.name || baseName,
|
|
57
|
-
description: metadata.description || baseName,
|
|
58
|
-
globs: metadata.globs || "",
|
|
59
|
-
alwaysApply: metadata.alwaysApply === true,
|
|
60
|
-
metadata,
|
|
61
|
-
body,
|
|
62
|
-
};
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function mergeRuleBodies(rules) {
|
|
67
|
-
return rules.map((r) => r.body).join("\n\n---\n\n");
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function writeMergedFile(filePath, rules) {
|
|
71
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
72
|
-
const content = `${GENERATED_MARKER}\n\n${mergeRuleBodies(rules)}\n`;
|
|
73
|
-
fs.writeFileSync(filePath, content);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
module.exports = {
|
|
77
|
-
GENERATED_MARKER,
|
|
78
|
-
parseFrontmatter,
|
|
79
|
-
loadRules,
|
|
80
|
-
mergeRuleBodies,
|
|
81
|
-
writeMergedFile,
|
|
82
|
-
};
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
const GENERATED_MARKER = "<!-- @generated by sync-rules - DO NOT EDIT -->";
|
|
7
|
+
|
|
8
|
+
function parseFrontmatter(content) {
|
|
9
|
+
const match = content.match(/^---\r?\n([\s\S]+?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
10
|
+
if (!match) return { metadata: {}, body: content.trim() };
|
|
11
|
+
|
|
12
|
+
const metadata = {};
|
|
13
|
+
match[1].split(/\r?\n/).forEach((line) => {
|
|
14
|
+
const idx = line.indexOf(":");
|
|
15
|
+
if (idx === -1) return;
|
|
16
|
+
const key = line.slice(0, idx).trim();
|
|
17
|
+
let value = line.slice(idx + 1).trim();
|
|
18
|
+
if (
|
|
19
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
20
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
21
|
+
) {
|
|
22
|
+
value = value.slice(1, -1);
|
|
23
|
+
}
|
|
24
|
+
if (value === "true") value = true;
|
|
25
|
+
else if (value === "false") value = false;
|
|
26
|
+
metadata[key] = value;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return { metadata, body: match[2].trim() };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function loadRules(rulesDir) {
|
|
33
|
+
if (!fs.existsSync(rulesDir)) {
|
|
34
|
+
console.error(`[Error] Rules directory not found: ${rulesDir}`);
|
|
35
|
+
console.error(" Ensure 'rules/' exists relative to the script location.");
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const files = fs
|
|
40
|
+
.readdirSync(rulesDir)
|
|
41
|
+
.filter((f) => f.endsWith(".md"))
|
|
42
|
+
.sort();
|
|
43
|
+
|
|
44
|
+
if (files.length === 0) {
|
|
45
|
+
console.error(`[Error] No .md files found in: ${rulesDir}`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return files.map((file) => {
|
|
50
|
+
const raw = fs.readFileSync(path.join(rulesDir, file), "utf8");
|
|
51
|
+
const { metadata, body } = parseFrontmatter(raw);
|
|
52
|
+
const baseName = path.basename(file, ".md");
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
fileName: file,
|
|
56
|
+
name: metadata.name || baseName,
|
|
57
|
+
description: metadata.description || baseName,
|
|
58
|
+
globs: metadata.globs || "",
|
|
59
|
+
alwaysApply: metadata.alwaysApply === true,
|
|
60
|
+
metadata,
|
|
61
|
+
body,
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function mergeRuleBodies(rules) {
|
|
67
|
+
return rules.map((r) => r.body).join("\n\n---\n\n");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function writeMergedFile(filePath, rules) {
|
|
71
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
72
|
+
const content = `${GENERATED_MARKER}\n\n${mergeRuleBodies(rules)}\n`;
|
|
73
|
+
fs.writeFileSync(filePath, content);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = {
|
|
77
|
+
GENERATED_MARKER,
|
|
78
|
+
parseFrontmatter,
|
|
79
|
+
loadRules,
|
|
80
|
+
mergeRuleBodies,
|
|
81
|
+
writeMergedFile,
|
|
82
|
+
};
|
package/scripts/lib/repo.js
CHANGED
|
@@ -1,80 +1,80 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const fs = require("fs");
|
|
4
|
-
const path = require("path");
|
|
5
|
-
const { execSync } = require("child_process");
|
|
6
|
-
|
|
7
|
-
const CACHE_DIR_NAME = path.join(".heymark", "cache");
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* 저장소 URL에서 캐시 폴더명으로 쓸 수 있는 문자열 추출
|
|
11
|
-
* https://github.com/org/repo -> org-repo
|
|
12
|
-
* git@github.com:org/repo.git -> org-repo
|
|
13
|
-
*/
|
|
14
|
-
function sanitizeRepoName(url) {
|
|
15
|
-
let s = url.trim();
|
|
16
|
-
if (s.endsWith(".git")) s = s.slice(0, -4);
|
|
17
|
-
const match =
|
|
18
|
-
s.match(/github\.com[:/]([^/]+\/[^/]+?)(?:\/|$)/) || s.match(/([^/]+\/[^/]+?)(?:\/|$)/);
|
|
19
|
-
if (match) {
|
|
20
|
-
return match[1].replace(/\//g, "-");
|
|
21
|
-
}
|
|
22
|
-
return s.replace(/[^a-zA-Z0-9._-]/g, "-") || "repo";
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* 원격 저장소를 clone 또는 pull하여, 규칙 .md가 있는 로컬 디렉터리 절대 경로를 반환합니다.
|
|
27
|
-
* Private repo는 사용자의 git 인증(SSH 키, credential)으로 접근해야 합니다.
|
|
28
|
-
* @param {string} projectRoot - 현재 프로젝트 루트
|
|
29
|
-
* @param {{ rulesSource: string, branch?: string, rulesSourceDir?: string }} config
|
|
30
|
-
* @returns {string} - .md 파일이 있는 디렉터리의 절대 경로
|
|
31
|
-
*/
|
|
32
|
-
function getRulesDirFromRepo(projectRoot, config) {
|
|
33
|
-
const url = config.rulesSource;
|
|
34
|
-
const branch = config.branch || "main";
|
|
35
|
-
const subDir = config.rulesSourceDir || "";
|
|
36
|
-
|
|
37
|
-
const cacheBase = path.join(projectRoot, CACHE_DIR_NAME);
|
|
38
|
-
const repoName = sanitizeRepoName(url);
|
|
39
|
-
const clonePath = path.join(cacheBase, repoName);
|
|
40
|
-
|
|
41
|
-
if (!fs.existsSync(clonePath)) {
|
|
42
|
-
fs.mkdirSync(cacheBase, { recursive: true });
|
|
43
|
-
try {
|
|
44
|
-
execSync(`git clone --depth 1 --branch "${branch}" "${url}" "${clonePath}"`, {
|
|
45
|
-
stdio: "inherit",
|
|
46
|
-
cwd: projectRoot,
|
|
47
|
-
});
|
|
48
|
-
} catch (err) {
|
|
49
|
-
console.error("[Error] Failed to clone rules repository.");
|
|
50
|
-
console.error(" For private repos, ensure you have access (SSH key or HTTPS token).");
|
|
51
|
-
console.error(" Example: heymark init https://github.com/org/repo.git");
|
|
52
|
-
process.exit(1);
|
|
53
|
-
}
|
|
54
|
-
} else {
|
|
55
|
-
try {
|
|
56
|
-
execSync(
|
|
57
|
-
"git fetch origin && git checkout --quiet . && git pull --quiet origin " + branch,
|
|
58
|
-
{
|
|
59
|
-
stdio: "pipe",
|
|
60
|
-
cwd: clonePath,
|
|
61
|
-
}
|
|
62
|
-
);
|
|
63
|
-
} catch (err) {
|
|
64
|
-
// pull 실패 시(네트워크 등) 기존 클론 내용으로 진행
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const rulesDir = subDir ? path.join(clonePath, subDir) : clonePath;
|
|
69
|
-
if (!fs.existsSync(rulesDir) || !fs.statSync(rulesDir).isDirectory()) {
|
|
70
|
-
console.error(`[Error] Rules directory not found in repo: ${subDir || "(root)"}`);
|
|
71
|
-
process.exit(1);
|
|
72
|
-
}
|
|
73
|
-
return rulesDir;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
module.exports = {
|
|
77
|
-
CACHE_DIR_NAME,
|
|
78
|
-
getRulesDirFromRepo,
|
|
79
|
-
sanitizeRepoName,
|
|
80
|
-
};
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { execSync } = require("child_process");
|
|
6
|
+
|
|
7
|
+
const CACHE_DIR_NAME = path.join(".heymark", "cache");
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 저장소 URL에서 캐시 폴더명으로 쓸 수 있는 문자열 추출
|
|
11
|
+
* https://github.com/org/repo -> org-repo
|
|
12
|
+
* git@github.com:org/repo.git -> org-repo
|
|
13
|
+
*/
|
|
14
|
+
function sanitizeRepoName(url) {
|
|
15
|
+
let s = url.trim();
|
|
16
|
+
if (s.endsWith(".git")) s = s.slice(0, -4);
|
|
17
|
+
const match =
|
|
18
|
+
s.match(/github\.com[:/]([^/]+\/[^/]+?)(?:\/|$)/) || s.match(/([^/]+\/[^/]+?)(?:\/|$)/);
|
|
19
|
+
if (match) {
|
|
20
|
+
return match[1].replace(/\//g, "-");
|
|
21
|
+
}
|
|
22
|
+
return s.replace(/[^a-zA-Z0-9._-]/g, "-") || "repo";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 원격 저장소를 clone 또는 pull하여, 규칙 .md가 있는 로컬 디렉터리 절대 경로를 반환합니다.
|
|
27
|
+
* Private repo는 사용자의 git 인증(SSH 키, credential)으로 접근해야 합니다.
|
|
28
|
+
* @param {string} projectRoot - 현재 프로젝트 루트
|
|
29
|
+
* @param {{ rulesSource: string, branch?: string, rulesSourceDir?: string }} config
|
|
30
|
+
* @returns {string} - .md 파일이 있는 디렉터리의 절대 경로
|
|
31
|
+
*/
|
|
32
|
+
function getRulesDirFromRepo(projectRoot, config) {
|
|
33
|
+
const url = config.rulesSource;
|
|
34
|
+
const branch = config.branch || "main";
|
|
35
|
+
const subDir = config.rulesSourceDir || "";
|
|
36
|
+
|
|
37
|
+
const cacheBase = path.join(projectRoot, CACHE_DIR_NAME);
|
|
38
|
+
const repoName = sanitizeRepoName(url);
|
|
39
|
+
const clonePath = path.join(cacheBase, repoName);
|
|
40
|
+
|
|
41
|
+
if (!fs.existsSync(clonePath)) {
|
|
42
|
+
fs.mkdirSync(cacheBase, { recursive: true });
|
|
43
|
+
try {
|
|
44
|
+
execSync(`git clone --depth 1 --branch "${branch}" "${url}" "${clonePath}"`, {
|
|
45
|
+
stdio: "inherit",
|
|
46
|
+
cwd: projectRoot,
|
|
47
|
+
});
|
|
48
|
+
} catch (err) {
|
|
49
|
+
console.error("[Error] Failed to clone rules repository.");
|
|
50
|
+
console.error(" For private repos, ensure you have access (SSH key or HTTPS token).");
|
|
51
|
+
console.error(" Example: heymark init https://github.com/org/repo.git");
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
try {
|
|
56
|
+
execSync(
|
|
57
|
+
"git fetch origin && git checkout --quiet . && git pull --quiet origin " + branch,
|
|
58
|
+
{
|
|
59
|
+
stdio: "pipe",
|
|
60
|
+
cwd: clonePath,
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
// pull 실패 시(네트워크 등) 기존 클론 내용으로 진행
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const rulesDir = subDir ? path.join(clonePath, subDir) : clonePath;
|
|
69
|
+
if (!fs.existsSync(rulesDir) || !fs.statSync(rulesDir).isDirectory()) {
|
|
70
|
+
console.error(`[Error] Rules directory not found in repo: ${subDir || "(root)"}`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
return rulesDir;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = {
|
|
77
|
+
CACHE_DIR_NAME,
|
|
78
|
+
getRulesDirFromRepo,
|
|
79
|
+
sanitizeRepoName,
|
|
80
|
+
};
|