code-foundry 1.2.1 → 1.3.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/.github/workflows/ci.yml +1 -1
- package/.oxfmtrc.json +7 -0
- package/.oxlintrc.json +5 -0
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +7 -0
- package/README.md +3 -2
- package/docs/CONFIGURATION.md +24 -24
- package/docs/WORKFLOWS.md +16 -16
- package/package.json +20 -17
- package/src/cli.mjs +60 -14
- package/src/commands/ci.mjs +166 -57
- package/src/commands/doctor.mjs +98 -31
- package/src/commands/fleet.mjs +139 -29
- package/src/commands/release.mjs +378 -96
- package/src/commands/sync.mjs +332 -104
- package/src/lib/config.mjs +8 -2
- package/src/lib/github-doctor.mjs +153 -51
- package/src/lib/overlay.mjs +6 -2
- package/src/lib/profile.mjs +26 -8
- package/src/lib/release-hook.mjs +17 -5
- package/src/lib/release-manifest.mjs +81 -19
- package/src/lib/release-policy.mjs +157 -55
- package/src/lib/test-discovery.mjs +7 -2
- package/src/runtime.mjs +259 -61
- package/.prettierignore +0 -9
- package/.prettierrc +0 -6
package/src/runtime.mjs
CHANGED
|
@@ -28,9 +28,11 @@ function hasRootJavascriptProject() {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
function hasRootPythonProject() {
|
|
31
|
-
return
|
|
31
|
+
return (
|
|
32
|
+
existsSync(resolve(root, 'pyproject.toml')) ||
|
|
32
33
|
existsSync(resolve(root, 'uv.lock')) ||
|
|
33
34
|
capture('git', ['ls-files', '*requirements*.txt']) !== ''
|
|
35
|
+
)
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
function hasRootRustProject() {
|
|
@@ -43,18 +45,25 @@ function hasScript(name) {
|
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
function readPackage() {
|
|
46
|
-
try {
|
|
48
|
+
try {
|
|
49
|
+
return JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf8'))
|
|
50
|
+
} catch {
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
/** @param {string} task */
|
|
50
56
|
function validation(task) {
|
|
51
57
|
if (task === 'mode') {
|
|
52
|
-
writeOutput(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
writeOutput(
|
|
59
|
+
'mode',
|
|
60
|
+
classifyValidationMode({
|
|
61
|
+
eventName: process.env.FOUNDRY_EVENT_NAME ?? '',
|
|
62
|
+
baseRef: process.env.FOUNDRY_BASE_REF ?? '',
|
|
63
|
+
headRef: process.env.FOUNDRY_HEAD_REF ?? '',
|
|
64
|
+
stagingMode: config.staging_validation_mode,
|
|
65
|
+
})
|
|
66
|
+
)
|
|
58
67
|
return
|
|
59
68
|
}
|
|
60
69
|
if (task === 'gate') {
|
|
@@ -78,7 +87,9 @@ function validation(task) {
|
|
|
78
87
|
}
|
|
79
88
|
if (task === 'release_diff') {
|
|
80
89
|
const baseSha = process.env.FOUNDRY_BASE_SHA ?? ''
|
|
81
|
-
const changedPaths = baseSha
|
|
90
|
+
const changedPaths = baseSha
|
|
91
|
+
? capture('git', ['diff', '--name-only', `${baseSha}...HEAD`]).split(/\r?\n/)
|
|
92
|
+
: []
|
|
82
93
|
const result = validateGeneratedReleaseDiff({
|
|
83
94
|
headRef: process.env.FOUNDRY_HEAD_REF ?? '',
|
|
84
95
|
headRepo: process.env.FOUNDRY_HEAD_REPO ?? '',
|
|
@@ -117,7 +128,12 @@ function commandExists(command) {
|
|
|
117
128
|
|
|
118
129
|
/** @param {string} command @param {string[]} [args] @param {Record<string, unknown>} [options] */
|
|
119
130
|
function run(command, args = [], options = {}) {
|
|
120
|
-
const result = spawnSync(command, args, {
|
|
131
|
+
const result = spawnSync(command, args, {
|
|
132
|
+
cwd: root,
|
|
133
|
+
stdio: 'inherit',
|
|
134
|
+
env: process.env,
|
|
135
|
+
...options,
|
|
136
|
+
})
|
|
121
137
|
if (result.error) throw result.error
|
|
122
138
|
if (result.status !== 0) process.exit(result.status ?? 1)
|
|
123
139
|
}
|
|
@@ -144,11 +160,16 @@ function taskTestFiles(task) {
|
|
|
144
160
|
/** @param {string[]} args @returns {[string|null, string[]]} */
|
|
145
161
|
function packageCommand(args) {
|
|
146
162
|
switch (packageManager) {
|
|
147
|
-
case 'bun':
|
|
148
|
-
|
|
149
|
-
case '
|
|
150
|
-
|
|
151
|
-
|
|
163
|
+
case 'bun':
|
|
164
|
+
return ['bun', args]
|
|
165
|
+
case 'pnpm':
|
|
166
|
+
return ['pnpm', args]
|
|
167
|
+
case 'yarn':
|
|
168
|
+
return ['yarn', args]
|
|
169
|
+
case 'npm':
|
|
170
|
+
return ['npm', args]
|
|
171
|
+
default:
|
|
172
|
+
return [null, args]
|
|
152
173
|
}
|
|
153
174
|
}
|
|
154
175
|
|
|
@@ -178,11 +199,21 @@ function runTool(tool, args = []) {
|
|
|
178
199
|
|
|
179
200
|
function install() {
|
|
180
201
|
if (existsSync(resolve(root, 'package.json'))) {
|
|
181
|
-
const lock = ['bun.lock', 'bun.lockb', 'pnpm-lock.yaml', 'yarn.lock', 'package-lock.json'].find(
|
|
202
|
+
const lock = ['bun.lock', 'bun.lockb', 'pnpm-lock.yaml', 'yarn.lock', 'package-lock.json'].find(
|
|
203
|
+
(file) => existsSync(resolve(root, file))
|
|
204
|
+
)
|
|
182
205
|
if (lock) {
|
|
183
206
|
/** @type {Record<string, [string, string[]]>} */
|
|
184
207
|
const commands = {
|
|
185
|
-
bun: [
|
|
208
|
+
bun: [
|
|
209
|
+
'bun',
|
|
210
|
+
[
|
|
211
|
+
'install',
|
|
212
|
+
'--frozen-lockfile',
|
|
213
|
+
'--ignore-scripts',
|
|
214
|
+
...(readPackage()?.workspaces ? ['--force', '--linker=hoisted'] : []),
|
|
215
|
+
],
|
|
216
|
+
],
|
|
186
217
|
pnpm: ['pnpm', ['install', '--frozen-lockfile', '--prefer-offline']],
|
|
187
218
|
yarn: ['yarn', ['install', '--immutable']],
|
|
188
219
|
npm: ['npm', ['ci', '--prefer-offline', '--no-audit', '--fund=false']],
|
|
@@ -192,7 +223,10 @@ function install() {
|
|
|
192
223
|
if (packageManager === 'bun') runScript(['prepare', 'postinstall'])
|
|
193
224
|
}
|
|
194
225
|
}
|
|
195
|
-
if (
|
|
226
|
+
if (
|
|
227
|
+
hasLanguage('python') &&
|
|
228
|
+
(existsSync(resolve(root, 'pyproject.toml')) || existsSync(resolve(root, 'requirements.txt')))
|
|
229
|
+
) {
|
|
196
230
|
if (commandExists('uv') && existsSync(resolve(root, 'uv.lock'))) run('uv', ['sync', '--frozen'])
|
|
197
231
|
else {
|
|
198
232
|
if (!existsSync(resolve(root, '.venv'))) run('python', ['-m', 'venv', '.venv'])
|
|
@@ -200,8 +234,24 @@ function install() {
|
|
|
200
234
|
if (spawnSync(python, ['-m', 'pip', '--version'], { stdio: 'ignore' }).status !== 0) {
|
|
201
235
|
run(python, ['-m', 'ensurepip', '--upgrade'])
|
|
202
236
|
}
|
|
203
|
-
if (existsSync(resolve(root, 'requirements.txt')))
|
|
204
|
-
|
|
237
|
+
if (existsSync(resolve(root, 'requirements.txt')))
|
|
238
|
+
run(python, [
|
|
239
|
+
'-m',
|
|
240
|
+
'pip',
|
|
241
|
+
'install',
|
|
242
|
+
'--disable-pip-version-check',
|
|
243
|
+
'-r',
|
|
244
|
+
'requirements.txt',
|
|
245
|
+
])
|
|
246
|
+
if (existsSync(resolve(root, 'requirements-dev.txt')))
|
|
247
|
+
run(python, [
|
|
248
|
+
'-m',
|
|
249
|
+
'pip',
|
|
250
|
+
'install',
|
|
251
|
+
'--disable-pip-version-check',
|
|
252
|
+
'-r',
|
|
253
|
+
'requirements-dev.txt',
|
|
254
|
+
])
|
|
205
255
|
}
|
|
206
256
|
}
|
|
207
257
|
if (hasLanguage('rust') && existsSync(resolve(root, 'Cargo.toml'))) run('cargo', ['fetch'])
|
|
@@ -222,19 +272,29 @@ function relevant(task) {
|
|
|
222
272
|
e2e: ['test:e2e', 'e2e'],
|
|
223
273
|
smoke: ['test:smoke', 'smoke'],
|
|
224
274
|
}[task]
|
|
225
|
-
if (scripted && scripted.some((candidate) => hasScript(candidate)))
|
|
275
|
+
if (scripted && scripted.some((candidate) => hasScript(candidate)))
|
|
276
|
+
return packageManager !== 'none'
|
|
226
277
|
if (task === 'format' || task === 'lint' || task === 'type_check' || task === 'build') {
|
|
227
|
-
return (
|
|
278
|
+
return (
|
|
279
|
+
(js && hasRootJavascriptProject()) ||
|
|
280
|
+
(python && hasRootPythonProject()) ||
|
|
281
|
+
(rust && hasRootRustProject())
|
|
282
|
+
)
|
|
228
283
|
}
|
|
229
284
|
if (['unit', 'integration', 'e2e', 'smoke'].includes(task)) {
|
|
230
|
-
if (taskTestFiles(/** @type {'unit'|'integration'|'e2e'|'smoke'} */ (task)).length > 0)
|
|
285
|
+
if (taskTestFiles(/** @type {'unit'|'integration'|'e2e'|'smoke'} */ (task)).length > 0)
|
|
286
|
+
return true
|
|
231
287
|
// A project with inline Rust/Python/JavaScript unit tests is still a unit
|
|
232
288
|
// test target even when it has no conventional test file path. Other
|
|
233
289
|
// categories must have an explicit script or discoverable test files so
|
|
234
290
|
// their workflow jobs become skipped instead of successful no-ops.
|
|
235
291
|
if (task === 'unit') {
|
|
236
|
-
return (
|
|
237
|
-
(
|
|
292
|
+
return (
|
|
293
|
+
(js && hasRootJavascriptProject()) ||
|
|
294
|
+
(python && hasRootPythonProject()) ||
|
|
295
|
+
(rust && hasRootRustProject()) ||
|
|
296
|
+
(hasLanguage('solidity') && hasRootJavascriptProject())
|
|
297
|
+
)
|
|
238
298
|
}
|
|
239
299
|
return false
|
|
240
300
|
}
|
|
@@ -245,34 +305,95 @@ function relevant(task) {
|
|
|
245
305
|
function hasDependencyManifest(ecosystem) {
|
|
246
306
|
if (ecosystem === 'javascript') {
|
|
247
307
|
const packageJson = readPackage()
|
|
248
|
-
return
|
|
249
|
-
|
|
308
|
+
return (
|
|
309
|
+
['bun.lock', 'bun.lockb', 'pnpm-lock.yaml', 'yarn.lock', 'package-lock.json'].some((file) =>
|
|
310
|
+
existsSync(resolve(root, file))
|
|
311
|
+
) ||
|
|
312
|
+
Boolean(
|
|
313
|
+
packageJson &&
|
|
314
|
+
['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'].some(
|
|
315
|
+
(group) => Object.keys(packageJson[group] ?? {}).length
|
|
316
|
+
)
|
|
317
|
+
)
|
|
318
|
+
)
|
|
250
319
|
}
|
|
251
320
|
if (ecosystem === 'rust') return existsSync(resolve(root, 'Cargo.toml'))
|
|
252
|
-
if (ecosystem === 'python')
|
|
321
|
+
if (ecosystem === 'python')
|
|
322
|
+
return (
|
|
323
|
+
existsSync(resolve(root, 'pyproject.toml')) ||
|
|
324
|
+
existsSync(resolve(root, 'uv.lock')) ||
|
|
325
|
+
capture('git', ['ls-files', '*requirements*.txt']) !== ''
|
|
326
|
+
)
|
|
253
327
|
return false
|
|
254
328
|
}
|
|
255
329
|
|
|
256
330
|
/**
|
|
257
|
-
* Detect repository-owned ESLint setup:
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
331
|
+
* Detect repository-owned ESLint setup: an eslint dependency (or script
|
|
332
|
+
* reference), or a tracked flat/legacy config file. Repositories without any
|
|
333
|
+
* of these have nothing for the fallback runner to enforce, so lint skips
|
|
334
|
+
* instead of forcing a network fetch of ESLint in CI.
|
|
261
335
|
* @returns {boolean}
|
|
262
336
|
*/
|
|
263
337
|
function hasEslintSetup() {
|
|
264
338
|
const pkg = readPackage() ?? {}
|
|
265
339
|
const scripts = pkg.scripts ?? {}
|
|
266
|
-
if (scripts.lint) return true
|
|
267
340
|
const dependencies = {
|
|
268
|
-
...
|
|
269
|
-
...
|
|
270
|
-
...
|
|
271
|
-
...
|
|
341
|
+
...pkg.dependencies,
|
|
342
|
+
...pkg.devDependencies,
|
|
343
|
+
...pkg.optionalDependencies,
|
|
344
|
+
...pkg.peerDependencies,
|
|
272
345
|
}
|
|
273
346
|
if (dependencies.eslint) return true
|
|
274
347
|
if (Object.values(scripts).some((value) => /\beslint\b/.test(String(value)))) return true
|
|
275
|
-
return repositoryTestFiles().some((file) =>
|
|
348
|
+
return repositoryTestFiles().some((file) =>
|
|
349
|
+
/(^|\/)(\.eslintrc[^/]*|eslint\.config\.[^/]*)$/.test(file)
|
|
350
|
+
)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Detect repository-owned Oxlint setup: an oxlint dependency (or script
|
|
355
|
+
* reference) or a tracked `.oxlintrc.json` / `oxlint.config.*` file. Oxlint
|
|
356
|
+
* is the baseline's preferred JavaScript linter; the ESLint fallback only
|
|
357
|
+
* runs when no Oxlint setup exists.
|
|
358
|
+
* @returns {boolean}
|
|
359
|
+
*/
|
|
360
|
+
function hasOxlintSetup() {
|
|
361
|
+
const pkg = readPackage() ?? {}
|
|
362
|
+
const scripts = pkg.scripts ?? {}
|
|
363
|
+
const dependencies = {
|
|
364
|
+
...pkg.dependencies,
|
|
365
|
+
...pkg.devDependencies,
|
|
366
|
+
...pkg.optionalDependencies,
|
|
367
|
+
...pkg.peerDependencies,
|
|
368
|
+
}
|
|
369
|
+
if (dependencies.oxlint) return true
|
|
370
|
+
if (Object.values(scripts).some((value) => /\boxlint\b/.test(String(value)))) return true
|
|
371
|
+
return repositoryTestFiles().some((file) =>
|
|
372
|
+
/(^|\/)(\.oxlintrc\.json|oxlint\.config\.[^/]*)$/.test(file)
|
|
373
|
+
)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Detect repository-owned Oxfmt setup: an oxfmt dependency (or script
|
|
378
|
+
* reference) or a tracked `.oxfmtrc.json` / `oxfmt.config.*` file. Oxfmt is
|
|
379
|
+
* the baseline's preferred JavaScript formatter; the Prettier fallback only
|
|
380
|
+
* runs when no Oxfmt setup exists.
|
|
381
|
+
* @returns {boolean}
|
|
382
|
+
*/
|
|
383
|
+
function hasOxfmtSetup() {
|
|
384
|
+
const pkg = readPackage() ?? {}
|
|
385
|
+
const scripts = pkg.scripts ?? {}
|
|
386
|
+
const dependencies = {
|
|
387
|
+
...pkg.dependencies,
|
|
388
|
+
...pkg.devDependencies,
|
|
389
|
+
...pkg.optionalDependencies,
|
|
390
|
+
...pkg.peerDependencies,
|
|
391
|
+
}
|
|
392
|
+
if (dependencies.oxfmt) return true
|
|
393
|
+
if (Object.values(scripts).some((value) => /\boxfmt\b/.test(String(value)))) return true
|
|
394
|
+
return repositoryTestFiles().some((file) =>
|
|
395
|
+
/(^|\/)(\.oxfmtrc\.json|oxfmt\.config\.[^/]*)$/.test(file)
|
|
396
|
+
)
|
|
276
397
|
}
|
|
277
398
|
|
|
278
399
|
/** @param {string} task */
|
|
@@ -281,23 +402,45 @@ function ci(task) {
|
|
|
281
402
|
if (task === 'should_run' || task === 'task_profile') {
|
|
282
403
|
const selected = process.argv[4]
|
|
283
404
|
writeOutput('applicable', relevant(selected) ? 'true' : 'false')
|
|
284
|
-
writeOutput(
|
|
405
|
+
writeOutput(
|
|
406
|
+
'javascript',
|
|
407
|
+
hasLanguage('typescript') || hasLanguage('javascript') ? 'true' : 'false'
|
|
408
|
+
)
|
|
285
409
|
writeOutput('python', hasLanguage('python') ? 'true' : 'false')
|
|
286
410
|
writeOutput('rust', hasLanguage('rust') ? 'true' : 'false')
|
|
287
411
|
return
|
|
288
412
|
}
|
|
289
413
|
if (task === 'format') {
|
|
290
414
|
const scripted = runScript(['format:check', 'format', 'fmt'])
|
|
291
|
-
if (
|
|
415
|
+
if (
|
|
416
|
+
!scripted &&
|
|
417
|
+
(hasLanguage('typescript') || hasLanguage('javascript')) &&
|
|
418
|
+
hasRootJavascriptProject()
|
|
419
|
+
) {
|
|
420
|
+
// Oxfmt is the preferred formatter; Prettier remains the fallback for
|
|
421
|
+
// repositories that have not migrated.
|
|
422
|
+
if (hasOxfmtSetup()) runTool('oxfmt', ['--check', '.'])
|
|
423
|
+
else runTool('prettier', ['--check', '.'])
|
|
424
|
+
}
|
|
292
425
|
if (hasLanguage('python') && hasRootPythonProject()) runTool('ruff', ['format', '--check', '.'])
|
|
293
426
|
if (hasLanguage('rust') && hasRootRustProject()) run('cargo', ['fmt', '--check'])
|
|
294
427
|
return
|
|
295
428
|
}
|
|
296
429
|
if (task === 'lint') {
|
|
297
430
|
const scripted = runScript(['lint'])
|
|
298
|
-
if (
|
|
431
|
+
if (
|
|
432
|
+
!scripted &&
|
|
433
|
+
(hasLanguage('typescript') || hasLanguage('javascript')) &&
|
|
434
|
+
hasRootJavascriptProject()
|
|
435
|
+
) {
|
|
436
|
+
// Oxlint is the preferred linter; ESLint remains the fallback for
|
|
437
|
+
// repositories that have not migrated.
|
|
438
|
+
if (hasOxlintSetup()) runTool('oxlint', [])
|
|
439
|
+
else if (hasEslintSetup()) runTool('eslint', ['.'])
|
|
440
|
+
}
|
|
299
441
|
if (hasLanguage('python') && hasRootPythonProject()) runTool('ruff', ['check', '.'])
|
|
300
|
-
if (hasLanguage('rust') && hasRootRustProject())
|
|
442
|
+
if (hasLanguage('rust') && hasRootRustProject())
|
|
443
|
+
run('cargo', ['clippy', '--all-targets', '--', '-D', 'warnings'])
|
|
301
444
|
return
|
|
302
445
|
}
|
|
303
446
|
if (task === 'type_check') {
|
|
@@ -308,7 +451,8 @@ function ci(task) {
|
|
|
308
451
|
}
|
|
309
452
|
if (task === 'build') {
|
|
310
453
|
const scripted = runScript(['build'])
|
|
311
|
-
if (!scripted && hasLanguage('rust') && hasRootRustProject())
|
|
454
|
+
if (!scripted && hasLanguage('rust') && hasRootRustProject())
|
|
455
|
+
run('cargo', ['build', '--all-targets'])
|
|
312
456
|
return
|
|
313
457
|
}
|
|
314
458
|
/** @type {Record<string, string[]>} */
|
|
@@ -329,12 +473,18 @@ function ci(task) {
|
|
|
329
473
|
// use Bun's native runner with only the discovered files for the requested
|
|
330
474
|
// category; this prevents smoke/integration files from being re-run as unit
|
|
331
475
|
// tests and avoids no-op category jobs.
|
|
332
|
-
if (
|
|
476
|
+
if (
|
|
477
|
+
!scripted &&
|
|
478
|
+
(hasLanguage('typescript') || hasLanguage('javascript') || hasLanguage('solidity')) &&
|
|
479
|
+
hasRootJavascriptProject()
|
|
480
|
+
) {
|
|
333
481
|
if (javascriptTests.length > 0 || task === 'unit') run('bun', ['test', ...javascriptTests])
|
|
334
482
|
}
|
|
335
483
|
|
|
336
484
|
if (hasLanguage('python') && hasRootPythonProject()) {
|
|
337
|
-
const python = existsSync(resolve(root, '.venv/bin/python'))
|
|
485
|
+
const python = existsSync(resolve(root, '.venv/bin/python'))
|
|
486
|
+
? resolve(root, '.venv/bin/python')
|
|
487
|
+
: 'python'
|
|
338
488
|
if (pythonTests.length > 0 || task === 'unit') run(python, ['-m', 'pytest', ...pythonTests])
|
|
339
489
|
}
|
|
340
490
|
|
|
@@ -342,7 +492,8 @@ function ci(task) {
|
|
|
342
492
|
if (task === 'unit') {
|
|
343
493
|
if (existsSync(resolve(root, 'src/lib.rs'))) run('cargo', ['test', '--lib'])
|
|
344
494
|
if (existsSync(resolve(root, 'src/main.rs'))) run('cargo', ['test', '--bin', packageName()])
|
|
345
|
-
if (!existsSync(resolve(root, 'src/lib.rs')) && !existsSync(resolve(root, 'src/main.rs')))
|
|
495
|
+
if (!existsSync(resolve(root, 'src/lib.rs')) && !existsSync(resolve(root, 'src/main.rs')))
|
|
496
|
+
run('cargo', ['test'])
|
|
346
497
|
} else {
|
|
347
498
|
for (const file of rustTests) {
|
|
348
499
|
const match = file.match(/^tests\/(.+)\.rs$/)
|
|
@@ -359,7 +510,11 @@ function packageName() {
|
|
|
359
510
|
|
|
360
511
|
/** @param {string} file */
|
|
361
512
|
function readFileSafe(file) {
|
|
362
|
-
try {
|
|
513
|
+
try {
|
|
514
|
+
return readFileSync(file, 'utf8')
|
|
515
|
+
} catch {
|
|
516
|
+
return null
|
|
517
|
+
}
|
|
363
518
|
}
|
|
364
519
|
|
|
365
520
|
/** @param {string} task @param {string} ecosystem */
|
|
@@ -368,8 +523,17 @@ function security(task, ecosystem) {
|
|
|
368
523
|
writeOutput('javascript', hasDependencyManifest('javascript') ? 'true' : 'false')
|
|
369
524
|
writeOutput('rust', hasDependencyManifest('rust') ? 'true' : 'false')
|
|
370
525
|
writeOutput('python', hasDependencyManifest('python') ? 'true' : 'false')
|
|
371
|
-
const requirements = capture('git', ['ls-files', '*requirements*.txt'])
|
|
372
|
-
|
|
526
|
+
const requirements = capture('git', ['ls-files', '*requirements*.txt'])
|
|
527
|
+
.split('\n')
|
|
528
|
+
.filter(Boolean)
|
|
529
|
+
writeOutput(
|
|
530
|
+
'python_requirements',
|
|
531
|
+
hasDependencyManifest('python')
|
|
532
|
+
? requirements.length
|
|
533
|
+
? requirements
|
|
534
|
+
: ['project']
|
|
535
|
+
: ['none']
|
|
536
|
+
)
|
|
373
537
|
writeOutput('dependency_review', featureEnabled('dependency_review') ? 'true' : 'false')
|
|
374
538
|
return
|
|
375
539
|
}
|
|
@@ -379,11 +543,16 @@ function security(task, ecosystem) {
|
|
|
379
543
|
}
|
|
380
544
|
if (ecosystem === 'javascript' && existsSync(resolve(root, 'package.json'))) {
|
|
381
545
|
const ignores = existsSync(resolve(root, '.github/security-audit-allowlist.txt'))
|
|
382
|
-
? readFileSync(resolve(root, '.github/security-audit-allowlist.txt'), 'utf8')
|
|
546
|
+
? readFileSync(resolve(root, '.github/security-audit-allowlist.txt'), 'utf8')
|
|
547
|
+
.split(/\r?\n/)
|
|
548
|
+
.filter((line) => line && !line.startsWith('#'))
|
|
383
549
|
: []
|
|
384
550
|
/** @type {Record<string, [string, string[]]>} */
|
|
385
551
|
const commands = {
|
|
386
|
-
bun: [
|
|
552
|
+
bun: [
|
|
553
|
+
'bun',
|
|
554
|
+
['audit', '--audit-level=high', ...ignores.flatMap((advisory) => ['--ignore', advisory])],
|
|
555
|
+
],
|
|
387
556
|
pnpm: ['pnpm', ['audit', '--audit-level', 'high']],
|
|
388
557
|
yarn: ['yarn', ['npm', 'audit', '--all', '--recursive']],
|
|
389
558
|
npm: ['npm', ['audit', '--audit-level=high']],
|
|
@@ -391,12 +560,15 @@ function security(task, ecosystem) {
|
|
|
391
560
|
const command = commands[packageManager]
|
|
392
561
|
if (command) run(command[0], command[1])
|
|
393
562
|
} else if (ecosystem === 'rust' && existsSync(resolve(root, 'Cargo.toml'))) {
|
|
394
|
-
if (!commandExists('cargo-audit'))
|
|
563
|
+
if (!commandExists('cargo-audit'))
|
|
564
|
+
run('cargo', ['install', 'cargo-audit', '--locked', '--quiet'])
|
|
395
565
|
run('cargo', ['audit'])
|
|
396
566
|
} else if (ecosystem === 'python' && hasDependencyManifest('python')) {
|
|
397
567
|
const requirement = process.env.REPO_FOUNDRY_PYTHON_REQUIREMENT
|
|
398
|
-
const auditArgs =
|
|
399
|
-
|
|
568
|
+
const auditArgs =
|
|
569
|
+
requirement && !['project', 'none'].includes(requirement) ? ['-r', requirement] : []
|
|
570
|
+
if (commandExists('uv'))
|
|
571
|
+
run('uv', ['tool', 'run', '--from', 'pip-audit==2.10.1', 'pip-audit', ...auditArgs])
|
|
400
572
|
else {
|
|
401
573
|
run('python', ['-m', 'pip', 'install', '--quiet', 'pip-audit==2.10.1'])
|
|
402
574
|
run('python', ['-m', 'pip_audit', ...auditArgs])
|
|
@@ -418,7 +590,8 @@ function isPublicRepository() {
|
|
|
418
590
|
}
|
|
419
591
|
|
|
420
592
|
function codeql() {
|
|
421
|
-
const enabled =
|
|
593
|
+
const enabled =
|
|
594
|
+
featureEnabled('codeql') &&
|
|
422
595
|
(isPublicRepository() || process.env.REPO_FOUNDRY_CODE_SECURITY === 'enabled')
|
|
423
596
|
writeOutput('enabled', enabled ? 'true' : 'false')
|
|
424
597
|
if (!enabled) {
|
|
@@ -432,12 +605,21 @@ function codeql() {
|
|
|
432
605
|
}
|
|
433
606
|
const available = []
|
|
434
607
|
if (existsSync(resolve(root, '.github/workflows'))) available.push('actions')
|
|
435
|
-
if (hasLanguage('typescript') || hasLanguage('javascript'))
|
|
608
|
+
if (hasLanguage('typescript') || hasLanguage('javascript'))
|
|
609
|
+
available.push('javascript-typescript')
|
|
436
610
|
if (hasLanguage('python')) available.push('python')
|
|
437
611
|
if (hasLanguage('rust')) available.push('rust')
|
|
438
612
|
// Upload every configured language on every analysis run so GitHub can
|
|
439
613
|
// compare pull requests against the base branch's code-scanning config.
|
|
440
|
-
const languagesJson = available.map((language) => ({
|
|
614
|
+
const languagesJson = available.map((language) => ({
|
|
615
|
+
language,
|
|
616
|
+
name:
|
|
617
|
+
language === 'javascript-typescript'
|
|
618
|
+
? 'TypeScript'
|
|
619
|
+
: language[0].toUpperCase() + language.slice(1),
|
|
620
|
+
'build-mode': 'none',
|
|
621
|
+
changed: true,
|
|
622
|
+
}))
|
|
441
623
|
writeOutput('languages', languagesJson)
|
|
442
624
|
for (const language of ['actions', 'javascript-typescript', 'python', 'rust']) {
|
|
443
625
|
const entry = languagesJson.find((item) => item.language === language)
|
|
@@ -452,7 +634,14 @@ function codeql() {
|
|
|
452
634
|
function printProfile(command) {
|
|
453
635
|
if (command === 'get') {
|
|
454
636
|
const key = process.argv[4]
|
|
455
|
-
console.log(
|
|
637
|
+
console.log(
|
|
638
|
+
config[key] ??
|
|
639
|
+
(key === 'languages'
|
|
640
|
+
? languages.join(',')
|
|
641
|
+
: key === 'package_manager'
|
|
642
|
+
? packageManager
|
|
643
|
+
: '')
|
|
644
|
+
)
|
|
456
645
|
return
|
|
457
646
|
}
|
|
458
647
|
writeOutput('languages', languages.join(','))
|
|
@@ -466,9 +655,18 @@ function preCommit() {
|
|
|
466
655
|
if (!changed) return
|
|
467
656
|
const check = spawnSync('git', ['diff', '--cached', '--check'], { cwd: root, stdio: 'inherit' })
|
|
468
657
|
if (check.status !== 0) process.exit(check.status ?? 1)
|
|
469
|
-
if (/\.(js|jsx|ts|tsx|json|md|mdx|yml|yaml)$/.test(changed)) {
|
|
470
|
-
|
|
471
|
-
|
|
658
|
+
if (/\.(js|jsx|ts|tsx|json|md|mdx|yml|yaml)$/.test(changed)) {
|
|
659
|
+
ci('format')
|
|
660
|
+
ci('lint')
|
|
661
|
+
}
|
|
662
|
+
if (/\.rs$|(^|\/)Cargo\.toml$/.test(changed)) {
|
|
663
|
+
run('cargo', ['fmt', '--check'])
|
|
664
|
+
run('cargo', ['clippy', '--all-targets', '--', '-D', 'warnings'])
|
|
665
|
+
}
|
|
666
|
+
if (/\.py$|(^|\/)(pyproject\.toml|requirements[^/]*\.txt)$/.test(changed)) {
|
|
667
|
+
runTool('ruff', ['format', '--check', '.'])
|
|
668
|
+
runTool('ruff', ['check', '.'])
|
|
669
|
+
}
|
|
472
670
|
}
|
|
473
671
|
|
|
474
672
|
const [area, task, ecosystem] = process.argv.slice(2)
|
package/.prettierignore
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
# Generated release metadata is intentionally managed by Release Please.
|
|
2
|
-
CHANGELOG.md
|
|
3
|
-
|
|
4
|
-
# Runtime files are checked out under .github during CI. Composite actions
|
|
5
|
-
# are copied to .github/actions for the workflow run and must not be policed
|
|
6
|
-
# by consumer formatters (their style follows the runtime baseline, not the
|
|
7
|
-
# consumer .prettierrc).
|
|
8
|
-
.github/.code-foundry
|
|
9
|
-
.github/actions/
|