x7-code-line 0.1.0 → 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/README.md +83 -9
- package/package.json +1 -1
- package/src/cache.js +14 -0
- package/src/cli.js +21 -3
- package/src/diff-lines.js +38 -6
- package/src/install.js +122 -18
package/README.md
CHANGED
|
@@ -18,6 +18,20 @@
|
|
|
18
18
|
|
|
19
19
|
作为 npm 依赖安装时,`postinstall` 脚本使用 `INIT_CWD` 作为当前安装目录,也就是执行 `npm install` 时所在的项目目录。
|
|
20
20
|
|
|
21
|
+
如果只是全局安装:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npm install -g x7-code-line
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
此时只安装 CLI,不会自动为任何项目写入 hooks。全局安装完成后,需要手动执行:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
x7-code-line install --dir <absolute-path>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
只有在 `X7_CODE_LINE_DIRS` 或 `X7_CODE_LINE_CONFIG` 明确提供目标目录时,`postinstall` 才会继续执行项目安装逻辑。
|
|
34
|
+
|
|
21
35
|
当前安装目录会创建或覆盖:
|
|
22
36
|
|
|
23
37
|
```text
|
|
@@ -100,6 +114,7 @@ git diff --no-ext-diff --unified=0 --no-color
|
|
|
100
114
|
- 变更类型:`add` 或 `delete`
|
|
101
115
|
- 行号
|
|
102
116
|
- 行内容
|
|
117
|
+
- 该文件当前的修改时间
|
|
103
118
|
|
|
104
119
|
如果该 ID 不在 AI diff 缓存中,就写入普通 diff 缓存:
|
|
105
120
|
|
|
@@ -146,6 +161,15 @@ git diff --cached --no-ext-diff --unified=0 --no-color
|
|
|
146
161
|
.x7-code-line/pending-commit.json
|
|
147
162
|
```
|
|
148
163
|
|
|
164
|
+
计算完成后,会清除当前仓库在以下两份中心缓存中的条目:
|
|
165
|
+
|
|
166
|
+
```text
|
|
167
|
+
.x7-code-line/ai-diff-line-ids.json
|
|
168
|
+
.x7-code-line/normal-diff-line-ids.json
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
其他项目的缓存不会被修改。
|
|
172
|
+
|
|
149
173
|
`commit-msg` 会读取该结果,并追加或替换 trailer:
|
|
150
174
|
|
|
151
175
|
```text
|
|
@@ -160,25 +184,45 @@ x7-ai-lines: 0
|
|
|
160
184
|
|
|
161
185
|
## 多项目安装
|
|
162
186
|
|
|
163
|
-
|
|
187
|
+
`install` 至少需要传入一个目录;0 目录场景会直接失败。
|
|
188
|
+
|
|
189
|
+
默认要求传入绝对路径:
|
|
190
|
+
|
|
191
|
+
```sh
|
|
192
|
+
npm install -g x7-code-line --dir /abs/path/repo-a --dir /abs/path/repo-b
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
例如,将 `x7-code-line` 安装在 C 盘目录,但指定 D 盘的 Git 项目:
|
|
196
|
+
|
|
197
|
+
```powershell
|
|
198
|
+
cd C:\x7-tools\x7-code-line-host
|
|
199
|
+
npm install -g x7-code-line --dir D:\work\repo-a --dir D:\work\repo-b
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
如果确实需要解析相对路径,必须显式传 `--relative`:
|
|
164
203
|
|
|
165
204
|
```sh
|
|
166
|
-
|
|
205
|
+
npm install -g x7-code-line --relative --dir ../repo-a --dir ../repo-b
|
|
167
206
|
```
|
|
168
207
|
|
|
169
|
-
|
|
208
|
+
此时相对路径基于当前执行 `install` 命令的目录解析。
|
|
209
|
+
|
|
210
|
+
也可以在安装时通过环境变量指定多个目标目录。
|
|
211
|
+
|
|
212
|
+
如果通过环境变量或配置文件传相对路径,需要同时设置 `X7_CODE_LINE_RELATIVE_PATHS=1`。
|
|
170
213
|
|
|
171
|
-
macOS / Linux
|
|
214
|
+
macOS / Linux 示例:
|
|
172
215
|
|
|
173
216
|
```sh
|
|
174
|
-
X7_CODE_LINE_DIRS="../repo-a:../repo-b" npm install x7-code-line
|
|
217
|
+
X7_CODE_LINE_RELATIVE_PATHS=1 X7_CODE_LINE_DIRS="../repo-a:../repo-b" npm install -g x7-code-line
|
|
175
218
|
```
|
|
176
219
|
|
|
177
220
|
Windows 使用 `;` 作为目录分隔符:
|
|
178
221
|
|
|
179
222
|
```powershell
|
|
223
|
+
$env:X7_CODE_LINE_RELATIVE_PATHS = "1"
|
|
180
224
|
$env:X7_CODE_LINE_DIRS = "../repo-a;../repo-b"
|
|
181
|
-
npm install x7-code-line
|
|
225
|
+
npm install -g x7-code-line
|
|
182
226
|
```
|
|
183
227
|
|
|
184
228
|
## 配置文件
|
|
@@ -231,10 +275,40 @@ which npx
|
|
|
231
275
|
$env:X7_CODE_LINE_DIRS = "D:\repo-a;D:\repo-b"
|
|
232
276
|
```
|
|
233
277
|
|
|
278
|
+
## 增量添加目录
|
|
279
|
+
|
|
280
|
+
在已经完成安装的当前安装目录下,可以使用 `addDir` 增量添加新的项目目录:
|
|
281
|
+
|
|
282
|
+
```sh
|
|
283
|
+
x7-code-line addDir --dir /abs/path/repo-c
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
这个命令只做两件事:
|
|
287
|
+
|
|
288
|
+
- 把新目录追加到当前安装目录 `.x7-code-line/projects.json`
|
|
289
|
+
- 为新目录安装 `.git/hooks/pre-commit` 和 `.git/hooks/commit-msg`
|
|
290
|
+
|
|
291
|
+
它不会重写当前安装目录的 `.cursor/hooks.json`、`.codex/hooks.json`,也不会重写已有项目集合。
|
|
292
|
+
|
|
293
|
+
`addDir` 会沿用与 `install` 相同的严格校验:
|
|
294
|
+
|
|
295
|
+
- 目录必须真实存在
|
|
296
|
+
- 目录必须可访问
|
|
297
|
+
- 目录必须包含可用的 `.git` 目录
|
|
298
|
+
|
|
299
|
+
任一新增目录不满足条件时,命令直接失败,不会继续追加。
|
|
300
|
+
|
|
301
|
+
默认只接受绝对路径;如果需要解析相对路径,需要显式传入:
|
|
302
|
+
|
|
303
|
+
```sh
|
|
304
|
+
x7-code-line addDir --relative --dir ../repo-c
|
|
305
|
+
```
|
|
306
|
+
|
|
234
307
|
## CLI 命令
|
|
235
308
|
|
|
236
309
|
```sh
|
|
237
|
-
x7-code-line install
|
|
310
|
+
x7-code-line install --dir <absolute-path> [--dir <absolute-path> ...] [--config path] [--relative]
|
|
311
|
+
x7-code-line addDir --dir <absolute-path> [--dir <absolute-path> ...] [--config path] [--relative]
|
|
238
312
|
x7-code-line prompt-submit
|
|
239
313
|
x7-code-line stop
|
|
240
314
|
x7-code-line git-pre-commit
|
|
@@ -274,7 +348,7 @@ npm login
|
|
|
274
348
|
确认 `package.json` 中的包名和版本号正确后,执行:
|
|
275
349
|
|
|
276
350
|
```sh
|
|
277
|
-
npm publish
|
|
351
|
+
npm publish --access public
|
|
278
352
|
```
|
|
279
353
|
|
|
280
354
|
如果 npm 账号开启了 2FA,通常需要附带 OTP:
|
|
@@ -291,7 +365,7 @@ npm publish --access public --otp <6位验证码>
|
|
|
291
365
|
|
|
292
366
|
### 更新已发布版本
|
|
293
367
|
|
|
294
|
-
|
|
368
|
+
每次发布新版本前,手动修改package.json版本号或命令提升版本号:
|
|
295
369
|
|
|
296
370
|
```sh
|
|
297
371
|
npm version patch
|
package/package.json
CHANGED
package/src/cache.js
CHANGED
|
@@ -67,6 +67,19 @@ function addProjectId(cache, projectDir, id) {
|
|
|
67
67
|
return false;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
function clearProjectIds(cache, projectDir) {
|
|
71
|
+
const key = path.resolve(projectDir);
|
|
72
|
+
if (!cache.projects || typeof cache.projects !== "object" || Array.isArray(cache.projects)) {
|
|
73
|
+
cache.projects = {};
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
if (Object.prototype.hasOwnProperty.call(cache.projects, key)) {
|
|
77
|
+
delete cache.projects[key];
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
70
83
|
function ensureJson(filePath, defaultValue) {
|
|
71
84
|
if (!fs.existsSync(filePath)) {
|
|
72
85
|
writeJson(filePath, defaultValue);
|
|
@@ -92,6 +105,7 @@ module.exports = {
|
|
|
92
105
|
PENDING_COMMIT_FILE,
|
|
93
106
|
PROJECTS_CACHE_FILE,
|
|
94
107
|
addProjectId,
|
|
108
|
+
clearProjectIds,
|
|
95
109
|
ensureCacheFiles,
|
|
96
110
|
getCacheDir,
|
|
97
111
|
getProjectIds,
|
package/src/cli.js
CHANGED
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
const fs = require("node:fs");
|
|
4
4
|
const path = require("node:path");
|
|
5
5
|
const { handlePromptSubmit, handleStop } = require("./agent-hooks");
|
|
6
|
-
const { install } = require("./install");
|
|
6
|
+
const { addDirs, install } = require("./install");
|
|
7
7
|
const { appendCommitTrailer } = require("./git-trailer");
|
|
8
8
|
const {
|
|
9
9
|
AI_DIFF_CACHE_FILE,
|
|
10
|
+
NORMAL_DIFF_CACHE_FILE,
|
|
10
11
|
PENDING_COMMIT_FILE,
|
|
12
|
+
clearProjectIds,
|
|
11
13
|
getCacheDir,
|
|
12
14
|
getProjectIds,
|
|
13
15
|
readLineIdCache
|
|
@@ -20,6 +22,8 @@ async function runCli(argv) {
|
|
|
20
22
|
switch (command) {
|
|
21
23
|
case "install":
|
|
22
24
|
return install(parseInstallOptions(rest));
|
|
25
|
+
case "addDir":
|
|
26
|
+
return addDirs(parseInstallOptions(rest));
|
|
23
27
|
case "prompt-submit":
|
|
24
28
|
case "cursor-before-submit-prompt":
|
|
25
29
|
case "codex-user-prompt-submit":
|
|
@@ -44,13 +48,16 @@ async function runCli(argv) {
|
|
|
44
48
|
function parseInstallOptions(args) {
|
|
45
49
|
const options = {
|
|
46
50
|
targetDirs: [],
|
|
47
|
-
configPath: undefined
|
|
51
|
+
configPath: undefined,
|
|
52
|
+
relativePaths: false
|
|
48
53
|
};
|
|
49
54
|
|
|
50
55
|
for (let index = 0; index < args.length; index += 1) {
|
|
51
56
|
const arg = args[index];
|
|
52
57
|
if (arg === "--config" || arg === "-c") {
|
|
53
58
|
options.configPath = requireValue(args, (index += 1), arg);
|
|
59
|
+
} else if (arg === "--relative") {
|
|
60
|
+
options.relativePaths = true;
|
|
54
61
|
} else if (arg === "--dir" || arg === "-d") {
|
|
55
62
|
options.targetDirs.push(requireValue(args, (index += 1), arg));
|
|
56
63
|
} else if (arg === "--dirs") {
|
|
@@ -75,6 +82,7 @@ async function runGitPreCommit() {
|
|
|
75
82
|
const baseDir = getBaseDir();
|
|
76
83
|
const projectDir = process.cwd();
|
|
77
84
|
const aiCache = readLineIdCache(baseDir, AI_DIFF_CACHE_FILE);
|
|
85
|
+
const normalCache = readLineIdCache(baseDir, NORMAL_DIFF_CACHE_FILE);
|
|
78
86
|
const aiIds = getProjectIds(aiCache, projectDir);
|
|
79
87
|
const stagedIds = getGitDiffLineIds(projectDir, { staged: true });
|
|
80
88
|
const aiLineCount = stagedIds.filter((id) => aiIds.has(id)).length;
|
|
@@ -83,6 +91,10 @@ async function runGitPreCommit() {
|
|
|
83
91
|
pendingPath,
|
|
84
92
|
`${JSON.stringify({ projects: { [path.resolve(projectDir)]: { aiLineCount } } }, null, 2)}\n`
|
|
85
93
|
);
|
|
94
|
+
clearProjectIds(aiCache, projectDir);
|
|
95
|
+
clearProjectIds(normalCache, projectDir);
|
|
96
|
+
writeCacheFile(baseDir, AI_DIFF_CACHE_FILE, aiCache);
|
|
97
|
+
writeCacheFile(baseDir, NORMAL_DIFF_CACHE_FILE, normalCache);
|
|
86
98
|
|
|
87
99
|
if (process.env.X7_CODE_LINE_DEBUG === "1") {
|
|
88
100
|
console.error(`[x7-code-line] git pre-commit ai lines: ${aiLineCount}`);
|
|
@@ -113,10 +125,16 @@ function readPendingAiLineCount(baseDir, projectDir) {
|
|
|
113
125
|
}
|
|
114
126
|
}
|
|
115
127
|
|
|
128
|
+
function writeCacheFile(baseDir, fileName, cache) {
|
|
129
|
+
const cacheDir = getCacheDir(baseDir);
|
|
130
|
+
fs.writeFileSync(path.join(cacheDir, fileName), `${JSON.stringify(cache, null, 2)}\n`);
|
|
131
|
+
}
|
|
132
|
+
|
|
116
133
|
function printHelp() {
|
|
117
134
|
const text = [
|
|
118
135
|
"Usage:",
|
|
119
|
-
" x7-code-line install
|
|
136
|
+
" x7-code-line install --dir <absolute-path> [--dir <absolute-path> ...] [--config path] [--relative]",
|
|
137
|
+
" x7-code-line addDir --dir <absolute-path> [--dir <absolute-path> ...] [--config path] [--relative]",
|
|
120
138
|
" x7-code-line prompt-submit",
|
|
121
139
|
" x7-code-line stop",
|
|
122
140
|
" x7-code-line git-pre-commit",
|
package/src/diff-lines.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const crypto = require("node:crypto");
|
|
4
|
+
const fs = require("node:fs");
|
|
4
5
|
const path = require("node:path");
|
|
5
6
|
const { execFileSync } = require("node:child_process");
|
|
6
7
|
|
|
@@ -16,14 +17,15 @@ function getGitDiffLineIds(projectDir, options = {}) {
|
|
|
16
17
|
stdio: ["ignore", "pipe", "ignore"]
|
|
17
18
|
});
|
|
18
19
|
|
|
19
|
-
return parseDiffLineIds(projectDir, diff);
|
|
20
|
+
return parseDiffLineIds(projectDir, diff, options);
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
function parseDiffLineIds(projectDir, diff) {
|
|
23
|
+
function parseDiffLineIds(projectDir, diff, options = {}) {
|
|
23
24
|
const ids = [];
|
|
24
25
|
let currentFile = "";
|
|
25
26
|
let oldLine = 0;
|
|
26
27
|
let newLine = 0;
|
|
28
|
+
const fileMtimes = new Map();
|
|
27
29
|
|
|
28
30
|
for (const line of diff.split(/\r?\n/u)) {
|
|
29
31
|
if (line.startsWith("+++ b/")) {
|
|
@@ -47,10 +49,21 @@ function parseDiffLineIds(projectDir, diff) {
|
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
50
|
-
ids.push(
|
|
52
|
+
ids.push(
|
|
53
|
+
makeLineId(projectDir, currentFile, "add", newLine, line.slice(1), getFileModifiedTime(projectDir, currentFile, fileMtimes))
|
|
54
|
+
);
|
|
51
55
|
newLine += 1;
|
|
52
56
|
} else if (line.startsWith("-") && !line.startsWith("---")) {
|
|
53
|
-
ids.push(
|
|
57
|
+
ids.push(
|
|
58
|
+
makeLineId(
|
|
59
|
+
projectDir,
|
|
60
|
+
currentFile,
|
|
61
|
+
"delete",
|
|
62
|
+
oldLine,
|
|
63
|
+
line.slice(1),
|
|
64
|
+
getFileModifiedTime(projectDir, currentFile, fileMtimes)
|
|
65
|
+
)
|
|
66
|
+
);
|
|
54
67
|
oldLine += 1;
|
|
55
68
|
} else if (line.startsWith(" ")) {
|
|
56
69
|
oldLine += 1;
|
|
@@ -61,7 +74,7 @@ function parseDiffLineIds(projectDir, diff) {
|
|
|
61
74
|
return ids;
|
|
62
75
|
}
|
|
63
76
|
|
|
64
|
-
function makeLineId(projectDir, filePath, changeType, lineNumber, content) {
|
|
77
|
+
function makeLineId(projectDir, filePath, changeType, lineNumber, content, modifiedTimeMs = 0) {
|
|
65
78
|
return crypto
|
|
66
79
|
.createHash("sha256")
|
|
67
80
|
.update(
|
|
@@ -70,12 +83,31 @@ function makeLineId(projectDir, filePath, changeType, lineNumber, content) {
|
|
|
70
83
|
file: filePath,
|
|
71
84
|
changeType,
|
|
72
85
|
lineNumber,
|
|
73
|
-
content
|
|
86
|
+
content,
|
|
87
|
+
modifiedTimeMs
|
|
74
88
|
})
|
|
75
89
|
)
|
|
76
90
|
.digest("hex");
|
|
77
91
|
}
|
|
78
92
|
|
|
93
|
+
function getFileModifiedTime(projectDir, filePath, fileMtimes) {
|
|
94
|
+
const key = `${path.resolve(projectDir)}\0${filePath}`;
|
|
95
|
+
if (fileMtimes.has(key)) {
|
|
96
|
+
return fileMtimes.get(key);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const absolutePath = path.resolve(projectDir, filePath);
|
|
100
|
+
let modifiedTimeMs = 0;
|
|
101
|
+
try {
|
|
102
|
+
modifiedTimeMs = fs.statSync(absolutePath).mtimeMs;
|
|
103
|
+
} catch {
|
|
104
|
+
modifiedTimeMs = 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
fileMtimes.set(key, modifiedTimeMs);
|
|
108
|
+
return modifiedTimeMs;
|
|
109
|
+
}
|
|
110
|
+
|
|
79
111
|
module.exports = {
|
|
80
112
|
getGitDiffLineIds,
|
|
81
113
|
makeLineId,
|
package/src/install.js
CHANGED
|
@@ -2,32 +2,70 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require("node:fs");
|
|
4
4
|
const path = require("node:path");
|
|
5
|
-
const { CACHE_DIR_NAME, ensureCacheFiles, writeProjects } = require("./cache");
|
|
5
|
+
const { CACHE_DIR_NAME, ensureCacheFiles, readProjects, writeProjects } = require("./cache");
|
|
6
6
|
|
|
7
7
|
const PACKAGE_ROOT = path.resolve(__dirname, "..");
|
|
8
8
|
|
|
9
9
|
function install(options = {}) {
|
|
10
|
+
const pathBaseDir = path.resolve(options.pathBaseDir || process.env.INIT_CWD || process.cwd());
|
|
10
11
|
const config = readConfig(options.configPath || process.env.X7_CODE_LINE_CONFIG);
|
|
11
|
-
const targetDirs = normalizeTargetDirs(options.targetDirs, config
|
|
12
|
-
|
|
12
|
+
const targetDirs = normalizeTargetDirs(options.targetDirs, config, {
|
|
13
|
+
relativePaths: Boolean(options.relativePaths || process.env.X7_CODE_LINE_RELATIVE_PATHS === "1"),
|
|
14
|
+
pathBaseDir
|
|
15
|
+
});
|
|
16
|
+
const cacheBaseDir = path.resolve(options.cacheBaseDir || pathBaseDir);
|
|
17
|
+
const validatedTargets = validateTargets(targetDirs, config);
|
|
13
18
|
|
|
14
19
|
installCurrentProjectFiles(cacheBaseDir);
|
|
15
|
-
for (const
|
|
16
|
-
installGitHooksInto(
|
|
20
|
+
for (const target of validatedTargets) {
|
|
21
|
+
installGitHooksInto(target, cacheBaseDir);
|
|
17
22
|
}
|
|
18
|
-
writeProjects(cacheBaseDir,
|
|
23
|
+
writeProjects(cacheBaseDir, validatedTargets.map((target) => target.targetDir));
|
|
19
24
|
|
|
20
|
-
return
|
|
25
|
+
return validatedTargets.map((target) => target.targetDir);
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
function installFromPostinstall() {
|
|
24
29
|
const envDirs = splitList(process.env.X7_CODE_LINE_DIRS);
|
|
25
|
-
const
|
|
30
|
+
const configPath = process.env.X7_CODE_LINE_CONFIG;
|
|
31
|
+
const config = readConfig(configPath);
|
|
32
|
+
const configuredDirs = Array.isArray(config.dirs) ? config.dirs.map((dir) => String(dir || "").trim()).filter(Boolean) : [];
|
|
33
|
+
|
|
34
|
+
if (envDirs.length === 0 && configuredDirs.length === 0) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
26
37
|
|
|
27
38
|
return install({
|
|
28
|
-
configPath
|
|
29
|
-
targetDirs
|
|
39
|
+
configPath,
|
|
40
|
+
targetDirs: envDirs,
|
|
41
|
+
relativePaths: process.env.X7_CODE_LINE_RELATIVE_PATHS === "1"
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function addDirs(options = {}) {
|
|
46
|
+
const explicitDirs = Array.isArray(options.targetDirs) ? options.targetDirs.map((dir) => String(dir || "").trim()).filter(Boolean) : [];
|
|
47
|
+
if (explicitDirs.length === 0) {
|
|
48
|
+
throw new Error("addDir requires at least one --dir value");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const pathBaseDir = path.resolve(options.pathBaseDir || process.env.INIT_CWD || process.cwd());
|
|
52
|
+
const config = readConfig(options.configPath || process.env.X7_CODE_LINE_CONFIG);
|
|
53
|
+
const targetDirs = normalizeTargetDirs(explicitDirs, config, {
|
|
54
|
+
relativePaths: Boolean(options.relativePaths || process.env.X7_CODE_LINE_RELATIVE_PATHS === "1"),
|
|
55
|
+
pathBaseDir
|
|
30
56
|
});
|
|
57
|
+
const cacheBaseDir = path.resolve(options.cacheBaseDir || pathBaseDir);
|
|
58
|
+
assertInstalledBase(cacheBaseDir);
|
|
59
|
+
|
|
60
|
+
const validatedTargets = validateTargets(targetDirs, config);
|
|
61
|
+
for (const target of validatedTargets) {
|
|
62
|
+
installGitHooksInto(target, cacheBaseDir);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const mergedProjects = [...new Set([...readProjects(cacheBaseDir), ...validatedTargets.map((target) => target.targetDir)])];
|
|
66
|
+
writeProjects(cacheBaseDir, mergedProjects);
|
|
67
|
+
|
|
68
|
+
return validatedTargets.map((target) => target.targetDir);
|
|
31
69
|
}
|
|
32
70
|
|
|
33
71
|
function installCurrentProjectFiles(cacheBaseDir) {
|
|
@@ -52,11 +90,8 @@ function installCurrentProjectFiles(cacheBaseDir) {
|
|
|
52
90
|
);
|
|
53
91
|
}
|
|
54
92
|
|
|
55
|
-
function installGitHooksInto(
|
|
56
|
-
const
|
|
57
|
-
ensureDirectory(resolvedTarget);
|
|
58
|
-
const gitTargets = resolveGitTargets(resolvedTarget, config);
|
|
59
|
-
for (const gitDir of gitTargets) {
|
|
93
|
+
function installGitHooksInto(target, cacheBaseDir = process.cwd()) {
|
|
94
|
+
for (const gitDir of target.gitDirs) {
|
|
60
95
|
installGitHooks(gitDir, cacheBaseDir);
|
|
61
96
|
}
|
|
62
97
|
}
|
|
@@ -87,6 +122,63 @@ function resolveGitTargets(targetDir, config = {}) {
|
|
|
87
122
|
});
|
|
88
123
|
}
|
|
89
124
|
|
|
125
|
+
function validateTargets(targetDirs, config = {}) {
|
|
126
|
+
return targetDirs.map((targetDir) => validateTarget(targetDir, config));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function validateTarget(targetDir, config = {}) {
|
|
130
|
+
const resolvedTarget = path.resolve(targetDir);
|
|
131
|
+
assertReachableDirectory(resolvedTarget);
|
|
132
|
+
const gitDirs = resolveGitTargets(resolvedTarget, config);
|
|
133
|
+
|
|
134
|
+
if (gitDirs.length === 0) {
|
|
135
|
+
throw new Error(`Target directory must contain a reachable .git directory: ${resolvedTarget}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const configured = Array.isArray(config.gitDirs) ? config.gitDirs : [];
|
|
139
|
+
if (configured.length > 0) {
|
|
140
|
+
const missing = configured
|
|
141
|
+
.map((gitDir) => path.resolve(resolvedTarget, gitDir))
|
|
142
|
+
.filter((gitDir) => !gitDirs.includes(gitDir));
|
|
143
|
+
if (missing.length > 0) {
|
|
144
|
+
throw new Error(`Configured gitDirs are not reachable in target directory: ${missing.join(", ")}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return { targetDir: resolvedTarget, gitDirs };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function assertReachableDirectory(directory) {
|
|
152
|
+
let stat;
|
|
153
|
+
try {
|
|
154
|
+
stat = fs.statSync(directory);
|
|
155
|
+
} catch {
|
|
156
|
+
throw new Error(`Target directory does not exist or is not reachable: ${directory}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!stat.isDirectory()) {
|
|
160
|
+
throw new Error(`Target path is not a directory: ${directory}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
fs.accessSync(directory, fs.constants.R_OK);
|
|
165
|
+
} catch {
|
|
166
|
+
throw new Error(`Target directory is not readable: ${directory}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function assertInstalledBase(cacheBaseDir) {
|
|
171
|
+
const installFile = path.join(cacheBaseDir, CACHE_DIR_NAME, "install.json");
|
|
172
|
+
try {
|
|
173
|
+
const stat = fs.statSync(installFile);
|
|
174
|
+
if (!stat.isFile()) {
|
|
175
|
+
throw new Error("");
|
|
176
|
+
}
|
|
177
|
+
} catch {
|
|
178
|
+
throw new Error(`Current directory is not an installed x7-code-line base: ${cacheBaseDir}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
90
182
|
function readConfig(configPath) {
|
|
91
183
|
if (!configPath) {
|
|
92
184
|
return {};
|
|
@@ -103,15 +195,26 @@ function readConfig(configPath) {
|
|
|
103
195
|
return config;
|
|
104
196
|
}
|
|
105
197
|
|
|
106
|
-
function normalizeTargetDirs(targetDirs = [], config = {}) {
|
|
198
|
+
function normalizeTargetDirs(targetDirs = [], config = {}, options = {}) {
|
|
107
199
|
const configuredDirs = Array.isArray(config.dirs) ? config.dirs : [];
|
|
108
200
|
const dirs = [...targetDirs, ...configuredDirs].map((dir) => String(dir || "").trim()).filter(Boolean);
|
|
109
201
|
|
|
110
202
|
if (dirs.length === 0) {
|
|
111
|
-
|
|
203
|
+
throw new Error("install requires at least one target directory");
|
|
112
204
|
}
|
|
113
205
|
|
|
114
|
-
|
|
206
|
+
const baseDir = path.resolve(options.pathBaseDir || process.env.INIT_CWD || process.cwd());
|
|
207
|
+
return [...new Set(dirs)].map((dir) => normalizeTargetDir(dir, options.relativePaths, baseDir));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function normalizeTargetDir(dir, relativePaths, baseDir) {
|
|
211
|
+
if (path.isAbsolute(dir)) {
|
|
212
|
+
return path.resolve(dir);
|
|
213
|
+
}
|
|
214
|
+
if (!relativePaths) {
|
|
215
|
+
throw new Error(`Target directory must be an absolute path unless --relative is used: ${dir}`);
|
|
216
|
+
}
|
|
217
|
+
return path.resolve(baseDir, dir);
|
|
115
218
|
}
|
|
116
219
|
|
|
117
220
|
function splitList(value) {
|
|
@@ -165,6 +268,7 @@ function ensureDirectory(directory) {
|
|
|
165
268
|
}
|
|
166
269
|
|
|
167
270
|
module.exports = {
|
|
271
|
+
addDirs,
|
|
168
272
|
CACHE_DIR_NAME,
|
|
169
273
|
install,
|
|
170
274
|
installFromPostinstall
|