code-foundry 1.53.3 → 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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
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
+
3
10
  ## [1.53.3](https://github.com/0xPlayerOne/code-foundry/compare/v1.53.2...v1.53.3) (2026-10-30)
4
11
 
5
12
 
@@ -143,16 +150,9 @@
143
150
 
144
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))
145
152
 
146
- ## [1.48.7](https://github.com/0xPlayerOne/code-foundry/compare/v1.48.6...v1.48.7) (2026-10-23)
147
-
148
-
149
- ### Bug Fixes
150
-
151
- * **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))
152
-
153
153
  <!-- code-foundry: packaged changelog -->
154
154
 
155
155
  ---
156
156
 
157
- This packaged changelog lists the 20 most recent releases; 274 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.
@@ -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.3",
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() {