@oisincoveney/pipeline 2.8.0 → 2.8.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/argo-submit.d.ts +2 -4
- package/dist/argo-submit.js +80 -80
- package/dist/cluster-doctor.js +89 -101
- package/dist/config/defaults.js +9 -19
- package/dist/config/load.js +32 -39
- package/dist/config/schemas.d.ts +1 -1
- package/dist/gates.js +6 -225
- package/dist/mcp/gateway-error.js +15 -0
- package/dist/mcp/gateway.js +119 -220
- package/dist/moka-global-config.js +20 -20
- package/dist/moka-submit.d.ts +1 -1
- package/dist/pipeline-runtime.js +580 -371
- package/dist/run-state/git-refs.js +124 -94
- package/dist/runner-command-contract.d.ts +2 -2
- package/dist/runner-event-sink.js +37 -69
- package/dist/runtime/agent-node/agent-node.js +214 -173
- package/dist/runtime/builtins/builtins.js +344 -57
- package/dist/runtime/changed-files/changed-files.js +15 -27
- package/dist/runtime/changed-files/index.js +2 -0
- package/dist/runtime/drain-merge/drain-merge.js +124 -82
- package/dist/runtime/gates/gates.js +46 -28
- package/dist/runtime/hooks/hooks.js +74 -29
- package/dist/runtime/opencode-server.js +27 -21
- package/dist/runtime/opencode-session-executor.js +101 -44
- package/dist/runtime/parallel-node/parallel-node.js +24 -5
- package/dist/runtime/select-candidate/select-candidate.js +45 -29
- package/dist/runtime/services/agent-node-runtime-service.js +15 -0
- package/dist/runtime/services/command-executor-service.js +8 -0
- package/dist/runtime/services/config-io-service.js +42 -0
- package/dist/runtime/services/drain-merge-git-service.js +10 -0
- package/dist/runtime/services/git-porcelain-service.js +38 -0
- package/dist/runtime/services/kubernetes-argo-service.d.ts +13 -0
- package/dist/runtime/services/kubernetes-argo-service.js +81 -0
- package/dist/runtime/services/mcp-gateway-service.js +184 -0
- package/dist/runtime/services/opencode-sdk-service.js +27 -0
- package/dist/runtime/services/runner-event-sink-http-service.js +80 -0
- package/dist/runtime/services/select-candidate-service.js +13 -0
- package/package.json +1 -1
|
@@ -1,70 +1,357 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parseJson } from "../../safe-json.js";
|
|
2
|
+
import { CommandExecutor, CommandExecutorLive } from "../services/command-executor-service.js";
|
|
2
3
|
import { executeDrainMergeBuiltin } from "../drain-merge/drain-merge.js";
|
|
3
4
|
import "../drain-merge/index.js";
|
|
4
5
|
import { executeSelectCandidateBuiltin } from "../select-candidate/select-candidate.js";
|
|
6
|
+
import { Effect } from "effect";
|
|
7
|
+
import { existsSync, readFileSync, renameSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { resolveCommand } from "package-manager-detector/commands";
|
|
10
|
+
import { detect } from "package-manager-detector/detect";
|
|
5
11
|
//#region src/runtime/builtins/builtins.ts
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
12
|
+
const JSCPD_DEFAULT_IGNORES = [
|
|
13
|
+
"**/node_modules/**",
|
|
14
|
+
"**/.git/**",
|
|
15
|
+
"**/dist/**",
|
|
16
|
+
"**/coverage/**",
|
|
17
|
+
"**/.next/**",
|
|
18
|
+
"**/.turbo/**",
|
|
19
|
+
"**/.cache/**",
|
|
20
|
+
"**/.serena/**",
|
|
21
|
+
"**/.opencode/**",
|
|
22
|
+
"**/.pipeline/host-resources/**",
|
|
23
|
+
"**/.pipeline/skills/**",
|
|
24
|
+
"**/.agents/skills/**"
|
|
25
|
+
];
|
|
26
|
+
const WHITESPACE_RE = /\s/;
|
|
27
|
+
const BUILTIN_HANDLERS = {
|
|
28
|
+
"drain-merge": (context, node) => Effect.tryPromise(() => executeDrainMergeBuiltin(context, node)),
|
|
29
|
+
duplication: (context) => executeDuplicationBuiltinEffect(context),
|
|
30
|
+
fallow: (context) => executeFallowBuiltinEffect(context),
|
|
31
|
+
lint: (context) => executeScriptBuiltinEffect(context, "lint"),
|
|
32
|
+
"select-candidate": (context, node) => Effect.tryPromise(() => executeSelectCandidateBuiltin(context, node)),
|
|
33
|
+
semgrep: (context) => executeSemgrepBuiltinEffect(context),
|
|
34
|
+
test: (context) => executeTestBuiltinEffect(context),
|
|
35
|
+
typecheck: (context) => executeScriptBuiltinEffect(context, "typecheck")
|
|
36
|
+
};
|
|
37
|
+
function executeBuiltin(builtin, context, node) {
|
|
38
|
+
const program = executeBuiltinEffect(builtin, context, node);
|
|
39
|
+
return Effect.runPromise(Effect.provide(program, CommandExecutorLive));
|
|
40
|
+
}
|
|
41
|
+
function executeBuiltinEffect(builtin, context, node) {
|
|
42
|
+
const handler = BUILTIN_HANDLERS[builtin];
|
|
43
|
+
return handler ? handler(context, node) : Effect.succeed(unsupportedBuiltin(builtin));
|
|
44
|
+
}
|
|
45
|
+
function unsupportedBuiltin(builtin) {
|
|
46
|
+
return {
|
|
47
|
+
evidence: [`unsupported builtin '${builtin}'`],
|
|
48
|
+
exitCode: 1,
|
|
49
|
+
output: ""
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function executeTestBuiltinEffect(context) {
|
|
53
|
+
return Effect.gen(function* () {
|
|
54
|
+
const command = yield* resolveRequiredScriptCommand(context.worktreePath, "PIPELINE_TEST_COMMAND", "test");
|
|
55
|
+
if (!command) return missingTestCommandResult();
|
|
56
|
+
const result = yield* executeProjectCommand(command, context);
|
|
57
|
+
return commandBuiltinResult("test", result, result.exitCode === 0 ? [] : parseFailingTests(result.output));
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function executeScriptBuiltinEffect(context, builtin) {
|
|
61
|
+
return Effect.gen(function* () {
|
|
62
|
+
const command = yield* resolveRequiredScriptCommand(context.worktreePath, builtinEnvName(builtin), builtin);
|
|
63
|
+
return command ? yield* executeScriptCommandBuiltin(builtin, command, context) : skippedBuiltinResult(builtin);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function executeScriptCommandBuiltin(builtin, command, context) {
|
|
67
|
+
return executeProjectCommand(command, context, builtin === "lint").pipe(Effect.map((result) => commandBuiltinResult(builtin, result)));
|
|
68
|
+
}
|
|
69
|
+
function executeFallowBuiltinEffect(context) {
|
|
70
|
+
return Effect.gen(function* () {
|
|
71
|
+
return commandBuiltinResult("fallow", yield* executeProjectCommand(yield* resolveFallowCommand(context.worktreePath), context, true));
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function executeSemgrepBuiltinEffect(context) {
|
|
75
|
+
return Effect.gen(function* () {
|
|
76
|
+
const command = yield* resolveSemgrepCommand(context);
|
|
77
|
+
if (!command) return semgrepNoChangedFilesResult();
|
|
78
|
+
return commandBuiltinResult("semgrep", yield* executeProjectCommand(command, context));
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
function executeDuplicationBuiltinEffect(context) {
|
|
82
|
+
return Effect.gen(function* () {
|
|
83
|
+
return duplicationBuiltinResult(parseJscpdOutput((yield* executeProjectCommand(jscpdCommand(), context)).output));
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function executeProjectCommand(command, context, hidePipelineRuns = false) {
|
|
87
|
+
const run = executeVisibleProjectCommand(command, context);
|
|
88
|
+
return hidePipelineRuns ? withHiddenPipelineRuns(context.worktreePath, run) : run;
|
|
89
|
+
}
|
|
90
|
+
function executeVisibleProjectCommand(command, context) {
|
|
91
|
+
return Effect.gen(function* () {
|
|
92
|
+
const result = yield* (yield* CommandExecutor).execute(projectCommandArray(command), context);
|
|
93
|
+
return {
|
|
94
|
+
command: displayCommand(command),
|
|
95
|
+
...result
|
|
62
96
|
};
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
function withHiddenPipelineRuns(worktreePath, effect) {
|
|
100
|
+
return Effect.acquireUseRelease(Effect.sync(() => hidePipelineRunsDirectory(worktreePath)), () => effect, (hiddenRuns) => Effect.sync(() => hiddenRuns?.restore()));
|
|
101
|
+
}
|
|
102
|
+
function resolveRequiredScriptCommand(worktreePath, envName, scriptName) {
|
|
103
|
+
return Effect.gen(function* () {
|
|
104
|
+
return envCommand(envName) ?? (yield* resolvePackageScript(worktreePath, scriptName));
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function resolveFallowCommand(worktreePath) {
|
|
108
|
+
return Effect.gen(function* () {
|
|
109
|
+
const script = yield* resolvePackageScript(worktreePath, "fallow");
|
|
110
|
+
const binary = yield* resolvePackageBinaryCommand(worktreePath, "fallow", ["audit"]);
|
|
111
|
+
return envCommand("PIPELINE_FALLOW_COMMAND") ?? script ?? binary ?? fallowCommand();
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function resolveSemgrepCommand(context) {
|
|
115
|
+
return Effect.sync(() => {
|
|
116
|
+
const override = envCommand("PIPELINE_SEMGREP_COMMAND");
|
|
117
|
+
const targets = semgrepTargets(context);
|
|
118
|
+
return override ?? semgrepScanCommand(targets);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
function resolvePackageScript(worktreePath, scriptName) {
|
|
122
|
+
return Effect.gen(function* () {
|
|
123
|
+
if (!readPackageScripts(worktreePath)[scriptName]) return null;
|
|
124
|
+
return packageManagerCommand(yield* detectPackageManagerAgent(worktreePath), [scriptName]);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function resolvePackageBinaryCommand(worktreePath, binary, args) {
|
|
128
|
+
return Effect.gen(function* () {
|
|
129
|
+
if (!existsSync(join(worktreePath, "package.json"))) return null;
|
|
130
|
+
return packageBinaryCommand(yield* detectPackageManagerAgent(worktreePath), binary, args);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function detectPackageManagerAgent(worktreePath) {
|
|
134
|
+
return Effect.tryPromise(async () => {
|
|
135
|
+
return (await detect({
|
|
136
|
+
cwd: worktreePath,
|
|
137
|
+
stopDir: worktreePath
|
|
138
|
+
}))?.agent ?? "npm";
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
function packageManagerCommand(agent, args) {
|
|
142
|
+
const resolved = resolveCommand(agent, "run", args);
|
|
143
|
+
return resolved ? {
|
|
144
|
+
args: resolved.args,
|
|
145
|
+
command: resolved.command
|
|
146
|
+
} : null;
|
|
147
|
+
}
|
|
148
|
+
function packageBinaryCommand(agent, binary, args) {
|
|
149
|
+
return {
|
|
150
|
+
bun: {
|
|
151
|
+
args: [
|
|
152
|
+
"x",
|
|
153
|
+
binary,
|
|
154
|
+
...args
|
|
155
|
+
],
|
|
156
|
+
command: "bun"
|
|
157
|
+
},
|
|
158
|
+
pnpm: {
|
|
159
|
+
args: [
|
|
160
|
+
"exec",
|
|
161
|
+
binary,
|
|
162
|
+
...args
|
|
163
|
+
],
|
|
164
|
+
command: "pnpm"
|
|
165
|
+
},
|
|
166
|
+
yarn: {
|
|
167
|
+
args: [
|
|
168
|
+
"exec",
|
|
169
|
+
binary,
|
|
170
|
+
...args
|
|
171
|
+
],
|
|
172
|
+
command: "yarn"
|
|
173
|
+
}
|
|
174
|
+
}[String(agent)] ?? {
|
|
175
|
+
args: [
|
|
176
|
+
"--yes",
|
|
177
|
+
binary,
|
|
178
|
+
...args
|
|
179
|
+
],
|
|
180
|
+
command: "npx"
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function envCommand(envName) {
|
|
184
|
+
const raw = process.env[envName]?.trim();
|
|
185
|
+
return raw ? envProjectCommand(raw) : null;
|
|
186
|
+
}
|
|
187
|
+
function envProjectCommand(raw) {
|
|
188
|
+
return WHITESPACE_RE.test(raw) ? shellProjectCommand(raw) : executableProjectCommand(raw);
|
|
189
|
+
}
|
|
190
|
+
function executableProjectCommand(command) {
|
|
191
|
+
return {
|
|
192
|
+
args: [],
|
|
193
|
+
command
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function shellProjectCommand(command) {
|
|
197
|
+
return {
|
|
198
|
+
args: ["-c", command],
|
|
199
|
+
command: "sh",
|
|
200
|
+
display: command
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function readPackageScripts(worktreePath) {
|
|
204
|
+
const text = readPackageJsonText(worktreePath);
|
|
205
|
+
return text ? packageScriptsFromJson(text) : {};
|
|
206
|
+
}
|
|
207
|
+
function readPackageJsonText(worktreePath) {
|
|
208
|
+
try {
|
|
209
|
+
return readFileSync(join(worktreePath, "package.json"), "utf-8");
|
|
210
|
+
} catch {
|
|
211
|
+
return null;
|
|
63
212
|
}
|
|
64
213
|
}
|
|
214
|
+
function packageScriptsFromJson(text) {
|
|
215
|
+
return parseJson(text, "package.json").scripts ?? {};
|
|
216
|
+
}
|
|
65
217
|
function runtimeChangedFiles(context) {
|
|
66
218
|
return [...new Set(context.nodeStateStore.changedFilesForAllNodes())].sort();
|
|
67
219
|
}
|
|
220
|
+
function semgrepTargets(context) {
|
|
221
|
+
const targets = runtimeChangedFiles(context).filter((file) => existsSync(join(context.worktreePath, file)));
|
|
222
|
+
return targets.length === 0 ? void 0 : targets;
|
|
223
|
+
}
|
|
224
|
+
function semgrepScanCommand(targets) {
|
|
225
|
+
if (!targets) return null;
|
|
226
|
+
return {
|
|
227
|
+
args: [
|
|
228
|
+
"semgrep",
|
|
229
|
+
"scan",
|
|
230
|
+
"--config=p/ci",
|
|
231
|
+
"--error",
|
|
232
|
+
"--",
|
|
233
|
+
...targets
|
|
234
|
+
],
|
|
235
|
+
command: "uvx"
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function jscpdCommand() {
|
|
239
|
+
return {
|
|
240
|
+
args: [
|
|
241
|
+
"jscpd",
|
|
242
|
+
"--min-tokens",
|
|
243
|
+
"50",
|
|
244
|
+
"--reporters",
|
|
245
|
+
"json",
|
|
246
|
+
"--gitignore",
|
|
247
|
+
"--ignore",
|
|
248
|
+
JSCPD_DEFAULT_IGNORES.join(","),
|
|
249
|
+
"."
|
|
250
|
+
],
|
|
251
|
+
command: "bunx"
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
function fallowCommand() {
|
|
255
|
+
return {
|
|
256
|
+
args: ["audit"],
|
|
257
|
+
command: "fallow"
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function projectCommandArray(command) {
|
|
261
|
+
return [command.command, ...command.args];
|
|
262
|
+
}
|
|
263
|
+
function displayCommand(command) {
|
|
264
|
+
return command.display ?? projectCommandArray(command).join(" ");
|
|
265
|
+
}
|
|
266
|
+
function builtinEnvName(builtin) {
|
|
267
|
+
return `PIPELINE_${builtin.toUpperCase()}_COMMAND`;
|
|
268
|
+
}
|
|
269
|
+
function missingTestCommandResult() {
|
|
270
|
+
return {
|
|
271
|
+
evidence: ["No test command found. Set PIPELINE_TEST_COMMAND or define a package test script."],
|
|
272
|
+
exitCode: 1,
|
|
273
|
+
output: "No test command found. Set PIPELINE_TEST_COMMAND or define a package test script."
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function skippedBuiltinResult(builtin) {
|
|
277
|
+
return {
|
|
278
|
+
evidence: builtinCommandEvidence(builtin, {
|
|
279
|
+
exitCode: 0,
|
|
280
|
+
output: "skipped"
|
|
281
|
+
}),
|
|
282
|
+
exitCode: 0,
|
|
283
|
+
output: "skipped"
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function semgrepNoChangedFilesResult() {
|
|
287
|
+
return commandBuiltinResult("semgrep", {
|
|
288
|
+
command: "uvx semgrep scan --config=p/ci --error",
|
|
289
|
+
exitCode: 0,
|
|
290
|
+
output: "skipped: no changed files to scan"
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
function duplicationBuiltinResult(violations) {
|
|
294
|
+
return {
|
|
295
|
+
evidence: violations.map((violation) => violation.message),
|
|
296
|
+
exitCode: violations.length === 0 ? 0 : 1,
|
|
297
|
+
output: JSON.stringify(violations)
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
function commandBuiltinResult(builtin, result, extraEvidence = []) {
|
|
301
|
+
return {
|
|
302
|
+
evidence: [...builtinCommandEvidence(builtin, result), ...extraEvidence],
|
|
303
|
+
exitCode: result.exitCode,
|
|
304
|
+
output: result.output
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function hidePipelineRunsDirectory(worktreePath) {
|
|
308
|
+
const paths = pipelineRunsPaths(worktreePath);
|
|
309
|
+
if (!existsSync(paths.runs)) return null;
|
|
310
|
+
renameSync(paths.runs, paths.hidden);
|
|
311
|
+
return { restore: () => restoreHiddenRuns(paths) };
|
|
312
|
+
}
|
|
313
|
+
function pipelineRunsPaths(worktreePath) {
|
|
314
|
+
const pipelineDir = join(worktreePath, ".pipeline");
|
|
315
|
+
return {
|
|
316
|
+
hidden: join(pipelineDir, `.runs-hidden-${process.pid}-${Date.now()}`),
|
|
317
|
+
runs: join(pipelineDir, "runs")
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
function restoreHiddenRuns(paths) {
|
|
321
|
+
if (existsSync(paths.hidden)) renameSync(paths.hidden, paths.runs);
|
|
322
|
+
}
|
|
323
|
+
const FAILING_TEST_RE = /^[✗×✕●]\s+(.+)$/;
|
|
324
|
+
function parseFailingTests(output) {
|
|
325
|
+
return output.split("\n").flatMap(parseFailingTestLine);
|
|
326
|
+
}
|
|
327
|
+
function parseFailingTestLine(line) {
|
|
328
|
+
const match = FAILING_TEST_RE.exec(line);
|
|
329
|
+
return match ? [match[1].trim()] : [];
|
|
330
|
+
}
|
|
331
|
+
function parseJscpdOutput(output) {
|
|
332
|
+
try {
|
|
333
|
+
return (parseJson(output, "jscpd output").duplicates ?? []).map(jscpdDuplicateViolation);
|
|
334
|
+
} catch {
|
|
335
|
+
return [];
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function jscpdDuplicateViolation(dup) {
|
|
339
|
+
const firstFile = jscpdFirstFileName(dup);
|
|
340
|
+
return {
|
|
341
|
+
file: firstFile ?? "unknown",
|
|
342
|
+
line: jscpdFirstFileStart(dup),
|
|
343
|
+
message: `Duplicate code block detected between ${firstFile} and ${jscpdSecondFileName(dup)}`
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
function jscpdFirstFileName(dup) {
|
|
347
|
+
return dup.firstFile?.name;
|
|
348
|
+
}
|
|
349
|
+
function jscpdFirstFileStart(dup) {
|
|
350
|
+
return dup.firstFile?.start;
|
|
351
|
+
}
|
|
352
|
+
function jscpdSecondFileName(dup) {
|
|
353
|
+
return dup.secondFile?.name;
|
|
354
|
+
}
|
|
68
355
|
function builtinCommandEvidence(builtin, result) {
|
|
69
356
|
const command = result.command ? `: ${result.command}` : "";
|
|
70
357
|
return [`builtin '${builtin}' exited ${result.exitCode}${command}`, result.output || `builtin '${builtin}' produced no output`];
|
|
@@ -1,35 +1,23 @@
|
|
|
1
|
+
import { GitPorcelainService, GitPorcelainServiceLive } from "../services/git-porcelain-service.js";
|
|
2
|
+
import { Effect } from "effect";
|
|
1
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
4
|
import { createHash } from "node:crypto";
|
|
3
5
|
import { join } from "node:path";
|
|
4
|
-
import { execFileSync } from "node:child_process";
|
|
5
6
|
//#region src/runtime/changed-files/changed-files.ts
|
|
6
7
|
function snapshotChangedFiles(worktreePath) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
]
|
|
21
|
-
});
|
|
22
|
-
const files = new Set(parsePorcelainStatus(stdout));
|
|
23
|
-
return {
|
|
24
|
-
files,
|
|
25
|
-
fingerprints: new Map([...files].map((file) => [file, fileFingerprint(worktreePath, file)]))
|
|
26
|
-
};
|
|
27
|
-
} catch {
|
|
28
|
-
return {
|
|
29
|
-
files: /* @__PURE__ */ new Set(),
|
|
30
|
-
fingerprints: /* @__PURE__ */ new Map()
|
|
31
|
-
};
|
|
32
|
-
}
|
|
8
|
+
return Effect.runSync(Effect.provide(snapshotChangedFilesEffect(worktreePath), GitPorcelainServiceLive));
|
|
9
|
+
}
|
|
10
|
+
function snapshotChangedFilesEffect(worktreePath) {
|
|
11
|
+
return Effect.gen(function* () {
|
|
12
|
+
const stdout = yield* (yield* GitPorcelainService).statusPorcelain(worktreePath).pipe(Effect.catchAll(() => Effect.succeed("")));
|
|
13
|
+
return changedFilesSnapshot(worktreePath, new Set(parsePorcelainStatus(stdout)));
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function changedFilesSnapshot(worktreePath, files) {
|
|
17
|
+
return {
|
|
18
|
+
files,
|
|
19
|
+
fingerprints: new Map([...files].map((file) => [file, fileFingerprint(worktreePath, file)]))
|
|
20
|
+
};
|
|
33
21
|
}
|
|
34
22
|
function parsePorcelainStatus(stdout) {
|
|
35
23
|
const entries = stdout.split("\0").filter(Boolean);
|