code-foundry 0.31.3 → 0.31.5
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/ci.yml +1 -1
- package/.github/workflows/codeql.yml +1 -1
- package/.github/workflows/draft-pr.yml +1 -1
- package/.github/workflows/promotion_self-ci.yml +58 -0
- package/.github/workflows/release-pr.yml +17 -3
- package/.github/workflows/release-pr_self-ci.yml +1 -0
- package/.github/workflows/release.yml +1 -1
- package/.github/workflows/security.yml +1 -1
- package/.github/workflows/test.yml +1 -1
- package/CHANGELOG.md +18 -0
- package/package.json +1 -1
- package/src/commands/fleet.mjs +12 -1
- package/src/lib/github-doctor.mjs +10 -0
- package/src/runtime.mjs +44 -14
package/.github/workflows/ci.yml
CHANGED
|
@@ -35,7 +35,7 @@ env:
|
|
|
35
35
|
REPO_FOUNDRY_BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before || '' }}
|
|
36
36
|
|
|
37
37
|
concurrency:
|
|
38
|
-
group:
|
|
38
|
+
group: code-foundry-ci-${{ github.event.pull_request.head.repo.full_name || github.repository }}-${{ github.event.pull_request.head.ref || github.ref_name }}
|
|
39
39
|
cancel-in-progress: true
|
|
40
40
|
|
|
41
41
|
jobs:
|
|
@@ -32,7 +32,7 @@ env:
|
|
|
32
32
|
REPO_FOUNDRY_FEATURES: ${{ vars.REPO_FOUNDRY_FEATURES }}
|
|
33
33
|
|
|
34
34
|
concurrency:
|
|
35
|
-
group:
|
|
35
|
+
group: code-foundry-codeql-${{ github.event.pull_request.head.repo.full_name || github.repository }}-${{ github.event.pull_request.head.ref || github.ref_name }}
|
|
36
36
|
cancel-in-progress: true
|
|
37
37
|
|
|
38
38
|
jobs:
|
|
@@ -22,7 +22,7 @@ jobs:
|
|
|
22
22
|
runs-on: ${{ inputs.runner }}
|
|
23
23
|
timeout-minutes: 10
|
|
24
24
|
concurrency:
|
|
25
|
-
group:
|
|
25
|
+
group: code-foundry-draft-pr-${{ github.repository }}-${{ github.ref }}
|
|
26
26
|
cancel-in-progress: true
|
|
27
27
|
steps:
|
|
28
28
|
- name: Check
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
name: Code Foundry
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [main]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
actions: read
|
|
10
|
+
contents: read
|
|
11
|
+
packages: read
|
|
12
|
+
pull-requests: read
|
|
13
|
+
security-events: write
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
ci:
|
|
17
|
+
name: CI
|
|
18
|
+
uses: ./.github/workflows/ci.yml
|
|
19
|
+
with:
|
|
20
|
+
runtime-repository: 0xPlayerOne/code-foundry
|
|
21
|
+
runtime-ref: ${{ github.sha }}
|
|
22
|
+
runner: ubuntu-latest
|
|
23
|
+
secrets: inherit
|
|
24
|
+
|
|
25
|
+
test:
|
|
26
|
+
name: Test
|
|
27
|
+
uses: ./.github/workflows/test.yml
|
|
28
|
+
with:
|
|
29
|
+
runtime-repository: 0xPlayerOne/code-foundry
|
|
30
|
+
runtime-ref: ${{ github.sha }}
|
|
31
|
+
runner: ubuntu-latest
|
|
32
|
+
unit-runner: ubuntu-slim
|
|
33
|
+
secrets: inherit
|
|
34
|
+
|
|
35
|
+
security:
|
|
36
|
+
name: Security
|
|
37
|
+
uses: ./.github/workflows/security.yml
|
|
38
|
+
with:
|
|
39
|
+
runtime-repository: 0xPlayerOne/code-foundry
|
|
40
|
+
runtime-ref: ${{ github.sha }}
|
|
41
|
+
runner: ubuntu-slim
|
|
42
|
+
secrets: inherit
|
|
43
|
+
|
|
44
|
+
codeql:
|
|
45
|
+
name: CodeQL
|
|
46
|
+
uses: ./.github/workflows/codeql.yml
|
|
47
|
+
with:
|
|
48
|
+
runtime-repository: 0xPlayerOne/code-foundry
|
|
49
|
+
runtime-ref: ${{ github.sha }}
|
|
50
|
+
runner: ubuntu-latest
|
|
51
|
+
secrets: inherit
|
|
52
|
+
|
|
53
|
+
release-pr:
|
|
54
|
+
name: Release PR / Create
|
|
55
|
+
runs-on: ubuntu-slim
|
|
56
|
+
steps:
|
|
57
|
+
- name: Validate promotion
|
|
58
|
+
run: echo 'Main promotion validation is handled by this workflow; version release remains a post-merge action.'
|
|
@@ -22,7 +22,7 @@ jobs:
|
|
|
22
22
|
runs-on: ${{ inputs.runner }}
|
|
23
23
|
timeout-minutes: 10
|
|
24
24
|
concurrency:
|
|
25
|
-
group:
|
|
25
|
+
group: code-foundry-release-pr-${{ github.repository }}-${{ github.ref }}
|
|
26
26
|
cancel-in-progress: true
|
|
27
27
|
steps:
|
|
28
28
|
- name: Check
|
|
@@ -91,9 +91,23 @@ jobs:
|
|
|
91
91
|
- After merge, Release Please opens a separate versioned release PR with the changelog.
|
|
92
92
|
EOF
|
|
93
93
|
|
|
94
|
-
gh pr create \
|
|
94
|
+
if ! gh pr create \
|
|
95
95
|
--repo "$GITHUB_REPOSITORY" \
|
|
96
96
|
--base main \
|
|
97
97
|
--head staging \
|
|
98
98
|
--title "Promote staging -> main ($DATE)" \
|
|
99
|
-
--body-file "$BODY_FILE"
|
|
99
|
+
--body-file "$BODY_FILE"; then
|
|
100
|
+
EXISTING_NUMBER=$(gh pr list \
|
|
101
|
+
--repo "$GITHUB_REPOSITORY" \
|
|
102
|
+
--limit 1 \
|
|
103
|
+
--base main \
|
|
104
|
+
--head staging \
|
|
105
|
+
--state open \
|
|
106
|
+
--json number \
|
|
107
|
+
--jq '.[0].number // empty')
|
|
108
|
+
if [ -z "$EXISTING_NUMBER" ]; then
|
|
109
|
+
echo 'Pull request creation failed and no existing promotion PR was found.' >&2
|
|
110
|
+
exit 1
|
|
111
|
+
fi
|
|
112
|
+
echo "Promotion PR #$EXISTING_NUMBER already exists; treating the create race as success."
|
|
113
|
+
fi
|
|
@@ -39,7 +39,7 @@ jobs:
|
|
|
39
39
|
runs-on: ${{ inputs.runner }}
|
|
40
40
|
timeout-minutes: 20
|
|
41
41
|
concurrency:
|
|
42
|
-
group:
|
|
42
|
+
group: code-foundry-release-${{ github.repository }}-${{ github.ref }}
|
|
43
43
|
cancel-in-progress: false
|
|
44
44
|
outputs:
|
|
45
45
|
release_created: ${{ steps.release.outputs.release_created || 'false' }}
|
|
@@ -31,7 +31,7 @@ env:
|
|
|
31
31
|
REPO_FOUNDRY_BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before || '' }}
|
|
32
32
|
|
|
33
33
|
concurrency:
|
|
34
|
-
group:
|
|
34
|
+
group: code-foundry-security-${{ github.event.pull_request.head.repo.full_name || github.repository }}-${{ github.event.pull_request.head.ref || github.ref_name }}
|
|
35
35
|
cancel-in-progress: true
|
|
36
36
|
|
|
37
37
|
jobs:
|
|
@@ -40,7 +40,7 @@ env:
|
|
|
40
40
|
REPO_FOUNDRY_BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before || '' }}
|
|
41
41
|
|
|
42
42
|
concurrency:
|
|
43
|
-
group:
|
|
43
|
+
group: code-foundry-test-${{ github.event.pull_request.head.repo.full_name || github.repository }}-${{ github.event.pull_request.head.ref || github.ref_name }}
|
|
44
44
|
cancel-in-progress: true
|
|
45
45
|
|
|
46
46
|
jobs:
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.31.5](https://github.com/0xPlayerOne/code-foundry/compare/v0.31.4...v0.31.5) (2026-07-30)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **ci:** isolate reusable workflow concurrency ([330a939](https://github.com/0xPlayerOne/code-foundry/commit/330a9396390aef11a482bf370dbbbee22151759e))
|
|
9
|
+
* **ci:** use stable workflow concurrency keys ([a7eca35](https://github.com/0xPlayerOne/code-foundry/commit/a7eca35836f24f9fb240234b999462f95fa4e7da))
|
|
10
|
+
* **ci:** validate main promotion pull requests ([00377bb](https://github.com/0xPlayerOne/code-foundry/commit/00377bb6e54288e296245efa1466e8e61477f25a))
|
|
11
|
+
* **release:** allow manual promotion validation ([aaa453f](https://github.com/0xPlayerOne/code-foundry/commit/aaa453f3bc63c4e256352f094468f983dad236a9))
|
|
12
|
+
* **release:** promote staging runtime safeguards ([e53df8c](https://github.com/0xPlayerOne/code-foundry/commit/e53df8cca59e59a8ffd7aad3f05effc915ba2196))
|
|
13
|
+
|
|
14
|
+
## [0.31.4](https://github.com/0xPlayerOne/code-foundry/compare/v0.31.3...v0.31.4) (2026-07-29)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
* harden fleet pins and github doctor ([c6efa7a](https://github.com/0xPlayerOne/code-foundry/commit/c6efa7a436308959950d958c5897574193cd763b))
|
|
20
|
+
|
|
3
21
|
## [0.31.3](https://github.com/0xPlayerOne/code-foundry/compare/v0.31.2...v0.31.3) (2026-07-29)
|
|
4
22
|
|
|
5
23
|
|
package/package.json
CHANGED
package/src/commands/fleet.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
import { existsSync, mkdtempSync, readFileSync, readdirSync } from 'node:fs'
|
|
3
|
+
import { existsSync, mkdtempSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
|
|
4
4
|
import { join } from 'node:path'
|
|
5
5
|
import { tmpdir } from 'node:os'
|
|
6
6
|
import { spawnSync } from 'node:child_process'
|
|
@@ -73,6 +73,17 @@ function upgradeRepository(repository, source, version) {
|
|
|
73
73
|
const add = spawnSync('git', ['-C', repository.path, 'worktree', 'add', '-b', branch, temporary, baseline.stdout.trim()], { encoding: 'utf8' })
|
|
74
74
|
if (add.status !== 0) return { path: repository.path, status: 'skipped', reason: add.stderr.trim() || 'unable to create isolated worktree' }
|
|
75
75
|
const result = syncRepository({ target: temporary, source, force: false })
|
|
76
|
+
const configFile = join(temporary, '.github/code-foundry.yml')
|
|
77
|
+
if (existsSync(configFile)) {
|
|
78
|
+
const current = readFileSync(configFile, 'utf8')
|
|
79
|
+
const updated = current.match(/^runtime_ref:\s*.*$/m)
|
|
80
|
+
? current.replace(/^runtime_ref:\s*.*$/m, `runtime_ref: ${version}`)
|
|
81
|
+
: `${current.trimEnd()}\nruntime_ref: ${version}\n`
|
|
82
|
+
if (updated !== current) {
|
|
83
|
+
writeFileSync(configFile, updated)
|
|
84
|
+
if (!result.changed.includes('.github/code-foundry.yml')) result.changed.push('.github/code-foundry.yml')
|
|
85
|
+
}
|
|
86
|
+
}
|
|
76
87
|
if (!result.changed.length) return { path: repository.path, status: 'unchanged', branch }
|
|
77
88
|
spawnSync('git', ['-C', temporary, 'add', '-A'], { stdio: 'ignore' })
|
|
78
89
|
const commit = spawnSync('git', ['-C', temporary, 'commit', '-m', `chore(code-foundry): upgrade runtime to ${version}`], { encoding: 'utf8' })
|
|
@@ -55,6 +55,16 @@ export function doctorGithub(root) {
|
|
|
55
55
|
if (['workflow-dispatch', 'dispatch'].includes(config.post_release_mode) && config.post_release !== 'false' && !details.secrets.releasePleaseTokenPresent) {
|
|
56
56
|
errors.push('post-release workflow-dispatch mode requires RELEASE_PLEASE_TOKEN to be present.')
|
|
57
57
|
}
|
|
58
|
+
const credentialChecks = {
|
|
59
|
+
npmTokenPresent: secretNames.includes('NPM_TOKEN'),
|
|
60
|
+
turboTokenPresent: secretNames.includes('TURBO_TOKEN'),
|
|
61
|
+
turboTeamPresent: Boolean(ghJson(['variable', 'list', '--repo', repository, '--json', 'name'])?.some(/** @param {{ name?: string }} variable */ (variable) => variable.name === 'TURBO_TEAM')),
|
|
62
|
+
opencodeApiKeyPresent: secretNames.includes('OPENCODE_API_KEY'),
|
|
63
|
+
}
|
|
64
|
+
details.credentials = credentialChecks
|
|
65
|
+
if (config.npm_publish === 'true' && !credentialChecks.npmTokenPresent) warnings.push('npm_publish is enabled but NPM_TOKEN is not configured; npm trusted publishing must be configured for tokenless publication.')
|
|
66
|
+
if (['true', 'auto'].includes(config.turbo_remote ?? 'false') && (!credentialChecks.turboTokenPresent || !credentialChecks.turboTeamPresent)) warnings.push('turbo_remote is enabled but TURBO_TOKEN and/or TURBO_TEAM is not configured; remote caching will be skipped.')
|
|
67
|
+
if (['true', 'auto'].includes(config.opencode_security ?? 'false') && !credentialChecks.opencodeApiKeyPresent) warnings.push('opencode_security is enabled but OPENCODE_API_KEY is not configured; the optional scan will be skipped.')
|
|
58
68
|
|
|
59
69
|
const prs = ghJson(['pr', 'list', '--repo', repository, '--state', 'open', '--base', 'main', '--json', 'number,title,headRefName'])
|
|
60
70
|
const openPrs = Array.isArray(prs) ? prs : []
|
package/src/runtime.mjs
CHANGED
|
@@ -20,6 +20,20 @@ function hasLanguage(language) {
|
|
|
20
20
|
return languages.includes(language) || languages.includes('all')
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
function hasRootJavascriptProject() {
|
|
24
|
+
return Boolean(readPackage()) && packageManager !== 'none'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function hasRootPythonProject() {
|
|
28
|
+
return existsSync(resolve(root, 'pyproject.toml')) ||
|
|
29
|
+
existsSync(resolve(root, 'uv.lock')) ||
|
|
30
|
+
capture('git', ['ls-files', '*requirements*.txt']) !== ''
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function hasRootRustProject() {
|
|
34
|
+
return existsSync(resolve(root, 'Cargo.toml'))
|
|
35
|
+
}
|
|
36
|
+
|
|
23
37
|
/** @param {string} name */
|
|
24
38
|
function hasScript(name) {
|
|
25
39
|
return readPackage()?.scripts?.[name] !== undefined
|
|
@@ -130,8 +144,24 @@ function relevant(task) {
|
|
|
130
144
|
const js = hasLanguage('typescript') || hasLanguage('javascript')
|
|
131
145
|
const python = hasLanguage('python')
|
|
132
146
|
const rust = hasLanguage('rust')
|
|
133
|
-
|
|
134
|
-
|
|
147
|
+
const scripted = {
|
|
148
|
+
format: ['format:check', 'format', 'fmt'],
|
|
149
|
+
lint: ['lint'],
|
|
150
|
+
type_check: ['type-check', 'typecheck', 'type:check'],
|
|
151
|
+
build: ['build'],
|
|
152
|
+
unit: ['test:unit', 'test:coverage', 'test'],
|
|
153
|
+
integration: ['test:integration'],
|
|
154
|
+
e2e: ['test:e2e', 'e2e'],
|
|
155
|
+
smoke: ['test:smoke', 'smoke'],
|
|
156
|
+
}[task]
|
|
157
|
+
if (scripted && scripted.some((candidate) => hasScript(candidate))) return packageManager !== 'none'
|
|
158
|
+
if (task === 'format' || task === 'lint' || task === 'type_check' || task === 'build') {
|
|
159
|
+
return (js && hasRootJavascriptProject()) || (python && hasRootPythonProject()) || (rust && hasRootRustProject())
|
|
160
|
+
}
|
|
161
|
+
if (['unit', 'integration', 'e2e', 'smoke'].includes(task)) {
|
|
162
|
+
return (js && hasRootJavascriptProject()) || (python && hasRootPythonProject()) ||
|
|
163
|
+
(rust && hasRootRustProject()) || (hasLanguage('solidity') && hasRootJavascriptProject())
|
|
164
|
+
}
|
|
135
165
|
return true
|
|
136
166
|
}
|
|
137
167
|
|
|
@@ -151,7 +181,7 @@ function hasDependencyManifest(ecosystem) {
|
|
|
151
181
|
function ci(task) {
|
|
152
182
|
if (task === 'install') return install()
|
|
153
183
|
if (task === 'should_run' || task === 'task_profile') {
|
|
154
|
-
const selected = process.argv[
|
|
184
|
+
const selected = process.argv[4]
|
|
155
185
|
writeOutput('applicable', relevant(selected) ? 'true' : 'false')
|
|
156
186
|
writeOutput('javascript', (hasLanguage('typescript') || hasLanguage('javascript')) ? 'true' : 'false')
|
|
157
187
|
writeOutput('python', hasLanguage('python') ? 'true' : 'false')
|
|
@@ -160,27 +190,27 @@ function ci(task) {
|
|
|
160
190
|
}
|
|
161
191
|
if (task === 'format') {
|
|
162
192
|
const scripted = runScript(['format:check', 'format', 'fmt'])
|
|
163
|
-
if (!scripted && (hasLanguage('typescript') || hasLanguage('javascript'))) runTool('prettier', ['--check', '.'])
|
|
164
|
-
if (hasLanguage('python')) runTool('ruff', ['format', '--check', '.'])
|
|
165
|
-
if (hasLanguage('rust')) run('cargo', ['fmt', '--check'])
|
|
193
|
+
if (!scripted && (hasLanguage('typescript') || hasLanguage('javascript')) && hasRootJavascriptProject()) runTool('prettier', ['--check', '.'])
|
|
194
|
+
if (hasLanguage('python') && hasRootPythonProject()) runTool('ruff', ['format', '--check', '.'])
|
|
195
|
+
if (hasLanguage('rust') && hasRootRustProject()) run('cargo', ['fmt', '--check'])
|
|
166
196
|
return
|
|
167
197
|
}
|
|
168
198
|
if (task === 'lint') {
|
|
169
199
|
const scripted = runScript(['lint'])
|
|
170
|
-
if (!scripted && (hasLanguage('typescript') || hasLanguage('javascript'))) runTool('eslint', ['.'])
|
|
171
|
-
if (hasLanguage('python')) runTool('ruff', ['check', '.'])
|
|
172
|
-
if (hasLanguage('rust')) run('cargo', ['clippy', '--all-targets', '--', '-D', 'warnings'])
|
|
200
|
+
if (!scripted && (hasLanguage('typescript') || hasLanguage('javascript')) && hasRootJavascriptProject()) runTool('eslint', ['.'])
|
|
201
|
+
if (hasLanguage('python') && hasRootPythonProject()) runTool('ruff', ['check', '.'])
|
|
202
|
+
if (hasLanguage('rust') && hasRootRustProject()) run('cargo', ['clippy', '--all-targets', '--', '-D', 'warnings'])
|
|
173
203
|
return
|
|
174
204
|
}
|
|
175
205
|
if (task === 'type_check') {
|
|
176
206
|
const scripted = runScript(['type-check', 'typecheck', 'type:check'])
|
|
177
207
|
if (!scripted && existsSync(resolve(root, 'tsconfig.json'))) runTool('tsc', ['--noEmit'])
|
|
178
|
-
if (hasLanguage('rust')) run('cargo', ['check', '--all-targets'])
|
|
208
|
+
if (hasLanguage('rust') && hasRootRustProject()) run('cargo', ['check', '--all-targets'])
|
|
179
209
|
return
|
|
180
210
|
}
|
|
181
211
|
if (task === 'build') {
|
|
182
212
|
const scripted = runScript(['build'])
|
|
183
|
-
if (!scripted && hasLanguage('rust')) run('cargo', ['build', '--all-targets'])
|
|
213
|
+
if (!scripted && hasLanguage('rust') && hasRootRustProject()) run('cargo', ['build', '--all-targets'])
|
|
184
214
|
return
|
|
185
215
|
}
|
|
186
216
|
/** @type {Record<string, string[]>} */
|
|
@@ -192,8 +222,8 @@ function ci(task) {
|
|
|
192
222
|
}
|
|
193
223
|
const scripts = scriptsByTask[task]
|
|
194
224
|
if (scripts) runScript(scripts)
|
|
195
|
-
if (hasLanguage('rust')) run('cargo', ['test'])
|
|
196
|
-
if (hasLanguage('python')) {
|
|
225
|
+
if (hasLanguage('rust') && hasRootRustProject()) run('cargo', ['test'])
|
|
226
|
+
if (hasLanguage('python') && hasRootPythonProject()) {
|
|
197
227
|
const python = existsSync(resolve(root, '.venv/bin/python')) ? resolve(root, '.venv/bin/python') : 'python'
|
|
198
228
|
const integrationTests = resolve(root, 'tests/integration')
|
|
199
229
|
if (task !== 'integration' || existsSync(integrationTests)) {
|
|
@@ -291,7 +321,7 @@ function codeql() {
|
|
|
291
321
|
/** @param {string} command */
|
|
292
322
|
function printProfile(command) {
|
|
293
323
|
if (command === 'get') {
|
|
294
|
-
const key = process.argv[
|
|
324
|
+
const key = process.argv[4]
|
|
295
325
|
console.log(config[key] ?? (key === 'languages' ? languages.join(',') : key === 'package_manager' ? packageManager : ''))
|
|
296
326
|
return
|
|
297
327
|
}
|