code-foundry 1.38.1 → 1.39.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.
- package/CHANGELOG.md +12 -0
- package/docs/WORKFLOWS.md +4 -1
- package/package.json +1 -1
- package/src/commands/ci.mjs +63 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.39.0](https://github.com/0xPlayerOne/code-foundry/compare/v1.38.1...v1.39.0) (2026-10-01)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **ci:** surface ungated scheduled workflows in the billing pause controls ([#681](https://github.com/0xPlayerOne/code-foundry/issues/681)) ([ef48f9a](https://github.com/0xPlayerOne/code-foundry/commit/ef48f9a25aa0b22f82b0ed8a512bc1a65d08d8e9))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Maintenance
|
|
12
|
+
|
|
13
|
+
* register the adea-ai ui and themes consumers in the fleet manifest ([#680](https://github.com/0xPlayerOne/code-foundry/issues/680)) ([f50e6c6](https://github.com/0xPlayerOne/code-foundry/commit/f50e6c6a4ef4b7f0396cdce21f53070fec818c50))
|
|
14
|
+
|
|
3
15
|
## [1.38.1](https://github.com/0xPlayerOne/code-foundry/compare/v1.38.0...v1.38.1) (2026-09-30)
|
|
4
16
|
|
|
5
17
|
|
package/docs/WORKFLOWS.md
CHANGED
|
@@ -123,7 +123,10 @@ throughout the bounded release flow.
|
|
|
123
123
|
|
|
124
124
|
Custom workflows are repository-owned and are not rewritten by sync. Add
|
|
125
125
|
`if: vars.CI_BILLING_PAUSED != 'true'` to each custom root job that should
|
|
126
|
-
honor the shared billing pause.
|
|
126
|
+
honor the shared billing pause. `npx code-foundry ci status` lists custom
|
|
127
|
+
scheduled workflows that lack the guard, and `pause` warns about them: a cron
|
|
128
|
+
workflow without the guard keeps allocating runners while CI is paused. The
|
|
129
|
+
optional OpenCode Security scan honors a
|
|
127
130
|
second toggle: the `OPENCODE_SECURITY` repository or organization variable
|
|
128
131
|
(`true`/`false`) is its only enablement control, so individual repositories can
|
|
129
132
|
opt in or out without a code change. When the variable is not `true`, no
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-foundry",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.39.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/commands/ci.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
import { spawnSync } from 'node:child_process'
|
|
4
|
-
import { existsSync, readFileSync } from 'node:fs'
|
|
4
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs'
|
|
5
5
|
import { readConfig } from '../lib/config.mjs'
|
|
6
6
|
|
|
7
7
|
export const CI_BILLING_PAUSED_VARIABLE = 'CI_BILLING_PAUSED'
|
|
@@ -29,12 +29,14 @@ export function manageCiBilling(root, action) {
|
|
|
29
29
|
|
|
30
30
|
if (action === 'status') {
|
|
31
31
|
const dependabot = dependabotPauseState(root)
|
|
32
|
+
const ungatedSchedules = ungatedScheduledWorkflows(root)
|
|
32
33
|
const result = {
|
|
33
34
|
repository,
|
|
34
35
|
paused,
|
|
35
36
|
requiredCheck: CI_BILLING_REQUIRED_CHECK,
|
|
36
37
|
backupPresent: Boolean(backup),
|
|
37
38
|
dependabot,
|
|
39
|
+
ungatedSchedules,
|
|
38
40
|
}
|
|
39
41
|
console.log(JSON.stringify(result, null, 2))
|
|
40
42
|
return result
|
|
@@ -92,6 +94,7 @@ export function manageCiBilling(root, action) {
|
|
|
92
94
|
const cancelledRuns = cancelActiveRuns(repository)
|
|
93
95
|
for (const change of changes) updateRuleset(repository, change.ruleset)
|
|
94
96
|
const dependabot = dependabotPauseState(root)
|
|
97
|
+
const ungatedSchedules = ungatedScheduledWorkflows(root)
|
|
95
98
|
const result = {
|
|
96
99
|
repository,
|
|
97
100
|
paused: true,
|
|
@@ -110,6 +113,19 @@ export function manageCiBilling(root, action) {
|
|
|
110
113
|
'and run a sync so dependabot.yml renders open-pull-requests-limit: 0.',
|
|
111
114
|
}
|
|
112
115
|
: {}),
|
|
116
|
+
// Cron workflows are repository-owned, so sync cannot add the guard.
|
|
117
|
+
// List any that would keep firing on schedule while paused.
|
|
118
|
+
ungatedSchedules,
|
|
119
|
+
...(ungatedSchedules.length
|
|
120
|
+
? {
|
|
121
|
+
scheduleWarning:
|
|
122
|
+
`Scheduled workflows without a ${CI_BILLING_PAUSED_VARIABLE} job guard keep ` +
|
|
123
|
+
`running while paused: ${ungatedSchedules
|
|
124
|
+
.map((/** @type {{ file: string }} */ entry) => entry.file)
|
|
125
|
+
.join(', ')}. Add ` +
|
|
126
|
+
"`if: vars.CI_BILLING_PAUSED != 'true'` to each of their jobs.",
|
|
127
|
+
}
|
|
128
|
+
: {}),
|
|
113
129
|
}
|
|
114
130
|
console.log(JSON.stringify(result, null, 2))
|
|
115
131
|
return result
|
|
@@ -175,6 +191,52 @@ export function dependabotPauseState(root) {
|
|
|
175
191
|
return { configured, active, file }
|
|
176
192
|
}
|
|
177
193
|
|
|
194
|
+
/**
|
|
195
|
+
* List custom cron workflows that the `CI_BILLING_PAUSED` variable does not
|
|
196
|
+
* gate. Sync keeps every managed workflow referencing the variable, so a
|
|
197
|
+
* scheduled workflow without that token is repository-owned, and its cron
|
|
198
|
+
* jobs keep allocating runners while CI is paused — the same gap Dependabot
|
|
199
|
+
* has, but one a one-line job guard can close.
|
|
200
|
+
*
|
|
201
|
+
* @param {string} root
|
|
202
|
+
* @returns {{ file: string, crons: string[] }[]}
|
|
203
|
+
*/
|
|
204
|
+
export function ungatedScheduledWorkflows(root) {
|
|
205
|
+
const dir = `${root.replace(/\/$/, '')}/.github/workflows`
|
|
206
|
+
if (!existsSync(dir)) return []
|
|
207
|
+
const gated = []
|
|
208
|
+
for (const entry of readdirSync(dir)) {
|
|
209
|
+
if (!/\.ya?ml$/.test(entry)) continue
|
|
210
|
+
const source = readFileSync(`${dir}/${entry}`, 'utf8')
|
|
211
|
+
if (source.includes(CI_BILLING_PAUSED_VARIABLE)) continue
|
|
212
|
+
const triggers = workflowTriggerBlock(source)
|
|
213
|
+
if (!triggers || !/^ schedule:/m.test(triggers)) continue
|
|
214
|
+
const crons = [...triggers.matchAll(/^\s*-\s*cron:\s*(.+?)\s*$/gm)].flatMap(
|
|
215
|
+
(/** @type {RegExpMatchArray} */ match) => [
|
|
216
|
+
match[1].replace(/^['"]|['"]$/g, '').replace(/\s+#.*$/, ''),
|
|
217
|
+
]
|
|
218
|
+
)
|
|
219
|
+
gated.push({ file: `.github/workflows/${entry}`, crons })
|
|
220
|
+
}
|
|
221
|
+
return gated
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Return the block-form `on:` section of a workflow, or null when the file
|
|
226
|
+
* has none. Dependency-free on purpose: the runtime avoids a YAML parser,
|
|
227
|
+
* and the block ends at the next top-level key.
|
|
228
|
+
*
|
|
229
|
+
* @param {string} source
|
|
230
|
+
* @returns {string | null}
|
|
231
|
+
*/
|
|
232
|
+
function workflowTriggerBlock(source) {
|
|
233
|
+
const start = source.search(/^on:\s*$/m)
|
|
234
|
+
if (start < 0) return null
|
|
235
|
+
const rest = source.slice(start + 3)
|
|
236
|
+
const end = rest.search(/^[A-Za-z_-]+:/m)
|
|
237
|
+
return end >= 0 ? rest.slice(0, end) : rest
|
|
238
|
+
}
|
|
239
|
+
|
|
178
240
|
/** @param {any} ruleset */
|
|
179
241
|
export function buildPausedRuleset(ruleset) {
|
|
180
242
|
const requiredRuleIndexes = (ruleset.rules ?? []).flatMap(
|