dorfl 0.14.2 → 0.15.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/dist/advance-ci-template.d.ts +17 -16
- package/dist/advance-ci-template.d.ts.map +1 -1
- package/dist/advance-ci-template.js +42 -53
- package/dist/advance-ci-template.js.map +1 -1
- package/dist/advance-drivers.d.ts +8 -0
- package/dist/advance-drivers.d.ts.map +1 -1
- package/dist/advance-drivers.js +28 -4
- package/dist/advance-drivers.js.map +1 -1
- package/dist/advance-isolated.d.ts.map +1 -1
- package/dist/advance-isolated.js +3 -2
- package/dist/advance-isolated.js.map +1 -1
- package/dist/advance-lifecycle-template.d.ts +41 -40
- package/dist/advance-lifecycle-template.d.ts.map +1 -1
- package/dist/advance-lifecycle-template.js +309 -489
- package/dist/advance-lifecycle-template.js.map +1 -1
- package/dist/advance-loop-driver.d.ts.map +1 -1
- package/dist/advance-loop-driver.js +3 -2
- package/dist/advance-loop-driver.js.map +1 -1
- package/dist/advance-treeless-publish.d.ts +31 -0
- package/dist/advance-treeless-publish.d.ts.map +1 -1
- package/dist/advance-treeless-publish.js +40 -0
- package/dist/advance-treeless-publish.js.map +1 -1
- package/dist/advance.d.ts +63 -3
- package/dist/advance.d.ts.map +1 -1
- package/dist/advance.js +80 -12
- package/dist/advance.js.map +1 -1
- package/dist/advancing-lock.d.ts +9 -0
- package/dist/advancing-lock.d.ts.map +1 -1
- package/dist/advancing-lock.js +21 -1
- package/dist/advancing-lock.js.map +1 -1
- package/dist/agent-env.d.ts +61 -0
- package/dist/agent-env.d.ts.map +1 -0
- package/dist/agent-env.js +144 -0
- package/dist/agent-env.js.map +1 -0
- package/dist/agent-stop.d.ts +35 -0
- package/dist/agent-stop.d.ts.map +1 -1
- package/dist/agent-stop.js +62 -0
- package/dist/agent-stop.js.map +1 -1
- package/dist/apply-merge-action.d.ts +76 -34
- package/dist/apply-merge-action.d.ts.map +1 -1
- package/dist/apply-merge-action.js +339 -129
- package/dist/apply-merge-action.js.map +1 -1
- package/dist/apply-stuck-action.js +2 -2
- package/dist/apply-stuck-action.js.map +1 -1
- package/dist/ci-agent-result.d.ts +151 -0
- package/dist/ci-agent-result.d.ts.map +1 -0
- package/dist/ci-agent-result.js +314 -0
- package/dist/ci-agent-result.js.map +1 -0
- package/dist/ci-handoff-apply.d.ts +162 -0
- package/dist/ci-handoff-apply.d.ts.map +1 -0
- package/dist/ci-handoff-apply.js +497 -0
- package/dist/ci-handoff-apply.js.map +1 -0
- package/dist/ci-handoff-format.d.ts +369 -0
- package/dist/ci-handoff-format.d.ts.map +1 -0
- package/dist/ci-handoff-format.js +646 -0
- package/dist/ci-handoff-format.js.map +1 -0
- package/dist/ci-handoff-lfs.d.ts +146 -0
- package/dist/ci-handoff-lfs.d.ts.map +1 -0
- package/dist/ci-handoff-lfs.js +397 -0
- package/dist/ci-handoff-lfs.js.map +1 -0
- package/dist/ci-handoff.d.ts +99 -0
- package/dist/ci-handoff.d.ts.map +1 -0
- package/dist/ci-handoff.js +277 -0
- package/dist/ci-handoff.js.map +1 -0
- package/dist/ci-lock-outputs.d.ts +93 -0
- package/dist/ci-lock-outputs.d.ts.map +1 -0
- package/dist/ci-lock-outputs.js +223 -0
- package/dist/ci-lock-outputs.js.map +1 -0
- package/dist/ci-phase-build.d.ts +178 -0
- package/dist/ci-phase-build.d.ts.map +1 -0
- package/dist/ci-phase-build.js +989 -0
- package/dist/ci-phase-build.js.map +1 -0
- package/dist/ci-phase-driver.d.ts +247 -0
- package/dist/ci-phase-driver.d.ts.map +1 -0
- package/dist/ci-phase-driver.js +370 -0
- package/dist/ci-phase-driver.js.map +1 -0
- package/dist/ci-phase-intake.d.ts +172 -0
- package/dist/ci-phase-intake.d.ts.map +1 -0
- package/dist/ci-phase-intake.js +555 -0
- package/dist/ci-phase-intake.js.map +1 -0
- package/dist/ci-phase-tasking.d.ts +205 -0
- package/dist/ci-phase-tasking.d.ts.map +1 -0
- package/dist/ci-phase-tasking.js +906 -0
- package/dist/ci-phase-tasking.js.map +1 -0
- package/dist/ci-phase-treeless.d.ts +224 -0
- package/dist/ci-phase-treeless.d.ts.map +1 -0
- package/dist/ci-phase-treeless.js +1482 -0
- package/dist/ci-phase-treeless.js.map +1 -0
- package/dist/ci-read-token.d.ts +47 -0
- package/dist/ci-read-token.d.ts.map +1 -0
- package/dist/ci-read-token.js +80 -0
- package/dist/ci-read-token.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +447 -22
- package/dist/cli.js.map +1 -1
- package/dist/close-job-template.d.ts +10 -1
- package/dist/close-job-template.d.ts.map +1 -1
- package/dist/close-job-template.js +46 -14
- package/dist/close-job-template.js.map +1 -1
- package/dist/close-job.d.ts +20 -5
- package/dist/close-job.d.ts.map +1 -1
- package/dist/close-job.js +65 -12
- package/dist/close-job.js.map +1 -1
- package/dist/complete.d.ts.map +1 -1
- package/dist/complete.js +20 -1
- package/dist/complete.js.map +1 -1
- package/dist/config.d.ts +41 -44
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -4
- package/dist/config.js.map +1 -1
- package/dist/continue-branch.d.ts +63 -15
- package/dist/continue-branch.d.ts.map +1 -1
- package/dist/continue-branch.js +75 -8
- package/dist/continue-branch.js.map +1 -1
- package/dist/crashed-run-save.d.ts +73 -0
- package/dist/crashed-run-save.d.ts.map +1 -0
- package/dist/crashed-run-save.js +86 -0
- package/dist/crashed-run-save.js.map +1 -0
- package/dist/cwd-section.d.ts.map +1 -1
- package/dist/cwd-section.js +10 -2
- package/dist/cwd-section.js.map +1 -1
- package/dist/decision-engine.d.ts +10 -0
- package/dist/decision-engine.d.ts.map +1 -1
- package/dist/decision-engine.js.map +1 -1
- package/dist/do-config.d.ts +2 -24
- package/dist/do-config.d.ts.map +1 -1
- package/dist/do-config.js +1 -33
- package/dist/do-config.js.map +1 -1
- package/dist/do.d.ts +149 -1
- package/dist/do.d.ts.map +1 -1
- package/dist/do.js +115 -25
- package/dist/do.js.map +1 -1
- package/dist/dorfl-item-template.d.ts +98 -0
- package/dist/dorfl-item-template.d.ts.map +1 -0
- package/dist/dorfl-item-template.js +504 -0
- package/dist/dorfl-item-template.js.map +1 -0
- package/dist/env-config.d.ts.map +1 -1
- package/dist/env-config.js +7 -5
- package/dist/env-config.js.map +1 -1
- package/dist/frontmatter.d.ts +52 -0
- package/dist/frontmatter.d.ts.map +1 -1
- package/dist/frontmatter.js +136 -2
- package/dist/frontmatter.js.map +1 -1
- package/dist/gc.d.ts +13 -0
- package/dist/gc.d.ts.map +1 -1
- package/dist/gc.js +16 -2
- package/dist/gc.js.map +1 -1
- package/dist/git.d.ts +18 -1
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +70 -5
- package/dist/git.js.map +1 -1
- package/dist/harness.d.ts +44 -5
- package/dist/harness.d.ts.map +1 -1
- package/dist/harness.js +24 -1
- package/dist/harness.js.map +1 -1
- package/dist/index.d.ts +11 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/install-ci-action-pins.d.ts +123 -0
- package/dist/install-ci-action-pins.d.ts.map +1 -0
- package/dist/install-ci-action-pins.js +190 -0
- package/dist/install-ci-action-pins.js.map +1 -0
- package/dist/install-ci-actions-pr-creation.d.ts +70 -0
- package/dist/install-ci-actions-pr-creation.d.ts.map +1 -0
- package/dist/install-ci-actions-pr-creation.js +106 -0
- package/dist/install-ci-actions-pr-creation.js.map +1 -0
- package/dist/install-ci-capabilities/dorfl-item.d.ts +12 -0
- package/dist/install-ci-capabilities/dorfl-item.d.ts.map +1 -0
- package/dist/install-ci-capabilities/dorfl-item.js +26 -0
- package/dist/install-ci-capabilities/dorfl-item.js.map +1 -0
- package/dist/install-ci-core.d.ts +155 -37
- package/dist/install-ci-core.d.ts.map +1 -1
- package/dist/install-ci-core.js +304 -200
- package/dist/install-ci-core.js.map +1 -1
- package/dist/install-ci-github.d.ts +47 -2
- package/dist/install-ci-github.d.ts.map +1 -1
- package/dist/install-ci-github.js +113 -1
- package/dist/install-ci-github.js.map +1 -1
- package/dist/install-ci.d.ts +25 -6
- package/dist/install-ci.d.ts.map +1 -1
- package/dist/install-ci.js +72 -36
- package/dist/install-ci.js.map +1 -1
- package/dist/intake-marker.d.ts +6 -1
- package/dist/intake-marker.d.ts.map +1 -1
- package/dist/intake-marker.js.map +1 -1
- package/dist/intake-triage.d.ts.map +1 -1
- package/dist/intake-triage.js +6 -2
- package/dist/intake-triage.js.map +1 -1
- package/dist/intake-trigger-template.d.ts +31 -25
- package/dist/intake-trigger-template.d.ts.map +1 -1
- package/dist/intake-trigger-template.js +107 -292
- package/dist/intake-trigger-template.js.map +1 -1
- package/dist/intake.d.ts +186 -4
- package/dist/intake.d.ts.map +1 -1
- package/dist/intake.js +533 -416
- package/dist/intake.js.map +1 -1
- package/dist/integration-core.d.ts +154 -1
- package/dist/integration-core.d.ts.map +1 -1
- package/dist/integration-core.js +759 -486
- package/dist/integration-core.js.map +1 -1
- package/dist/integrator.d.ts +12 -0
- package/dist/integrator.d.ts.map +1 -1
- package/dist/integrator.js +21 -0
- package/dist/integrator.js.map +1 -1
- package/dist/isolation.d.ts +12 -2
- package/dist/isolation.d.ts.map +1 -1
- package/dist/isolation.js +19 -8
- package/dist/isolation.js.map +1 -1
- package/dist/item-lock.d.ts +93 -11
- package/dist/item-lock.d.ts.map +1 -1
- package/dist/item-lock.js +233 -65
- package/dist/item-lock.js.map +1 -1
- package/dist/land-question-residue.d.ts +58 -0
- package/dist/land-question-residue.d.ts.map +1 -0
- package/dist/land-question-residue.js +95 -0
- package/dist/land-question-residue.js.map +1 -0
- package/dist/lifecycle-gather.d.ts +16 -0
- package/dist/lifecycle-gather.d.ts.map +1 -1
- package/dist/lifecycle-gather.js +54 -6
- package/dist/lifecycle-gather.js.map +1 -1
- package/dist/merge-answer.d.ts +41 -0
- package/dist/merge-answer.d.ts.map +1 -0
- package/dist/merge-answer.js +60 -0
- package/dist/merge-answer.js.map +1 -0
- package/dist/merge-question-surfacer.d.ts +105 -30
- package/dist/merge-question-surfacer.d.ts.map +1 -1
- package/dist/merge-question-surfacer.js +202 -91
- package/dist/merge-question-surfacer.js.map +1 -1
- package/dist/merge-question-tick.d.ts +64 -0
- package/dist/merge-question-tick.d.ts.map +1 -0
- package/dist/merge-question-tick.js +96 -0
- package/dist/merge-question-tick.js.map +1 -0
- package/dist/mint-adr.d.ts.map +1 -1
- package/dist/mint-adr.js +20 -2
- package/dist/mint-adr.js.map +1 -1
- package/dist/needs-attention.d.ts +72 -2
- package/dist/needs-attention.d.ts.map +1 -1
- package/dist/needs-attention.js +275 -38
- package/dist/needs-attention.js.map +1 -1
- package/dist/phase-recorder.d.ts +168 -0
- package/dist/phase-recorder.d.ts.map +1 -0
- package/dist/phase-recorder.js +218 -0
- package/dist/phase-recorder.js.map +1 -0
- package/dist/phase.d.ts +94 -0
- package/dist/phase.d.ts.map +1 -0
- package/dist/phase.js +148 -0
- package/dist/phase.js.map +1 -0
- package/dist/pi-harness.d.ts.map +1 -1
- package/dist/pi-harness.js +19 -6
- package/dist/pi-harness.js.map +1 -1
- package/dist/prepare.d.ts.map +1 -1
- package/dist/prepare.js +4 -0
- package/dist/prepare.js.map +1 -1
- package/dist/prompt.d.ts +4 -0
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +48 -1
- package/dist/prompt.js.map +1 -1
- package/dist/protocol/CLAIM-PROTOCOL.md +3 -1
- package/dist/protocol/WORK-CONTRACT.md +1 -1
- package/dist/reap-branches.d.ts +48 -0
- package/dist/reap-branches.d.ts.map +1 -1
- package/dist/reap-branches.js +64 -0
- package/dist/reap-branches.js.map +1 -1
- package/dist/ref-write.d.ts +198 -0
- package/dist/ref-write.d.ts.map +1 -0
- package/dist/ref-write.js +79 -0
- package/dist/ref-write.js.map +1 -0
- package/dist/repo-config.js +8 -8
- package/dist/repo-config.js.map +1 -1
- package/dist/repo-mirror.d.ts +36 -0
- package/dist/repo-mirror.d.ts.map +1 -1
- package/dist/repo-mirror.js +91 -1
- package/dist/repo-mirror.js.map +1 -1
- package/dist/review-verdict.d.ts +1 -1
- package/dist/review-verdict.js +1 -1
- package/dist/run.d.ts +7 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +28 -4
- package/dist/run.js.map +1 -1
- package/dist/scan.d.ts +19 -1
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +21 -8
- package/dist/scan.js.map +1 -1
- package/dist/sidecar.d.ts +14 -0
- package/dist/sidecar.d.ts.map +1 -1
- package/dist/sidecar.js +21 -0
- package/dist/sidecar.js.map +1 -1
- package/dist/single-job-warning.d.ts +63 -0
- package/dist/single-job-warning.d.ts.map +1 -0
- package/dist/single-job-warning.js +110 -0
- package/dist/single-job-warning.js.map +1 -0
- package/dist/skills/setup/protocol/CLAIM-PROTOCOL.md +3 -1
- package/dist/skills/setup/protocol/WORK-CONTRACT.md +1 -1
- package/dist/spec-complete.d.ts +13 -6
- package/dist/spec-complete.d.ts.map +1 -1
- package/dist/spec-complete.js +23 -11
- package/dist/spec-complete.js.map +1 -1
- package/dist/start.d.ts.map +1 -1
- package/dist/start.js +47 -14
- package/dist/start.js.map +1 -1
- package/dist/status.d.ts +12 -0
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +11 -3
- package/dist/status.js.map +1 -1
- package/dist/tasking.d.ts +246 -1
- package/dist/tasking.d.ts.map +1 -1
- package/dist/tasking.js +452 -358
- package/dist/tasking.js.map +1 -1
- package/dist/triage-persist.d.ts.map +1 -1
- package/dist/triage-persist.js +21 -2
- package/dist/triage-persist.js.map +1 -1
- package/dist/verify-workflow-template.d.ts.map +1 -1
- package/dist/verify-workflow-template.js +8 -4
- package/dist/verify-workflow-template.js.map +1 -1
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +4 -0
- package/dist/verify.js.map +1 -1
- package/dist/watch-session.d.ts +42 -13
- package/dist/watch-session.d.ts.map +1 -1
- package/dist/watch-session.js +52 -23
- package/dist/watch-session.js.map +1 -1
- package/dist/work-layout.d.ts +10 -4
- package/dist/work-layout.d.ts.map +1 -1
- package/dist/work-layout.js +11 -3
- package/dist/work-layout.js.map +1 -1
- package/dist/workspace.d.ts +65 -4
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +159 -11
- package/dist/workspace.js.map +1 -1
- package/package.json +1 -1
- package/src/advance-ci-template.ts +80 -81
- package/src/advance-drivers.ts +45 -6
- package/src/advance-isolated.ts +3 -5
- package/src/advance-lifecycle-template.ts +375 -538
- package/src/advance-loop-driver.ts +3 -5
- package/src/advance-treeless-publish.ts +61 -0
- package/src/advance.ts +145 -15
- package/src/advancing-lock.ts +36 -1
- package/src/agent-env.ts +174 -0
- package/src/agent-stop.ts +81 -0
- package/src/apply-merge-action.ts +401 -154
- package/src/apply-stuck-action.ts +2 -2
- package/src/ci-agent-result.ts +421 -0
- package/src/ci-handoff-apply.ts +737 -0
- package/src/ci-handoff-format.ts +926 -0
- package/src/ci-handoff-lfs.ts +519 -0
- package/src/ci-handoff.ts +392 -0
- package/src/ci-lock-outputs.ts +273 -0
- package/src/ci-phase-build.ts +1328 -0
- package/src/ci-phase-driver.ts +548 -0
- package/src/ci-phase-intake.ts +861 -0
- package/src/ci-phase-tasking.ts +1255 -0
- package/src/ci-phase-treeless.ts +2028 -0
- package/src/ci-read-token.ts +90 -0
- package/src/cli.ts +591 -29
- package/src/close-job-template.ts +52 -15
- package/src/close-job.ts +77 -12
- package/src/complete.ts +19 -1
- package/src/config.ts +43 -48
- package/src/continue-branch.ts +121 -16
- package/src/crashed-run-save.ts +162 -0
- package/src/cwd-section.ts +10 -0
- package/src/decision-engine.ts +10 -0
- package/src/do-config.ts +2 -57
- package/src/do.ts +170 -28
- package/src/dorfl-item-template.ts +556 -0
- package/src/env-config.ts +7 -5
- package/src/frontmatter.ts +155 -2
- package/src/gc.ts +27 -2
- package/src/git.ts +83 -5
- package/src/harness.ts +58 -6
- package/src/index.ts +50 -1
- package/src/install-ci-action-pins.ts +209 -0
- package/src/install-ci-actions-pr-creation.ts +186 -0
- package/src/install-ci-capabilities/dorfl-item.ts +33 -0
- package/src/install-ci-core.ts +388 -233
- package/src/install-ci-github.ts +149 -1
- package/src/install-ci.ts +83 -37
- package/src/intake-marker.ts +6 -1
- package/src/intake-triage.ts +7 -3
- package/src/intake-trigger-template.ts +126 -327
- package/src/intake.ts +725 -544
- package/src/integration-core.ts +975 -523
- package/src/integrator.ts +28 -0
- package/src/isolation.ts +29 -10
- package/src/item-lock.ts +308 -92
- package/src/land-question-residue.ts +122 -0
- package/src/lifecycle-gather.ts +82 -10
- package/src/merge-answer.ts +82 -0
- package/src/merge-question-surfacer.ts +320 -112
- package/src/merge-question-tick.ts +182 -0
- package/src/mint-adr.ts +28 -2
- package/src/needs-attention.ts +350 -42
- package/src/phase-recorder.ts +350 -0
- package/src/phase.ts +177 -0
- package/src/pi-harness.ts +25 -5
- package/src/prepare.ts +4 -0
- package/src/prompt.ts +68 -1
- package/src/reap-branches.ts +107 -0
- package/src/ref-write.ts +324 -0
- package/src/repo-config.ts +8 -8
- package/src/repo-mirror.ts +107 -1
- package/src/review-verdict.ts +1 -1
- package/src/run.ts +47 -6
- package/src/scan.ts +37 -7
- package/src/sidecar.ts +36 -0
- package/src/single-job-warning.ts +135 -0
- package/src/spec-complete.ts +36 -14
- package/src/start.ts +55 -16
- package/src/status.ts +29 -3
- package/src/tasking.ts +593 -378
- package/src/triage-persist.ts +24 -2
- package/src/verify-workflow-template.ts +8 -4
- package/src/verify.ts +4 -0
- package/src/watch-session.ts +86 -28
- package/src/work-layout.ts +11 -3
- package/src/workspace.ts +210 -14
package/src/install-ci-core.ts
CHANGED
|
@@ -10,8 +10,7 @@
|
|
|
10
10
|
* This module is the PROVIDER-AGNOSTIC half: the config model (`ProviderEntry` /
|
|
11
11
|
* `AuthMode` / `CIConfigFile`), the `models.json` builder, the secret-orchestration
|
|
12
12
|
* LOGIC (which secrets, dedup, prompt-or-take-from-config), the `--export-config`
|
|
13
|
-
* (+ `--include-secrets`) round-trip, the composite-setup-action
|
|
14
|
-
* script generators, the `--fake` snapshot mechanism (write to `.fake/` instead of
|
|
13
|
+
* (+ `--include-secrets`) round-trip, the composite-setup-action generators, the `--fake` snapshot mechanism (write to `.fake/` instead of
|
|
15
14
|
* `.github/`), and the capability-emitter REGISTRY seam. It imports NOTHING
|
|
16
15
|
* GitHub-specific: the thin GitHub adapter (`github-ci.ts`) plugs into the
|
|
17
16
|
* {@link CIProviderContext} seam, so a second provider could slot in WITHOUT
|
|
@@ -25,24 +24,50 @@
|
|
|
25
24
|
* `DORFL_*` env block, NOT a new config knob minted here.
|
|
26
25
|
*/
|
|
27
26
|
|
|
28
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
readFileSync,
|
|
29
|
+
writeFileSync,
|
|
30
|
+
mkdirSync,
|
|
31
|
+
readdirSync,
|
|
32
|
+
existsSync,
|
|
33
|
+
} from 'node:fs';
|
|
29
34
|
import {dirname, join, relative} from 'node:path';
|
|
30
35
|
import {fileURLToPath} from 'node:url';
|
|
31
36
|
import type {HarnessAdapter} from './config.js';
|
|
37
|
+
import {
|
|
38
|
+
ACTION_PINS,
|
|
39
|
+
pinnedUses,
|
|
40
|
+
preserveExistingPins,
|
|
41
|
+
} from './install-ci-action-pins.js';
|
|
32
42
|
|
|
33
43
|
// ─── Config model ──────────────────────────────────────────────────────────
|
|
34
44
|
|
|
35
45
|
/**
|
|
36
|
-
* The
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
46
|
+
* The auth mode. `models-json` is the ONLY mode: one GitHub secret per provider
|
|
47
|
+
* API key; the harness `models.json` is generated inline and references the env
|
|
48
|
+
* vars (`$NAME`). Credential rotation (e.g. OAuth) is NOT done in CI: point a
|
|
49
|
+
* provider's `baseUrl` at a proxy that rotates credentials outside GitHub.
|
|
50
|
+
*
|
|
51
|
+
* The former `auth-json` mode (a `PI_AUTH_JSON` secret + an OAuth-refresh
|
|
52
|
+
* script that wrote the rotated token back with a write-capable `GH_PAT`) was
|
|
53
|
+
* REMOVED: its refresh needed a write token in the agent job, which ADR
|
|
54
|
+
* `ci-agent-job-holds-no-write-token` forbids, and without the refresh the
|
|
55
|
+
* OAuth token expires within hours. A config still naming it is refused by
|
|
56
|
+
* {@link loadCIConfigFile} with {@link AUTH_JSON_REMOVED_MESSAGE}. The type
|
|
57
|
+
* (and the `authMode` config key) is kept so existing `models-json` configs
|
|
58
|
+
* still parse and export unchanged.
|
|
59
|
+
*/
|
|
60
|
+
export type AuthMode = 'models-json';
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The refusal a config naming the removed `auth-json` mode gets: it says the
|
|
64
|
+
* mode is gone and names the replacement (`models-json` + a proxy `baseUrl`).
|
|
44
65
|
*/
|
|
45
|
-
export
|
|
66
|
+
export const AUTH_JSON_REMOVED_MESSAGE =
|
|
67
|
+
'config file "authMode": "auth-json" is no longer supported (the mode was ' +
|
|
68
|
+
'removed: its OAuth refresh needed a write-capable GH_PAT in the agent job). ' +
|
|
69
|
+
'Use "authMode": "models-json" and point a provider\'s "baseUrl" at a proxy ' +
|
|
70
|
+
'that handles credential rotation outside GitHub.';
|
|
46
71
|
|
|
47
72
|
/**
|
|
48
73
|
* Where the composite setup action gets the `dorfl` CLI from:
|
|
@@ -84,7 +109,7 @@ export interface ProviderEntry {
|
|
|
84
109
|
export interface CIConfigFile {
|
|
85
110
|
/** The auth mode (`models-json` default). */
|
|
86
111
|
authMode: AuthMode;
|
|
87
|
-
/** The configured providers (empty
|
|
112
|
+
/** The configured providers (non-empty). */
|
|
88
113
|
providers: ProviderEntry[];
|
|
89
114
|
/** The default provider name. */
|
|
90
115
|
defaultProvider: string;
|
|
@@ -197,6 +222,20 @@ export const DEFAULT_INSTALL_SOURCE: InstallSource = 'registry';
|
|
|
197
222
|
|
|
198
223
|
// ─── The CI-provider SEAM (provider-agnostic; GitHub is the first adapter) ───
|
|
199
224
|
|
|
225
|
+
/**
|
|
226
|
+
* A repository's GitHub Actions workflow permissions, as
|
|
227
|
+
* `GET /repos/{owner}/{repo}/actions/permissions/workflow` answers them (the
|
|
228
|
+
* same two fields the PUT accepts). `defaultWorkflowPermissions` is `read` or
|
|
229
|
+
* `write` on GitHub; typed as a string so an unexpected value is carried
|
|
230
|
+
* through untouched rather than rewritten.
|
|
231
|
+
*/
|
|
232
|
+
export interface ActionsWorkflowPermissions {
|
|
233
|
+
/** The GITHUB_TOKEN's default scope (`read` / `write`); preserved on set. */
|
|
234
|
+
defaultWorkflowPermissions: string;
|
|
235
|
+
/** "Allow GitHub Actions to create and approve pull requests". */
|
|
236
|
+
canApprovePullRequestReviews: boolean;
|
|
237
|
+
}
|
|
238
|
+
|
|
200
239
|
/**
|
|
201
240
|
* The thin CI-provider seam (whitesmith's proven `GitHubCIContext` shape,
|
|
202
241
|
* generalised so a second provider could slot in without touching the core). A
|
|
@@ -277,6 +316,25 @@ export interface CIProviderContext {
|
|
|
277
316
|
* GitHub provider. Throws on failure (caller catches + logs).
|
|
278
317
|
*/
|
|
279
318
|
setBranchRuleset?(ruleset: unknown): Promise<void>;
|
|
319
|
+
/**
|
|
320
|
+
* OPTIONALLY read the repo's Actions workflow permissions (GitHub:
|
|
321
|
+
* `GET /repos/{owner}/{repo}/actions/permissions/workflow`), whose
|
|
322
|
+
* `can_approve_pull_request_reviews` is the "Allow GitHub Actions to create
|
|
323
|
+
* and approve pull requests" setting propose mode needs. Returns `undefined`
|
|
324
|
+
* when the read fails. Absent on a provider with no such setting.
|
|
325
|
+
*/
|
|
326
|
+
getActionsWorkflowPermissions?(): Promise<
|
|
327
|
+
ActionsWorkflowPermissions | undefined
|
|
328
|
+
>;
|
|
329
|
+
/**
|
|
330
|
+
* OPTIONALLY set the repo's Actions workflow permissions (GitHub:
|
|
331
|
+
* `PUT /repos/{owner}/{repo}/actions/permissions/workflow`). A field left
|
|
332
|
+
* `undefined` is not sent, so GitHub keeps its current value. Throws on
|
|
333
|
+
* failure (caller catches + logs). Absent on a provider with no such setting.
|
|
334
|
+
*/
|
|
335
|
+
setActionsWorkflowPermissions?(
|
|
336
|
+
permissions: Partial<ActionsWorkflowPermissions>,
|
|
337
|
+
): Promise<void>;
|
|
280
338
|
/**
|
|
281
339
|
* OPTIONALLY render the user's project-setup payload (the value of
|
|
282
340
|
* {@link CIConfigFile.projectSetup}[providerId]) to the NATIVE-SYNTAX
|
|
@@ -405,9 +463,12 @@ function pathToFileUrl(absPath: string): string {
|
|
|
405
463
|
|
|
406
464
|
/**
|
|
407
465
|
* Build the harness `models.json` object from the providers. Built-in providers
|
|
408
|
-
* (`anthropic`/`openai`) get `{apiKey:
|
|
409
|
-
* var at runtime); a custom provider carries its `baseUrl`/`api`/`models`/`compat`.
|
|
466
|
+
* (`anthropic`/`openai`) get `{apiKey: "$<ENV_VAR>"}` (the harness resolves the
|
|
467
|
+
* env var at runtime); a custom provider carries its `baseUrl`/`api`/`models`/`compat`.
|
|
410
468
|
* Mirrors whitesmith's `buildModelsJson`.
|
|
469
|
+
*
|
|
470
|
+
* The `apiKey` is an env-var REFERENCE (see {@link modelsJsonEnvRef}), never the
|
|
471
|
+
* bare env-var name: pi treats a bare value as a LITERAL key.
|
|
411
472
|
*/
|
|
412
473
|
export function buildModelsJson(providers: ProviderEntry[]): {
|
|
413
474
|
providers: Record<string, unknown>;
|
|
@@ -415,14 +476,16 @@ export function buildModelsJson(providers: ProviderEntry[]): {
|
|
|
415
476
|
const providersObj: Record<string, unknown> = {};
|
|
416
477
|
for (const p of providers) {
|
|
417
478
|
if (p.builtin) {
|
|
418
|
-
const entry: Record<string, unknown> = {
|
|
479
|
+
const entry: Record<string, unknown> = {
|
|
480
|
+
apiKey: modelsJsonEnvRef(p.apiKeyEnvVar),
|
|
481
|
+
};
|
|
419
482
|
if (p.baseUrl) entry.baseUrl = p.baseUrl;
|
|
420
483
|
providersObj[p.name] = entry;
|
|
421
484
|
} else {
|
|
422
485
|
const entry: Record<string, unknown> = {
|
|
423
486
|
baseUrl: p.baseUrl,
|
|
424
487
|
api: p.api,
|
|
425
|
-
apiKey: p.apiKeyEnvVar,
|
|
488
|
+
apiKey: modelsJsonEnvRef(p.apiKeyEnvVar),
|
|
426
489
|
models: p.models,
|
|
427
490
|
};
|
|
428
491
|
if (p.compat) entry.compat = p.compat;
|
|
@@ -432,19 +495,27 @@ export function buildModelsJson(providers: ProviderEntry[]): {
|
|
|
432
495
|
return {providers: providersObj};
|
|
433
496
|
}
|
|
434
497
|
|
|
498
|
+
/**
|
|
499
|
+
* The `models.json` config value that makes the pi harness read `envVarName`
|
|
500
|
+
* from the environment: `"$NAME"`. The pinned harness
|
|
501
|
+
* (`@earendil-works/pi-coding-agent`, `dist/core/resolve-config-value.js`)
|
|
502
|
+
* resolves a config value as a shell command when it starts with `!`, as an env
|
|
503
|
+
* var when it is `$NAME` / `${NAME}`, and as a LITERAL otherwise. Writing the bare
|
|
504
|
+
* name therefore sent the string `ANTHROPIC_API_KEY` as the token (`401
|
|
505
|
+
* Unauthorized` on every CI launch).
|
|
506
|
+
*/
|
|
507
|
+
export function modelsJsonEnvRef(envVarName: string): string {
|
|
508
|
+
return `$${envVarName}`;
|
|
509
|
+
}
|
|
510
|
+
|
|
435
511
|
// ─── secret-orchestration LOGIC (which secrets, dedup) ───────────────────────
|
|
436
512
|
|
|
437
513
|
/**
|
|
438
514
|
* The DEDUPLICATED set of secret env-var names the config requires, in first-seen
|
|
439
|
-
* order
|
|
440
|
-
* for `auth-json` mode it is the fixed `PI_AUTH_JSON` + `GH_PAT` pair (the sharp
|
|
441
|
-
* edge). This is the pure secret-orchestration LOGIC the adapter's setter
|
|
515
|
+
* order: one per distinct provider `apiKeyEnvVar`. This is the pure secret-orchestration LOGIC the adapter's setter
|
|
442
516
|
* consumes — which secrets, deduped — separated from any provider I/O.
|
|
443
517
|
*/
|
|
444
518
|
export function requiredSecretNames(config: ResolvedCIConfig): string[] {
|
|
445
|
-
if (config.authMode === 'auth-json') {
|
|
446
|
-
return ['PI_AUTH_JSON', 'GH_PAT'];
|
|
447
|
-
}
|
|
448
519
|
const seen = new Set<string>();
|
|
449
520
|
const names: string[] = [];
|
|
450
521
|
for (const p of config.providers) {
|
|
@@ -457,13 +528,13 @@ export function requiredSecretNames(config: ResolvedCIConfig): string[] {
|
|
|
457
528
|
|
|
458
529
|
/**
|
|
459
530
|
* The GitHub secret the propose/merge CI legs read as `GH_TOKEN` to open PRs
|
|
460
|
-
* under a CHOSEN identity (a PAT / App token).
|
|
461
|
-
* distinct purpose from `auth-json` mode's `GH_PAT` (which exists only to rotate
|
|
462
|
-
* the OAuth `PI_AUTH_JSON` secret). The generated workflows reference it as
|
|
531
|
+
* under a CHOSEN identity (a PAT / App token). The generated workflows reference it as
|
|
463
532
|
* `${{ secrets.DORFL_GH_TOKEN || secrets.GITHUB_TOKEN }}` so it is
|
|
464
533
|
* OPTIONAL: unset ⇒ the legs fall back to the built-in `GITHUB_TOKEN` (PRs are
|
|
465
|
-
* `github-actions[bot]
|
|
466
|
-
*
|
|
534
|
+
* `github-actions[bot]`, and GitHub holds their `on: pull_request` runs in
|
|
535
|
+
* `action_required` until someone approves them); set ⇒ PRs carry the token's
|
|
536
|
+
* identity and trigger those workflows on their own. `intake.yml` never passes
|
|
537
|
+
* it, so intake PRs always need the approval (`docs/ci/README.md`).
|
|
467
538
|
*/
|
|
468
539
|
export const PR_IDENTITY_SECRET_NAME = 'DORFL_GH_TOKEN';
|
|
469
540
|
|
|
@@ -490,14 +561,13 @@ export function optionalSecretNames(config: ResolvedCIConfig): string[] {
|
|
|
490
561
|
* entry). Each entry maps the action input (named identically to the secret) to
|
|
491
562
|
* `${{ secrets.<NAME> }}`.
|
|
492
563
|
*
|
|
493
|
-
* Empty string when there are no provider keys (
|
|
564
|
+
* Empty string when there are no provider keys (no providers),
|
|
494
565
|
* so the `uses:` line is emitted bare and unchanged. This is the SINGLE place the
|
|
495
566
|
* provider→`with:` shape lives, shared by every capability workflow generator, so
|
|
496
567
|
* no template re-derives it.
|
|
497
568
|
*/
|
|
498
569
|
export function providerSecretsWithBlock(config: ResolvedCIConfig): string {
|
|
499
|
-
const names =
|
|
500
|
-
config.authMode === 'models-json' ? requiredSecretNames(config) : [];
|
|
570
|
+
const names = providerSecretNames(config);
|
|
501
571
|
if (names.length === 0) {
|
|
502
572
|
return '';
|
|
503
573
|
}
|
|
@@ -507,6 +577,17 @@ export function providerSecretsWithBlock(config: ResolvedCIConfig): string {
|
|
|
507
577
|
return `\n with:\n${entries}`;
|
|
508
578
|
}
|
|
509
579
|
|
|
580
|
+
/**
|
|
581
|
+
* The provider API key secrets the AGENT-running jobs forward to the setup
|
|
582
|
+
* action: one per distinct provider key (the same set as
|
|
583
|
+
* {@link requiredSecretNames}). The split item workflow declares exactly these (plus
|
|
584
|
+
* {@link PR_IDENTITY_SECRET_NAME}) as its `workflow_call` secrets, and hands
|
|
585
|
+
* them to its agent job only.
|
|
586
|
+
*/
|
|
587
|
+
export function providerSecretNames(config: ResolvedCIConfig): string[] {
|
|
588
|
+
return requiredSecretNames(config);
|
|
589
|
+
}
|
|
590
|
+
|
|
510
591
|
/** The outcome of orchestrating one secret through the provider seam. */
|
|
511
592
|
export interface SecretSetResult {
|
|
512
593
|
/** The secret env-var name. */
|
|
@@ -604,12 +685,17 @@ export function loadCIConfigFile(filePath: string): CIConfigFile {
|
|
|
604
685
|
(err instanceof Error ? err.message : String(err)),
|
|
605
686
|
);
|
|
606
687
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
688
|
+
// `authMode` is kept as a key (so existing configs still parse) but the only
|
|
689
|
+
// accepted value is `models-json`. The removed `auth-json` mode gets a refusal
|
|
690
|
+
// that names its replacement rather than the generic "must be" message.
|
|
691
|
+
const rawAuthMode: unknown = (data as {authMode?: unknown}).authMode;
|
|
692
|
+
if (rawAuthMode === 'auth-json') {
|
|
693
|
+
throw new CIConfigError(AUTH_JSON_REMOVED_MESSAGE);
|
|
694
|
+
}
|
|
695
|
+
if (rawAuthMode !== undefined && rawAuthMode !== 'models-json') {
|
|
696
|
+
throw new CIConfigError(`config file "authMode" must be "models-json"`);
|
|
612
697
|
}
|
|
698
|
+
const authMode: AuthMode = 'models-json';
|
|
613
699
|
if (!data.defaultProvider) {
|
|
614
700
|
throw new CIConfigError('config file must contain "defaultProvider"');
|
|
615
701
|
}
|
|
@@ -638,20 +724,18 @@ export function loadCIConfigFile(filePath: string): CIConfigFile {
|
|
|
638
724
|
);
|
|
639
725
|
}
|
|
640
726
|
}
|
|
641
|
-
if (
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
);
|
|
650
|
-
}
|
|
727
|
+
if (
|
|
728
|
+
!data.providers ||
|
|
729
|
+
!Array.isArray(data.providers) ||
|
|
730
|
+
data.providers.length === 0
|
|
731
|
+
) {
|
|
732
|
+
throw new CIConfigError(
|
|
733
|
+
'config file (models-json) must contain a non-empty "providers" array',
|
|
734
|
+
);
|
|
651
735
|
}
|
|
652
736
|
return {
|
|
653
737
|
authMode,
|
|
654
|
-
providers: data.providers
|
|
738
|
+
providers: data.providers,
|
|
655
739
|
defaultProvider: data.defaultProvider,
|
|
656
740
|
defaultModel: data.defaultModel,
|
|
657
741
|
harness: data.harness,
|
|
@@ -707,7 +791,7 @@ export function exportCIConfig(
|
|
|
707
791
|
return JSON.stringify(file, null, 2) + '\n';
|
|
708
792
|
}
|
|
709
793
|
|
|
710
|
-
// ─── composite setup action
|
|
794
|
+
// ─── composite setup action generation ───────────────────────────────────────
|
|
711
795
|
|
|
712
796
|
/** Indent every non-blank line of `text` by `spaces` spaces. */
|
|
713
797
|
function indent(text: string, spaces: number): string {
|
|
@@ -750,6 +834,55 @@ export const PI_HARNESS_PACKAGE = '@earendil-works/pi-coding-agent';
|
|
|
750
834
|
*/
|
|
751
835
|
export const PI_HARNESS_VERSION = '0.80.6';
|
|
752
836
|
|
|
837
|
+
/**
|
|
838
|
+
* The `pi` packages the harness LOADS besides itself, each pinned to an exact
|
|
839
|
+
* version that is known to load with {@link PI_HARNESS_VERSION}.
|
|
840
|
+
*
|
|
841
|
+
* `pi-coding-agent` declares its sibling packages with a caret range
|
|
842
|
+
* (`@earendil-works/pi-ai: ^0.80.6`), which on a `0.x` version floats within
|
|
843
|
+
* the minor. `pi-ai@0.80.10` removed the `getOAuthApiKey` export that
|
|
844
|
+
* `pi-coding-agent@0.80.6` imports, so a resolver that honours the caret kills
|
|
845
|
+
* `pi` on start. npm happens to be protected by the `npm-shrinkwrap.json` the
|
|
846
|
+
* package publishes; pnpm ignores that file and was broken. Pinning the family
|
|
847
|
+
* explicitly (as npm `overrides`, see {@link piHarnessManifest}) makes the
|
|
848
|
+
* install reproducible whatever the resolver.
|
|
849
|
+
*
|
|
850
|
+
* The set is `pi-coding-agent`'s own `@earendil-works/*` dependencies plus
|
|
851
|
+
* theirs (`npm view @earendil-works/pi-coding-agent@<v> dependencies`,
|
|
852
|
+
* recursively): at 0.80.6 that is pi-ai, pi-agent-core (which depends on pi-ai)
|
|
853
|
+
* and pi-tui. Re-derive it on every {@link PI_HARNESS_VERSION} bump: a newer
|
|
854
|
+
* harness may add a package (0.87 adds `@earendil-works/chord`).
|
|
855
|
+
*/
|
|
856
|
+
export const PI_HARNESS_PINNED_DEPENDENCIES: Readonly<Record<string, string>> =
|
|
857
|
+
{
|
|
858
|
+
'@earendil-works/pi-agent-core': PI_HARNESS_VERSION,
|
|
859
|
+
'@earendil-works/pi-ai': PI_HARNESS_VERSION,
|
|
860
|
+
'@earendil-works/pi-tui': PI_HARNESS_VERSION,
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* The throwaway `package.json` the setup action installs the harness from: the
|
|
865
|
+
* harness at {@link PI_HARNESS_VERSION} as the only dependency, and every pi
|
|
866
|
+
* package it loads forced to its exact pin through npm `overrides`.
|
|
867
|
+
*
|
|
868
|
+
* A project-local install (not `npm install -g`) because `overrides` only apply
|
|
869
|
+
* to the ROOT project, and a global install makes each package its own root. The
|
|
870
|
+
* same file is used in `registry` and `workspace` mode: pnpm 11 also isolates
|
|
871
|
+
* every global package, so listing the pins next to the harness in
|
|
872
|
+
* `pnpm add -g` would not constrain the harness's own tree either.
|
|
873
|
+
*/
|
|
874
|
+
export function piHarnessManifest(): {
|
|
875
|
+
private: true;
|
|
876
|
+
dependencies: Record<string, string>;
|
|
877
|
+
overrides: Record<string, string>;
|
|
878
|
+
} {
|
|
879
|
+
return {
|
|
880
|
+
private: true,
|
|
881
|
+
dependencies: {[PI_HARNESS_PACKAGE]: PI_HARNESS_VERSION},
|
|
882
|
+
overrides: {...PI_HARNESS_PINNED_DEPENDENCIES},
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
|
|
753
886
|
/**
|
|
754
887
|
* The version of THIS dorfl package, read at runtime from its own
|
|
755
888
|
* `package.json` (the single source changesets bumps on release). The composite
|
|
@@ -787,29 +920,48 @@ export function dorflPackageVersion(): string {
|
|
|
787
920
|
return version;
|
|
788
921
|
}
|
|
789
922
|
|
|
923
|
+
/** Where the setup action installs the harness (under the job's temp dir). */
|
|
924
|
+
const PI_HARNESS_DIR = '$RUNNER_TEMP/dorfl-harness';
|
|
925
|
+
|
|
790
926
|
/**
|
|
791
|
-
* The install
|
|
792
|
-
*
|
|
793
|
-
*
|
|
794
|
-
* `$GITHUB_PATH
|
|
927
|
+
* The install + smoke steps for the configured harness (the `pi` CLI; `''` ⇒
|
|
928
|
+
* none). Identical in `registry` and `workspace` mode: the harness is installed
|
|
929
|
+
* with npm from {@link piHarnessManifest} into a job-local directory whose
|
|
930
|
+
* `node_modules/.bin` is added to `$GITHUB_PATH`, so every pi package it loads
|
|
931
|
+
* is pinned whatever package manager the repository uses. The smoke step then
|
|
932
|
+
* runs `pi --version`, which imports the harness's whole module graph, so a
|
|
933
|
+
* broken install fails setup with a clear message instead of failing inside a
|
|
934
|
+
* dorfl agent launch.
|
|
795
935
|
*/
|
|
796
|
-
function harnessInstallStep(
|
|
797
|
-
harness: HarnessAdapter,
|
|
798
|
-
installSource: InstallSource,
|
|
799
|
-
): string {
|
|
936
|
+
function harnessInstallStep(harness: HarnessAdapter): string {
|
|
800
937
|
if (harness === 'pi') {
|
|
801
|
-
const
|
|
802
|
-
const run =
|
|
803
|
-
installSource === 'workspace'
|
|
804
|
-
? `pnpm add -g ${spec}`
|
|
805
|
-
: `npm install -g ${spec}`;
|
|
938
|
+
const manifest = JSON.stringify(piHarnessManifest(), null, 2);
|
|
806
939
|
return `
|
|
807
940
|
# Pinned to the exact harness version this dorfl release declares
|
|
808
|
-
# (PI_HARNESS_VERSION)
|
|
809
|
-
#
|
|
941
|
+
# (PI_HARNESS_VERSION), with every pi package it loads pinned too (npm
|
|
942
|
+
# \`overrides\`; a caret range on a 0.x version floats within the minor): the
|
|
943
|
+
# job holds a provider key, so no version may move on its own. Re-run
|
|
944
|
+
# \`dorfl install-ci\` to upgrade.
|
|
810
945
|
- name: Install agent harness (pi)
|
|
811
946
|
shell: bash
|
|
812
|
-
run:
|
|
947
|
+
run: |
|
|
948
|
+
mkdir -p "${PI_HARNESS_DIR}"
|
|
949
|
+
cd "${PI_HARNESS_DIR}"
|
|
950
|
+
cat > package.json << 'HARNESS_EOF'
|
|
951
|
+
${indent(manifest, 8)}
|
|
952
|
+
HARNESS_EOF
|
|
953
|
+
npm install --no-audit --no-fund
|
|
954
|
+
echo "${PI_HARNESS_DIR}/node_modules/.bin" >> "$GITHUB_PATH"
|
|
955
|
+
|
|
956
|
+
# \`pi --version\` loads the harness's module graph, so an install whose pi
|
|
957
|
+
# packages do not load together fails HERE, not inside a dorfl agent launch.
|
|
958
|
+
- name: Check the agent harness loads (pi)
|
|
959
|
+
shell: bash
|
|
960
|
+
run: |
|
|
961
|
+
if ! pi --version; then
|
|
962
|
+
echo "::error title=dorfl-setup::The agent harness (pi) is installed but cannot start, so its pi packages do not load together. Re-run dorfl install-ci with a dorfl release that pins a working harness."
|
|
963
|
+
exit 1
|
|
964
|
+
fi`;
|
|
813
965
|
}
|
|
814
966
|
return '';
|
|
815
967
|
}
|
|
@@ -822,14 +974,11 @@ function harnessInstallStep(
|
|
|
822
974
|
* `uses: ./.github/actions/dorfl-setup`. The advance-loop seed template
|
|
823
975
|
* already references this exact action name + path (`docs/ci/README.md`).
|
|
824
976
|
*
|
|
825
|
-
* The auth step
|
|
826
|
-
* `~/.pi/agent/models.json` inline (the conservative default) AND exports each
|
|
977
|
+
* The auth step writes a generated `~/.pi/agent/models.json` inline AND exports each
|
|
827
978
|
* configured provider's API key from a same-named ACTION INPUT to `$GITHUB_ENV`,
|
|
828
979
|
* so the agent step (`pi`) can resolve the env var `models.json` references; the
|
|
829
980
|
* workflow passes the secrets ONCE via `with:` on the `uses:` line (no
|
|
830
|
-
* per-step / per-capability provider enumeration).
|
|
831
|
-
* `~/.pi/agent/auth.json` from `$PI_AUTH_JSON` + runs the OAuth-refresh script
|
|
832
|
-
* (the sharp edge). Deterministic: the same config produces byte-identical output.
|
|
981
|
+
* per-step / per-capability provider enumeration). Deterministic: the same config produces byte-identical output.
|
|
833
982
|
*/
|
|
834
983
|
export function generateSetupAction(
|
|
835
984
|
config: ResolvedCIConfig,
|
|
@@ -843,51 +992,32 @@ export function generateSetupAction(
|
|
|
843
992
|
*/
|
|
844
993
|
projectSetupSteps?: string,
|
|
845
994
|
): string {
|
|
846
|
-
|
|
847
|
-
//
|
|
848
|
-
//
|
|
849
|
-
|
|
850
|
-
const
|
|
851
|
-
config.
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
null,
|
|
873
|
-
2,
|
|
874
|
-
);
|
|
875
|
-
// Map each provider-key INPUT into the step env, then append the non-empty
|
|
876
|
-
// ones to `$GITHUB_ENV` so the agent step inherits them. Values flow via env
|
|
877
|
-
// (never interpolated into the script body), and Actions masks the secret in
|
|
878
|
-
// logs. `models.json`'s `apiKey` is the env-var NAME; this puts the value there.
|
|
879
|
-
const exportEnvLines = providerKeyNames
|
|
880
|
-
.map((name) => ` ${name}: \${{ inputs.${name} }}`)
|
|
881
|
-
.join('\n');
|
|
882
|
-
const exportRunLines = providerKeyNames
|
|
883
|
-
.map(
|
|
884
|
-
(name) =>
|
|
885
|
-
` if [ -n "\${${name}}" ]; then echo "${name}=\${${name}}" >> "$GITHUB_ENV"; fi`,
|
|
886
|
-
)
|
|
887
|
-
.join('\n');
|
|
888
|
-
const exportStep =
|
|
889
|
-
providerKeyNames.length > 0
|
|
890
|
-
? `\n
|
|
995
|
+
// models-json mode (the only mode) declares one ACTION INPUT per distinct
|
|
996
|
+
// provider key (named identically to the secret / env var) and exports it to
|
|
997
|
+
// `$GITHUB_ENV`, so every later step inherits it.
|
|
998
|
+
const providerKeyNames = providerSecretNames(config);
|
|
999
|
+
const modelsJsonStr = JSON.stringify(
|
|
1000
|
+
buildModelsJson(config.providers),
|
|
1001
|
+
null,
|
|
1002
|
+
2,
|
|
1003
|
+
);
|
|
1004
|
+
// Map each provider-key INPUT into the step env, then append the non-empty
|
|
1005
|
+
// ones to `$GITHUB_ENV` so the agent step inherits them. Values flow via env
|
|
1006
|
+
// (never interpolated into the script body), and Actions masks the secret in
|
|
1007
|
+
// logs. `models.json`'s `apiKey` is a `$NAME` env-var REFERENCE (pi resolves it at
|
|
1008
|
+
// runtime; a bare name would be a literal key); this puts the value there.
|
|
1009
|
+
const exportEnvLines = providerKeyNames
|
|
1010
|
+
.map((name) => ` ${name}: \${{ inputs.${name} }}`)
|
|
1011
|
+
.join('\n');
|
|
1012
|
+
const exportRunLines = providerKeyNames
|
|
1013
|
+
.map(
|
|
1014
|
+
(name) =>
|
|
1015
|
+
` if [ -n "\${${name}}" ]; then echo "${name}=\${${name}}" >> "$GITHUB_ENV"; fi`,
|
|
1016
|
+
)
|
|
1017
|
+
.join('\n');
|
|
1018
|
+
const exportStep =
|
|
1019
|
+
providerKeyNames.length > 0
|
|
1020
|
+
? `\n
|
|
891
1021
|
# Surface the configured provider API key(s) to the environment so the agent
|
|
892
1022
|
# step (\`pi\`) can resolve the env var \`models.json\` references. The workflow
|
|
893
1023
|
# passes the secret(s) via \`with:\` on the \`uses:\` line; we forward the
|
|
@@ -898,8 +1028,8 @@ export function generateSetupAction(
|
|
|
898
1028
|
${exportEnvLines}
|
|
899
1029
|
run: |
|
|
900
1030
|
${exportRunLines}`
|
|
901
|
-
|
|
902
|
-
|
|
1031
|
+
: '';
|
|
1032
|
+
const authStep = `\
|
|
903
1033
|
- name: Configure agent models (models.json)
|
|
904
1034
|
shell: bash
|
|
905
1035
|
run: |
|
|
@@ -907,25 +1037,21 @@ ${exportRunLines}`
|
|
|
907
1037
|
cat > ~/.pi/agent/models.json << 'MODELS_EOF'
|
|
908
1038
|
${indent(modelsJsonStr, 8)}
|
|
909
1039
|
MODELS_EOF${exportStep}`;
|
|
910
|
-
}
|
|
911
1040
|
|
|
912
|
-
const installHarness = harnessInstallStep(
|
|
913
|
-
config.harness,
|
|
914
|
-
config.installSource,
|
|
915
|
-
);
|
|
1041
|
+
const installHarness = harnessInstallStep(config.harness);
|
|
916
1042
|
|
|
917
1043
|
// The CLI-install block branches on installSource. `registry` installs the
|
|
918
1044
|
// published CLI via `npm install -g dorfl` (the default for every
|
|
919
1045
|
// consumer repo). `workspace` builds the CLI from the checked-out source and
|
|
920
1046
|
// links it onto PATH — for the self-hosting dorfl monorepo, which is
|
|
921
1047
|
// not published under that npm name. We add pnpm's global bin to $GITHUB_PATH
|
|
922
|
-
// so the linked `dorfl`
|
|
923
|
-
//
|
|
1048
|
+
// so the linked `dorfl` is on PATH in all subsequent steps (the harness adds
|
|
1049
|
+
// its own bin dir, the same way in both modes); we always rebuild because source changes per commit.
|
|
924
1050
|
let installSteps: string;
|
|
925
1051
|
if (config.installSource === 'workspace') {
|
|
926
1052
|
installSteps = `\
|
|
927
1053
|
- name: Setup pnpm
|
|
928
|
-
uses:
|
|
1054
|
+
uses: ${pinnedUses(ACTION_PINS.pnpmSetup)}
|
|
929
1055
|
|
|
930
1056
|
- name: Add pnpm global bin to PATH
|
|
931
1057
|
shell: bash
|
|
@@ -975,10 +1101,9 @@ ${indent(modelsJsonStr, 8)}
|
|
|
975
1101
|
// double-resolution. The install step below leaves `dorfl` on `$PATH` as the
|
|
976
1102
|
// bootstrap; the forward does the pinning.
|
|
977
1103
|
|
|
978
|
-
// One optional ACTION INPUT per provider key
|
|
979
|
-
//
|
|
980
|
-
//
|
|
981
|
-
// step skips an empty value. auth-json mode declares no inputs.
|
|
1104
|
+
// One optional ACTION INPUT per provider key, named identically to the
|
|
1105
|
+
// secret/env var. Optional + default '' so a workflow that does not pass it is
|
|
1106
|
+
// valid; the export step skips an empty value.
|
|
982
1107
|
const inputsBlock =
|
|
983
1108
|
providerKeyNames.length > 0
|
|
984
1109
|
? `inputs:
|
|
@@ -1021,15 +1146,16 @@ runs:
|
|
|
1021
1146
|
using: composite
|
|
1022
1147
|
steps:
|
|
1023
1148
|
${projectSetupBlock} - name: Setup Node.js
|
|
1024
|
-
uses:
|
|
1149
|
+
uses: ${pinnedUses(ACTION_PINS.setupNode)}
|
|
1025
1150
|
with:
|
|
1026
1151
|
node-version: '22'
|
|
1027
|
-
# setup-node
|
|
1028
|
-
#
|
|
1029
|
-
# before that manager
|
|
1030
|
-
# locate executable file").
|
|
1031
|
-
#
|
|
1032
|
-
#
|
|
1152
|
+
# setup-node (v5 and later) auto-enables package-manager caching. In v5
|
|
1153
|
+
# it probed whatever manager package.json's packageManager named, during
|
|
1154
|
+
# its own run and before that manager was on PATH, and crashed ("Unable
|
|
1155
|
+
# to locate executable file"). Since v6 it only fires when package.json
|
|
1156
|
+
# declares npm, and then fails if there is no npm lockfile. Either way
|
|
1157
|
+
# it is the project's cache, not dorfl's: dorfl provisions ONLY its own
|
|
1158
|
+
# runtime (toolchain boundary), so disable it explicitly.
|
|
1033
1159
|
package-manager-cache: false
|
|
1034
1160
|
|
|
1035
1161
|
- name: Configure git identity
|
|
@@ -1044,111 +1170,128 @@ ${authStep}
|
|
|
1044
1170
|
`;
|
|
1045
1171
|
}
|
|
1046
1172
|
|
|
1173
|
+
/** The local path a workflow step `uses:` for the agent-role setup action. */
|
|
1174
|
+
export const SETUP_ACTION_USES = './.github/actions/dorfl-setup';
|
|
1175
|
+
|
|
1176
|
+
/** The local path a workflow step `uses:` for the writer-role setup action. */
|
|
1177
|
+
export const WRITER_SETUP_ACTION_USES = './.github/actions/dorfl-setup-writer';
|
|
1178
|
+
|
|
1047
1179
|
/**
|
|
1048
|
-
*
|
|
1049
|
-
* `
|
|
1050
|
-
*
|
|
1051
|
-
*
|
|
1052
|
-
*
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
*
|
|
1180
|
+
* Generate the WRITER-ROLE composite setup action (`dorfl-setup-writer`, spec
|
|
1181
|
+
* `ci-agent-job-without-write-token` §9, ADR
|
|
1182
|
+
* `ci-agent-job-holds-no-write-token`; task `ci-split-generate-workflows`).
|
|
1183
|
+
*
|
|
1184
|
+
* The jobs that hold a write token and run no agent (the split item
|
|
1185
|
+
* workflow's lock and apply jobs, `reap-merged-branches`, `close-job`, and the
|
|
1186
|
+
* read-only `enumerate`) install Node and dorfl ONLY: no harness, no provider
|
|
1187
|
+
* key, no `models.json`, no project-setup hook, no install of the project's
|
|
1188
|
+
* dependencies, and no Actions cache. In `registry` mode the dorfl install runs
|
|
1189
|
+
* from `$RUNNER_TEMP`, outside the checkout, with `--ignore-scripts`, so a
|
|
1190
|
+
* repository `.npmrc` cannot redirect the registry and no install script runs.
|
|
1058
1191
|
*
|
|
1059
|
-
*
|
|
1060
|
-
*
|
|
1061
|
-
*
|
|
1062
|
-
*
|
|
1192
|
+
* In `workspace` mode (dorfl's own repository) dorfl is built from source
|
|
1193
|
+
* (decision 8): the tree of `source-ref` (default the checkout's `HEAD`) is
|
|
1194
|
+
* exported with `git archive` into `$RUNNER_TEMP` and built there with
|
|
1195
|
+
* `--ignore-scripts`. In merge mode that is code an earlier agent landed on
|
|
1196
|
+
* `main`, which is the trust merge mode already implies.
|
|
1063
1197
|
*
|
|
1064
|
-
*
|
|
1198
|
+
* It is a SIBLING action rather than a `role` input on `dorfl-setup`: the
|
|
1199
|
+
* project-setup hook is an opaque fragment spliced first into `dorfl-setup`
|
|
1200
|
+
* (ADR `install-ci-project-provisioning-native-passthrough`), and a composite
|
|
1201
|
+
* action cannot skip an opaque block of steps by input. `dorfl-setup` stays the
|
|
1202
|
+
* agent role, byte for byte.
|
|
1065
1203
|
*/
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1204
|
+
export function generateWriterSetupAction(config: ResolvedCIConfig): string {
|
|
1205
|
+
let installSteps: string;
|
|
1206
|
+
let inputsBlock = '';
|
|
1207
|
+
if (config.installSource === 'workspace') {
|
|
1208
|
+
inputsBlock = `inputs:
|
|
1209
|
+
source-ref:
|
|
1210
|
+
description: 'The commit to build dorfl from (workspace mode). Blank: the checkout HEAD.'
|
|
1211
|
+
required: false
|
|
1212
|
+
default: ''
|
|
1072
1213
|
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1214
|
+
`;
|
|
1215
|
+
installSteps = `\
|
|
1216
|
+
- name: Setup pnpm
|
|
1217
|
+
uses: ${pinnedUses(ACTION_PINS.pnpmSetup)}
|
|
1076
1218
|
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1219
|
+
- name: Add pnpm global bin to PATH
|
|
1220
|
+
shell: bash
|
|
1221
|
+
run: |
|
|
1222
|
+
pnpm setup
|
|
1223
|
+
echo "$HOME/.local/share/pnpm" >> "$GITHUB_PATH"
|
|
1081
1224
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1225
|
+
# Workspace mode (dorfl's own repository, decision 8 of ADR
|
|
1226
|
+
# ci-agent-job-holds-no-write-token): build dorfl from the tree of
|
|
1227
|
+
# \`source-ref\`, exported into $RUNNER_TEMP (outside the checkout), with
|
|
1228
|
+
# \`--ignore-scripts\`. In merge mode this is code an earlier agent landed on
|
|
1229
|
+
# main running in a write job, the trust merge mode already implies.
|
|
1230
|
+
# Only dorfl and its workspace dependencies (\`--filter 'dorfl...'\`) are
|
|
1231
|
+
# installed and built: the other workspace packages (the website) are not
|
|
1232
|
+
# needed to run dorfl, so they do not run next to the write token.
|
|
1233
|
+
- name: Build dorfl from the base commit (outside the checkout)
|
|
1234
|
+
shell: bash
|
|
1235
|
+
env:
|
|
1236
|
+
SOURCE_REF: \${{ inputs.source-ref }}
|
|
1237
|
+
run: |
|
|
1238
|
+
set -euo pipefail
|
|
1239
|
+
src="$RUNNER_TEMP/dorfl-src"
|
|
1240
|
+
rm -rf "$src"
|
|
1241
|
+
mkdir -p "$src"
|
|
1242
|
+
git archive "$(git rev-parse --verify "\${SOURCE_REF:-HEAD}^{commit}")" | tar -x -C "$src"
|
|
1243
|
+
cd "$src"
|
|
1244
|
+
pnpm install --frozen-lockfile --ignore-scripts --filter 'dorfl...'
|
|
1245
|
+
pnpm --filter 'dorfl...' build
|
|
1246
|
+
cd packages/dorfl && pnpm link --global`;
|
|
1247
|
+
} else {
|
|
1248
|
+
installSteps = `\
|
|
1249
|
+
# Pinned to the dorfl version that generated this action. Installed from
|
|
1250
|
+
# $RUNNER_TEMP (outside the checkout, so a repository .npmrc cannot redirect
|
|
1251
|
+
# the registry) with --ignore-scripts (no install script runs next to the
|
|
1252
|
+
# write token).
|
|
1253
|
+
- name: Install dorfl (outside the checkout, no install scripts)
|
|
1254
|
+
shell: bash
|
|
1255
|
+
run: |
|
|
1256
|
+
cd "$RUNNER_TEMP"
|
|
1257
|
+
npm install -g --ignore-scripts dorfl@${dorflPackageVersion()}`;
|
|
1258
|
+
}
|
|
1259
|
+
return `\
|
|
1260
|
+
name: Setup dorfl (writer role)
|
|
1261
|
+
description: Install Node.js and dorfl only, for the CI jobs that run no agent (lock, apply, reap, close, enumerate)
|
|
1262
|
+
|
|
1263
|
+
${inputsBlock}# THE WRITER ROLE (ADR ci-agent-job-holds-no-write-token): the jobs that use
|
|
1264
|
+
# this action hold a write token (or run next to one) and run NO agent and NO
|
|
1265
|
+
# repository code. So it installs Node.js and dorfl ONLY: no agent harness, no
|
|
1266
|
+
# provider API key, no models.json, no project-setup hook, no install of the
|
|
1267
|
+
# project's dependencies, and no Actions cache (an agent can write
|
|
1268
|
+
# default-branch cache entries, so a restore here would run its bytes with
|
|
1269
|
+
# write access). The agent role is ./.github/actions/dorfl-setup.
|
|
1270
|
+
runs:
|
|
1271
|
+
using: composite
|
|
1272
|
+
steps:
|
|
1273
|
+
- name: Setup Node.js
|
|
1274
|
+
uses: ${pinnedUses(ACTION_PINS.setupNode)}
|
|
1275
|
+
with:
|
|
1276
|
+
node-version: '22'
|
|
1277
|
+
# No cache in the writer role (see above).
|
|
1278
|
+
package-manager-cache: false
|
|
1086
1279
|
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
const response = await fetch(ANTHROPIC_TOKEN_URL, {
|
|
1094
|
-
method: "POST",
|
|
1095
|
-
headers: {
|
|
1096
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
1097
|
-
Accept: "application/json",
|
|
1098
|
-
},
|
|
1099
|
-
body: new URLSearchParams({
|
|
1100
|
-
grant_type: "refresh_token",
|
|
1101
|
-
client_id: ANTHROPIC_CLIENT_ID,
|
|
1102
|
-
refresh_token: cred.refresh,
|
|
1103
|
-
}).toString(),
|
|
1104
|
-
signal: AbortSignal.timeout(30_000),
|
|
1105
|
-
});
|
|
1106
|
-
|
|
1107
|
-
const data = await response.json();
|
|
1108
|
-
|
|
1109
|
-
if (!response.ok) {
|
|
1110
|
-
console.error("Refresh failed:", response.status, JSON.stringify(data));
|
|
1111
|
-
process.exit(1);
|
|
1112
|
-
}
|
|
1280
|
+
- name: Configure git identity
|
|
1281
|
+
shell: bash
|
|
1282
|
+
run: |
|
|
1283
|
+
git config user.name "dorfl[bot]"
|
|
1284
|
+
git config user.email "dorfl[bot]@users.noreply.github.com"
|
|
1113
1285
|
|
|
1114
|
-
|
|
1115
|
-
type: "oauth",
|
|
1116
|
-
refresh: data.refresh_token,
|
|
1117
|
-
access: data.access_token,
|
|
1118
|
-
expires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,
|
|
1119
|
-
};
|
|
1120
|
-
|
|
1121
|
-
writeFileSync(authPath, JSON.stringify(auth, null, 2));
|
|
1122
|
-
chmodSync(authPath, 0o600);
|
|
1123
|
-
console.log(
|
|
1124
|
-
"Token refreshed, new expiry:",
|
|
1125
|
-
new Date(auth.anthropic.expires).toISOString()
|
|
1126
|
-
);
|
|
1127
|
-
|
|
1128
|
-
// Update the GitHub secret so the next run has the latest refresh token.
|
|
1129
|
-
const repo = process.env.GITHUB_REPOSITORY;
|
|
1130
|
-
const token = process.env.GH_PAT;
|
|
1131
|
-
if (repo && token) {
|
|
1132
|
-
try {
|
|
1133
|
-
execSync(\`gh secret set PI_AUTH_JSON --repo "\${repo}"\`, {
|
|
1134
|
-
input: JSON.stringify(auth),
|
|
1135
|
-
env: { ...process.env, GH_TOKEN: token },
|
|
1136
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1137
|
-
});
|
|
1138
|
-
console.log("PI_AUTH_JSON secret updated");
|
|
1139
|
-
} catch (err) {
|
|
1140
|
-
console.warn("Failed to update secret (non-fatal):", err.stderr?.toString() || err.message);
|
|
1141
|
-
}
|
|
1142
|
-
} else {
|
|
1143
|
-
console.log("Skipping secret update (no GH_PAT or GITHUB_REPOSITORY)");
|
|
1144
|
-
}
|
|
1286
|
+
${installSteps}
|
|
1145
1287
|
`;
|
|
1288
|
+
}
|
|
1146
1289
|
|
|
1147
1290
|
// ─── the --fake snapshot mechanism + artifact assembly ───────────────────────
|
|
1148
1291
|
|
|
1149
1292
|
/**
|
|
1150
|
-
* The setup ARTIFACTS this task generates: the composite setup
|
|
1151
|
-
*
|
|
1293
|
+
* The setup ARTIFACTS this task generates: the composite setup actions, plus
|
|
1294
|
+
* any selected capabilities'
|
|
1152
1295
|
* workflow files. This task emits NO capability workflow itself (those are the
|
|
1153
1296
|
* sibling tasks); `capabilities` defaults to none.
|
|
1154
1297
|
*/
|
|
@@ -1168,13 +1311,11 @@ export function buildSetupArtifacts(
|
|
|
1168
1311
|
path: join('actions', 'dorfl-setup', 'action.yml'),
|
|
1169
1312
|
content: generateSetupAction(config, options?.projectSetupSteps),
|
|
1170
1313
|
},
|
|
1314
|
+
{
|
|
1315
|
+
path: join('actions', 'dorfl-setup-writer', 'action.yml'),
|
|
1316
|
+
content: generateWriterSetupAction(config),
|
|
1317
|
+
},
|
|
1171
1318
|
];
|
|
1172
|
-
if (config.authMode === 'auth-json') {
|
|
1173
|
-
files.push({
|
|
1174
|
-
path: join('scripts', 'refresh-oauth-token.mjs'),
|
|
1175
|
-
content: REFRESH_OAUTH_SCRIPT,
|
|
1176
|
-
});
|
|
1177
|
-
}
|
|
1178
1319
|
for (const cap of capabilities) {
|
|
1179
1320
|
files.push(...cap.emit(config));
|
|
1180
1321
|
}
|
|
@@ -1195,6 +1336,13 @@ export function outputBaseName(fake: boolean): string {
|
|
|
1195
1336
|
* Write the artifacts under `<workDir>/<base>/` (base = `.fake` when `fake`, else
|
|
1196
1337
|
* `.github`). Returns the repo-relative paths actually written. Pure filesystem
|
|
1197
1338
|
* I/O — no secrets, no git. Creates parent dirs as needed.
|
|
1339
|
+
*
|
|
1340
|
+
* PIN PRESERVATION. Before writing a YAML artifact, any `uses:` of an action the
|
|
1341
|
+
* repository's EXISTING `.github/<path>` already pins to a full commit SHA keeps
|
|
1342
|
+
* that reference and its trailing comment (see {@link preserveExistingPins}),
|
|
1343
|
+
* so a Dependabot bump survives regeneration and a pin is never swapped for a
|
|
1344
|
+
* tag. The existing file is always read from `.github/`, also in `--fake` mode,
|
|
1345
|
+
* so the `.fake/` preview shows what a real run would write.
|
|
1198
1346
|
*/
|
|
1199
1347
|
export function writeArtifacts(options: {
|
|
1200
1348
|
workDir: string;
|
|
@@ -1205,8 +1353,15 @@ export function writeArtifacts(options: {
|
|
|
1205
1353
|
const written: string[] = [];
|
|
1206
1354
|
for (const file of options.files) {
|
|
1207
1355
|
const abs = join(options.workDir, base, file.path);
|
|
1356
|
+
let content = file.content;
|
|
1357
|
+
if (/\.ya?ml$/.test(file.path)) {
|
|
1358
|
+
const current = join(options.workDir, outputBaseName(false), file.path);
|
|
1359
|
+
if (existsSync(current)) {
|
|
1360
|
+
content = preserveExistingPins(content, readFileSync(current, 'utf8'));
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1208
1363
|
mkdirSync(dirname(abs), {recursive: true});
|
|
1209
|
-
writeFileSync(abs,
|
|
1364
|
+
writeFileSync(abs, content, 'utf8');
|
|
1210
1365
|
written.push(relative(options.workDir, abs));
|
|
1211
1366
|
}
|
|
1212
1367
|
return written;
|