code-foundry 1.53.2 → 1.53.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.
@@ -129,8 +129,8 @@ jobs:
129
129
  if-no-files-found: error
130
130
  retention-days: 14
131
131
 
132
- - name: Save Vite+ task cache
133
- uses: ./.github/actions/vite-plus-cache-save
132
+ - name: Save task caches
133
+ uses: ./.github/actions/cache-save
134
134
 
135
135
  lint:
136
136
  name: Lint
@@ -193,8 +193,8 @@ jobs:
193
193
  if-no-files-found: error
194
194
  retention-days: 14
195
195
 
196
- - name: Save Vite+ task cache
197
- uses: ./.github/actions/vite-plus-cache-save
196
+ - name: Save task caches
197
+ uses: ./.github/actions/cache-save
198
198
 
199
199
  type-check:
200
200
  name: Type-Check
@@ -262,8 +262,8 @@ jobs:
262
262
  if-no-files-found: error
263
263
  retention-days: 14
264
264
 
265
- - name: Save Vite+ task cache
266
- uses: ./.github/actions/vite-plus-cache-save
265
+ - name: Save task caches
266
+ uses: ./.github/actions/cache-save
267
267
 
268
268
  build:
269
269
  name: Build
@@ -357,5 +357,5 @@ jobs:
357
357
  if-no-files-found: error
358
358
  retention-days: 14
359
359
 
360
- - name: Save Vite+ task cache
361
- uses: ./.github/actions/vite-plus-cache-save
360
+ - name: Save task caches
361
+ uses: ./.github/actions/cache-save
@@ -125,5 +125,5 @@ jobs:
125
125
  if-no-files-found: error
126
126
  retention-days: 14
127
127
 
128
- - name: Save Vite+ task cache
129
- uses: ./.github/actions/vite-plus-cache-save
128
+ - name: Save task caches
129
+ uses: ./.github/actions/cache-save
@@ -166,8 +166,8 @@ jobs:
166
166
  if-no-files-found: ignore
167
167
  retention-days: 14
168
168
 
169
- - name: Save Vite+ task cache
170
- uses: ./.github/actions/vite-plus-cache-save
169
+ - name: Save task caches
170
+ uses: ./.github/actions/cache-save
171
171
 
172
172
  performance:
173
173
  name: Performance
@@ -255,8 +255,8 @@ jobs:
255
255
  if-no-files-found: ignore
256
256
  retention-days: 14
257
257
 
258
- - name: Save Vite+ task cache
259
- uses: ./.github/actions/vite-plus-cache-save
258
+ - name: Save task caches
259
+ uses: ./.github/actions/cache-save
260
260
 
261
261
  integration:
262
262
  name: Integration
@@ -332,8 +332,8 @@ jobs:
332
332
  if-no-files-found: error
333
333
  retention-days: 14
334
334
 
335
- - name: Save Vite+ task cache
336
- uses: ./.github/actions/vite-plus-cache-save
335
+ - name: Save task caches
336
+ uses: ./.github/actions/cache-save
337
337
 
338
338
  integration-sharded:
339
339
  name: Integration shard
@@ -412,8 +412,8 @@ jobs:
412
412
  if-no-files-found: error
413
413
  retention-days: 14
414
414
 
415
- - name: Save Vite+ task cache
416
- uses: ./.github/actions/vite-plus-cache-save
415
+ - name: Save task caches
416
+ uses: ./.github/actions/cache-save
417
417
 
418
418
  e2e:
419
419
  name: E2E
@@ -464,6 +464,7 @@ jobs:
464
464
  if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false' && steps.applicability.outputs.browser == 'true'
465
465
  uses: ./.github/actions/cache
466
466
  with:
467
+ id: e2e-browsers
467
468
  save: ${{ github.event_name == 'push' }}
468
469
  path: |
469
470
  ~/.cache/ms-playwright
@@ -508,8 +509,8 @@ jobs:
508
509
  if-no-files-found: error
509
510
  retention-days: 14
510
511
 
511
- - name: Save Vite+ task cache
512
- uses: ./.github/actions/vite-plus-cache-save
512
+ - name: Save task caches
513
+ uses: ./.github/actions/cache-save
513
514
 
514
515
  smoke:
515
516
  name: Smoke
@@ -581,5 +582,5 @@ jobs:
581
582
  if-no-files-found: error
582
583
  retention-days: 14
583
584
 
584
- - name: Save Vite+ task cache
585
- uses: ./.github/actions/vite-plus-cache-save
585
+ - name: Save task caches
586
+ uses: ./.github/actions/cache-save
@@ -543,8 +543,8 @@ jobs:
543
543
  if-no-files-found: error
544
544
  retention-days: 14
545
545
 
