code-foundry 0.37.2 → 0.37.4
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/.github/workflows/codeql.yml +2 -2
- package/.github/workflows/release-pr.yml +1 -1
- package/.github/workflows/release.yml +10 -40
- package/.github/workflows/security.yml +1 -1
- package/CHANGELOG.md +14 -0
- package/package.json +1 -1
- package/src/lib/release-policy.mjs +60 -3
- package/src/runtime.mjs +1 -0
|
@@ -402,7 +402,7 @@ jobs:
|
|
|
402
402
|
needs.detect.outputs.enabled == 'true' &&
|
|
403
403
|
needs.detect.outputs.rust_available == 'true' &&
|
|
404
404
|
needs.detect.outputs.rust_changed == 'true'
|
|
405
|
-
uses: github/codeql-action/init@
|
|
405
|
+
uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4
|
|
406
406
|
with:
|
|
407
407
|
languages: rust
|
|
408
408
|
build-mode: ${{ needs.detect.outputs.rust_build_mode }}
|
|
@@ -413,7 +413,7 @@ jobs:
|
|
|
413
413
|
needs.detect.outputs.enabled == 'true' &&
|
|
414
414
|
needs.detect.outputs.rust_available == 'true' &&
|
|
415
415
|
needs.detect.outputs.rust_changed == 'true'
|
|
416
|
-
uses: github/codeql-action/analyze@
|
|
416
|
+
uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4
|
|
417
417
|
with:
|
|
418
418
|
category: /language:rust/${{ steps.scope.outputs.scope_id }}
|
|
419
419
|
upload-database: false
|
|
@@ -31,7 +31,7 @@ jobs:
|
|
|
31
31
|
cancel-in-progress: true
|
|
32
32
|
steps:
|
|
33
33
|
- name: Checkout
|
|
34
|
-
uses: actions/checkout@
|
|
34
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
35
35
|
with:
|
|
36
36
|
persist-credentials: false
|
|
37
37
|
fetch-depth: 0
|
|
@@ -245,44 +245,14 @@ jobs:
|
|
|
245
245
|
fi
|
|
246
246
|
node "$RUNNER_TEMP/code-foundry/src/cli.mjs" release validate-prs
|
|
247
247
|
for pr in "${release_prs[@]}"; do
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
--jq 'length' 2>/dev/null); then
|
|
257
|
-
if [ "$required_count" -gt 0 ]; then
|
|
258
|
-
checks_seen=true
|
|
259
|
-
break
|
|
260
|
-
fi
|
|
261
|
-
fi
|
|
262
|
-
sleep 5
|
|
263
|
-
done
|
|
264
|
-
if [ "$checks_seen" != true ]; then
|
|
265
|
-
echo "Required checks were not registered for release PR #$pr within the readiness window." >&2
|
|
266
|
-
exit 1
|
|
267
|
-
fi
|
|
268
|
-
gh pr checks "$pr" \
|
|
269
|
-
--repo "$GITHUB_REPOSITORY" \
|
|
270
|
-
--required \
|
|
271
|
-
--watch \
|
|
272
|
-
--fail-fast \
|
|
273
|
-
--interval 10
|
|
274
|
-
# gh pr checks --watch only waits for checks registered when the
|
|
275
|
-
# watch starts; a ruleset may register additional required checks
|
|
276
|
-
# afterwards (observed: "Validation / Gate" appearing after
|
|
277
|
-
# "Tauri 2 / Linux" had passed), which leaves the PR blocked by
|
|
278
|
-
# branch policy at merge time. Poll mergeStateStatus and accept
|
|
279
|
-
# only states GitHub reports as mergeable: CLEAN, or UNSTABLE with
|
|
280
|
-
# mergeable MERGEABLE. UNSTABLE is still mergeable when only
|
|
281
|
-
# non-required checks are failing or still running (observed: Kilo
|
|
282
|
-
# Code Review running after every required check passed); keep
|
|
283
|
-
# waiting for BLOCKED/BEHIND/UNKNOWN, fail fast on
|
|
284
|
-
# DIRTY/CONFLICTING, and fail closed on a bounded timeout so the
|
|
285
|
-
# workflow never sleeps indefinitely.
|
|
248
|
+
# GitHub's merge state is the authoritative required-check gate.
|
|
249
|
+
# `gh pr checks --required` is not reliable for ruleset-backed
|
|
250
|
+
# required contexts in private repositories: it can return an
|
|
251
|
+
# empty result even while the required aggregate check is present
|
|
252
|
+
# and green. Polling mergeStateStatus avoids that discovery race;
|
|
253
|
+
# GitHub reports MERGEABLE only after branch policy permits the
|
|
254
|
+
# requested merge strategy. Non-required checks may leave the PR
|
|
255
|
+
# UNSTABLE, so accept that state only with mergeable MERGEABLE.
|
|
286
256
|
echo "Waiting for release PR #$pr to become mergeable under branch policy"
|
|
287
257
|
merge_state=UNKNOWN
|
|
288
258
|
mergeable=UNKNOWN
|
|
@@ -506,12 +476,12 @@ jobs:
|
|
|
506
476
|
ref: ${{ needs.release.outputs.tag_name }}
|
|
507
477
|
- name: Setup Node (trusted)
|
|
508
478
|
if: env.NPM_TOKEN == ''
|
|
509
|
-
uses: actions/setup-node@
|
|
479
|
+
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
510
480
|
with:
|
|
511
481
|
node-version: 24
|
|
512
482
|
- name: Setup Node (token)
|
|
513
483
|
if: env.NPM_TOKEN != ''
|
|
514
|
-
uses: actions/setup-node@
|
|
484
|
+
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
515
485
|
with:
|
|
516
486
|
node-version: 24
|
|
517
487
|
registry-url: https://registry.npmjs.org
|
|
@@ -215,7 +215,7 @@ jobs:
|
|
|
215
215
|
cache-save: ${{ github.event_name != 'pull_request' }}
|
|
216
216
|
- name: Install cargo-audit
|
|
217
217
|
if: steps.applicability.outputs.applicable == 'true'
|
|
218
|
-
uses: taiki-e/install-action@
|
|
218
|
+
uses: taiki-e/install-action@cb33e69fad06166ca28a42b2575e4dadabf62ee8 # v2
|
|
219
219
|
with:
|
|
220
220
|
tool: cargo-audit
|
|
221
221
|
- name: Audit dependencies
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.37.4](https://github.com/0xPlayerOne/code-foundry/compare/v0.37.3...v0.37.4) (2026-08-23)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* release-workspace-state ([#395](https://github.com/0xPlayerOne/code-foundry/issues/395)) ([a708bc0](https://github.com/0xPlayerOne/code-foundry/commit/a708bc07272b9751b22a457771cc386601bbe3d8))
|
|
9
|
+
|
|
10
|
+
## [0.37.3](https://github.com/0xPlayerOne/code-foundry/compare/v0.37.2...v0.37.3) (2026-08-23)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* release-required-check-discovery ([#392](https://github.com/0xPlayerOne/code-foundry/issues/392)) ([d086cb5](https://github.com/0xPlayerOne/code-foundry/commit/d086cb5b6d1dc25eb28b6551ae56467887637296))
|
|
16
|
+
|
|
3
17
|
## [0.37.2](https://github.com/0xPlayerOne/code-foundry/compare/v0.37.1...v0.37.2) (2026-08-23)
|
|
4
18
|
|
|
5
19
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
import { existsSync, readFileSync } from 'node:fs'
|
|
4
|
-
import { join } from 'node:path'
|
|
4
|
+
import { dirname, join } from 'node:path'
|
|
5
5
|
import { isReleasePleaseHead } from './validation-policy.mjs'
|
|
6
6
|
|
|
7
7
|
const DEFAULT_RELEASE_FILES = new Set([
|
|
@@ -71,6 +71,62 @@ export function unexpectedReleasePaths(paths, allowed) {
|
|
|
71
71
|
.filter((path) => !allowed.has(path))
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Verify Cargo package versions remain reproducible after Release Please
|
|
76
|
+
* updates a manifest. Release Please's Node updater can change Cargo.toml
|
|
77
|
+
* while leaving a configured generic Cargo.lock extra-file untouched.
|
|
78
|
+
* @param {string} root
|
|
79
|
+
* @param {Record<string, unknown>} config
|
|
80
|
+
* @returns {string[]}
|
|
81
|
+
*/
|
|
82
|
+
export function validateCargoLockVersions(root, config = {}) {
|
|
83
|
+
const paths = new Set(['Cargo.lock'])
|
|
84
|
+
/** @param {unknown} entries @param {string} [prefix] */
|
|
85
|
+
const addEntries = (entries, prefix = '') => {
|
|
86
|
+
if (!Array.isArray(entries)) return
|
|
87
|
+
for (const entry of entries) {
|
|
88
|
+
const value = typeof entry === 'string' ? entry : entry && typeof entry === 'object' && 'path' in entry ? entry.path : ''
|
|
89
|
+
if (typeof value !== 'string' || !value.endsWith('Cargo.lock')) continue
|
|
90
|
+
const clean = value.replace(/^\.\//, '').replace(/\\/g, '/')
|
|
91
|
+
paths.add(prefix ? `${prefix}/${clean}` : clean)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
addEntries(config['extra-files'])
|
|
95
|
+
const packages = config.packages
|
|
96
|
+
if (packages && typeof packages === 'object' && !Array.isArray(packages)) {
|
|
97
|
+
for (const [directory, value] of Object.entries(packages)) {
|
|
98
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
99
|
+
addEntries(value['extra-files'], directory === '.' ? '' : directory.replace(/\/$/, ''))
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** @type {string[]} */
|
|
105
|
+
const errors = []
|
|
106
|
+
for (const relativeLockPath of paths) {
|
|
107
|
+
const lockPath = join(root, relativeLockPath)
|
|
108
|
+
const manifestPath = join(root, dirname(relativeLockPath), 'Cargo.toml')
|
|
109
|
+
if (!existsSync(lockPath) || !existsSync(manifestPath)) continue
|
|
110
|
+
const manifestLines = readFileSync(manifestPath, 'utf8').split(/\r?\n/)
|
|
111
|
+
const packageStart = manifestLines.findIndex((line) => line.trim() === '[package]')
|
|
112
|
+
if (packageStart < 0) continue
|
|
113
|
+
const nextSection = manifestLines.findIndex((line, index) => index > packageStart && /^\s*\[/.test(line))
|
|
114
|
+
const packageSection = manifestLines.slice(packageStart + 1, nextSection < 0 ? manifestLines.length : nextSection).join('\n')
|
|
115
|
+
const packageName = packageSection.match(/^name\s*=\s*"([^"]+)"\s*$/m)?.[1]
|
|
116
|
+
const packageVersion = packageSection.match(/^version\s*=\s*"([^"]+)"\s*$/m)?.[1]
|
|
117
|
+
if (!packageName || !packageVersion) continue
|
|
118
|
+
const lock = readFileSync(lockPath, 'utf8')
|
|
119
|
+
const lockPackage = [...lock.matchAll(/\[\[package\]\]\s+name\s*=\s*"([^"]+)"\s+version\s*=\s*"([^"]+)"/g)]
|
|
120
|
+
.find((match) => match[1] === packageName)
|
|
121
|
+
if (!lockPackage) {
|
|
122
|
+
errors.push(`${relativeLockPath} is missing the ${packageName} package entry.`)
|
|
123
|
+
} else if (lockPackage[2] !== packageVersion) {
|
|
124
|
+
errors.push(`${relativeLockPath} version ${lockPackage[2]} does not match ${relativeLockPath.replace(/Cargo\.lock$/, 'Cargo.toml')} version ${packageVersion}.`)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return errors
|
|
128
|
+
}
|
|
129
|
+
|
|
74
130
|
/**
|
|
75
131
|
* Detect release-only divergence after a rebased staging promotion. A normal
|
|
76
132
|
* main-ancestor relationship must always remain promotable, even when the new
|
|
@@ -140,11 +196,11 @@ const VERSION_METADATA_FILES = new Set([
|
|
|
140
196
|
* must carry the exact approved Release Please prefix, come from the same
|
|
141
197
|
* repository, change at least one path, change no unexpected paths, and bump
|
|
142
198
|
* at least one version-metadata file (or a declared extra file).
|
|
143
|
-
* @param {{ headRef?: string, headRepo?: string, repository?: string, changedPaths?: string[], config?: Record<string, unknown
|
|
199
|
+
* @param {{ headRef?: string, headRepo?: string, repository?: string, changedPaths?: string[], config?: Record<string, unknown>, root?: string }} input
|
|
144
200
|
* @returns {{ valid: boolean, errors: string[], changedPaths: string[] }}
|
|
145
201
|
*/
|
|
146
202
|
export function validateGeneratedReleaseDiff(input) {
|
|
147
|
-
const { headRef = '', headRepo = '', repository = '', changedPaths = [], config = {} } = input
|
|
203
|
+
const { headRef = '', headRepo = '', repository = '', changedPaths = [], config = {}, root = process.cwd() } = input
|
|
148
204
|
/** @type {string[]} */
|
|
149
205
|
const errors = []
|
|
150
206
|
if (!isReleasePleaseHead(headRef)) {
|
|
@@ -173,6 +229,7 @@ export function validateGeneratedReleaseDiff(input) {
|
|
|
173
229
|
if (!paths.some((path) => versionMetadata.has(path))) {
|
|
174
230
|
errors.push('Generated release pull request changes no version metadata.')
|
|
175
231
|
}
|
|
232
|
+
errors.push(...validateCargoLockVersions(root, config))
|
|
176
233
|
return { valid: errors.length === 0, errors, changedPaths: paths }
|
|
177
234
|
}
|
|
178
235
|
|
package/src/runtime.mjs
CHANGED