code-foundry 1.10.1 → 1.11.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.
@@ -0,0 +1,794 @@
1
+ #!/usr/bin/env node
2
+ // @ts-check
3
+
4
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
5
+ import { resolve } from 'node:path'
6
+ import { spawnSync } from 'node:child_process'
7
+ import { detectPackageManager, resolveProfile } from './lib/profile.mjs'
8
+ import { configured, readConfig } from './lib/config.mjs'
9
+ import { classifyTestFiles } from './lib/test-discovery.mjs'
10
+ import { classifyValidationMode, evaluateValidationGate } from './lib/validation-policy.mjs'
11
+ import { readReleaseConfig, validateGeneratedReleaseDiff } from './lib/release-policy.mjs'
12
+ import { runNodePackagePerformance } from './lib/node-package-performance.mjs'
13
+
14
+ const root = process.cwd()
15
+ const config = readConfig(resolve(root, '.github/code-foundry.yml'))
16
+ const repoProfile = resolveProfile(root)
17
+ /** @type {string[]} */
18
+ const languages = configured(config.languages, repoProfile.languages).split(',').filter(Boolean)
19
+ const packageManager = configured(config.package_manager, detectPackageManager(root))
20
+ const output = process.env.GITHUB_OUTPUT
21
+
22
+ /** @param {string} language */
23
+ function hasLanguage(language) {
24
+ return languages.includes(language) || languages.includes('all')
25
+ }
26
+
27
+ function hasRootJavascriptProject() {
28
+ return Boolean(readPackage()) && packageManager !== 'none'
29
+ }
30
+
31
+ function hasRootPythonProject() {
32
+ return (
33
+ existsSync(resolve(root, 'pyproject.toml')) ||
34
+ existsSync(resolve(root, 'uv.lock')) ||
35
+ capture('git', ['ls-files', '*requirements*.txt']) !== ''
36
+ )
37
+ }
38
+
39
+ function hasRootRustProject() {
40
+ return existsSync(resolve(root, 'Cargo.toml'))
41
+ }
42
+
43
+ /** @param {string} name */
44
+ function hasScript(name) {
45
+ return readPackage()?.scripts?.[name] !== undefined
46
+ }
47
+
48
+ function readPackage() {
49
+ try {
50
+ return JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf8'))
51
+ } catch {
52
+ return null
53
+ }
54
+ }
55
+
56
+ function performanceEnabled() {
57
+ return configured(config.performance, 'auto') !== 'false'
58
+ }
59
+
60
+ function performanceCommands() {
61
+ const raw = configured(config.performance_command, '').trim()
62
+ if (!raw) return []
63
+ let command
64
+ try {
65
+ command = JSON.parse(raw)
66
+ } catch {
67
+ throw new Error('performance_command must be a JSON argv array or an array of argv arrays.')
68
+ }
69
+ if (!Array.isArray(command) || command.length === 0)
70
+ throw new Error('performance_command must be a non-empty JSON array.')
71
+ const commands = command.every((argument) => typeof argument === 'string') ? [command] : command
72
+ if (
73
+ !commands.every(
74
+ (argv) =>
75
+ Array.isArray(argv) &&
76
+ argv.length > 0 &&
77
+ argv.every((argument) => typeof argument === 'string' && argument.length > 0)
78
+ )
79
+ )
80
+ throw new Error(
81
+ 'performance_command must contain one argv array or only non-empty argv arrays of non-empty strings.'
82
+ )
83
+ return /** @type {string[][]} */ (commands)
84
+ }
85
+
86
+ function performanceProfile() {
87
+ const profile = configured(config.performance_profile, '').trim()
88
+ if (!profile) return ''
89
+ if (profile !== 'node-package')
90
+ throw new Error(`Unsupported performance_profile: ${profile}. Expected node-package.`)
91
+ return profile
92
+ }
93
+
94
+ const performanceResultsDirectory = 'performance-results'
95
+
96
+ /** @returns {{source: string, argv: string[]}[]} */
97
+ function selectedPerformanceCommands() {
98
+ const name = ['performance:check', 'perf:check'].find((candidate) => hasScript(candidate))
99
+ if (name) {
100
+ const [manager, args] = packageCommand(['run', name])
101
+ return manager ? [{ source: `package-script:${name}`, argv: [manager, ...args] }] : []
102
+ }
103
+ return performanceCommands().map((argv) => ({ source: 'configuration', argv }))
104
+ }
105
+
106
+ /** @param {string} startedAt @param {'passed'|'failed'} status @param {{source: string, argv: string[], status: number}[]} commands @param {string[]} artifacts @param {string|null} error */
107
+ function writePerformanceSummary(startedAt, status, commands, artifacts, error = null) {
108
+ const directory = resolve(root, performanceResultsDirectory)
109
+ mkdirSync(directory, { recursive: true })
110
+ writeFileSync(
111
+ resolve(directory, 'summary.json'),
112
+ `${JSON.stringify(
113
+ {
114
+ schemaVersion: 1,
115
+ kind: 'code-foundry-performance-summary',
116
+ status,
117
+ startedAt,
118
+ completedAt: new Date().toISOString(),
119
+ commands,
120
+ profile: performanceProfile() || null,
121
+ artifacts,
122
+ error,
123
+ },
124
+ null,
125
+ 2
126
+ )}\n`
127
+ )
128
+ }
129
+
130
+ function runPerformance() {
131
+ if (!performanceEnabled()) return
132
+ const startedAt = new Date().toISOString()
133
+ /** @type {{source: string, argv: string[], status: number}[]} */
134
+ const records = []
135
+ /** @type {string[]} */
136
+ const artifacts = []
137
+ try {
138
+ for (const command of selectedPerformanceCommands()) {
139
+ const result = spawnSync(command.argv[0], command.argv.slice(1), {
140
+ cwd: root,
141
+ stdio: 'inherit',
142
+ env: process.env,
143
+ })
144
+ if (result.error) throw result.error
145
+ const status = result.status ?? 1
146
+ records.push({ ...command, status })
147
+ if (status !== 0) {
148
+ writePerformanceSummary(startedAt, 'failed', records, artifacts, `command exited ${status}`)
149
+ process.exitCode = status
150
+ return
151
+ }
152
+ }
153
+ if (performanceProfile() === 'node-package') {
154
+ const result = runNodePackagePerformance(
155
+ root,
156
+ configured(config.performance_budget_file, 'performance-package-budgets.json'),
157
+ performanceResultsDirectory
158
+ )
159
+ artifacts.push(`${performanceResultsDirectory}/node-package.json`)
160
+ if (!result.passed) {
161
+ writePerformanceSummary(startedAt, 'failed', records, artifacts, result.failures.join('; '))
162
+ process.exitCode = 1
163
+ return
164
+ }
165
+ }
166
+ writePerformanceSummary(startedAt, 'passed', records, artifacts)
167
+ } catch (error) {
168
+ const message = error instanceof Error ? error.message : String(error)
169
+ writePerformanceSummary(startedAt, 'failed', records, artifacts, message)
170
+ throw error
171
+ }
172
+ }
173
+
174
+ /** @param {string} task */
175
+ function validation(task) {
176
+ if (task === 'mode') {
177
+ writeOutput(
178
+ 'mode',
179
+ classifyValidationMode({
180
+ eventName: process.env.FOUNDRY_EVENT_NAME ?? '',
181
+ baseRef: process.env.FOUNDRY_BASE_REF ?? '',
182
+ headRef: process.env.FOUNDRY_HEAD_REF ?? '',
183
+ stagingMode: config.staging_validation_mode,
184
+ })
185
+ )
186
+ return
187
+ }
188
+ if (task === 'gate') {
189
+ const mode = process.env.FOUNDRY_MODE ?? ''
190
+ const gate = evaluateValidationGate({
191
+ mode,
192
+ results: {
193
+ ci: process.env.FOUNDRY_CI,
194
+ test: process.env.FOUNDRY_TEST,
195
+ security: process.env.FOUNDRY_SECURITY,
196
+ codeql: process.env.FOUNDRY_CODEQL,
197
+ },
198
+ })
199
+ if (gate.valid) {
200
+ console.log(`Validation gate passed for ${mode} mode.`)
201
+ return
202
+ }
203
+ for (const failure of gate.failures) console.error(`::error::${failure.job}: ${failure.result}`)
204
+ process.exitCode = 1
205
+ return
206
+ }
207
+ if (task === 'release_diff') {
208
+ const baseSha = process.env.FOUNDRY_BASE_SHA ?? ''
209
+ const changedPaths = baseSha
210
+ ? capture('git', ['diff', '--name-only', `${baseSha}...HEAD`]).split(/\r?\n/)
211
+ : []
212
+ const result = validateGeneratedReleaseDiff({
213
+ headRef: process.env.FOUNDRY_HEAD_REF ?? '',
214
+ headRepo: process.env.FOUNDRY_HEAD_REPO ?? '',
215
+ repository: process.env.FOUNDRY_REPOSITORY ?? '',
216
+ changedPaths,
217
+ config: readReleaseConfig(root),
218
+ root,
219
+ })
220
+ if (!result.valid) {
221
+ for (const error of result.errors) console.error(`::error::${error}`)
222
+ process.exitCode = 1
223
+ return
224
+ }
225
+ console.log(`Release policy passed: ${result.changedPaths.length} approved changed path(s).`)
226
+ return
227
+ }
228
+ throw new Error(`Unknown validation task: ${task || '(missing)'}`)
229
+ }
230
+
231
+ /** @param {string} key @param {unknown} value */
232
+ function writeOutput(key, value) {
233
+ const line = `${key}=${typeof value === 'string' ? value : JSON.stringify(value)}\n`
234
+ if (output) requireWrite(output, line)
235
+ else console.log(line.trimEnd())
236
+ }
237
+
238
+ /** @param {string} file @param {string} content */
239
+ function requireWrite(file, content) {
240
+ appendFileSync(file, content)
241
+ }
242
+
243
+ /** @param {string} command */
244
+ function commandExists(command) {
245
+ return spawnSync(command, ['--version'], { stdio: 'ignore' }).status === 0
246
+ }
247
+
248
+ /** @param {string} command @param {string[]} [args] @param {Record<string, unknown>} [options] */
249
+ function run(command, args = [], options = {}) {
250
+ const result = spawnSync(command, args, {
251
+ cwd: root,
252
+ stdio: 'inherit',
253
+ env: process.env,
254
+ ...options,
255
+ })
256
+ if (result.error) throw result.error
257
+ if (result.status !== 0) process.exit(result.status ?? 1)
258
+ }
259
+
260
+ /** @param {string} command @param {string[]} [args] */
261
+ function capture(command, args = []) {
262
+ const result = spawnSync(command, args, { cwd: root, encoding: 'utf8', env: process.env })
263
+ return result.status === 0 ? result.stdout.trim() : ''
264
+ }
265
+
266
+ /** @returns {string[]} */
267
+ function repositoryTestFiles() {
268
+ return capture('git', ['ls-files'])
269
+ .split(/\r?\n/)
270
+ .map((file) => file.trim())
271
+ .filter(Boolean)
272
+ }
273
+
274
+ /** @param {'unit'|'integration'|'e2e'|'smoke'} task */
275
+ function taskTestFiles(task) {
276
+ return classifyTestFiles(repositoryTestFiles(), task)
277
+ }
278
+
279
+ /** @param {string[]} args @returns {[string|null, string[]]} */
280
+ function packageCommand(args) {
281
+ switch (packageManager) {
282
+ case 'bun':
283
+ return ['bun', args]
284
+ case 'pnpm':
285
+ return ['pnpm', args]
286
+ case 'yarn':
287
+ return ['yarn', args]
288
+ case 'npm':
289
+ return ['npm', args]
290
+ default:
291
+ return [null, args]
292
+ }
293
+ }
294
+
295
+ /** @param {string[]} names */
296
+ function runScript(names) {
297
+ const name = names.find((candidate) => hasScript(candidate))
298
+ if (!name) return false
299
+ const [manager, args] = packageCommand(['run', name])
300
+ if (!manager) return false
301
+ run(manager, args)
302
+ return true
303
+ }
304
+
305
+ /** @param {string} tool @param {string[]} [args] */
306
+ function runTool(tool, args = []) {
307
+ if (['ruff', 'pytest', 'pylint'].includes(tool)) {
308
+ const venvTool = resolve(root, `.venv/bin/${tool}`)
309
+ if (existsSync(venvTool)) return run(venvTool, args)
310
+ }
311
+ const [manager] = packageCommand([])
312
+ if (manager === 'bun') return run('bunx', ['--no-install', tool, ...args])
313
+ if (manager === 'pnpm') return run('pnpm', ['exec', tool, ...args])
314
+ if (manager === 'yarn') return run('yarn', ['exec', tool, ...args])
315
+ if (manager === 'npm') return run('npx', ['--no-install', tool, ...args])
316
+ return run(tool, args)
317
+ }
318
+
319
+ function install() {
320
+ if (existsSync(resolve(root, 'package.json'))) {
321
+ const lock = ['bun.lock', 'bun.lockb', 'pnpm-lock.yaml', 'yarn.lock', 'package-lock.json'].find(
322
+ (file) => existsSync(resolve(root, file))
323
+ )
324
+ if (lock) {
325
+ /** @type {Record<string, [string, string[]]>} */
326
+ const commands = {
327
+ bun: [
328
+ 'bun',
329
+ [
330
+ 'install',
331
+ '--frozen-lockfile',
332
+ '--ignore-scripts',
333
+ ...(readPackage()?.workspaces ? ['--force', '--linker=hoisted'] : []),
334
+ ],
335
+ ],
336
+ pnpm: ['pnpm', ['install', '--frozen-lockfile', '--prefer-offline']],
337
+ yarn: ['yarn', ['install', '--immutable']],
338
+ npm: ['npm', ['ci', '--prefer-offline', '--no-audit', '--fund=false']],
339
+ }
340
+ const command = commands[packageManager]
341
+ if (command) run(command[0], command[1])
342
+ if (packageManager === 'bun') runScript(['prepare', 'postinstall'])
343
+ }
344
+ }
345
+ if (
346
+ hasLanguage('python') &&
347
+ (existsSync(resolve(root, 'pyproject.toml')) || existsSync(resolve(root, 'requirements.txt')))
348
+ ) {
349
+ if (commandExists('uv') && existsSync(resolve(root, 'uv.lock'))) run('uv', ['sync', '--frozen'])
350
+ else {
351
+ if (!existsSync(resolve(root, '.venv'))) run('python', ['-m', 'venv', '.venv'])
352
+ const python = resolve(root, '.venv/bin/python')
353
+ if (spawnSync(python, ['-m', 'pip', '--version'], { stdio: 'ignore' }).status !== 0) {
354
+ run(python, ['-m', 'ensurepip', '--upgrade'])
355
+ }
356
+ if (existsSync(resolve(root, 'requirements.txt')))
357
+ run(python, [
358
+ '-m',
359
+ 'pip',
360
+ 'install',
361
+ '--disable-pip-version-check',
362
+ '-r',
363
+ 'requirements.txt',
364
+ ])
365
+ if (existsSync(resolve(root, 'requirements-dev.txt')))
366
+ run(python, [
367
+ '-m',
368
+ 'pip',
369
+ 'install',
370
+ '--disable-pip-version-check',
371
+ '-r',
372
+ 'requirements-dev.txt',
373
+ ])
374
+ }
375
+ }
376
+ if (hasLanguage('rust') && existsSync(resolve(root, 'Cargo.toml'))) run('cargo', ['fetch'])
377
+ }
378
+
379
+ /** @param {string} task */
380
+ function relevant(task) {
381
+ const js = hasLanguage('typescript') || hasLanguage('javascript')
382
+ const python = hasLanguage('python')
383
+ const rust = hasLanguage('rust')
384
+ const scripted = {
385
+ format: ['format:check', 'format', 'fmt'],
386
+ lint: ['lint'],
387
+ type_check: ['type-check', 'typecheck', 'type:check'],
388
+ build: ['build'],
389
+ unit: ['test:unit', 'test:coverage', 'test'],
390
+ integration: ['test:integration'],
391
+ e2e: ['test:e2e', 'e2e'],
392
+ smoke: ['test:smoke', 'smoke'],
393
+ performance: ['performance:check', 'perf:check'],
394
+ }[task]
395
+ if (task === 'performance') {
396
+ if (!performanceEnabled()) return false
397
+ return Boolean(
398
+ (scripted && scripted.some((candidate) => hasScript(candidate))) ||
399
+ performanceCommands().length > 0 ||
400
+ performanceProfile()
401
+ )
402
+ }
403
+ if (scripted && scripted.some((candidate) => hasScript(candidate)))
404
+ return packageManager !== 'none'
405
+ if (task === 'format' || task === 'lint' || task === 'type_check' || task === 'build') {
406
+ return (
407
+ (js && hasRootJavascriptProject()) ||
408
+ (python && hasRootPythonProject()) ||
409
+ (rust && hasRootRustProject())
410
+ )
411
+ }
412
+ if (['unit', 'integration', 'e2e', 'smoke'].includes(task)) {
413
+ if (taskTestFiles(/** @type {'unit'|'integration'|'e2e'|'smoke'} */ (task)).length > 0)
414
+ return true
415
+ // A project with inline Rust/Python/JavaScript unit tests is still a unit
416
+ // test target even when it has no conventional test file path. Other
417
+ // categories must have an explicit script or discoverable test files so
418
+ // their workflow jobs become skipped instead of successful no-ops.
419
+ if (task === 'unit') {
420
+ return (
421
+ (js && hasRootJavascriptProject()) ||
422
+ (python && hasRootPythonProject()) ||
423
+ (rust && hasRootRustProject()) ||
424
+ (hasLanguage('solidity') && hasRootJavascriptProject())
425
+ )
426
+ }
427
+ return false
428
+ }
429
+ return true
430
+ }
431
+
432
+ /** @param {string} ecosystem */
433
+ function hasDependencyManifest(ecosystem) {
434
+ if (ecosystem === 'javascript') {
435
+ const packageJson = readPackage()
436
+ return (
437
+ ['bun.lock', 'bun.lockb', 'pnpm-lock.yaml', 'yarn.lock', 'package-lock.json'].some((file) =>
438
+ existsSync(resolve(root, file))
439
+ ) ||
440
+ Boolean(
441
+ packageJson &&
442
+ ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'].some(
443
+ (group) => Object.keys(packageJson[group] ?? {}).length
444
+ )
445
+ )
446
+ )
447
+ }
448
+ if (ecosystem === 'rust') return existsSync(resolve(root, 'Cargo.toml'))
449
+ if (ecosystem === 'python')
450
+ return (
451
+ existsSync(resolve(root, 'pyproject.toml')) ||
452
+ existsSync(resolve(root, 'uv.lock')) ||
453
+ capture('git', ['ls-files', '*requirements*.txt']) !== ''
454
+ )
455
+ return false
456
+ }
457
+
458
+ /**
459
+ * Detect repository-owned Oxlint setup: an oxlint dependency (or script
460
+ * reference) or a tracked `.oxlintrc.json` / `oxlint.config.*` file. Oxlint
461
+ * is the baseline's preferred JavaScript linter; repositories without one
462
+ * fall back to their own `lint` script if defined.
463
+ * @returns {boolean}
464
+ */
465
+ function hasOxlintSetup() {
466
+ const pkg = readPackage() ?? {}
467
+ const scripts = pkg.scripts ?? {}
468
+ const dependencies = {
469
+ ...pkg.dependencies,
470
+ ...pkg.devDependencies,
471
+ ...pkg.optionalDependencies,
472
+ ...pkg.peerDependencies,
473
+ }
474
+ if (dependencies.oxlint) return true
475
+ if (Object.values(scripts).some((value) => /\boxlint\b/.test(String(value)))) return true
476
+ return repositoryTestFiles().some((file) =>
477
+ /(^|\/)(\.oxlintrc\.json|oxlint\.config\.[^/]*)$/.test(file)
478
+ )
479
+ }
480
+
481
+ /**
482
+ * Detect repository-owned Oxfmt setup: an oxfmt dependency (or script
483
+ * reference) or a tracked `.oxfmtrc.json` / `oxfmt.config.*` file. Oxfmt is
484
+ * the baseline's formatter; repositories using a different formatter opt out
485
+ * through their own `format`/`fmt` scripts, which the script fallback honors.
486
+ * @returns {boolean}
487
+ */
488
+ function hasOxfmtSetup() {
489
+ const pkg = readPackage() ?? {}
490
+ const scripts = pkg.scripts ?? {}
491
+ const dependencies = {
492
+ ...pkg.dependencies,
493
+ ...pkg.devDependencies,
494
+ ...pkg.optionalDependencies,
495
+ ...pkg.peerDependencies,
496
+ }
497
+ if (dependencies.oxfmt) return true
498
+ if (Object.values(scripts).some((value) => /\boxfmt\b/.test(String(value)))) return true
499
+ return repositoryTestFiles().some((file) =>
500
+ /(^|\/)(\.oxfmtrc\.json|oxfmt\.config\.[^/]*)$/.test(file)
501
+ )
502
+ }
503
+
504
+ /** @param {string} task */
505
+ function ci(task) {
506
+ if (task === 'install') return install()
507
+ if (task === 'should_run' || task === 'task_profile') {
508
+ const selected = process.argv[4]
509
+ writeOutput('applicable', relevant(selected) ? 'true' : 'false')
510
+ writeOutput(
511
+ 'javascript',
512
+ hasLanguage('typescript') || hasLanguage('javascript') ? 'true' : 'false'
513
+ )
514
+ writeOutput('python', hasLanguage('python') ? 'true' : 'false')
515
+ writeOutput('rust', hasLanguage('rust') ? 'true' : 'false')
516
+ return
517
+ }
518
+ if (task === 'format') {
519
+ const scripted = runScript(['format:check', 'format', 'fmt'])
520
+ if (
521
+ !scripted &&
522
+ (hasLanguage('typescript') || hasLanguage('javascript')) &&
523
+ hasRootJavascriptProject() &&
524
+ hasOxfmtSetup()
525
+ ) {
526
+ // Oxfmt is the baseline's formatter. Repositories that use a different
527
+ // formatter keep full control through their own `format`/`fmt` scripts,
528
+ // which the runScript fallback above already honors.
529
+ runTool('oxfmt', ['--check', '.'])
530
+ }
531
+ if (hasLanguage('python') && hasRootPythonProject()) runTool('ruff', ['format', '--check', '.'])
532
+ if (hasLanguage('rust') && hasRootRustProject()) run('cargo', ['fmt', '--check'])
533
+ return
534
+ }
535
+ if (task === 'lint') {
536
+ const scripted = runScript(['lint'])
537
+ if (
538
+ !scripted &&
539
+ (hasLanguage('typescript') || hasLanguage('javascript')) &&
540
+ hasRootJavascriptProject() &&
541
+ hasOxlintSetup()
542
+ ) {
543
+ // Oxlint is the baseline's linter. Repositories that use a different
544
+ // linter keep full control through their own `lint` script, which the
545
+ // runScript fallback above already honors.
546
+ runTool('oxlint', [])
547
+ }
548
+ if (hasLanguage('python') && hasRootPythonProject()) runTool('ruff', ['check', '.'])
549
+ if (hasLanguage('rust') && hasRootRustProject())
550
+ run('cargo', ['clippy', '--all-targets', '--', '-D', 'warnings'])
551
+ return
552
+ }
553
+ if (task === 'type_check') {
554
+ const scripted = runScript(['type-check', 'typecheck', 'type:check'])
555
+ if (!scripted && existsSync(resolve(root, 'tsconfig.json'))) runTool('tsc', ['--noEmit'])
556
+ if (hasLanguage('rust') && hasRootRustProject()) run('cargo', ['check', '--all-targets'])
557
+ return
558
+ }
559
+ if (task === 'build') {
560
+ const scripted = runScript(['build'])
561
+ if (!scripted && hasLanguage('rust') && hasRootRustProject())
562
+ run('cargo', ['build', '--all-targets'])
563
+ return
564
+ }
565
+ if (task === 'performance') {
566
+ return runPerformance()
567
+ }
568
+ /** @type {Record<string, string[]>} */
569
+ const scriptsByTask = {
570
+ unit: ['test:unit', 'test:coverage', 'test'],
571
+ integration: ['test:integration'],
572
+ e2e: ['test:e2e', 'e2e'],
573
+ smoke: ['test:smoke', 'smoke'],
574
+ }
575
+ const scripts = scriptsByTask[task]
576
+ const scripted = scripts ? runScript(scripts) : false
577
+ const testFiles = taskTestFiles(/** @type {'unit'|'integration'|'e2e'|'smoke'} */ (task))
578
+ const javascriptTests = testFiles.filter((file) => /\.(?:[cm]?[jt]sx?)$/i.test(file))
579
+ const pythonTests = testFiles.filter((file) => /\.py$/i.test(file))
580
+ const rustTests = testFiles.filter((file) => /\.rs$/i.test(file))
581
+
582
+ // A repository script is authoritative for that package. When it is absent,
583
+ // use Bun's native runner with only the discovered files for the requested
584
+ // category; this prevents smoke/integration files from being re-run as unit
585
+ // tests and avoids no-op category jobs.
586
+ if (
587
+ !scripted &&
588
+ (hasLanguage('typescript') || hasLanguage('javascript') || hasLanguage('solidity')) &&
589
+ hasRootJavascriptProject()
590
+ ) {
591
+ if (javascriptTests.length > 0 || task === 'unit') run('bun', ['test', ...javascriptTests])
592
+ }
593
+
594
+ if (hasLanguage('python') && hasRootPythonProject()) {
595
+ const python = existsSync(resolve(root, '.venv/bin/python'))
596
+ ? resolve(root, '.venv/bin/python')
597
+ : 'python'
598
+ if (pythonTests.length > 0 || task === 'unit') run(python, ['-m', 'pytest', ...pythonTests])
599
+ }
600
+
601
+ if (hasLanguage('rust') && hasRootRustProject()) {
602
+ if (task === 'unit') {
603
+ if (existsSync(resolve(root, 'src/lib.rs'))) run('cargo', ['test', '--lib'])
604
+ if (existsSync(resolve(root, 'src/main.rs'))) run('cargo', ['test', '--bin', packageName()])
605
+ if (!existsSync(resolve(root, 'src/lib.rs')) && !existsSync(resolve(root, 'src/main.rs')))
606
+ run('cargo', ['test'])
607
+ } else {
608
+ for (const file of rustTests) {
609
+ const match = file.match(/^tests\/(.+)\.rs$/)
610
+ if (match && !match[1].includes('/')) run('cargo', ['test', '--test', match[1]])
611
+ }
612
+ }
613
+ }
614
+ }
615
+
616
+ function packageName() {
617
+ const cargo = readFileSafe(resolve(root, 'Cargo.toml'))
618
+ return cargo?.match(/^name\s*=\s*"([^"]+)"/m)?.[1] ?? 'app'
619
+ }
620
+
621
+ /** @param {string} file */
622
+ function readFileSafe(file) {
623
+ try {
624
+ return readFileSync(file, 'utf8')
625
+ } catch {
626
+ return null
627
+ }
628
+ }
629
+
630
+ /** @param {string} task @param {string} ecosystem */
631
+ function security(task, ecosystem) {
632
+ if (task === 'profile') {
633
+ writeOutput('javascript', hasDependencyManifest('javascript') ? 'true' : 'false')
634
+ writeOutput('rust', hasDependencyManifest('rust') ? 'true' : 'false')
635
+ writeOutput('python', hasDependencyManifest('python') ? 'true' : 'false')
636
+ const requirements = capture('git', ['ls-files', '*requirements*.txt'])
637
+ .split('\n')
638
+ .filter(Boolean)
639
+ writeOutput(
640
+ 'python_requirements',
641
+ hasDependencyManifest('python')
642
+ ? requirements.length
643
+ ? requirements
644
+ : ['project']
645
+ : ['none']
646
+ )
647
+ writeOutput('dependency_review', featureEnabled('dependency_review') ? 'true' : 'false')
648
+ return
649
+ }
650
+ if (task === 'should_run') {
651
+ writeOutput('applicable', hasDependencyManifest(ecosystem) ? 'true' : 'false')
652
+ return
653
+ }
654
+ if (ecosystem === 'javascript' && existsSync(resolve(root, 'package.json'))) {
655
+ const ignores = existsSync(resolve(root, '.github/security-audit-allowlist.txt'))
656
+ ? readFileSync(resolve(root, '.github/security-audit-allowlist.txt'), 'utf8')
657
+ .split(/\r?\n/)
658
+ .filter((line) => line && !line.startsWith('#'))
659
+ : []
660
+ /** @type {Record<string, [string, string[]]>} */
661
+ const commands = {
662
+ bun: [
663
+ 'bun',
664
+ ['audit', '--audit-level=high', ...ignores.flatMap((advisory) => ['--ignore', advisory])],
665
+ ],
666
+ pnpm: ['pnpm', ['audit', '--audit-level', 'high']],
667
+ yarn: ['yarn', ['npm', 'audit', '--all', '--recursive']],
668
+ npm: ['npm', ['audit', '--audit-level=high']],
669
+ }
670
+ const command = commands[packageManager]
671
+ if (command) run(command[0], command[1])
672
+ } else if (ecosystem === 'rust' && existsSync(resolve(root, 'Cargo.toml'))) {
673
+ if (!commandExists('cargo-audit'))
674
+ run('cargo', ['install', 'cargo-audit', '--locked', '--quiet'])
675
+ run('cargo', ['audit'])
676
+ } else if (ecosystem === 'python' && hasDependencyManifest('python')) {
677
+ const requirement = process.env.REPO_FOUNDRY_PYTHON_REQUIREMENT
678
+ const auditArgs =
679
+ requirement && !['project', 'none'].includes(requirement) ? ['-r', requirement] : []
680
+ if (commandExists('uv'))
681
+ run('uv', ['tool', 'run', '--from', 'pip-audit==2.10.1', 'pip-audit', ...auditArgs])
682
+ else {
683
+ run('python', ['-m', 'pip', 'install', '--quiet', 'pip-audit==2.10.1'])
684
+ run('python', ['-m', 'pip_audit', ...auditArgs])
685
+ }
686
+ }
687
+ }
688
+
689
+ /** @param {string} key */
690
+ function featureEnabled(key) {
691
+ const policy = config[key] ?? 'auto'
692
+ if (policy === 'false') return false
693
+ if (policy === 'true') return true
694
+ return isPublicRepository()
695
+ }
696
+
697
+ function isPublicRepository() {
698
+ const visibility = process.env.REPO_FOUNDRY_VISIBILITY
699
+ return visibility ? visibility === 'public' : process.env.REPO_FOUNDRY_PRIVATE !== 'true'
700
+ }
701
+
702
+ function codeql() {
703
+ const enabled =
704
+ featureEnabled('codeql') &&
705
+ (isPublicRepository() || process.env.REPO_FOUNDRY_CODE_SECURITY === 'enabled')
706
+ writeOutput('enabled', enabled ? 'true' : 'false')
707
+ if (!enabled) {
708
+ writeOutput('languages', [])
709
+ for (const language of ['actions', 'javascript', 'python', 'rust']) {
710
+ writeOutput(`${language}_available`, 'false')
711
+ writeOutput(`${language}_changed`, 'false')
712
+ writeOutput(`${language}_build_mode`, 'none')
713
+ }
714
+ return
715
+ }
716
+ const available = []
717
+ if (existsSync(resolve(root, '.github/workflows'))) available.push('actions')
718
+ if (hasLanguage('typescript') || hasLanguage('javascript'))
719
+ available.push('javascript-typescript')
720
+ if (hasLanguage('python')) available.push('python')
721
+ if (hasLanguage('rust')) available.push('rust')
722
+ // Upload every configured language on every analysis run so GitHub can
723
+ // compare pull requests against the base branch's code-scanning config.
724
+ const languagesJson = available.map((language) => ({
725
+ language,
726
+ name:
727
+ language === 'javascript-typescript'
728
+ ? 'TypeScript'
729
+ : language[0].toUpperCase() + language.slice(1),
730
+ 'build-mode': 'none',
731
+ changed: true,
732
+ }))
733
+ writeOutput('languages', languagesJson)
734
+ for (const language of ['actions', 'javascript-typescript', 'python', 'rust']) {
735
+ const entry = languagesJson.find((item) => item.language === language)
736
+ const prefix = language === 'javascript-typescript' ? 'javascript' : language
737
+ writeOutput(`${prefix}_available`, entry ? 'true' : 'false')
738
+ writeOutput(`${prefix}_changed`, entry?.changed ? 'true' : 'false')
739
+ writeOutput(`${prefix}_build_mode`, entry?.['build-mode'] ?? 'none')
740
+ }
741
+ }
742
+
743
+ /** @param {string} command */
744
+ function printProfile(command) {
745
+ if (command === 'get') {
746
+ const key = process.argv[4]
747
+ console.log(
748
+ config[key] ??
749
+ (key === 'languages'
750
+ ? languages.join(',')
751
+ : key === 'package_manager'
752
+ ? packageManager
753
+ : '')
754
+ )
755
+ return
756
+ }
757
+ writeOutput('languages', languages.join(','))
758
+ writeOutput('package_manager', packageManager)
759
+ writeOutput('release_type', config.release_type ?? 'auto')
760
+ writeOutput('npm_publish', config.npm_publish ?? 'false')
761
+ }
762
+
763
+ function preCommit() {
764
+ const changed = capture('git', ['diff', '--cached', '--name-only'])
765
+ if (!changed) return
766
+ const check = spawnSync('git', ['diff', '--cached', '--check'], { cwd: root, stdio: 'inherit' })
767
+ if (check.status !== 0) process.exit(check.status ?? 1)
768
+ if (/\.(js|jsx|ts|tsx|json|md|mdx|yml|yaml)$/.test(changed)) {
769
+ ci('format')
770
+ ci('lint')
771
+ }
772
+ if (/\.rs$|(^|\/)Cargo\.toml$/.test(changed)) {
773
+ run('cargo', ['fmt', '--check'])
774
+ run('cargo', ['clippy', '--all-targets', '--', '-D', 'warnings'])
775
+ }
776
+ if (/\.py$|(^|\/)(pyproject\.toml|requirements[^/]*\.txt)$/.test(changed)) {
777
+ runTool('ruff', ['format', '--check', '.'])
778
+ runTool('ruff', ['check', '.'])
779
+ }
780
+ }
781
+
782
+ const [area, task, ecosystem] = process.argv.slice(2)
783
+ try {
784
+ if (area === 'ci') ci(task)
785
+ else if (area === 'security') security(task, ecosystem)
786
+ else if (area === 'codeql') codeql()
787
+ else if (area === 'profile') printProfile(task)
788
+ else if (area === 'validation') validation(task)
789
+ else if (area === 'pre-commit') preCommit()
790
+ else throw new Error(`Unknown runtime command: ${area || '(missing)'}`)
791
+ } catch (error) {
792
+ console.error(error instanceof Error ? error.message : String(error))
793
+ process.exitCode = 1
794
+ }