546
- - name: Save Vite+ task cache
547
- uses: ./.github/actions/vite-plus-cache-save
546
+ - name: Save task caches
547
+ uses: ./.github/actions/cache-save
548
548
 
549
549
  lint:
550
550
  name: Lint
@@ -614,8 +614,8 @@ jobs:
614
614
  if-no-files-found: error
615
615
  retention-days: 14
616
616
 
617
- - name: Save Vite+ task cache
618
- uses: ./.github/actions/vite-plus-cache-save
617
+ - name: Save task caches
618
+ uses: ./.github/actions/cache-save
619
619
 
620
620
  type-check:
621
621
  name: Type-Check
@@ -686,8 +686,8 @@ jobs:
686
686
  if-no-files-found: error
687
687
  retention-days: 14
688
688
 
689
- - name: Save Vite+ task cache
690
- uses: ./.github/actions/vite-plus-cache-save
689
+ - name: Save task caches
690
+ uses: ./.github/actions/cache-save
691
691
 
692
692
  unit:
693
693
  name: Unit
@@ -782,8 +782,8 @@ jobs:
782
782
  if-no-files-found: ignore
783
783
  retention-days: 14
784
784
 
785
- - name: Save Vite+ task cache
786
- uses: ./.github/actions/vite-plus-cache-save
785
+ - name: Save task caches
786
+ uses: ./.github/actions/cache-save
787
787
 
788
788
  e2e:
789
789
  name: E2E
@@ -838,6 +838,7 @@ jobs:
838
838
  if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false' && steps.applicability.outputs.browser == 'true'
839
839
  uses: ./.github/actions/cache
840
840
  with:
841
+ id: e2e-browsers
841
842
  save: ${{ github.event_name == 'push' }}
842
843
  path: |
843
844
  ~/.cache/ms-playwright
@@ -882,8 +883,8 @@ jobs:
882
883
  if-no-files-found: error
883
884
  retention-days: 14
884
885
 
885
- - name: Save Vite+ task cache
886
- uses: ./.github/actions/vite-plus-cache-save
886
+ - name: Save task caches
887
+ uses: ./.github/actions/cache-save
887
888
 
888
889
  # Profile and dependency audit merged into one job. The split orchestrators
