@oisincoveney/pipeline 3.24.2 → 4.0.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 +14 -2
- package/defaults/pipeline.yaml +23 -5
- package/defaults/profiles.yaml +104 -21
- package/dist/argo-graph.js +90 -88
- package/dist/argo-submit.d.ts +110 -138
- package/dist/argo-submit.js +363 -378
- package/dist/argo-workflow.d.ts +156 -177
- package/dist/argo-workflow.js +96 -138
- package/dist/backlog.js +5 -4
- package/dist/bench/eval-report.js +21 -12
- package/dist/claude-settings-config.js +23 -31
- package/dist/claude-user-config.js +6 -16
- package/dist/cli/bootstrap-commands.js +3 -3
- package/dist/cli/doctor.d.ts +1 -1
- package/dist/cli/doctor.js +176 -171
- package/dist/cli/factory-commands.js +13 -13
- package/dist/cli/format.js +92 -86
- package/dist/cli/loop-commands.js +21 -21
- package/dist/cli/mcp-gateway-commands.js +12 -12
- package/dist/cli/plan-commands.js +85 -116
- package/dist/cli/program.d.ts +3 -3
- package/dist/cli/program.js +41 -43
- package/dist/cli/run-command.d.ts +1 -1
- package/dist/cli/run-command.js +25 -25
- package/dist/cli/run-commands.js +31 -29
- package/dist/cli/run-resolver.d.ts +1 -1
- package/dist/cli/run-resolver.js +30 -32
- package/dist/cli/run-service.d.ts +2 -3
- package/dist/cli/run-service.js +229 -239
- package/dist/cli/submit-options.js +85 -83
- package/dist/cluster-doctor.js +147 -175
- package/dist/codex-config.js +24 -30
- package/dist/commands/bench-command.js +9 -7
- package/dist/commands/coordinator-command.js +35 -0
- package/dist/commands/pipeline-command.js +13 -7
- package/dist/commands/runner-command-command.js +39 -24
- package/dist/commands/runner-node-command.js +31 -0
- package/dist/commands/ticket/complete.js +29 -33
- package/dist/commands/ticket/create.js +84 -82
- package/dist/commands/ticket/graph-check.js +11 -11
- package/dist/commands/ticket/next.js +36 -36
- package/dist/commands/ticket/sequence.js +11 -12
- package/dist/commands/ticket/shared.d.ts +5 -2
- package/dist/commands/ticket/shared.js +63 -66
- package/dist/commands/ticket/start.js +58 -67
- package/dist/commands/ticket-command.js +2 -2
- package/dist/config/defaults.d.ts +139 -140
- package/dist/config/defaults.js +81 -80
- package/dist/config/lint.js +52 -64
- package/dist/config/load.d.ts +4 -5
- package/dist/config/load.js +64 -74
- package/dist/config/schema/catalog.d.ts +2 -1
- package/dist/config/schema/catalog.js +3 -2
- package/dist/config/schema/mcp.js +46 -52
- package/dist/config/schemas.d.ts +382 -528
- package/dist/config/schemas.js +400 -398
- package/dist/config/validate.d.ts +1 -2
- package/dist/config/validate.js +236 -201
- package/dist/config.d.ts +2 -2
- package/dist/context/repo-map.js +105 -119
- package/dist/coordinator/composition.js +169 -0
- package/dist/coordinator/config.js +71 -0
- package/dist/coordinator/run-owner.js +64 -0
- package/dist/coordinator/wave-advance.js +148 -0
- package/dist/coordinator/wave-scheduler.js +50 -0
- package/dist/credentials/broker.d.ts +6 -8
- package/dist/credentials/broker.js +17 -16
- package/dist/credentials/codex-config.js +15 -19
- package/dist/credentials/file-targets.js +9 -15
- package/dist/credentials/local-codex-auth-sync.js +55 -58
- package/dist/credentials/opencode-config.js +28 -32
- package/dist/credentials/runner.js +13 -13
- package/dist/effect-sync-errors.js +28 -0
- package/dist/factory/create-experiment.js +285 -121
- package/dist/factory/exec.js +10 -12
- package/dist/factory/git-credentials.js +4 -4
- package/dist/factory/stamp-answers.js +10 -11
- package/dist/factory/template-update.js +65 -44
- package/dist/factory-lane.d.ts +26 -40
- package/dist/factory-lane.js +26 -25
- package/dist/gates.js +3 -3
- package/dist/git-remote-url.js +17 -19
- package/dist/hooks.d.ts +26 -26
- package/dist/hooks.js +20 -22
- package/dist/index.d.ts +4 -3
- package/dist/index.js +27 -36
- package/dist/install-commands/claude-code.js +100 -92
- package/dist/install-commands/host-selection.js +3 -6
- package/dist/install-commands/opencode.js +264 -259
- package/dist/install-commands/planner.js +163 -181
- package/dist/install-commands/result-format.js +1 -3
- package/dist/install-commands/shared.js +25 -42
- package/dist/install-commands/writer.js +22 -25
- package/dist/install-commands.js +8 -8
- package/dist/jscpd-output.js +29 -0
- package/dist/json-config-merge.js +14 -20
- package/dist/json-line-values.js +17 -6
- package/dist/loop/argo-poll.d.ts +0 -1
- package/dist/loop/argo-poll.js +15 -95
- package/dist/loop/backlog-records.js +1 -3
- package/dist/loop/controller-deps.js +150 -161
- package/dist/loop/controller.js +164 -186
- package/dist/loop/gh-checks.js +62 -61
- package/dist/loop/gh-runner.js +22 -25
- package/dist/loop/loop-command.js +66 -58
- package/dist/loop/loop-controller-entrypoint.js +29 -30
- package/dist/loop/merge.js +36 -40
- package/dist/loop/run-terminal-poll.js +69 -0
- package/dist/mcp/gateway-config.js +25 -29
- package/dist/mcp/gateway-doctor.js +77 -119
- package/dist/mcp/gateway-reconcile.js +17 -22
- package/dist/mcp/gateway-runtime.js +1 -3
- package/dist/mcp/host-config.js +37 -40
- package/dist/mcp/host-renderers.js +10 -18
- package/dist/mcp/repo-local-backends.js +51 -54
- package/dist/mcp/toolhive-vmcp.js +39 -39
- package/dist/model-resolver.js +29 -33
- package/dist/moka-global-config.d.ts +78 -55
- package/dist/moka-global-config.js +93 -92
- package/dist/moka-submit.d.ts +2179 -866
- package/dist/moka-submit.js +97 -99
- package/dist/opencode-project-config.js +34 -40
- package/dist/package-assets.js +1 -3
- package/dist/path-refs.js +2 -2
- package/dist/pipeline-init.js +25 -25
- package/dist/pipeline-runtime.d.ts +15 -14
- package/dist/pipeline-runtime.js +51 -50
- package/dist/planned-node.js +6 -3
- package/dist/planning/compile.d.ts +5 -5
- package/dist/planning/compile.js +140 -139
- package/dist/planning/dependency-refs.js +5 -5
- package/dist/planning/generate.d.ts +1296 -1296
- package/dist/planning/generate.js +307 -314
- package/dist/planning/graph.d.ts +0 -1
- package/dist/planning/graph.js +131 -149
- package/dist/remote/argo/model.d.ts +104 -122
- package/dist/remote/argo/model.js +205 -197
- package/dist/remote/argo/policy.js +39 -47
- package/dist/remote/argo/storage.js +81 -85
- package/dist/remote/jobs/execution-identity.js +70 -0
- package/dist/remote/jobs/job-outcome.js +108 -0
- package/dist/remote/jobs/manifest.js +96 -0
- package/dist/remote/jobs/model.d.ts +158 -0
- package/dist/remote/jobs/model.js +162 -0
- package/dist/remote/jobs/node-bootstrap.js +213 -0
- package/dist/remote/jobs/policy.js +123 -0
- package/dist/remote/jobs/reconcile.js +100 -0
- package/dist/remote/jobs/run-spec-client.js +64 -0
- package/dist/remote/jobs/run-spec-path.js +31 -0
- package/dist/remote/jobs/run-spec-server.d.ts +34 -0
- package/dist/remote/jobs/run-spec-server.js +116 -0
- package/dist/remote/jobs/submit.d.ts +23 -0
- package/dist/remote/jobs/submit.js +116 -0
- package/dist/remote/jobs/watch.js +59 -0
- package/dist/remote/submit/argo-submission.d.ts +3 -1
- package/dist/remote/submit/argo-submission.js +53 -58
- package/dist/remote/submit/compilation.d.ts +2 -0
- package/dist/remote/submit/compilation.js +46 -49
- package/dist/remote/submit/event-boundary.js +62 -76
- package/dist/remote/submit/hook-events.d.ts +6 -0
- package/dist/remote/submit/io.d.ts +7 -1
- package/dist/remote/submit/io.js +40 -43
- package/dist/remote/submit/jobs-submission.d.ts +59 -0
- package/dist/remote/submit/jobs-submission.js +97 -0
- package/dist/remote/submit/jobs-submit-result.d.ts +25 -0
- package/dist/remote/submit/jobs-submit-result.js +34 -0
- package/dist/remote/submit/options.d.ts +49 -0
- package/dist/remote/submit/options.js +27 -0
- package/dist/remote/submit/service.d.ts +40 -1
- package/dist/remote/submit/service.js +67 -49
- package/dist/run-control/command-context.js +6 -8
- package/dist/run-control/commands.js +10 -10
- package/dist/run-control/contracts.d.ts +52 -101
- package/dist/run-control/contracts.js +83 -69
- package/dist/run-control/detach.js +66 -72
- package/dist/run-control/execution-store.js +0 -0
- package/dist/run-control/file-errors.js +1 -3
- package/dist/run-control/logical-segment.js +3 -3
- package/dist/run-control/next-node.js +67 -78
- package/dist/run-control/postgres/postgres-run-control-store.js +196 -225
- package/dist/run-control/resume-command.js +34 -41
- package/dist/run-control/run-artifacts-command.js +84 -105
- package/dist/run-control/run-command-domain.js +7 -11
- package/dist/run-control/run-control-store.js +9 -9
- package/dist/run-control/run-query-command.js +73 -100
- package/dist/run-control/run-record.js +5 -3
- package/dist/run-control/run-state-lock.js +5 -5
- package/dist/run-control/runtime-event-projection.js +38 -50
- package/dist/run-control/runtime-reporter.js +86 -102
- package/dist/run-control/stop-command.js +42 -53
- package/dist/run-control/store-fs-effects.js +3 -1
- package/dist/run-control/store-manifest.js +38 -13
- package/dist/run-control/store-paths.js +3 -3
- package/dist/run-control/store.js +1 -1
- package/dist/run-control/submit-result.js +21 -21
- package/dist/run-control/supervisor.js +78 -68
- package/dist/run-control/workspace.js +2 -2
- package/dist/run-state/git-refs.js +258 -307
- package/dist/runner/opencode-excludes.js +4 -4
- package/dist/runner/subprocess-result.js +28 -37
- package/dist/runner/subprocess.js +41 -41
- package/dist/runner/timeouts.js +14 -13
- package/dist/runner-command/dynamic-command.js +46 -39
- package/dist/runner-command/environment-commands.js +91 -0
- package/dist/runner-command/finalize.js +174 -153
- package/dist/runner-command/lifecycle-context.js +115 -130
- package/dist/runner-command/lifecycle.js +78 -72
- package/dist/runner-command/pre-schedule.js +203 -223
- package/dist/runner-command/run.js +450 -448
- package/dist/runner-command/schedule-source-options.js +8 -10
- package/dist/runner-command/select-ready-wave.js +39 -30
- package/dist/runner-command/task-descriptor.js +11 -17
- package/dist/runner-command-contract.d.ts +147 -157
- package/dist/runner-command-contract.js +403 -411
- package/dist/runner-event-schema.d.ts +364 -505
- package/dist/runner-event-schema.js +187 -175
- package/dist/runner-event-sink.js +37 -43
- package/dist/runner-output.js +6 -6
- package/dist/runner.d.ts +57 -37
- package/dist/runner.js +176 -127
- package/dist/runtime/actor-ids.d.ts +1 -0
- package/dist/runtime/actor-ids.js +2 -2
- package/dist/runtime/agent-node/agent-node.js +99 -110
- package/dist/runtime/agent-node/handoff-finalization.js +23 -24
- package/dist/runtime/agent-node/model-selection.js +19 -22
- package/dist/runtime/agent-node/output-finalization.js +174 -204
- package/dist/runtime/agent-node/prompt-rendering.js +208 -251
- package/dist/runtime/agent-node/session-execution.js +43 -39
- package/dist/runtime/builtins/builtins.js +218 -284
- package/dist/runtime/changed-files/changed-files.js +29 -34
- package/dist/runtime/command-executor/command-executor.js +31 -23
- package/dist/runtime/config-error.js +1 -3
- package/dist/runtime/context/context.js +35 -37
- package/dist/runtime/contracts/contracts.d.ts +3 -0
- package/dist/runtime/contracts/index.d.ts +1 -1
- package/dist/runtime/detached-race.js +11 -14
- package/dist/runtime/drain-merge/drain-merge.js +139 -164
- package/dist/runtime/durable-store/acquisition.js +5 -3
- package/dist/runtime/durable-store/durable-store.d.ts +3 -1
- package/dist/runtime/durable-store/postgres/client.js +29 -0
- package/dist/runtime/durable-store/postgres/migrate-substrate.js +34 -29
- package/dist/runtime/durable-store/postgres/postgres-store.js +18 -20
- package/dist/runtime/events/events.js +273 -331
- package/dist/runtime/events/index.js +2 -2
- package/dist/runtime/execution-envelope.js +44 -0
- package/dist/runtime/gates/adjudication/llm-judge.js +21 -24
- package/dist/runtime/gates/adjudication/structured-claim.js +15 -16
- package/dist/runtime/gates/adjudicator/adjudicator.js +27 -27
- package/dist/runtime/gates/adjudicator/index.js +2 -2
- package/dist/runtime/gates/contract.js +5 -9
- package/dist/runtime/gates/gates.js +9 -8
- package/dist/runtime/gates/kinds/acceptance/acceptance.js +68 -71
- package/dist/runtime/gates/kinds/acceptance/index.js +2 -2
- package/dist/runtime/gates/kinds/artifact/artifact.js +4 -4
- package/dist/runtime/gates/kinds/artifact/index.js +2 -2
- package/dist/runtime/gates/kinds/builtin/builtin.js +4 -4
- package/dist/runtime/gates/kinds/builtin/index.js +2 -2
- package/dist/runtime/gates/kinds/changed-files/changed-files.js +31 -35
- package/dist/runtime/gates/kinds/changed-files/index.js +2 -2
- package/dist/runtime/gates/kinds/command/command.js +3 -3
- package/dist/runtime/gates/kinds/command/index.js +2 -2
- package/dist/runtime/gates/kinds/json-schema/index.js +2 -2
- package/dist/runtime/gates/kinds/json-schema/json-schema.js +7 -6
- package/dist/runtime/gates/kinds/verdict/index.js +2 -2
- package/dist/runtime/gates/kinds/verdict/verdict.js +3 -3
- package/dist/runtime/gates/orchestrator.js +72 -86
- package/dist/runtime/gates/registry.js +1 -3
- package/dist/runtime/handoff.d.ts +2 -1
- package/dist/runtime/handoff.js +43 -46
- package/dist/runtime/hooks/command-hook.js +57 -71
- package/dist/runtime/hooks/context.js +21 -25
- package/dist/runtime/hooks/events.js +84 -100
- package/dist/runtime/hooks/execution.js +6 -12
- package/dist/runtime/hooks/hooks.js +34 -23
- package/dist/runtime/hooks/invocation.js +23 -29
- package/dist/runtime/hooks/module-hook.js +36 -35
- package/dist/runtime/hooks/policy.js +32 -29
- package/dist/runtime/hooks/results.js +56 -66
- package/dist/runtime/journal-acquisition.d.ts +3 -2
- package/dist/runtime/journal-acquisition.js +14 -14
- package/dist/runtime/json-validation/json-validation.js +51 -72
- package/dist/runtime/launch-plan-args.js +39 -0
- package/dist/runtime/local-scheduler.js +46 -48
- package/dist/runtime/node-execution.js +376 -445
- package/dist/runtime/node-protocol/node-protocol.js +36 -79
- package/dist/runtime/node-state-store.d.ts +4 -1
- package/dist/runtime/node-state-store.js +13 -57
- package/dist/runtime/node-state-tracker.js +46 -54
- package/dist/runtime/open-pull-request/open-pull-request.js +201 -187
- package/dist/runtime/opencode-adapter.js +23 -24
- package/dist/runtime/opencode-agent-name.js +6 -6
- package/dist/runtime/opencode-runtime.js +115 -93
- package/dist/runtime/opencode-server.js +39 -53
- package/dist/runtime/opencode-session-executor.js +312 -344
- package/dist/runtime/parallel-node/parallel-node.js +123 -134
- package/dist/runtime/parallel-worktrees/parallel-worktrees.js +90 -73
- package/dist/runtime/protected-paths/protected-paths.js +70 -71
- package/dist/runtime/remediation/remediation.js +202 -231
- package/dist/runtime/retry.js +8 -10
- package/dist/runtime/run-journal.d.ts +2 -0
- package/dist/runtime/run-journal.js +7 -7
- package/dist/runtime/runtime-results.js +54 -69
- package/dist/runtime/scheduled-dependencies.d.ts +2 -1
- package/dist/runtime/scheduled-dependencies.js +61 -75
- package/dist/runtime/scheduler.js +179 -202
- package/dist/runtime/services/agent-node-runtime-service.js +3 -3
- package/dist/runtime/services/backlog-service.d.ts +1 -2
- package/dist/runtime/services/backlog-service.js +8 -14
- package/dist/runtime/services/command-executor-service.js +1 -1
- package/dist/runtime/services/config-io-service.js +24 -21
- package/dist/runtime/services/drain-merge-git-service.js +1 -1
- package/dist/runtime/services/file-system-service.js +9 -7
- package/dist/runtime/services/git-porcelain-service.js +6 -6
- package/dist/runtime/services/kubernetes-argo-service.d.ts +10 -3
- package/dist/runtime/services/kubernetes-argo-service.js +90 -84
- package/dist/runtime/services/kubernetes-jobs-read-service.js +115 -0
- package/dist/runtime/services/kubernetes-jobs-service.d.ts +37 -0
- package/dist/runtime/services/kubernetes-jobs-service.js +45 -0
- package/dist/runtime/services/kubernetes-jobs-watch-service.js +61 -0
- package/dist/runtime/services/kubernetes-lease-service.js +21 -0
- package/dist/runtime/services/mcp-gateway-service.js +143 -94
- package/dist/runtime/services/open-pull-request-git-service.js +1 -3
- package/dist/runtime/services/opencode-runtime-server-service.js +10 -12
- package/dist/runtime/services/opencode-sdk-service.js +7 -7
- package/dist/runtime/services/repo-io-service.js +10 -12
- package/dist/runtime/services/run-journal-file-service.js +2 -2
- package/dist/runtime/services/runner-command-io-service.js +48 -52
- package/dist/runtime/services/runner-event-sink-http-service.js +32 -39
- package/dist/runtime/step/step-node.js +15 -15
- package/dist/runtime/workflow-execution.js +56 -57
- package/dist/runtime/workflow-lifecycle.js +82 -101
- package/dist/runtime/yeet-event-record.js +104 -0
- package/dist/runtime/yeet-executor.js +184 -0
- package/dist/safe-json.js +16 -16
- package/dist/schedule/backlog-context.js +122 -116
- package/dist/schedule/baseline.js +249 -255
- package/dist/schedule/passes/coverage.js +63 -68
- package/dist/schedule/passes/drain-merge.js +34 -40
- package/dist/schedule/passes/ids.js +15 -17
- package/dist/schedule/passes/models.js +40 -37
- package/dist/schedule/passes/open-pull-request.js +16 -20
- package/dist/schedule/passes/references.js +18 -22
- package/dist/schedule/prompts.js +135 -141
- package/dist/schedule/scheduling-roles.js +8 -14
- package/dist/schema-boundary.d.ts +4 -0
- package/dist/schema-boundary.js +145 -0
- package/dist/serialized-write-queue.js +2 -2
- package/dist/standard-output-schemas.js +61 -59
- package/dist/strings.js +12 -12
- package/dist/task-ref.js +5 -5
- package/dist/tickets/apply-ticket-plan.js +52 -61
- package/dist/tickets/backlog-task-store.d.ts +15 -23
- package/dist/tickets/backlog-task-store.js +82 -94
- package/dist/tickets/completion/complete-ticket.js +53 -59
- package/dist/tickets/ticket-graph-dto.d.ts +24 -48
- package/dist/tickets/ticket-graph-dto.js +40 -40
- package/dist/tickets/ticket-graph.d.ts +7 -2
- package/dist/tickets/ticket-graph.js +36 -50
- package/dist/tickets/ticket-plan-command-args.js +6 -10
- package/dist/tickets/ticket-plan-render.js +10 -16
- package/dist/tickets/ticket-plan.d.ts +32 -41
- package/dist/tickets/ticket-plan.js +97 -77
- package/dist/tickets/ticket-selection.d.ts +3 -1
- package/dist/tickets/ticket-selection.js +37 -52
- package/dist/tickets/ticket-task-index.js +13 -17
- package/dist/tickets/validation-error-format.js +3 -10
- package/dist/token-estimator.js +12 -7
- package/dist/unknown-fields.js +21 -0
- package/dist/workflow-submit-contract.d.ts +9 -10
- package/dist/workflow-submit-contract.js +10 -9
- package/docs/adr-moka-postgres-schema-isolation.md +48 -0
- package/docs/adr-opencode-first-goal-loop-runtime.md +11 -11
- package/docs/config-architecture.md +13 -13
- package/docs/mcp-host-isolation.md +1 -0
- package/docs/moka-orchestrator-design.md +3 -3
- package/docs/operator-guide.md +1 -1
- package/docs/pipeline-console-runner-contract.md +10 -6
- package/docs/runtime-actor-model.md +18 -18
- package/docs/runtime-test-subsystems.md +200 -0
- package/docs/slash-command-adapter-contract.md +3 -3
- package/package.json +49 -85
- package/dist/config/schema/reference-validation.js +0 -83
|
@@ -1,257 +1,163 @@
|
|
|
1
|
+
import { isStringValue } from "../schema-boundary.js";
|
|
2
|
+
import "../safe-json.js";
|
|
1
3
|
import { GitPorcelainService, GitPorcelainServiceLive } from "../runtime/services/git-porcelain-service.js";
|
|
2
|
-
import { Effect } from "effect";
|
|
3
4
|
import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
4
5
|
import { tmpdir } from "node:os";
|
|
5
6
|
import { dirname, resolve } from "node:path";
|
|
7
|
+
import { Effect, Option } from "effect";
|
|
6
8
|
//#region src/run-state/git-refs.ts
|
|
7
9
|
const DEFAULT_WORKSPACE_PATH = "/workspace";
|
|
8
10
|
const DEFAULT_GIT_CREDENTIALS_DIR = "/etc/pipeline/git-credentials";
|
|
9
11
|
const WRITABLE_GIT_CREDENTIAL_STORE = resolve(tmpdir(), "pipeline-git-credentials");
|
|
10
12
|
const LS_REMOTE_FIELD_SEPARATOR_RE = /\s+/u;
|
|
11
13
|
const SCP_LIKE_SSH_REMOTE_RE = /^[^@\s]+@[^:\s]+:.+/u;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
const MISSING_REMOTE_URL = Option.none();
|
|
15
|
+
let preparedBasicAuthCredentialStore = Option.none();
|
|
16
|
+
const runnerGitRefs = (payload, nodeId) => {
|
|
14
17
|
const prefix = `refs/heads/pipeline/runs/${payload.run.id}/${payload.workflow.id}`;
|
|
15
18
|
return {
|
|
16
19
|
finalRef: `${prefix}/final`,
|
|
17
20
|
nodeRef: `${prefix}/nodes/${nodeId}`,
|
|
18
21
|
prefix
|
|
19
22
|
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
function commitAndPushNodeRefEffect(input) {
|
|
67
|
-
return Effect.gen(function* () {
|
|
68
|
-
yield* commitChangesIfNeeded(input.worktreePath, input.nodeId, input.committer);
|
|
69
|
-
const sha = yield* headSha(input.worktreePath);
|
|
70
|
-
yield* pushGeneratedRef(input.worktreePath, runnerGitRefs(input.payload, input.nodeId).nodeRef);
|
|
71
|
-
return sha;
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Run a single git command through the runner's authenticated path: per-command
|
|
76
|
-
* credential-helper store (basic-auth from the mounted git-credentials) plus
|
|
77
|
-
* GIT_TERMINAL_PROMPT=0 so a missing credential fails fast instead of blocking
|
|
78
|
-
* forever on an interactive username prompt. This is the ONE git-auth primitive;
|
|
79
|
-
* every runner git operation (node delivery, dependency merge, open-pull-request)
|
|
80
|
-
* must route through it rather than spawning naked git.
|
|
81
|
-
*/
|
|
82
|
-
async function runAuthenticatedGit(cwd, args) {
|
|
83
|
-
return await Effect.runPromise(Effect.provide(runGit(cwd, args), GitPorcelainServiceLive));
|
|
84
|
-
}
|
|
85
|
-
async function promoteFinalRef(input) {
|
|
86
|
-
return await Effect.runPromise(Effect.provide(promoteFinalRefEffect(input), GitPorcelainServiceLive));
|
|
87
|
-
}
|
|
88
|
-
function promoteFinalRefEffect(input) {
|
|
89
|
-
return Effect.gen(function* () {
|
|
90
|
-
yield* mergeDependencyRefsEffect({
|
|
91
|
-
committer: input.committer,
|
|
92
|
-
dependencyNodeIds: input.sourceNodeIds,
|
|
93
|
-
payload: input.payload,
|
|
94
|
-
worktreePath: input.worktreePath
|
|
95
|
-
});
|
|
96
|
-
yield* commitChangesIfNeeded(input.worktreePath, "final", input.committer);
|
|
97
|
-
const sha = yield* headSha(input.worktreePath);
|
|
98
|
-
yield* pushGeneratedRef(input.worktreePath, runnerGitRefs(input.payload, "final").finalRef);
|
|
99
|
-
return sha;
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
function pushGeneratedRef(worktreePath, ref) {
|
|
103
|
-
return Effect.gen(function* () {
|
|
104
|
-
yield* runGit(worktreePath, [
|
|
105
|
-
"push",
|
|
106
|
-
`--force-with-lease=${ref}:${(yield* remoteHeadSha(worktreePath, ref)) ?? ""}`,
|
|
107
|
-
"origin",
|
|
108
|
-
`HEAD:${ref}`
|
|
109
|
-
]);
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
function remoteHeadSha(worktreePath, ref) {
|
|
113
|
-
return Effect.map(runGit(worktreePath, [
|
|
114
|
-
"ls-remote",
|
|
115
|
-
"--heads",
|
|
116
|
-
"origin",
|
|
117
|
-
ref
|
|
118
|
-
]), (stdout) => parseLsRemoteSha(stdout));
|
|
119
|
-
}
|
|
120
|
-
function parseLsRemoteSha(stdout) {
|
|
121
|
-
const firstLine = stdout.trim().split("\n")[0];
|
|
122
|
-
return firstLine ? firstLine.split(LS_REMOTE_FIELD_SEPARATOR_RE)[0] : void 0;
|
|
123
|
-
}
|
|
124
|
-
function headSha(worktreePath) {
|
|
125
|
-
return Effect.map(runGit(worktreePath, ["rev-parse", "HEAD"]), (sha) => sha.trim());
|
|
126
|
-
}
|
|
127
|
-
function runnerCommitMessage(nodeId) {
|
|
128
|
-
return `chore(pipeline): ${nodeId}`;
|
|
129
|
-
}
|
|
130
|
-
function commitChangesIfNeeded(worktreePath, nodeId, committer) {
|
|
131
|
-
return Effect.gen(function* () {
|
|
132
|
-
if ((yield* runGit(worktreePath, [
|
|
133
|
-
"status",
|
|
134
|
-
"--porcelain",
|
|
135
|
-
"--untracked-files=all"
|
|
136
|
-
])).trim().length === 0) return;
|
|
137
|
-
yield* runGit(worktreePath, ["add", "--all"]);
|
|
138
|
-
yield* configureGitCommitter(worktreePath, committer);
|
|
139
|
-
yield* runGit(worktreePath, [
|
|
140
|
-
"commit",
|
|
141
|
-
"-m",
|
|
142
|
-
runnerCommitMessage(nodeId)
|
|
143
|
-
]);
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
function configureGitCommitter(worktreePath, committer) {
|
|
147
|
-
return Effect.gen(function* () {
|
|
148
|
-
yield* runGit(worktreePath, [
|
|
149
|
-
"config",
|
|
150
|
-
"--local",
|
|
151
|
-
"user.name",
|
|
152
|
-
committer.name
|
|
153
|
-
]);
|
|
154
|
-
yield* runGit(worktreePath, [
|
|
155
|
-
"config",
|
|
156
|
-
"--local",
|
|
157
|
-
"user.email",
|
|
158
|
-
committer.email
|
|
159
|
-
]);
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
function runnerGitCommandArgs(args, remoteUrl) {
|
|
163
|
-
return [...gitCredentialConfigArgs(remoteUrl), ...args];
|
|
164
|
-
}
|
|
165
|
-
function runGit(cwd, args) {
|
|
166
|
-
return Effect.gen(function* () {
|
|
167
|
-
const remoteUrl = yield* remoteUrlFromGitArgs(cwd, args);
|
|
168
|
-
yield* Effect.try({
|
|
169
|
-
try: () => assertSshCredentialsAvailable(remoteUrl),
|
|
170
|
-
catch: (error) => error
|
|
171
|
-
});
|
|
172
|
-
const git = yield* GitPorcelainService;
|
|
173
|
-
const commandArgs = runnerGitCommandArgs(args, remoteUrl);
|
|
174
|
-
return yield* git.run(cwd, commandArgs, runnerGitEnv(remoteUrl));
|
|
23
|
+
};
|
|
24
|
+
const parseLsRemoteSha = (stdout) => {
|
|
25
|
+
const [firstLine = ""] = stdout.trim().split("\n");
|
|
26
|
+
if (firstLine.length === 0) return Option.none();
|
|
27
|
+
const [sha = ""] = firstLine.split(LS_REMOTE_FIELD_SEPARATOR_RE);
|
|
28
|
+
return Option.some(sha);
|
|
29
|
+
};
|
|
30
|
+
const runnerCommitMessage = (nodeId) => `chore(pipeline): ${nodeId}`;
|
|
31
|
+
const gitCredentialsDir = () => process.env.PIPELINE_GIT_CREDENTIALS_DIR ?? DEFAULT_GIT_CREDENTIALS_DIR;
|
|
32
|
+
const writableGitCredentialStore = () => process.env.PIPELINE_WRITABLE_GIT_CREDENTIAL_STORE ?? WRITABLE_GIT_CREDENTIAL_STORE;
|
|
33
|
+
const writeGitCredentialStore = (credentials, writablePath, host) => {
|
|
34
|
+
mkdirSync(dirname(writablePath), { recursive: true });
|
|
35
|
+
writeFileSync(writablePath, `https://${encodeURIComponent(credentials.username)}:${encodeURIComponent(credentials.password)}@${host}\n`, { mode: 384 });
|
|
36
|
+
chmodSync(writablePath, 384);
|
|
37
|
+
};
|
|
38
|
+
const existingPreparedBasicAuthCredentialStore = (writablePath) => Option.isSome(preparedBasicAuthCredentialStore) && preparedBasicAuthCredentialStore.value.path === writablePath ? Option.some(writablePath) : Option.none();
|
|
39
|
+
const isPreparedBasicAuthCredentialStore = (writablePath, host) => Option.isSome(preparedBasicAuthCredentialStore) && preparedBasicAuthCredentialStore.value.path === writablePath && preparedBasicAuthCredentialStore.value.host === host;
|
|
40
|
+
const errorCode = (error) => error instanceof Error && "code" in error && isStringValue(error.code) ? Option.some(error.code) : Option.none();
|
|
41
|
+
const isReadOnlyFileSystemError = (error) => Option.contains("EROFS")(errorCode(error));
|
|
42
|
+
const isOwnerReadOnly = (path) => {
|
|
43
|
+
const permissions = statSync(path).mode.toString(8).slice(-3);
|
|
44
|
+
return [
|
|
45
|
+
"4",
|
|
46
|
+
"5",
|
|
47
|
+
"6",
|
|
48
|
+
"7"
|
|
49
|
+
].includes(permissions[0]) && permissions.slice(1) === "00";
|
|
50
|
+
};
|
|
51
|
+
const ensureSshIdentityPermissions = (identityPath) => {
|
|
52
|
+
try {
|
|
53
|
+
chmodSync(identityPath, 256);
|
|
54
|
+
return;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (!(isReadOnlyFileSystemError(error) && isOwnerReadOnly(identityPath))) throw error;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const readCredentialFile = (path) => readFileSync(path, "utf-8").trim();
|
|
60
|
+
const availableBasicAuthCredentials = () => {
|
|
61
|
+
const credentialsDir = gitCredentialsDir();
|
|
62
|
+
const usernamePath = resolve(credentialsDir, "username");
|
|
63
|
+
const passwordPath = resolve(credentialsDir, "password");
|
|
64
|
+
if (!(existsSync(usernamePath) && existsSync(passwordPath))) return Option.none();
|
|
65
|
+
return Option.some({
|
|
66
|
+
password: readCredentialFile(passwordPath),
|
|
67
|
+
username: readCredentialFile(usernamePath)
|
|
175
68
|
});
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
|
|
69
|
+
};
|
|
70
|
+
const remoteArgFromGitArgs = (args) => Option.fromUndefinedOr(args.slice(1).find((arg) => !arg.startsWith("-")));
|
|
71
|
+
const gitRemoteUrl = (cwd, remoteName) => Effect.gen(function* effectBody() {
|
|
72
|
+
const trimmed = (yield* (yield* GitPorcelainService).run(cwd, [
|
|
73
|
+
"remote",
|
|
74
|
+
"get-url",
|
|
75
|
+
remoteName
|
|
76
|
+
], {
|
|
77
|
+
...process.env,
|
|
78
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
79
|
+
})).trim();
|
|
80
|
+
return trimmed.length > 0 ? Option.some(trimmed) : Option.none();
|
|
81
|
+
});
|
|
82
|
+
const remoteUrlForName = (cwd, remoteName) => Option.isSome(remoteName) ? gitRemoteUrl(cwd, remoteName.value) : Effect.succeed(MISSING_REMOTE_URL);
|
|
83
|
+
const isHttpRemote = (value) => value.startsWith("http://") || value.startsWith("https://");
|
|
84
|
+
const isScpLikeSshRemote = (value) => SCP_LIKE_SSH_REMOTE_RE.test(value);
|
|
85
|
+
const isRemoteUrl = (value) => value.startsWith("http://") || value.startsWith("https://") || value.startsWith("ssh://") || isScpLikeSshRemote(value);
|
|
86
|
+
const literalRemoteUrlFromGitArgs = (args) => {
|
|
87
|
+
if (args[0] === "clone") return Option.fromUndefinedOr(args.find((arg) => isRemoteUrl(arg)));
|
|
88
|
+
if (args[0] === "fetch" || args[0] === "push" || args[0] === "ls-remote") {
|
|
89
|
+
const remoteArg = remoteArgFromGitArgs(args);
|
|
90
|
+
if (Option.isSome(remoteArg) && isRemoteUrl(remoteArg.value)) return remoteArg;
|
|
91
|
+
}
|
|
92
|
+
return Option.none();
|
|
93
|
+
};
|
|
94
|
+
const remoteNameFromGitArgs = (args) => {
|
|
95
|
+
if (args[0] === "fetch" || args[0] === "push" || args[0] === "ls-remote") {
|
|
96
|
+
const remoteArg = remoteArgFromGitArgs(args);
|
|
97
|
+
if (Option.isSome(remoteArg) && !isRemoteUrl(remoteArg.value)) return remoteArg;
|
|
98
|
+
}
|
|
99
|
+
return Option.none();
|
|
100
|
+
};
|
|
101
|
+
const remoteUrlFromGitArgs = (cwd, args) => Effect.gen(function* effectBody() {
|
|
102
|
+
const literalRemoteUrl = literalRemoteUrlFromGitArgs(args);
|
|
103
|
+
if (Option.isSome(literalRemoteUrl)) return literalRemoteUrl;
|
|
104
|
+
const remoteName = remoteNameFromGitArgs(args);
|
|
105
|
+
return yield* remoteUrlForName(cwd, remoteName);
|
|
106
|
+
});
|
|
107
|
+
const isSshRemote = (value) => value.startsWith("ssh://") || isScpLikeSshRemote(value);
|
|
108
|
+
const assertSshCredentialsAvailable = (remoteUrl) => {
|
|
109
|
+
if (Option.isNone(remoteUrl) || !isSshRemote(remoteUrl.value)) return;
|
|
179
110
|
const credentialsDir = gitCredentialsDir();
|
|
180
111
|
const missing = [["identity", resolve(credentialsDir, "identity")], ["known_hosts", resolve(credentialsDir, "known_hosts")]].filter(([, filePath]) => !existsSync(filePath)).map(([name]) => name);
|
|
181
112
|
if (missing.length === 0) return;
|
|
182
|
-
throw new Error(`SSH git remote ${remoteUrl} requires mounted git credential file(s): ${missing.join(", ")}`);
|
|
183
|
-
}
|
|
184
|
-
|
|
113
|
+
throw new Error(`SSH git remote ${remoteUrl.value} requires mounted git credential file(s): ${missing.join(", ")}`);
|
|
114
|
+
};
|
|
115
|
+
const credentialHost = (remoteUrl) => {
|
|
116
|
+
try {
|
|
117
|
+
const { host } = new URL(remoteUrl);
|
|
118
|
+
return host.length > 0 ? Option.some(host) : Option.none();
|
|
119
|
+
} catch {
|
|
120
|
+
return Option.none();
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
const prepareBasicAuthCredentialStore = (credentials, writablePath, remoteUrl) => {
|
|
124
|
+
const host = Option.isSome(remoteUrl) ? credentialHost(remoteUrl.value) : Option.none();
|
|
125
|
+
if (Option.isNone(host)) return existingPreparedBasicAuthCredentialStore(writablePath);
|
|
126
|
+
if (isPreparedBasicAuthCredentialStore(writablePath, host.value)) return Option.some(writablePath);
|
|
127
|
+
writeGitCredentialStore(credentials, writablePath, host.value);
|
|
128
|
+
preparedBasicAuthCredentialStore = Option.some({
|
|
129
|
+
host: host.value,
|
|
130
|
+
path: writablePath
|
|
131
|
+
});
|
|
132
|
+
return Option.some(writablePath);
|
|
133
|
+
};
|
|
134
|
+
const prepareWritableGitCredentialStore = (remoteUrl) => {
|
|
135
|
+
if (Option.isNone(remoteUrl)) return existingPreparedBasicAuthCredentialStore(writableGitCredentialStore());
|
|
136
|
+
if (!isHttpRemote(remoteUrl.value)) return Option.none();
|
|
137
|
+
const writablePath = writableGitCredentialStore();
|
|
138
|
+
const basicAuth = availableBasicAuthCredentials();
|
|
139
|
+
if (Option.isSome(basicAuth)) return prepareBasicAuthCredentialStore(basicAuth.value, writablePath, remoteUrl);
|
|
140
|
+
return Option.none();
|
|
141
|
+
};
|
|
142
|
+
const gitCredentialConfigArgs = (remoteUrl) => {
|
|
185
143
|
const writablePath = prepareWritableGitCredentialStore(remoteUrl);
|
|
186
|
-
if (
|
|
144
|
+
if (Option.isNone(writablePath)) return [];
|
|
187
145
|
return [
|
|
188
146
|
"-c",
|
|
189
147
|
"credential.helper=",
|
|
190
148
|
"-c",
|
|
191
|
-
`credential.helper=store --file=${writablePath}`
|
|
149
|
+
`credential.helper=store --file=${writablePath.value}`
|
|
192
150
|
];
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const writablePath = writableGitCredentialStore();
|
|
198
|
-
const basicAuth = availableBasicAuthCredentials();
|
|
199
|
-
if (basicAuth) return prepareBasicAuthCredentialStore(basicAuth, writablePath, remoteUrl);
|
|
200
|
-
}
|
|
201
|
-
function availableBasicAuthCredentials() {
|
|
202
|
-
const credentialsDir = gitCredentialsDir();
|
|
203
|
-
const usernamePath = resolve(credentialsDir, "username");
|
|
204
|
-
const passwordPath = resolve(credentialsDir, "password");
|
|
205
|
-
if (!(existsSync(usernamePath) && existsSync(passwordPath))) return;
|
|
206
|
-
return {
|
|
207
|
-
password: readCredentialFile(passwordPath),
|
|
208
|
-
username: readCredentialFile(usernamePath)
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
function gitCredentialsDir() {
|
|
212
|
-
return process.env.PIPELINE_GIT_CREDENTIALS_DIR ?? DEFAULT_GIT_CREDENTIALS_DIR;
|
|
213
|
-
}
|
|
214
|
-
function writableGitCredentialStore() {
|
|
215
|
-
return process.env.PIPELINE_WRITABLE_GIT_CREDENTIAL_STORE ?? WRITABLE_GIT_CREDENTIAL_STORE;
|
|
216
|
-
}
|
|
217
|
-
function writeGitCredentialStore(credentials, writablePath, host) {
|
|
218
|
-
mkdirSync(dirname(writablePath), { recursive: true });
|
|
219
|
-
writeFileSync(writablePath, `https://${encodeURIComponent(credentials.username)}:${encodeURIComponent(credentials.password)}@${host}\n`, { mode: 384 });
|
|
220
|
-
chmodSync(writablePath, 384);
|
|
221
|
-
}
|
|
222
|
-
function prepareBasicAuthCredentialStore(credentials, writablePath, remoteUrl) {
|
|
223
|
-
const host = remoteUrl ? credentialHost(remoteUrl) : void 0;
|
|
224
|
-
if (!host) return existingPreparedBasicAuthCredentialStore(writablePath);
|
|
225
|
-
if (isPreparedBasicAuthCredentialStore(writablePath, host)) return writablePath;
|
|
226
|
-
writeGitCredentialStore(credentials, writablePath, host);
|
|
227
|
-
preparedBasicAuthCredentialStore = {
|
|
228
|
-
host,
|
|
229
|
-
path: writablePath
|
|
230
|
-
};
|
|
231
|
-
return writablePath;
|
|
232
|
-
}
|
|
233
|
-
function existingPreparedBasicAuthCredentialStore(writablePath) {
|
|
234
|
-
return preparedBasicAuthCredentialStore?.path === writablePath ? writablePath : void 0;
|
|
235
|
-
}
|
|
236
|
-
function isPreparedBasicAuthCredentialStore(writablePath, host) {
|
|
237
|
-
return preparedBasicAuthCredentialStore?.path === writablePath && preparedBasicAuthCredentialStore.host === host;
|
|
238
|
-
}
|
|
239
|
-
function runnerGitEnv(remoteUrl) {
|
|
240
|
-
const env = {
|
|
241
|
-
...process.env,
|
|
242
|
-
GIT_TERMINAL_PROMPT: "0"
|
|
243
|
-
};
|
|
244
|
-
const sshCommand = gitSshCommand();
|
|
245
|
-
if (sshCommand && remoteUrl && isSshRemote(remoteUrl)) env.GIT_SSH_COMMAND = sshCommand;
|
|
246
|
-
return env;
|
|
247
|
-
}
|
|
248
|
-
function gitSshCommand() {
|
|
151
|
+
};
|
|
152
|
+
const runnerGitCommandArgs = (args, remoteUrl) => [...gitCredentialConfigArgs(remoteUrl), ...args];
|
|
153
|
+
const shellQuote = (value) => `'${value.replaceAll("'", `'\\''`)}'`;
|
|
154
|
+
const gitSshCommand = () => {
|
|
249
155
|
const credentialsDir = gitCredentialsDir();
|
|
250
156
|
const identityPath = resolve(credentialsDir, "identity");
|
|
251
157
|
const knownHostsPath = resolve(credentialsDir, "known_hosts");
|
|
252
|
-
if (!(existsSync(identityPath) && existsSync(knownHostsPath))) return;
|
|
158
|
+
if (!(existsSync(identityPath) && existsSync(knownHostsPath))) return Option.none();
|
|
253
159
|
ensureSshIdentityPermissions(identityPath);
|
|
254
|
-
return [
|
|
160
|
+
return Option.some([
|
|
255
161
|
"ssh",
|
|
256
162
|
"-i",
|
|
257
163
|
shellQuote(identityPath),
|
|
@@ -261,90 +167,135 @@ function gitSshCommand() {
|
|
|
261
167
|
`UserKnownHostsFile=${shellQuote(knownHostsPath)}`,
|
|
262
168
|
"-o",
|
|
263
169
|
"StrictHostKeyChecking=yes"
|
|
264
|
-
].join(" ");
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
"6",
|
|
283
|
-
"7"
|
|
284
|
-
].includes(permissions[0] ?? "") && permissions.slice(1) === "00";
|
|
285
|
-
}
|
|
286
|
-
function readCredentialFile(path) {
|
|
287
|
-
return readFileSync(path, "utf8").trim();
|
|
288
|
-
}
|
|
289
|
-
function remoteUrlFromGitArgs(cwd, args) {
|
|
290
|
-
return Effect.gen(function* () {
|
|
291
|
-
const literalRemoteUrl = literalRemoteUrlFromGitArgs(args);
|
|
292
|
-
if (literalRemoteUrl) return literalRemoteUrl;
|
|
293
|
-
return yield* remoteUrlForName(cwd, remoteNameFromGitArgs(args));
|
|
170
|
+
].join(" "));
|
|
171
|
+
};
|
|
172
|
+
const runnerGitEnv = (remoteUrl) => {
|
|
173
|
+
const env = {
|
|
174
|
+
...process.env,
|
|
175
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
176
|
+
};
|
|
177
|
+
const sshCommand = gitSshCommand();
|
|
178
|
+
if (Option.isSome(sshCommand) && Option.isSome(remoteUrl) && isSshRemote(remoteUrl.value)) env.GIT_SSH_COMMAND = sshCommand.value;
|
|
179
|
+
return env;
|
|
180
|
+
};
|
|
181
|
+
const runGit = (cwd, args) => Effect.gen(function* effectBody() {
|
|
182
|
+
const remoteUrl = yield* remoteUrlFromGitArgs(cwd, args);
|
|
183
|
+
yield* Effect.try({
|
|
184
|
+
catch: (error) => error,
|
|
185
|
+
try: () => {
|
|
186
|
+
assertSshCredentialsAvailable(remoteUrl);
|
|
187
|
+
}
|
|
294
188
|
});
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
return
|
|
298
|
-
}
|
|
299
|
-
function
|
|
300
|
-
if (
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
189
|
+
const git = yield* GitPorcelainService;
|
|
190
|
+
const commandArgs = runnerGitCommandArgs(args, remoteUrl);
|
|
191
|
+
return yield* git.run(cwd, commandArgs, runnerGitEnv(remoteUrl));
|
|
192
|
+
});
|
|
193
|
+
const prepareRunnerGitWorkspaceEffect = (payload, options) => Effect.gen(function* effectBody() {
|
|
194
|
+
if (options.cwd !== void 0 && options.cwd.length > 0) return resolve(options.cwd);
|
|
195
|
+
const worktreePath = options.workspacePath ?? DEFAULT_WORKSPACE_PATH;
|
|
196
|
+
yield* Effect.sync(() => mkdirSync(dirname(worktreePath), { recursive: true }));
|
|
197
|
+
yield* runGit(dirname(worktreePath), [
|
|
198
|
+
"clone",
|
|
199
|
+
"--no-tags",
|
|
200
|
+
payload.repository.url,
|
|
201
|
+
worktreePath
|
|
202
|
+
]);
|
|
203
|
+
yield* runGit(worktreePath, ["checkout", payload.repository.sha ?? `origin/${payload.repository.baseBranch}`]);
|
|
204
|
+
return worktreePath;
|
|
205
|
+
});
|
|
206
|
+
const prepareRunnerGitWorkspace = async (payload, options = {}) => await Effect.runPromise(Effect.provide(prepareRunnerGitWorkspaceEffect(payload, options), GitPorcelainServiceLive));
|
|
207
|
+
const mergeDependencyRef = (worktreePath, ref) => Effect.gen(function* effectBody() {
|
|
208
|
+
yield* runGit(worktreePath, [
|
|
209
|
+
"fetch",
|
|
210
|
+
"origin",
|
|
211
|
+
ref
|
|
212
|
+
]);
|
|
213
|
+
yield* runGit(worktreePath, [
|
|
214
|
+
"merge",
|
|
215
|
+
"--no-ff",
|
|
216
|
+
"--no-edit",
|
|
217
|
+
"FETCH_HEAD"
|
|
218
|
+
]);
|
|
219
|
+
});
|
|
220
|
+
/**
|
|
221
|
+
* Run a single git command through the runner's authenticated path: per-command
|
|
222
|
+
* credential-helper store (basic-auth from the mounted git-credentials) plus
|
|
223
|
+
* GIT_TERMINAL_PROMPT=0 so a missing credential fails fast instead of blocking
|
|
224
|
+
* forever on an interactive username prompt. This is the ONE git-auth primitive;
|
|
225
|
+
* every runner git operation (node delivery, dependency merge, open-pull-request)
|
|
226
|
+
* must route through it rather than spawning naked git.
|
|
227
|
+
*/
|
|
228
|
+
const runAuthenticatedGit = async (cwd, args) => await Effect.runPromise(Effect.provide(runGit(cwd, args), GitPorcelainServiceLive));
|
|
229
|
+
const remoteHeadSha = (worktreePath, ref) => Effect.map(runGit(worktreePath, [
|
|
230
|
+
"ls-remote",
|
|
231
|
+
"--heads",
|
|
232
|
+
"origin",
|
|
233
|
+
ref
|
|
234
|
+
]), (stdout) => parseLsRemoteSha(stdout));
|
|
235
|
+
const pushGeneratedRef = (worktreePath, ref) => Effect.gen(function* effectBody() {
|
|
236
|
+
const expectedSha = yield* remoteHeadSha(worktreePath, ref);
|
|
237
|
+
yield* runGit(worktreePath, [
|
|
238
|
+
"push",
|
|
239
|
+
`--force-with-lease=${ref}:${Option.getOrElse(expectedSha, () => "")}`,
|
|
240
|
+
"origin",
|
|
241
|
+
`HEAD:${ref}`
|
|
242
|
+
]);
|
|
243
|
+
});
|
|
244
|
+
const headSha = (worktreePath) => Effect.map(runGit(worktreePath, ["rev-parse", "HEAD"]), (sha) => sha.trim());
|
|
245
|
+
const configureGitCommitter = (worktreePath, committer) => Effect.gen(function* effectBody() {
|
|
246
|
+
yield* runGit(worktreePath, [
|
|
247
|
+
"config",
|
|
248
|
+
"--local",
|
|
249
|
+
"user.name",
|
|
250
|
+
committer.name
|
|
251
|
+
]);
|
|
252
|
+
yield* runGit(worktreePath, [
|
|
253
|
+
"config",
|
|
254
|
+
"--local",
|
|
255
|
+
"user.email",
|
|
256
|
+
committer.email
|
|
257
|
+
]);
|
|
258
|
+
});
|
|
259
|
+
const mergeDependencyRefsEffect = (input) => Effect.gen(function* effectBody() {
|
|
260
|
+
yield* configureGitCommitter(input.worktreePath, input.committer);
|
|
261
|
+
yield* Effect.forEach(input.dependencyNodeIds, (nodeId) => mergeDependencyRef(input.worktreePath, runnerGitRefs(input.payload, nodeId).nodeRef));
|
|
262
|
+
});
|
|
263
|
+
const mergeDependencyRefs = async (input) => {
|
|
264
|
+
await Effect.runPromise(Effect.provide(mergeDependencyRefsEffect(input), GitPorcelainServiceLive));
|
|
265
|
+
};
|
|
266
|
+
const commitChangesIfNeeded = (worktreePath, nodeId, committer) => Effect.gen(function* effectBody() {
|
|
267
|
+
if ((yield* runGit(worktreePath, [
|
|
268
|
+
"status",
|
|
269
|
+
"--porcelain",
|
|
270
|
+
"--untracked-files=all"
|
|
271
|
+
])).trim().length === 0) return;
|
|
272
|
+
yield* runGit(worktreePath, ["add", "--all"]);
|
|
273
|
+
yield* configureGitCommitter(worktreePath, committer);
|
|
274
|
+
yield* runGit(worktreePath, [
|
|
275
|
+
"commit",
|
|
276
|
+
"-m",
|
|
277
|
+
runnerCommitMessage(nodeId)
|
|
278
|
+
]);
|
|
279
|
+
});
|
|
280
|
+
const commitAndPushNodeRefEffect = (input) => Effect.gen(function* effectBody() {
|
|
281
|
+
yield* commitChangesIfNeeded(input.worktreePath, input.nodeId, input.committer);
|
|
282
|
+
const sha = yield* headSha(input.worktreePath);
|
|
283
|
+
yield* pushGeneratedRef(input.worktreePath, runnerGitRefs(input.payload, input.nodeId).nodeRef);
|
|
284
|
+
return sha;
|
|
285
|
+
});
|
|
286
|
+
const commitAndPushNodeRef = async (input) => await Effect.runPromise(Effect.provide(commitAndPushNodeRefEffect(input), GitPorcelainServiceLive));
|
|
287
|
+
const promoteFinalRefEffect = (input) => Effect.gen(function* effectBody() {
|
|
288
|
+
yield* mergeDependencyRefsEffect({
|
|
289
|
+
committer: input.committer,
|
|
290
|
+
dependencyNodeIds: input.sourceNodeIds,
|
|
291
|
+
payload: input.payload,
|
|
292
|
+
worktreePath: input.worktreePath
|
|
325
293
|
});
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
}
|
|
333
|
-
function isHttpRemote(value) {
|
|
334
|
-
return value.startsWith("http://") || value.startsWith("https://");
|
|
335
|
-
}
|
|
336
|
-
function isScpLikeSshRemote(value) {
|
|
337
|
-
return SCP_LIKE_SSH_REMOTE_RE.test(value);
|
|
338
|
-
}
|
|
339
|
-
function credentialHost(remoteUrl) {
|
|
340
|
-
try {
|
|
341
|
-
return new URL(remoteUrl).host || void 0;
|
|
342
|
-
} catch {
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
function shellQuote(value) {
|
|
347
|
-
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
348
|
-
}
|
|
294
|
+
yield* commitChangesIfNeeded(input.worktreePath, "final", input.committer);
|
|
295
|
+
const sha = yield* headSha(input.worktreePath);
|
|
296
|
+
yield* pushGeneratedRef(input.worktreePath, runnerGitRefs(input.payload, "final").finalRef);
|
|
297
|
+
return sha;
|
|
298
|
+
});
|
|
299
|
+
const promoteFinalRef = async (input) => await Effect.runPromise(Effect.provide(promoteFinalRefEffect(input), GitPorcelainServiceLive));
|
|
349
300
|
//#endregion
|
|
350
301
|
export { commitAndPushNodeRef, mergeDependencyRefs, prepareRunnerGitWorkspace, promoteFinalRef, runAuthenticatedGit, runnerCommitMessage };
|
|
@@ -9,15 +9,15 @@ const OPENCODE_EXCLUDES = [
|
|
|
9
9
|
"build/",
|
|
10
10
|
"coverage/"
|
|
11
11
|
];
|
|
12
|
-
const LINE_RE = /\r?\n
|
|
13
|
-
|
|
12
|
+
const LINE_RE = /\r?\n/u;
|
|
13
|
+
const ensureOpencodeGitExcludes = (worktreePath) => {
|
|
14
14
|
const excludePath = join(worktreePath, ".git", "info", "exclude");
|
|
15
15
|
if (!existsSync(excludePath)) return;
|
|
16
|
-
const existing = readFileSync(excludePath, "
|
|
16
|
+
const existing = readFileSync(excludePath, "utf-8");
|
|
17
17
|
const missing = OPENCODE_EXCLUDES.filter((entry) => !existing.split(LINE_RE).includes(entry));
|
|
18
18
|
if (missing.length === 0) return;
|
|
19
19
|
mkdirSync(join(worktreePath, ".git", "info"), { recursive: true });
|
|
20
20
|
appendFileSync(excludePath, `${existing.endsWith("\n") ? "" : "\n"}# oisin-pipeline opencode excludes\n${missing.join("\n")}\n`);
|
|
21
|
-
}
|
|
21
|
+
};
|
|
22
22
|
//#endregion
|
|
23
23
|
export { ensureOpencodeGitExcludes };
|