code-foundry 0.37.3 → 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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
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
+
3
10
  ## [0.37.3](https://github.com/0xPlayerOne/code-foundry/compare/v0.37.2...v0.37.3) (2026-08-23)
4
11
 
5
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-foundry",
3
- "version": "0.37.3",
3
+ "version": "0.37.4",
4
4
  "description": "A fast, language-aware repository factory for agent-ready workflows, testing, security, and release automation.",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-or-later",
@@ -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> }} input
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
@@ -85,6 +85,7 @@ function validation(task) {
85
85
  repository: process.env.FOUNDRY_REPOSITORY ?? '',
86
86
  changedPaths,
87
87
  config: readReleaseConfig(root),
88
+ root,
88
89
  })
89
90
  if (!result.valid) {
90
91
  for (const error of result.errors) console.error(`::error::${error}`)