code-foundry 0.31.13 → 0.31.15
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/code-foundry.yml +3 -0
- package/.github/workflows/codeql.yml +86 -4
- package/.github/workflows/codeql_self-ci.yml +3 -0
- package/.github/workflows/release-pr.yml +66 -3
- package/CHANGELOG.md +14 -0
- package/docs/CONFIGURATION.md +17 -0
- package/package.json +1 -1
- package/src/commands/sync.mjs +62 -3
- package/src/lib/release-policy.mjs +20 -0
package/.github/code-foundry.yml
CHANGED
|
@@ -18,6 +18,21 @@ on:
|
|
|
18
18
|
required: false
|
|
19
19
|
type: string
|
|
20
20
|
default: ubuntu-latest
|
|
21
|
+
rust-shards:
|
|
22
|
+
description: JSON array of Rust scope sharding values. Use ["all"] for single-pass behavior.
|
|
23
|
+
required: false
|
|
24
|
+
type: string
|
|
25
|
+
default: '["all"]'
|
|
26
|
+
rust-threads:
|
|
27
|
+
description: Threads used for Rust extraction and analysis. Values above 1 opt into local parallelism.
|
|
28
|
+
required: false
|
|
29
|
+
type: string
|
|
30
|
+
default: '1'
|
|
31
|
+
rust-max-parallel:
|
|
32
|
+
description: Maximum Rust shard jobs allowed to run concurrently.
|
|
33
|
+
required: false
|
|
34
|
+
type: number
|
|
35
|
+
default: 1
|
|
21
36
|
|
|
22
37
|
permissions:
|
|
23
38
|
actions: read
|
|
@@ -280,6 +295,11 @@ jobs:
|
|
|
280
295
|
if: >-
|
|
281
296
|
needs.detect.outputs.enabled == 'true' &&
|
|
282
297
|
needs.detect.outputs.rust_available == 'true'
|
|
298
|
+
strategy:
|
|
299
|
+
fail-fast: false
|
|
300
|
+
max-parallel: ${{ inputs.rust-max-parallel }}
|
|
301
|
+
matrix:
|
|
302
|
+
shard: ${{ fromJson(inputs.rust-shards) }}
|
|
283
303
|
runs-on: ${{ inputs.runner }}
|
|
284
304
|
timeout-minutes: 30
|
|
285
305
|
steps:
|
|
@@ -312,16 +332,78 @@ jobs:
|
|
|
312
332
|
mkdir -p .github/actions
|
|
313
333
|
mv .github/.code-foundry "$RUNNER_TEMP/code-foundry"
|
|
314
334
|
cp -R "$RUNNER_TEMP/code-foundry/.github/actions/." .github/actions/
|
|
315
|
-
- name:
|
|
335
|
+
- name: Configure Rust scope
|
|
316
336
|
if: >-
|
|
317
337
|
needs.detect.outputs.enabled == 'true' &&
|
|
318
338
|
needs.detect.outputs.rust_available == 'true' &&
|
|
319
339
|
needs.detect.outputs.rust_changed == 'true'
|
|
320
|
-
|
|
340
|
+
id: scope
|
|
341
|
+
env:
|
|
342
|
+
RUST_SCOPE: ${{ matrix.shard }}
|
|
343
|
+
run: |
|
|
344
|
+
set -euo pipefail
|
|
345
|
+
scope="$RUST_SCOPE"
|
|
346
|
+
scope_id="$(node -e 'process.stdout.write(require("node:crypto").createHash("sha256").update(process.argv[1]).digest("hex").slice(0, 12))' "$scope")"
|
|
347
|
+
config_file="$RUNNER_TEMP/codeql-rust-$scope_id.yml"
|
|
348
|
+
cat > "$config_file" <<'EOF'
|
|
349
|
+
name: code-foundry-rust
|
|
350
|
+
EOF
|
|
351
|
+
if [ "$scope" != "all" ]; then
|
|
352
|
+
IFS=',' read -ra paths <<< "$scope"
|
|
353
|
+
if [ "${#paths[@]}" -eq 0 ]; then
|
|
354
|
+
echo "Empty Rust scope shard for CodeQL"
|
|
355
|
+
exit 1
|
|
356
|
+
fi
|
|
357
|
+
echo "paths:" >> "$config_file"
|
|
358
|
+
for path in "${paths[@]}"; do
|
|
359
|
+
path="${path#"${path%%[![:space:]]*}"}"
|
|
360
|
+
path="${path%"${path##*[![:space:]]}"}"
|
|
361
|
+
if [ -z "$path" ] ||
|
|
362
|
+
[[ "$path" = /* ]] ||
|
|
363
|
+
[[ "/$path/" = *"/../"* ]] ||
|
|
364
|
+
[[ ! "$path" =~ ^[A-Za-z0-9._/@+\ -]+$ ]]; then
|
|
365
|
+
echo "Invalid Rust CodeQL shard path: $path" >&2
|
|
366
|
+
exit 1
|
|
367
|
+
fi
|
|
368
|
+
if ! git ls-files -- "$path" | grep -Eq '(^|/)(Cargo\.toml|[^/]+\.rs)$'; then
|
|
369
|
+
echo "Rust CodeQL shard path contains no tracked Rust source: $path" >&2
|
|
370
|
+
exit 1
|
|
371
|
+
fi
|
|
372
|
+
printf " - '%s'\n" "$path" >> "$config_file"
|
|
373
|
+
done
|
|
374
|
+
fi
|
|
375
|
+
if [ -f Cargo.toml ] && grep -q '^paths:' "$config_file"; then
|
|
376
|
+
printf " - 'Cargo.toml'\n" >> "$config_file"
|
|
377
|
+
fi
|
|
378
|
+
if [ -f Cargo.lock ] && grep -q '^paths:' "$config_file"; then
|
|
379
|
+
printf " - 'Cargo.lock'\n" >> "$config_file"
|
|
380
|
+
fi
|
|
381
|
+
if [ -f rust-toolchain.toml ] && grep -q '^paths:' "$config_file"; then
|
|
382
|
+
printf " - 'rust-toolchain.toml'\n" >> "$config_file"
|
|
383
|
+
fi
|
|
384
|
+
echo "config_file=$config_file" >> "$GITHUB_OUTPUT"
|
|
385
|
+
echo "scope_id=$scope_id" >> "$GITHUB_OUTPUT"
|
|
386
|
+
- name: Initialize
|
|
387
|
+
if: >-
|
|
388
|
+
needs.detect.outputs.enabled == 'true' &&
|
|
389
|
+
needs.detect.outputs.rust_available == 'true' &&
|
|
390
|
+
needs.detect.outputs.rust_changed == 'true'
|
|
391
|
+
uses: github/codeql-action/init@v4
|
|
321
392
|
with:
|
|
322
|
-
|
|
393
|
+
languages: rust
|
|
323
394
|
build-mode: ${{ needs.detect.outputs.rust_build_mode }}
|
|
324
|
-
|
|
395
|
+
config-file: ${{ steps.scope.outputs.config_file }}
|
|
396
|
+
threads: ${{ inputs.rust-threads }}
|
|
397
|
+
- name: Analyze
|
|
398
|
+
if: >-
|
|
399
|
+
needs.detect.outputs.enabled == 'true' &&
|
|
400
|
+
needs.detect.outputs.rust_available == 'true' &&
|
|
401
|
+
needs.detect.outputs.rust_changed == 'true'
|
|
402
|
+
uses: github/codeql-action/analyze@v4
|
|
403
|
+
with:
|
|
404
|
+
category: /language:rust/${{ steps.scope.outputs.scope_id }}
|
|
405
|
+
upload-database: false
|
|
406
|
+
wait-for-processing: false
|
|
325
407
|
- name: Not applicable
|
|
326
408
|
if: >-
|
|
327
409
|
needs.detect.outputs.enabled != 'true' ||
|
|
@@ -25,6 +25,12 @@ jobs:
|
|
|
25
25
|
group: code-foundry-release-pr-${{ github.repository }}-${{ github.ref }}
|
|
26
26
|
cancel-in-progress: true
|
|
27
27
|
steps:
|
|
28
|
+
- name: Checkout
|
|
29
|
+
uses: actions/checkout@v6
|
|
30
|
+
with:
|
|
31
|
+
fetch-depth: 0
|
|
32
|
+
filter: blob:none
|
|
33
|
+
|
|
28
34
|
- name: Check
|
|
29
35
|
id: check-pr
|
|
30
36
|
run: |
|
|
@@ -44,16 +50,73 @@ jobs:
|
|
|
44
50
|
fi
|
|
45
51
|
COMPARE_URL="repos/${GITHUB_REPOSITORY}/compare/main...staging"
|
|
46
52
|
TOTAL_COMMITS=$(gh api "$COMPARE_URL?per_page=1" --jq '.total_commits // 0')
|
|
53
|
+
|
|
54
|
+
MAIN_IS_ANCESTOR=false
|
|
55
|
+
if git merge-base --is-ancestor origin/main origin/staging; then
|
|
56
|
+
MAIN_IS_ANCESTOR=true
|
|
57
|
+
fi
|
|
58
|
+
DIRECT_FILES="$RUNNER_TEMP/release-direct-files.txt"
|
|
59
|
+
git diff --name-only origin/staging origin/main > "$DIRECT_FILES"
|
|
60
|
+
RELEASE_ONLY=false
|
|
61
|
+
if [ "$MAIN_IS_ANCESTOR" != true ] && [ -s "$DIRECT_FILES" ]; then
|
|
62
|
+
if node - "$DIRECT_FILES" <<'NODE'
|
|
63
|
+
const { existsSync, readFileSync } = require('node:fs')
|
|
64
|
+
const directFiles = readFileSync(process.argv[2], 'utf8').split(/\r?\n/).filter(Boolean)
|
|
65
|
+
const defaults = [
|
|
66
|
+
'.release-please-manifest.json', 'CHANGELOG.md', 'Cargo.lock', 'Cargo.toml',
|
|
67
|
+
'bun.lock', 'bun.lockb', 'package-lock.json', 'package.json', 'pnpm-lock.yaml',
|
|
68
|
+
'pyproject.toml', 'uv.lock', 'version.txt', 'yarn.lock',
|
|
69
|
+
]
|
|
70
|
+
const allowed = new Set(defaults)
|
|
71
|
+
let config = {}
|
|
72
|
+
if (existsSync('release-please-config.json')) {
|
|
73
|
+
try { config = JSON.parse(readFileSync('release-please-config.json', 'utf8')) } catch {}
|
|
74
|
+
}
|
|
75
|
+
const addPath = (prefix, file) => {
|
|
76
|
+
const clean = file.replace(/^\.\//, '').replace(/\\/g, '/')
|
|
77
|
+
allowed.add(prefix ? `${prefix}/${clean}` : clean)
|
|
78
|
+
}
|
|
79
|
+
const addExtras = (entries, prefix = '') => {
|
|
80
|
+
if (!Array.isArray(entries)) return
|
|
81
|
+
for (const entry of entries) {
|
|
82
|
+
const file = typeof entry === 'string' ? entry : entry && typeof entry === 'object' ? entry.path : ''
|
|
83
|
+
if (typeof file === 'string' && file) addPath(prefix, file)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
addExtras(config['extra-files'])
|
|
87
|
+
if (config.packages && typeof config.packages === 'object' && !Array.isArray(config.packages)) {
|
|
88
|
+
for (const [directory, value] of Object.entries(config.packages)) {
|
|
89
|
+
const prefix = directory === '.' ? '' : directory.replace(/\/$/, '')
|
|
90
|
+
for (const file of defaults) addPath(prefix, file)
|
|
91
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) addExtras(value['extra-files'], prefix)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const unexpected = directFiles.filter((file) => !allowed.has(file))
|
|
95
|
+
if (unexpected.length) {
|
|
96
|
+
console.error(`Direct branch diff includes non-release paths: ${unexpected.join(', ')}`)
|
|
97
|
+
process.exit(1)
|
|
98
|
+
}
|
|
99
|
+
NODE
|
|
100
|
+
then
|
|
101
|
+
RELEASE_ONLY=true
|
|
102
|
+
fi
|
|
103
|
+
fi
|
|
47
104
|
echo "existing=$EXISTING" >> "$GITHUB_OUTPUT"
|
|
48
105
|
echo "commits=$TOTAL_COMMITS" >> "$GITHUB_OUTPUT"
|
|
49
106
|
echo "tree_equal=$TREE_EQUAL" >> "$GITHUB_OUTPUT"
|
|
107
|
+
echo "release_only=$RELEASE_ONLY" >> "$GITHUB_OUTPUT"
|
|
50
108
|
|
|
51
109
|
- name: No changes
|
|
52
|
-
if: steps.check-pr.outputs.existing == '0' && (steps.check-pr.outputs.commits == '0' || steps.check-pr.outputs.tree_equal == 'true')
|
|
53
|
-
run:
|
|
110
|
+
if: steps.check-pr.outputs.existing == '0' && (steps.check-pr.outputs.commits == '0' || steps.check-pr.outputs.tree_equal == 'true' || steps.check-pr.outputs.release_only == 'true')
|
|
111
|
+
run: |
|
|
112
|
+
if [ "${{ steps.check-pr.outputs.release_only }}" = true ]; then
|
|
113
|
+
echo 'Only approved release metadata differs between rebased branch histories; reconciliation from main to staging is pending, so no promotion PR is needed.'
|
|
114
|
+
else
|
|
115
|
+
echo 'staging is already aligned with main; no promotion PR is needed.'
|
|
116
|
+
fi
|
|
54
117
|
|
|
55
118
|
- name: Create
|
|
56
|
-
if: steps.check-pr.outputs.existing == '0' && steps.check-pr.outputs.commits != '0' && steps.check-pr.outputs.tree_equal != 'true'
|
|
119
|
+
if: steps.check-pr.outputs.existing == '0' && steps.check-pr.outputs.commits != '0' && steps.check-pr.outputs.tree_equal != 'true' && steps.check-pr.outputs.release_only != 'true'
|
|
57
120
|
run: |
|
|
58
121
|
DATE=$(date +%Y-%m-%d)
|
|
59
122
|
BODY_FILE="$RUNNER_TEMP/release-pr.md"
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.31.15](https://github.com/0xPlayerOne/code-foundry/compare/v0.31.14...v0.31.15) (2026-07-30)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **release:** suppress release-only promotion loops ([fb35a6f](https://github.com/0xPlayerOne/code-foundry/commit/fb35a6f6e03a8ab4da65eeaeeeec579389b508b3))
|
|
9
|
+
|
|
10
|
+
## [0.31.14](https://github.com/0xPlayerOne/code-foundry/compare/v0.31.13...v0.31.14) (2026-07-30)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **codeql:** make Rust parallelism safe and configurable ([545454f](https://github.com/0xPlayerOne/code-foundry/commit/545454fa42a8bb4e6970ff858af186f1cba09900))
|
|
16
|
+
|
|
3
17
|
## [0.31.13](https://github.com/0xPlayerOne/code-foundry/compare/v0.31.12...v0.31.13) (2026-07-30)
|
|
4
18
|
|
|
5
19
|
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -37,6 +37,9 @@ repository manifests and source
|
|
|
37
37
|
| `toolchain` | `auto`, `native`, `mise` | Environment setup policy; defaults to `auto` |
|
|
38
38
|
| `features` | `all` or a list | Standard workflow callers |
|
|
39
39
|
| `codeql` | `auto`, `true`, `false` | CodeQL policy; public repositories default to enabled, non-public repositories default to disabled |
|
|
40
|
+
| `codeql_rust_shards` | JSON array of paths | Rust scan scopes; `["all"]` keeps the safe single full scan |
|
|
41
|
+
| `codeql_rust_threads` | integer, 1-64 | Threads per Rust CodeQL job; values above 1 opt into local parallelism |
|
|
42
|
+
| `codeql_rust_max_parallel` | integer, 1-8 | Maximum Rust shard jobs allowed to run concurrently |
|
|
40
43
|
| `dependency_review` | `auto`, `true`, `false` | Dependency Review policy; public repositories default to enabled, non-public repositories default to disabled |
|
|
41
44
|
| `prune_standard` | `true` or `false` | Remove disabled standard callers |
|
|
42
45
|
| `runtime_repository` | `OWNER/REPO` | Reusable workflow source |
|
|
@@ -59,3 +62,17 @@ short and replaceable; custom workflows and project documentation are kept.
|
|
|
59
62
|
|
|
60
63
|
The generated configuration includes all defaults so humans and agents can
|
|
61
64
|
understand the repository without memorizing flags or environment variables.
|
|
65
|
+
|
|
66
|
+
Rust CodeQL defaults to one full scan with one worker. Large multi-crate
|
|
67
|
+
repositories can opt into bounded parallelism, for example:
|
|
68
|
+
|
|
69
|
+
```yaml
|
|
70
|
+
codeql_rust_shards: '["crates/api","crates/worker"]'
|
|
71
|
+
codeql_rust_threads: 2
|
|
72
|
+
codeql_rust_max_parallel: 2
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Each scoped shard must contain tracked Rust source. Code Foundry rejects
|
|
76
|
+
absolute paths, parent traversal, duplicates, empty scopes, and more than eight
|
|
77
|
+
shards. Do not split a single crate by arbitrary non-Rust directories: use
|
|
78
|
+
`["all"]` when complete, non-overlapping source scopes are not available.
|
package/package.json
CHANGED
package/src/commands/sync.mjs
CHANGED
|
@@ -68,6 +68,7 @@ export function syncRepository(options) {
|
|
|
68
68
|
let runtimeRef = configured(config.runtime_ref, sourceRuntimeRef)
|
|
69
69
|
const toolchain = configured(config.toolchain, 'auto')
|
|
70
70
|
const overlays = overlayPolicy(target, config)
|
|
71
|
+
const rustCodeql = validateRustCodeqlConfig(config)
|
|
71
72
|
if (!['auto', 'native', 'mise'].includes(toolchain)) {
|
|
72
73
|
throw new Error(`Unsupported toolchain: ${toolchain}; use auto, native, or mise.`)
|
|
73
74
|
}
|
|
@@ -103,7 +104,7 @@ export function syncRepository(options) {
|
|
|
103
104
|
content = Buffer.from(renderReleaseConfig(target, sourceFile))
|
|
104
105
|
}
|
|
105
106
|
if (file.endsWith('.yml') && file.startsWith('.github/workflows/')) {
|
|
106
|
-
content = Buffer.from(renderWorkflow(content.toString('utf8'), config, runtimeRepository, runtimeRef))
|
|
107
|
+
content = Buffer.from(renderWorkflow(content.toString('utf8'), config, runtimeRepository, runtimeRef, rustCodeql))
|
|
107
108
|
}
|
|
108
109
|
if (file === '.gitignore' && existsSync(destination)) {
|
|
109
110
|
content = Buffer.from(mergeGitignore(content.toString('utf8'), readFileSync(destination, 'utf8')))
|
|
@@ -198,8 +199,14 @@ function sourcePath(source, file) {
|
|
|
198
199
|
return join(source, file)
|
|
199
200
|
}
|
|
200
201
|
|
|
201
|
-
/**
|
|
202
|
-
|
|
202
|
+
/**
|
|
203
|
+
* @param {string} content
|
|
204
|
+
* @param {Record<string,string>} config
|
|
205
|
+
* @param {string} repository
|
|
206
|
+
* @param {string} ref
|
|
207
|
+
* @param {{ shards: string, threads: string, maxParallel: string }} rustCodeql
|
|
208
|
+
*/
|
|
209
|
+
function renderWorkflow(content, config, repository, ref, rustCodeql) {
|
|
203
210
|
const localPrefix = 'uses: ./.github/workflows/'
|
|
204
211
|
const remotePrefix = `uses: ${repository}/.github/workflows/`
|
|
205
212
|
let rendered = content.replaceAll(localPrefix, remotePrefix)
|
|
@@ -218,9 +225,60 @@ function renderWorkflow(content, config, repository, ref) {
|
|
|
218
225
|
const workflow = content.match(/\.github\/workflows\/([^/]+)\.yml/)?.[1]
|
|
219
226
|
const runner = workflow ? runners[workflow] : undefined
|
|
220
227
|
if (runner) rendered = rendered.replace(/^(\s+runner:)\s+.*$/m, `$1 ${runner}`)
|
|
228
|
+
if (workflow === 'codeql') {
|
|
229
|
+
rendered = rendered.replace(/^(\s+rust-shards:)\s+.*$/m, `$1 '${rustCodeql.shards}'`)
|
|
230
|
+
rendered = rendered.replace(/^(\s+rust-threads:)\s+.*$/m, `$1 '${rustCodeql.threads}'`)
|
|
231
|
+
rendered = rendered.replace(/^(\s+rust-max-parallel:)\s+.*$/m, `$1 ${rustCodeql.maxParallel}`)
|
|
232
|
+
}
|
|
221
233
|
return rendered
|
|
222
234
|
}
|
|
223
235
|
|
|
236
|
+
/** @param {Record<string,string>} config */
|
|
237
|
+
function validateRustCodeqlConfig(config) {
|
|
238
|
+
const threads = configured(config.codeql_rust_threads, '1')
|
|
239
|
+
const maxParallel = configured(config.codeql_rust_max_parallel, '1')
|
|
240
|
+
if (!/^(?:[1-9]|[1-5][0-9]|6[0-4])$/.test(threads)) {
|
|
241
|
+
throw new Error('Unsupported codeql_rust_threads; use an integer from 1 to 64.')
|
|
242
|
+
}
|
|
243
|
+
if (!/^(?:[1-8])$/.test(maxParallel)) {
|
|
244
|
+
throw new Error('Unsupported codeql_rust_max_parallel; use an integer from 1 to 8.')
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const rawShards = configured(config.codeql_rust_shards, '["all"]')
|
|
248
|
+
let shards
|
|
249
|
+
try {
|
|
250
|
+
shards = JSON.parse(rawShards)
|
|
251
|
+
} catch {
|
|
252
|
+
throw new Error('Invalid codeql_rust_shards; use a JSON array of relative Rust source paths.')
|
|
253
|
+
}
|
|
254
|
+
if (!Array.isArray(shards) || shards.length === 0 || shards.length > 8) {
|
|
255
|
+
throw new Error('Invalid codeql_rust_shards; configure between 1 and 8 shards.')
|
|
256
|
+
}
|
|
257
|
+
const seen = new Set()
|
|
258
|
+
for (const shard of shards) {
|
|
259
|
+
if (typeof shard !== 'string' || shard.length === 0 || shard.length > 512 || seen.has(shard)) {
|
|
260
|
+
throw new Error('Invalid codeql_rust_shards; shards must be unique non-empty strings.')
|
|
261
|
+
}
|
|
262
|
+
seen.add(shard)
|
|
263
|
+
if (shard === 'all') continue
|
|
264
|
+
for (const candidate of shard.split(',')) {
|
|
265
|
+
const path = candidate.trim()
|
|
266
|
+
if (
|
|
267
|
+
!path ||
|
|
268
|
+
path.startsWith('/') ||
|
|
269
|
+
path.split('/').includes('..') ||
|
|
270
|
+
!/^[A-Za-z0-9._/@+ -]+$/.test(path)
|
|
271
|
+
) {
|
|
272
|
+
throw new Error(`Invalid Rust CodeQL shard path: ${path || '(empty)'}`)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (seen.has('all') && shards.length !== 1) {
|
|
277
|
+
throw new Error('Invalid codeql_rust_shards; "all" cannot be combined with scoped shards.')
|
|
278
|
+
}
|
|
279
|
+
return { shards: JSON.stringify(shards), threads, maxParallel }
|
|
280
|
+
}
|
|
281
|
+
|
|
224
282
|
/** @param {string} value */
|
|
225
283
|
function escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') }
|
|
226
284
|
|
|
@@ -269,6 +327,7 @@ function createDefaultConfig(root, source) {
|
|
|
269
327
|
const runners = recommendRunners(root)
|
|
270
328
|
return {
|
|
271
329
|
version: '1', profile: detectProfile(root), languages, features: 'all', codeql: 'auto', dependency_review: 'auto', package_manager: packageManager,
|
|
330
|
+
codeql_rust_shards: '["all"]', codeql_rust_threads: '1', codeql_rust_max_parallel: '1',
|
|
272
331
|
runtime_repository: '0xPlayerOne/code-foundry', runtime_ref: `v${readPackageVersion(source)}`,
|
|
273
332
|
...runners,
|
|
274
333
|
toolchain: 'auto',
|
|
@@ -70,6 +70,26 @@ export function unexpectedReleasePaths(paths, allowed) {
|
|
|
70
70
|
.filter((path) => !allowed.has(path))
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Detect release-only divergence after a rebased staging promotion. A normal
|
|
75
|
+
* main-ancestor relationship must always remain promotable, even when the new
|
|
76
|
+
* staging commit happens to touch only release metadata.
|
|
77
|
+
* @param {{ mainIsAncestor: boolean, directChangedPaths?: string[], allowed?: Set<string> }} input
|
|
78
|
+
*/
|
|
79
|
+
export function classifyPromotion(input) {
|
|
80
|
+
const {
|
|
81
|
+
mainIsAncestor,
|
|
82
|
+
directChangedPaths = [],
|
|
83
|
+
allowed = approvedReleaseFiles(),
|
|
84
|
+
} = input
|
|
85
|
+
const paths = [...new Set(directChangedPaths.map((path) => path.trim()).filter(Boolean))]
|
|
86
|
+
const unexpected = unexpectedReleasePaths(paths, allowed)
|
|
87
|
+
return {
|
|
88
|
+
releaseOnly: !mainIsAncestor && paths.length > 0 && unexpected.length === 0,
|
|
89
|
+
unexpected,
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
73
93
|
/** @param {{ releasePleaseToken?: string, githubToken?: string }} input */
|
|
74
94
|
export function selectReleaseCredential(input) {
|
|
75
95
|
if (input.releasePleaseToken) return { token: input.releasePleaseToken, source: 'release-please-token', autoMerge: true }
|