889
890
  # run a profile job that feeds an audit matrix; here the language blocks run
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.53.4](https://github.com/0xPlayerOne/code-foundry/compare/v1.53.3...v1.53.4) (2026-10-30)
4
+
5
+
6
+ ### Performance
7
+
8
+ * **ci:** run mixed unit runners concurrently ([#821](https://github.com/0xPlayerOne/code-foundry/issues/821)) ([dac0d93](https://github.com/0xPlayerOne/code-foundry/commit/dac0d9313cc2fc6309e0f55aa45274e3dbbff767))
9
+
10
+ ## [1.53.3](https://github.com/0xPlayerOne/code-foundry/compare/v1.53.2...v1.53.3) (2026-10-30)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * save repository caches through an explicit save action ([#819](https://github.com/0xPlayerOne/code-foundry/issues/819)) ([a21f1d0](https://github.com/0xPlayerOne/code-foundry/commit/a21f1d0c153e14252bc784f301379029026121da))
16
+
3
17
  ## [1.53.2](https://github.com/0xPlayerOne/code-foundry/compare/v1.53.1...v1.53.2) (2026-10-30)
4
18
 
5
19
 
@@ -136,23 +150,9 @@
136
150
 
137
151
  * **release:** hydrate audit blobs and refresh changing PR state ([#779](https://github.com/0xPlayerOne/code-foundry/issues/779)) ([8734ed7](https://github.com/0xPlayerOne/code-foundry/commit/8734ed77d159818cd5ff613f40a5dab796789d8a))
138
152
 
139
- ## [1.48.7](https://github.com/0xPlayerOne/code-foundry/compare/v1.48.6...v1.48.7) (2026-10-23)
140
-
141
-
142
- ### Bug Fixes
143
-
144
- * **release:** sync bun.lock workspace versions on generated release PRs ([#777](https://github.com/0xPlayerOne/code-foundry/issues/777)) ([b24bfae](https://github.com/0xPlayerOne/code-foundry/commit/b24bfae309ac7003fcc4cd57f79ddc418c27b039))
145
-
146
- ## [1.48.6](https://github.com/0xPlayerOne/code-foundry/compare/v1.48.5...v1.48.6) (2026-10-23)
147
-
148
-
149
- ### Bug Fixes
150
-
151
- * **release:** audit generated release content for stale snapshots ([#775](https://github.com/0xPlayerOne/code-foundry/issues/775)) ([f842c27](https://github.com/0xPlayerOne/code-foundry/commit/f842c271e6b270b63a6d9b839795a849237c7a7d))
152
-
153
153
  <!-- code-foundry: packaged changelog -->
154
154
 
155
155
  ---
156
156
 
157
- This packaged changelog lists the 20 most recent releases; 273 older releases are omitted to keep the package small.
157
+ This packaged changelog lists the 20 most recent releases; 275 older releases are omitted to keep the package small.
158
158
  See the [full changelog](https://github.com/0xPlayerOne/code-foundry/blob/main/CHANGELOG.md) for the complete history.
package/docs/CACHING.md CHANGED
@@ -11,6 +11,13 @@ Repositories should measure before enabling large installed-tree or build
11
11
  caches. Set repository variables such as `REPO_FOUNDRY_CACHE_PACKAGES` and
12
12
  `REPO_FOUNDRY_CACHE_BUILD` only when a repeatable warm-cache benefit is proven.
13
13
 
14
+ These caches are restored on every event and saved on push only, so pull
15
+ requests and scheduled runs cannot poison them. The setup action restores them
16
+ and publishes a save contract; the `.github/actions/cache-save` action at the
17
+ end of each task job saves every published cache that did not restore an exact
18
+ key. Custom workflows that use `.github/actions/setup` must call it after their
19
+ tasks, or nothing is saved.
20
+
14
21
  ## Turborepo and Vercel Remote Caching
15
22
 
16
23
  Turborepo repositories can opt into Vercel Remote Caching with:
@@ -35,7 +42,8 @@ When opted in, the setup action:
35
42
  - restores `node_modules/.vite/task-cache` as its last step, after Install and
36
43
  after every step that archives `node_modules` (Install can replace it);
37
44
  - publishes a cache path, key, and save-intent contract for the explicit
38
- `.github/actions/vite-plus-cache-save` action to use after repository tasks;
45
+ `.github/actions/cache-save` action, which also saves the repository caches,
46
+ to use after repository tasks;
39
47
  - saves it push-only, so pull requests and scheduled runs cannot poison it;
40
48
  - keeps it out of the generic JavaScript build-state archive.
41
49
 
@@ -350,7 +350,7 @@ behavior:
350
350
  (not a config key). It controls the local Vite+ task cache only when
351
351
  `monorepo_tool: vite-plus` is set.
352
352
  - Custom workflows using the setup action must add
353
- `.github/actions/vite-plus-cache-save` after task execution. The generated
353
+ `.github/actions/cache-save` after task execution. The generated
354
354
  Code Foundry workflows already include this save boundary; see
355
355
  [Caching and remote caching](CACHING.md).
356
356
 
@@ -31,7 +31,7 @@ changelog size and its packaged (trimmed) size as informational metrics. At
31
31
  v1.44.0 the measured artifact was 280,902 packed bytes, 1,070,333 unpacked bytes,
32
32
  and 118 files.
33
33
 
34
- v1.52.1 main no-changelog: 316,788 packed, 1,224,968 unpacked, 120 files. Branch checker: 316,744 packed, 1,224,894 unpacked, 120 files. v1.52.0 published (trimmed changelog): 316,867 packed, 1,225,194 unpacked. Limits: 317,000 packed, 1,225,000 unpacked, 120 files.
34
+ v1.53.3 main no-changelog, plus the concurrent unit runner (`src/lib/run-concurrent.mjs`): 317,788 packed, 1,226,187 unpacked, 120 files. Limits: 318,000 packed, 1,227,000 unpacked, 120 files.
35
35
 
36
36
  The published package ships a changelog trimmed to the 20 most recent releases
37
37
  with a link to the full history on GitHub. `scripts/package-changelog.mjs`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-foundry",
3
- "version": "1.53.2",
3
+ "version": "1.53.4",
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": {
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+ // Runs independent test commands at the same time so a mixed repository's
3
+ // unit wall time is its slowest runner, not the sum of all of them. Each line
4
+ // of output is prefixed with its runner's label, and the exit status is the
5
+ // first failing command's in the order given, so the result stays stable.
6
+ import { spawn } from 'node:child_process'
7
+ import { createInterface } from 'node:readline'
8
+ import { resolve } from 'node:path'
9
+ import { pathToFileURL } from 'node:url'
10
+
11
+ /** @typedef {{ label: string, command: string, args: string[] }} Job */
12
+
13
+ /** @param {Job} job @returns {Promise<number>} */
14
+ function runJob(job) {
15
+ return new Promise((resolvePromise) => {
16
+ const child = spawn(job.command, job.args, {
17
+ cwd: process.cwd(),
18
+ env: process.env,
19
+ stdio: ['ignore', 'pipe', 'pipe'],
20
+ })
21
+ const prefix = `[${job.label}] `
22
+ /** @param {import('node:stream').Readable} stream @param {NodeJS.WriteStream} sink */
23
+ const relay = (stream, sink) =>
24
+ createInterface({ input: stream, crlfDelay: Infinity }).on('line', (line) =>
25
+ sink.write(`${prefix}${line}\n`)
26
+ )
27
+ relay(child.stdout, process.stdout)
28
+ relay(child.stderr, process.stderr)
29
+ child.on('error', (error) => {
30
+ process.stderr.write(`${prefix}${error.message}\n`)
31
+ resolvePromise(1)
32
+ })
33
+ child.on('close', (code, signal) => resolvePromise(signal ? 1 : (code ?? 1)))
34
+ })
35
+ }
36
+
37
+ /** @param {Job[]} jobs */
38
+ export async function runConcurrent(jobs) {
39
+ const statuses = await Promise.all(jobs.map(runJob))
40
+ return statuses.find((status) => status !== 0) ?? 0
41
+ }
42
+
43
+ if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
44
+ process.exitCode = await runConcurrent(JSON.parse(process.argv[2] ?? '[]'))
45
+ }
@@ -835,8 +835,18 @@ function ci(task) {
835
835
  e2e: ['test:e2e', 'e2e'],
836
836
  smoke: ['test:smoke', 'smoke'],
837
837
  }
838
- const scripts = scriptsByTask[task]
839
- const scripted = scripts ? runScript(scripts) : false
838
+ // Unit runners share no state, so they run together and the task takes as
839
+ // long as its slowest runner. Other categories may share services or ports
840
+ // and keep running one after another.
841
+ /** @type {{ label: string, command: string, args: string[] }[]} */
842
+ const jobs = []
843
+ /** @param {string} label @param {string} command @param {string[]} args */
844
+ const queue = (label, command, args) =>
845
+ task === 'unit' ? jobs.push({ label, command, args }) : run(command, args)
846
+ const scriptName = scriptsByTask[task]?.find((candidate) => hasScript(candidate))
847
+ const [scriptManager, scriptArgs] = scriptName ? packageCommand(['run', scriptName]) : [null, []]
848
+ const scripted = Boolean(scriptManager)
849
+ if (scriptName && scriptManager) queue(scriptName, scriptManager, scriptArgs)
840
850
  const testFiles = taskTestFiles(/** @type {'unit'|'integration'|'e2e'|'smoke'} */ (task))
841
851
  const javascriptTests = testFiles.filter((file) => /\.(?:[cm]?[jt]sx?)$/i.test(file))
842
852
  const pythonTests = testFiles.filter((file) => /\.py$/i.test(file))
@@ -851,14 +861,16 @@ function ci(task) {
851
861
  (hasLanguage('typescript') || hasLanguage('javascript') || hasLanguage('solidity')) &&
852
862
  hasRootJavascriptProject()
853
863
  ) {
854
- if (javascriptTests.length > 0 || task === 'unit') run('bun', ['test', ...javascriptTests])
864
+ if (javascriptTests.length > 0 || task === 'unit')
865
+ queue('bun', 'bun', ['test', ...javascriptTests])
855
866
  }
856
867
 
857
868
  if (hasLanguage('python') && hasRootPythonProject()) {
858
869
  const python = existsSync(resolve(root, '.venv/bin/python'))
859
870
  ? resolve(root, '.venv/bin/python')
860
871
  : 'python'
861
- if (pythonTests.length > 0 || task === 'unit') run(python, ['-m', 'pytest', ...pythonTests])
872
+ if (pythonTests.length > 0 || task === 'unit')
873
+ queue('pytest', python, ['-m', 'pytest', ...pythonTests])
862
874
  }
863
875
 
864
876
  if (hasLanguage('rust') && hasRootRustProject()) {
@@ -872,7 +884,7 @@ function ci(task) {
872
884
  const args = [...testArgs]
873
885
  if (existsSync(resolve(root, 'src/lib.rs'))) args.push('--lib')
874
886
  if (existsSync(resolve(root, 'src/main.rs'))) args.push('--bin', packageName())
875
- run('cargo', args)
887
+ queue('cargo', 'cargo', args)
876
888
  } else {
877
889
  // Keep --test selection narrow; --tests includes unit targets.
878
890
  const targets = rustTests.flatMap((file) => {
@@ -882,6 +894,13 @@ function ci(task) {
882
894
  if (targets.length > 0) run('cargo', [...testArgs, ...targets])
883
895
  }
884
896
  }
897
+
898
+ if (jobs.length === 1) run(jobs[0].command, jobs[0].args)
899
+ else if (jobs.length > 1)
900
+ run(process.execPath, [
901
+ fileURLToPath(new URL('./lib/run-concurrent.mjs', import.meta.url)),
902
+ JSON.stringify(jobs),
903
+ ])
885
904
  }
886
905
 
887
906
  function packageName() {