code-foundry 1.17.0 → 1.19.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.
@@ -0,0 +1,144 @@
1
+ name: Qualified Foundry Publication
2
+
3
+ on:
4
+ workflow_call:
5
+ inputs:
6
+ tag:
7
+ description: Published immutable release tag at the caller commit.
8
+ required: true
9
+ type: string
10
+ asset:
11
+ description: Exact pre-attached Code Foundry npm tarball name.
12
+ required: true
13
+ type: string
14
+ environment:
15
+ description: Protected GitHub environment for npm publication.
16
+ required: false
17
+ default: npm
18
+ type: string
19
+ secrets:
20
+ NPM_TOKEN:
21
+ required: false
22
+
23
+ permissions:
24
+ attestations: read
25
+ contents: read
26
+
27
+ jobs:
28
+ qualify:
29
+ name: Qualify Node ${{ matrix.node }}
30
+ if: vars.CI_BILLING_PAUSED != 'true' && github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
31
+ runs-on: ubuntu-24.04
32
+ timeout-minutes: 15
33
+ strategy:
34
+ fail-fast: false
35
+ matrix:
36
+ node: ['20', '22', '24']
37
+ steps:
38
+ - name: Checkout trusted qualification runtime
39
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
40
+ with:
41
+ persist-credentials: false
42
+ ref: ${{ github.sha }}
43
+ - name: Setup Node
44
+ uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
45
+ with:
46
+ node-version: ${{ matrix.node }}
47
+ - name: Install pinned Actionlint
48
+ run: go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.12
49
+ - name: Download and cryptographically verify the release archive
50
+ env:
51
+ GH_TOKEN: ${{ github.token }}
52
+ RELEASE_TAG: ${{ inputs.tag }}
53
+ RELEASE_ASSET: ${{ inputs.asset }}
54
+ run: |
55
+ set -euo pipefail
56
+ node src/commands/qualified-publication.mjs download \
57
+ "$GITHUB_REPOSITORY" "$RELEASE_TAG" "$GITHUB_SHA" "$RELEASE_ASSET" \
58
+ "$RUNNER_TEMP/candidate" > "$RUNNER_TEMP/download.json"
59
+ - name: Qualify those exact bytes
60
+ env:
61
+ RELEASE_ASSET: ${{ inputs.asset }}
62
+ run: |
63
+ set -euo pipefail
64
+ node src/lib/consumer-qualification.mjs \
65
+ "$RUNNER_TEMP/candidate/$RELEASE_ASSET" "$GITHUB_SHA" \
66
+ "$RUNNER_TEMP/report.json" "$(go env GOPATH)/bin/actionlint"
67
+ - name: Retain this attempt's qualification report
68
+ if: always()
69
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
70
+ with:
71
+ name: qualified-${{ github.run_attempt }}-node-${{ matrix.node }}
72
+ path: ${{ runner.temp }}/report.json
73
+ if-no-files-found: error
74
+ retention-days: 14
75
+
76
+ publish:
77
+ name: Publish qualified archive
78
+ needs: qualify
79
+ if: vars.CI_BILLING_PAUSED != 'true' && github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
80
+ runs-on: ubuntu-24.04
81
+ timeout-minutes: 15
82
+ environment: ${{ inputs.environment }}
83
+ concurrency:
84
+ group: qualified-foundry-npm-${{ github.repository }}-${{ inputs.tag }}
85
+ cancel-in-progress: false
86
+ permissions:
87
+ actions: read
88
+ attestations: read
89
+ contents: read
90
+ id-token: write
91
+ env:
92
+ HAS_NPM_TOKEN: ${{ secrets.NPM_TOKEN != '' }}
93
+ RELEASE_TAG: ${{ inputs.tag }}
94
+ RELEASE_ASSET: ${{ inputs.asset }}
95
+ steps:
96
+ - name: Checkout trusted publication runtime
97
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
98
+ with:
99
+ persist-credentials: false
100
+ ref: ${{ github.sha }}
101
+ - name: Setup Node for trusted publishing
102
+ if: env.HAS_NPM_TOKEN != 'true'
103
+ uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
104
+ with:
105
+ node-version: '24'
106
+ - name: Setup Node for token publishing
107
+ if: env.HAS_NPM_TOKEN == 'true'
108
+ uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
109
+ with:
110
+ node-version: '24'
111
+ registry-url: https://registry.npmjs.org
112
+ - name: Retrieve only this run attempt's evidence
113
+ env:
114
+ GH_TOKEN: ${{ github.token }}
115
+ run: |
116
+ set -euo pipefail
117
+ for node in 20 22 24; do
118
+ gh run download "$GITHUB_RUN_ID" --repo "$GITHUB_REPOSITORY" \
119
+ --name "qualified-$GITHUB_RUN_ATTEMPT-node-$node" \
120
+ --dir "$RUNNER_TEMP/reports/$node"
121
+ done
122
+ node src/commands/qualified-publication.mjs download \
123
+ "$GITHUB_REPOSITORY" "$RELEASE_TAG" "$GITHUB_SHA" "$RELEASE_ASSET" \
124
+ "$RUNNER_TEMP/candidate" > "$RUNNER_TEMP/download.json"
125
+ - name: Reverify and publish the tested tarball without lifecycle hooks
126
+ env:
127
+ GH_TOKEN: ${{ github.token }}
128
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
129
+ run: |
130
+ set -euo pipefail
131
+ node src/commands/qualified-publication.mjs publish \
132
+ "$GITHUB_REPOSITORY" "$RELEASE_TAG" "$GITHUB_SHA" "$RELEASE_ASSET" \
133
+ "$RUNNER_TEMP/candidate" \
134
+ "$RUNNER_TEMP/reports/20/report.json" \
135
+ "$RUNNER_TEMP/reports/22/report.json" \
136
+ "$RUNNER_TEMP/reports/24/report.json" > "$RUNNER_TEMP/publication.json"
137
+ - name: Retain publication identity
138
+ if: always()
139
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
140
+ with:
141
+ name: qualified-publication-${{ github.run_attempt }}
142
+ path: ${{ runner.temp }}/publication.json
143
+ if-no-files-found: warn
144
+ retention-days: 14
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.19.0](https://github.com/0xPlayerOne/code-foundry/compare/v1.18.0...v1.19.0) (2026-09-09)
4
+
5
+
6
+ ### Features
7
+
8
+ * **fleet:** gate upgrades on verified release qualification ([#548](https://github.com/0xPlayerOne/code-foundry/issues/548)) ([3247cc6](https://github.com/0xPlayerOne/code-foundry/commit/3247cc6f2295c65c42bb43e4ec8f87a08f3dd02d))
9
+
10
+ ## [1.18.0](https://github.com/0xPlayerOne/code-foundry/compare/v1.17.0...v1.18.0) (2026-09-09)
11
+
12
+
13
+ ### Features
14
+
15
+ * **releases:** stage qualified assets and publish verified identical archives ([#547](https://github.com/0xPlayerOne/code-foundry/issues/547)) ([dffcb83](https://github.com/0xPlayerOne/code-foundry/commit/dffcb834c8ee8c5c2bcba606350276e6a7de58d0))
16
+
3
17
  ## [1.17.0](https://github.com/0xPlayerOne/code-foundry/compare/v1.16.0...v1.17.0) (2026-09-09)
4
18
 
5
19
 
@@ -15,21 +15,19 @@ belong to the run that produced them, not the source tree.
15
15
  | Format, lint, type-check, and build | 15 s | Bound the local CI feedback loop |
16
16
  | Runtime dependencies | 0 | Keep the installed CLI dependency-free |
17
17
  | Development dependencies | 4 | Prevent unreviewed toolchain growth |
18
- | Packed artifact | 240 kB | Bound registry transfer and install cost |
19
- | Unpacked artifact | 900 kB | Bound installed footprint |
18
+ | Packed artifact | 250 kB | Bound registry transfer and install cost |
19
+ | Unpacked artifact | 930 kB | Bound installed footprint |
20
20
  | Packed files | 110 | Detect accidental release contents |
21
21
 
22
22
  The performance workflow disables build-cache reads and writes for this task.
23
23
  That makes timing comparisons independent of a warm protected-branch cache and
24
24
  prevents benchmark code from populating shared cache entries.
25
25
 
26
- The consumer qualification workflow adds the installed-package harness and
27
- workflow contract to the distributable. The merged candidate measured 221,495
28
- packed bytes, 853,573 unpacked bytes, and 100 files on Node 24.18.0; the updated
29
- budgets leave a small margin while continuing to bound package growth. The
30
- product-quality profiles and consumer qualification workflow measured 232,650
31
- packed bytes, 890,418 unpacked bytes, and 103 files on Node 24.18.0, so the
32
- limits remain intentionally bounded while covering both opt-in feature sets.
26
+ The merged candidate includes the release-integrity verifier, fleet eligibility,
27
+ consumer qualification harness, product-quality profiles, and qualified
28
+ publication workflow. It measured 243,466 packed bytes, 930,389 unpacked bytes,
29
+ and 109 files on Node 24.18.0; the 250 kB, 945 kB, and 110-file limits retain a
30
+ small margin while continuing to bound package growth.
33
31
 
34
32
  ## v1.6.1 baseline
35
33
 
@@ -63,4 +61,6 @@ pull request, run the complete validation gate, and merge only when the new
63
61
  result artifact is available. A Release Please PR then carries the change into
64
62
  the next version. Never raise a budget solely to clear CI; include before/after
65
63
  measurements and the expected effect on local feedback, hosted runner time,
66
- package transfer, or installed footprint.
64
+ package transfer, or installed footprint. The current budget covers the measured
65
+ combined release-integrity, fleet, consumer-qualification, and product-quality
66
+ package footprint described above.
@@ -0,0 +1,80 @@
1
+ # Fleet Release Eligibility
2
+
3
+ Require a verified, qualified runtime before any fleet upgrade mutates repositories.
4
+
5
+ **Dependency:** Installed release-integrity verifier (#537).
6
+ **Activation:** Consumer-owned `.code-foundry-release-policy.json` at the fleet root.
7
+
8
+ The public `upgradeFleet` entrypoint now applies an optional release eligibility
9
+ guard before delegating to the unchanged legacy/manifest rollout implementation.
10
+ The implementation was moved verbatim to `fleet-core.mjs`; discovery exports and
11
+ existing callers retain their interface. No policy file means existing behavior.
12
+ A malformed or symlinked policy is an error, not an opt-out. Dry runs use the same
13
+ guard, and `--force` cannot bypass source identity or qualification.
14
+
15
+ ```json
16
+ {
17
+ "schema_version": 1,
18
+ "repository": "owner/code-foundry",
19
+ "workflow": ".github/workflows/release_self-ci.yml",
20
+ "branch": "main",
21
+ "required_jobs": [
22
+ "Qualify consumers / Node 20",
23
+ "Qualify consumers / Node 22",
24
+ "Qualify consumers / Node 24"
25
+ ]
26
+ }
27
+ ```
28
+
29
+ This is a shape example, not a completed fleet policy. Populate canonical repository
30
+ identity, actual workflow path, and exact job names from the qualified release
31
+ caller's Actions API results. Include the publication job when successful registry
32
+ publication is required before adoption. Require every relevant job: a workflow's
33
+ aggregate success alone can conceal skipped jobs. The qualification gate in #544
34
+ and verified publisher in #547 provide the producer side of this policy.
35
+
36
+ Keep the policy alongside the **consumer workspace's** fleet inventory. Do not put
37
+ private repository names, local layouts, or deployment credentials into this
38
+ reusable baseline. Commit policy changes for review; the guard does not secretly
39
+ change branch protection, environments, rollout cohorts, or required capabilities.
40
+
41
+ ## What is verified
42
+
43
+ The proposed runtime must be a clean Git source checkout. Its exact HEAD must
44
+ match the cryptographically verified immutable release tag. An installed npm
45
+ package without Git metadata cannot substitute for a verifiable source checkout;
46
+ use the existing `--source` option with the checked-out release. The verifier comes
47
+ from the trusted running Foundry installation, not the unverified candidate.
48
+
49
+ The guard resolves the configured workflow's identity, retrieves every page of
50
+ runs for the exact source, and selects the latest matching protected-branch push
51
+ or manual run. It rejects pending/failed/cancelled runs, fork provenance, disabled
52
+ workflows, missing/ambiguous/skipped jobs, and incomplete pagination. An older
53
+ success does not override a newer failed run. Job evidence comes from the precise
54
+ run attempt; that attempt is rechecked before proceeding. Source HEAD, cleanliness,
55
+ and the policy's bytes are rechecked before the upgrade callback executes.
56
+
57
+ Successful eligibility emits JSON to stderr with source SHA, policy digest,
58
+ workflow/run/attempt identifiers, and required job names. It does not write an
59
+ approval into the source or silently make PRs ready. Existing resumable rollouts,
60
+ canary validation, draft creation, and provenance markers stay in the original
61
+ fleet engine. The guard is policy enforcement in the supported public entrypoint,
62
+ not a sandbox against someone intentionally importing private implementation files
63
+ or using Git directly. Normal credentials still determine what remote mutations
64
+ are possible after eligibility passes.
65
+
66
+ ## Activation and validation
67
+
68
+ First release and validate the qualification/verification producer and confirm
69
+ real workflow/job identities. Then opt a consumer workspace into this policy and
70
+ exercise `fleet upgrade --dry-run --root <fleet-root> --source <clean-release-checkout>`.
71
+ When `--source` is omitted, the installed package is used and an enabled policy
72
+ will reject it unless that installation is itself a clean Git checkout. Older mutable
73
+ releases or unavailable permissions should fail; do not weaken the policy just to
74
+ make an old release eligible. No real fleet inventory is fabricated by this PR.
75
+
76
+ The focused suite verifies guard ordering and failure propagation with GitHub/CLI
77
+ fixtures. Live authenticated verification, exact production job naming, and the
78
+ full existing fleet-engine suite must pass before activation. The original fleet
79
+ engine's Git blob is retained byte-for-byte; the source move still needs full
80
+ repository import/type-check/packaging validation in CI.
@@ -55,11 +55,12 @@ Adopt those requirements in consumer repositories before requiring them here.
55
55
 
56
56
  ```sh
57
57
  code-foundry fleet status --root /path/to/fleet
58
- code-foundry fleet upgrade --root /path/to/fleet --dry-run
59
- code-foundry fleet upgrade --root /path/to/fleet --create-pr
58
+ code-foundry fleet upgrade --root /path/to/fleet --source /path/to/release-checkout --dry-run
59
+ code-foundry fleet upgrade --root /path/to/fleet --source /path/to/release-checkout --create-pr
60
60
  ```
61
61
 
62
- Run from the intended released Code Foundry installation/checkout. The existing
62
+ Use `--source` to provide the clean Code Foundry release checkout used for the
63
+ upgrade; when omitted, the installed Code Foundry package is used. The existing
63
64
  source-version guard still rejects mismatched `--version` requests. Manifest mode
64
65
  requires `--create-pr` or `--dry-run` and never syncs original checkouts in place.
65
66
  `--force` does not bypass dirty-tree safety in manifest mode. Dry-run reports
@@ -0,0 +1,111 @@
1
+ # Qualified Publication
2
+
3
+ Publish only the same immutable Code Foundry archive that passed consumer qualification.
4
+
5
+ **Dependencies:** Consumer qualification (#544) and release integrity (#537).
6
+ **Activation:** Opt-in replacement publisher; no existing publisher is silently changed.
7
+
8
+ The reusable `qualified-foundry-publish.yml` downloads an explicitly named npm
9
+ archive from an already-published immutable release, verifies the release and
10
+ asset attestations against the caller's exact commit, qualifies that same archive
11
+ on Node 20/22/24, and only then admits a protected npm publication job. The final
12
+ job downloads only reports from its own workflow run **and run attempt**, rechecks
13
+ all required fixtures and archive/source identities, repeats cryptographic asset
14
+ verification, validates the package name/version, and publishes the tarball with
15
+ lifecycle scripts disabled. It never publishes a directory or rebuilds the package.
16
+
17
+ The publishing job uses a GitHub environment, serializes publication for a tag,
18
+ and has no dependency installation/build step. Qualification has no npm credential.
19
+ Normal npm trusted publishing is preferred; an explicit optional token supports
20
+ existing consumers. Configure the trusted publisher for the **actual caller and
21
+ reusable-workflow relationship** before enabling this route. Existing version
22
+ publication is not overwritten: retrying an already-published version fails rather
23
+ than treating a registry conflict or network error as proof of identity.
24
+
25
+ ## Release producer contract
26
+
27
+ Build and test a package once, attach its tarball to a **draft** release, then
28
+ publish that release with immutability enabled. The archive must contain the
29
+ Code Foundry CLI/templates; this is intentionally not a generic package harness.
30
+ The candidate's `package.json` name must be `code-foundry` and its version must
31
+ match the explicit tag. All qualification modules must exist in the caller commit.
32
+ A recent GitHub CLI with `release verify` and `release verify-asset` is required;
33
+ missing support or authentication errors fail closed.
34
+
35
+ This workflow blocks npm publication, not a GitHub Release that was already
36
+ published. It does **not** add assets after an immutable release is published.
37
+ The `stage` command implements the draft-asset producer: it verifies qualification,
38
+ checks immutability without changing settings, resolves the existing tag to the
39
+ qualified commit, uploads the archive and digest-bound qualification receipt,
40
+ rechecks uploaded digests, then publishes and verifies the release. It requires
41
+ an existing draft and existing tag; it never creates/moves tags or overwrites
42
+ conflicting assets. Settings permission failures block writes. Matching existing
43
+ assets can resume staging; conflicting assets require manual reconciliation.
44
+ The pre-release gate in #544 supplies the required matrix reports.
45
+ The legacy Release Please workflow creates releases without
46
+ this archive staging step. Wire the new `stage` command into a draft-producing
47
+ caller instead of pointing the old direct-release caller at the new publisher:
48
+
49
+ ```sh
50
+ node src/commands/qualified-publication.mjs stage \
51
+ "$GITHUB_REPOSITORY" "$TAG" "$SOURCE_SHA" "$ASSET" "$CANDIDATE_DIRECTORY" \
52
+ "$REPORT_NODE_20" "$REPORT_NODE_22" "$REPORT_NODE_24"
53
+ ```
54
+
55
+ The stage command needs a credential with immutable-setting read, release
56
+ write, and attestation-read access; a normal Actions token may lack the
57
+ administration-read permission.
58
+ No elevated credential is installed or requested automatically. The producer must
59
+ also prevent concurrent tag mutation (for example with protected release-tag
60
+ rules and a single tag-scoped producer); GitHub does not offer an atomic
61
+ "publish this draft only if the tag still resolves to SHA" operation. The final
62
+ verification blocks npm if that invariant is violated, but cannot undo an
63
+ already-published immutable release.
64
+
65
+ Disable the old npm path before
66
+ activating the replacement to prevent racing publishers. None of those production
67
+ settings or existing release workflows are changed in this PR.
68
+
69
+ Example caller job after its release producer (illustrative job IDs):
70
+
71
+ ```yaml
72
+ permissions:
73
+ actions: read
74
+ attestations: read
75
+ contents: read
76
+ id-token: write
77
+ jobs:
78
+ publish:
79
+ needs: release-producer
80
+ uses: ./.github/workflows/qualified-foundry-publish.yml
81
+ with:
82
+ tag: ${{ needs.release-producer.outputs.tag }}
83
+ asset: ${{ needs.release-producer.outputs.npm-asset }}
84
+ environment: npm
85
+ secrets:
86
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
87
+ ```
88
+
89
+ Only main-branch `push` and `workflow_dispatch` callers are admitted. Pull-request
90
+ events (including fork PRs), release-event shortcuts, and billing-paused runs
91
+ cannot publish through this workflow. The event guard does not itself verify
92
+ branch protection or prohibit a fork's independent main-branch workflow; configure
93
+ branch/environment protections and registry publisher identity separately.
94
+ The tag must resolve to `github.sha`, not a caller-selected old commit. To retry
95
+ an older release after main moves, use a separately reviewed recovery procedure;
96
+ do not weaken the identity gate ad hoc.
97
+
98
+ ## Local policy tests and trust
99
+
100
+ `node --test test/qualified-publication.test.mjs` uses CLI/verifier fixtures to
101
+ exercise missing matrix members, skipped checks, absent Actionlint, changed
102
+ archives, invalid asset names, wrong package identity, and prevention of npm
103
+ execution before verification. These tests do not establish live GitHub signing,
104
+ OIDC permissions, registry publication, or runner tool availability.
105
+
106
+ Qualification reports are not standalone signatures: they are trusted only after
107
+ selection from this workflow's successful jobs in the same run attempt. Supplying
108
+ arbitrary local JSON to the library is not a security boundary. The reusable
109
+ workflow and its caller must be reviewed/trusted and protected; repository-owned
110
+ code executes with the permissions of its job. No credentials or production
111
+ resources were configured by adding this feature.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-foundry",
3
- "version": "1.17.0",
3
+ "version": "1.19.0",
4
4
  "description": "A fast, language-aware repository factory for agent-ready workflows, testing, security, and release automation.",
5
5
  "homepage": "https://github.com/0xPlayerOne/code-foundry#readme",
6
6
  "bugs": {
package/src/cli.mjs CHANGED
@@ -7,7 +7,7 @@ import { fileURLToPath } from 'node:url'
7
7
 
8
8
  const packageRoot = resolve(fileURLToPath(new URL('..', import.meta.url)))
9
9
 
10
- /** @typedef {{ target: string, root: string, dryRun: boolean, force: boolean, github: boolean, createPr: boolean, exclude: string[], base: string, head: string, tag: string, workflow: string, mode: string, version: string, repository: string, expectedSha: string, assets: string[], rootProvided: boolean, releaseSubcommand?: string, fleetSubcommand?: string, integritySubcommand?: 'settings'|'release'|'manifest', ciSubcommand?: 'pause'|'resume'|'status' }} Options */
10
+ /** @typedef {{ target: string, root: string, source: string, dryRun: boolean, force: boolean, github: boolean, createPr: boolean, exclude: string[], base: string, head: string, tag: string, workflow: string, mode: string, version: string, versionExplicit: boolean, repository: string, expectedSha: string, assets: string[], rootProvided: boolean, releaseSubcommand?: string, fleetSubcommand?: string, integritySubcommand?: 'settings'|'release'|'manifest', ciSubcommand?: 'pause'|'resume'|'status' }} Options */
11
11
  /** @typedef {{ command: string, options: Options }} ParsedArgs */
12
12
 
13
13
  const usage = `code-foundry — initialize and maintain agent-ready repositories
@@ -25,7 +25,7 @@ Usage:
25
25
  npx code-foundry release-integrity release --repo OWNER/REPO --tag TAG
26
26
  npx code-foundry release-integrity manifest --asset PATH [--root PATH]
27
27
  npx code-foundry fleet status [--root PATH]
28
- npx code-foundry fleet upgrade [--root PATH] [--dry-run] [--create-pr]
28
+ npx code-foundry fleet upgrade [--root PATH] [--source PATH] [--dry-run] [--create-pr]
29
29
 
30
30
  The repository configuration lives in .github/code-foundry.yml.
31
31
  init detects the repository, creates that file, and renders the baseline.
@@ -45,6 +45,7 @@ Options:
45
45
  --expected-sha SHA Expected source commit for release-integrity verification
46
46
  --asset PATH Selected release-integrity artifact (repeatable)
47
47
  --root PATH Fleet root containing repositories (default: current directory)
48
+ --source PATH Clean Code Foundry release checkout used for fleet upgrades
48
49
  --create-pr Create isolated upgrade branches and pull requests
49
50
  --version TAG Runtime tag to report in fleet upgrade branches
50
51
  --exclude NAME Skip a repository path or owner/name in fleet operations
@@ -66,6 +67,7 @@ function parseArgs(argv) {
66
67
  const options = {
67
68
  target: process.cwd(),
68
69
  root: process.cwd(),
70
+ source: packageRoot,
69
71
  dryRun: false,
70
72
  force: false,
71
73
  github: false,
@@ -77,6 +79,7 @@ function parseArgs(argv) {
77
79
  workflow: '',
78
80
  mode: 'auto',
79
81
  version: `v${readPackageVersion(packageRoot)}`,
82
+ versionExplicit: false,
80
83
  repository: '',
81
84
  expectedSha: '',
82
85
  assets: [],
@@ -146,9 +149,17 @@ function parseArgs(argv) {
146
149
  else if (arg === '--root') {
147
150
  options.root = argv.shift() ?? fail('--root requires a path')
148
151
  options.rootProvided = true
152
+ } else if (arg === '--source') {
153
+ if (command !== 'fleet' || options.fleetSubcommand !== 'upgrade')
154
+ fail('--source is only supported by fleet upgrade')
155
+ const value = argv.shift()
156
+ if (!value || value.startsWith('-')) fail('--source requires a path')
157
+ options.source = value
149
158
  } else if (arg === '--create-pr') options.createPr = true
150
- else if (arg === '--version') options.version = argv.shift() ?? fail('--version requires a tag')
151
- else if (arg === '--exclude')
159
+ else if (arg === '--version') {
160
+ options.version = argv.shift() ?? fail('--version requires a tag')
161
+ options.versionExplicit = true
162
+ } else if (arg === '--exclude')
152
163
  options.exclude.push(argv.shift() ?? fail('--exclude requires a name'))
153
164
  else fail(`unknown option: ${arg}; run --help for the supported options`)
154
165
  }
@@ -241,11 +252,13 @@ async function main() {
241
252
  if (options.fleetSubcommand === 'status')
242
253
  console.log(JSON.stringify(discoverRepositories(root), null, 2))
243
254
  else
244
- upgradeFleet(root, packageRoot, {
255
+ upgradeFleet(root, resolve(options.source), {
245
256
  createPr: options.createPr,
246
257
  dryRun: options.dryRun,
247
258
  force: options.force,
248
- version: options.version,
259
+ version: options.versionExplicit
260
+ ? options.version
261
+ : `v${readPackageVersion(resolve(options.source))}`,
249
262
  exclude: options.exclude,
250
263
  })
251
264
  } catch (error) {