code-foundry 1.10.1 → 1.12.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/code-foundry.yml +4 -0
- package/.github/workflows/ci.yml +4 -0
- package/.github/workflows/cloudflare-delivery.yml +354 -0
- package/.github/workflows/cloudflare-deploy.yml +90 -47
- package/.github/workflows/codeql.yml +2 -0
- package/.github/workflows/security.yml +2 -0
- package/.github/workflows/test.yml +5 -0
- package/.github/workflows/validation-no-codeql.yml +1 -0
- package/.github/workflows/validation.yml +1 -0
- package/.github/workflows/validation_self-ci.yml +1 -0
- package/CHANGELOG.md +14 -0
- package/docs/CONFIGURATION.md +23 -10
- package/docs/README.md +1 -0
- package/docs/cloudflare-delivery.md +188 -0
- package/docs/required-capabilities.md +82 -0
- package/package.json +1 -1
- package/src/commands/cloudflare-delivery.mjs +351 -0
- package/src/commands/sync.mjs +4 -0
- package/src/lib/cloudflare-delivery.mjs +233 -0
- package/src/lib/task-policy.mjs +290 -0
- package/src/runtime-core.mjs +794 -0
- package/src/runtime.mjs +179 -764
package/src/runtime.mjs
CHANGED
|
@@ -1,794 +1,209 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// @ts-check
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
appendFileSync,
|
|
6
|
+
existsSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
realpathSync,
|
|
9
|
+
renameSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
} from 'node:fs'
|
|
12
|
+
import { relative, resolve, sep } from 'node:path'
|
|
13
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
6
14
|
import { spawnSync } from 'node:child_process'
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
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, {
|
|
15
|
+
import {
|
|
16
|
+
coverageFiles,
|
|
17
|
+
describeTask,
|
|
18
|
+
evaluateCoverage,
|
|
19
|
+
fingerprint,
|
|
20
|
+
readTaskPolicy,
|
|
21
|
+
TASKS,
|
|
22
|
+
} from './lib/task-policy.mjs'
|
|
23
|
+
|
|
24
|
+
const core = fileURLToPath(new URL('./runtime-core.mjs', import.meta.url))
|
|
25
|
+
|
|
26
|
+
/** @param {string} root @param {string} task @param {string} [entry] */
|
|
27
|
+
export function taskProfile(root, task, entry = core) {
|
|
28
|
+
const result = spawnSync(process.execPath, [entry, 'ci', 'task_profile', task], {
|
|
251
29
|
cwd: root,
|
|
252
|
-
|
|
253
|
-
env: process.env,
|
|
254
|
-
...options,
|
|
30
|
+
encoding: 'utf8',
|
|
31
|
+
env: { ...process.env, GITHUB_OUTPUT: '' },
|
|
255
32
|
})
|
|
256
33
|
if (result.error) throw result.error
|
|
257
|
-
if (result.status !== 0)
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
.
|
|
270
|
-
.
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/** @param {
|
|
275
|
-
function
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
-
)
|
|
34
|
+
if (result.status !== 0) throw new Error(result.stderr.trim() || `Task discovery failed: ${task}`)
|
|
35
|
+
const profile = Object.fromEntries(
|
|
36
|
+
result.stdout.split(/\r?\n/).flatMap((line) => {
|
|
37
|
+
const match = line.match(/^([a-z_]+)=(.*)$/)
|
|
38
|
+
return match ? [[match[1], match[2]]] : []
|
|
39
|
+
})
|
|
40
|
+
)
|
|
41
|
+
if (!['true', 'false'].includes(profile.applicable))
|
|
42
|
+
throw new Error(`Task discovery returned no applicability: ${task}`)
|
|
43
|
+
return {
|
|
44
|
+
...describeTask(root, task, profile),
|
|
45
|
+
javascript: profile.javascript ?? 'false',
|
|
46
|
+
python: profile.python ?? 'false',
|
|
47
|
+
rust: profile.rust ?? 'false',
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** @param {string} root @param {string} task @param {unknown} result */
|
|
52
|
+
function saveResult(root, task, result) {
|
|
53
|
+
const base = realpathSync(root)
|
|
54
|
+
const directory = resolve(base, '.code-foundry/results')
|
|
55
|
+
for (const ancestor of [resolve(base, '.code-foundry'), directory]) {
|
|
56
|
+
if (!existsSync(ancestor)) mkdirSync(ancestor)
|
|
57
|
+
const actual = relative(base, realpathSync(ancestor))
|
|
58
|
+
if (actual === '..' || actual.startsWith(`..${sep}`))
|
|
59
|
+
throw new Error('Result directory escapes repository')
|
|
60
|
+
}
|
|
61
|
+
const destination = resolve(directory, `${task}.json`)
|
|
62
|
+
const temporary = `${destination}.${process.pid}.tmp`
|
|
63
|
+
writeFileSync(temporary, `${JSON.stringify(result, null, 2)}\n`, { flag: 'wx' })
|
|
64
|
+
renameSync(temporary, destination)
|
|
65
|
+
if (process.env.GITHUB_STEP_SUMMARY) {
|
|
66
|
+
const json = JSON.stringify(result, null, 2)
|
|
67
|
+
.replace(/&/g, '&')
|
|
68
|
+
.replace(/</g, '<')
|
|
69
|
+
.replace(/>/g, '>')
|
|
70
|
+
appendFileSync(
|
|
71
|
+
process.env.GITHUB_STEP_SUMMARY,
|
|
72
|
+
`\n<details><summary>Code Foundry: ${task}</summary><pre>${json}</pre></details>\n`
|
|
446
73
|
)
|
|
447
74
|
}
|
|
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
75
|
}
|
|
457
76
|
|
|
458
|
-
/**
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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
|
-
)
|
|
77
|
+
/** @param {string} root */
|
|
78
|
+
function sourceSha(root) {
|
|
79
|
+
const result = spawnSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' })
|
|
80
|
+
return result.status === 0 ? result.stdout.trim() : null
|
|
479
81
|
}
|
|
480
82
|
|
|
481
83
|
/**
|
|
482
|
-
*
|
|
483
|
-
*
|
|
484
|
-
*
|
|
485
|
-
*
|
|
486
|
-
* @returns {boolean}
|
|
84
|
+
* Stable public runtime; the original ecosystem executor is kept private and unchanged.
|
|
85
|
+
* Tests can substitute an executor without installing consumer dependencies.
|
|
86
|
+
* @param {string[]} args @param {string} [root] @param {string} [entry]
|
|
87
|
+
* @returns {number}
|
|
487
88
|
*/
|
|
488
|
-
function
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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'
|
|
89
|
+
export function runRuntime(args, root = process.cwd(), entry = core) {
|
|
90
|
+
const [area, task, selected] = args
|
|
91
|
+
if (area !== 'ci' || task === 'install') {
|
|
92
|
+
const result = spawnSync(process.execPath, [entry, ...args], { cwd: root, stdio: 'inherit' })
|
|
93
|
+
if (result.error) throw result.error
|
|
94
|
+
return result.status ?? 1
|
|
95
|
+
}
|
|
96
|
+
const policy = readTaskPolicy(root)
|
|
97
|
+
if (task === 'plan') {
|
|
98
|
+
console.log(
|
|
99
|
+
JSON.stringify(
|
|
100
|
+
{ schemaVersion: 1, tasks: TASKS.map((name) => taskProfile(root, name, entry)) },
|
|
101
|
+
null,
|
|
102
|
+
2
|
|
103
|
+
)
|
|
513
104
|
)
|
|
514
|
-
|
|
515
|
-
writeOutput('rust', hasLanguage('rust') ? 'true' : 'false')
|
|
516
|
-
return
|
|
105
|
+
return 0
|
|
517
106
|
}
|
|
518
|
-
if (task === '
|
|
519
|
-
const
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
107
|
+
if (task === 'should_run' || task === 'task_profile') {
|
|
108
|
+
const profile = taskProfile(root, selected, entry)
|
|
109
|
+
for (const requiredName of policy.required.filter(
|
|
110
|
+
(name) => name !== 'coverage' && name !== selected
|
|
111
|
+
))
|
|
112
|
+
taskProfile(root, requiredName, entry)
|
|
113
|
+
if (!profile.applicable) {
|
|
114
|
+
const now = new Date().toISOString()
|
|
115
|
+
saveResult(root, selected, {
|
|
116
|
+
schemaVersion: 1,
|
|
117
|
+
kind: 'code-foundry-task-result',
|
|
118
|
+
task: selected,
|
|
119
|
+
sourceSha: sourceSha(root),
|
|
120
|
+
startedAt: now,
|
|
121
|
+
completedAt: now,
|
|
122
|
+
status: 'skipped',
|
|
123
|
+
reason: profile.reason,
|
|
124
|
+
required: false,
|
|
125
|
+
commands: [],
|
|
126
|
+
artifacts: [],
|
|
127
|
+
})
|
|
530
128
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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', [])
|
|
129
|
+
for (const [key, value] of Object.entries({
|
|
130
|
+
applicable: String(profile.applicable),
|
|
131
|
+
javascript: profile.javascript ?? 'false',
|
|
132
|
+
python: profile.python ?? 'false',
|
|
133
|
+
rust: profile.rust ?? 'false',
|
|
134
|
+
})) {
|
|
135
|
+
const line = `${key}=${value}\n`
|
|
136
|
+
if (process.env.GITHUB_OUTPUT) appendFileSync(process.env.GITHUB_OUTPUT, line)
|
|
137
|
+
else process.stdout.write(line)
|
|
547
138
|
}
|
|
548
|
-
|
|
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])
|
|
139
|
+
return 0
|
|
599
140
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
141
|
+
if (!TASKS.includes(task)) throw new Error(`Unknown CI task: ${task || '(missing)'}`)
|
|
142
|
+
const startedAt = new Date().toISOString()
|
|
143
|
+
/** @type {Record<string, any>} */
|
|
144
|
+
const report = {
|
|
145
|
+
schemaVersion: 1,
|
|
146
|
+
kind: 'code-foundry-task-result',
|
|
147
|
+
task,
|
|
148
|
+
sourceSha: sourceSha(root),
|
|
149
|
+
startedAt,
|
|
150
|
+
completedAt: startedAt,
|
|
151
|
+
status: 'failed',
|
|
152
|
+
commands: [],
|
|
153
|
+
artifacts: [],
|
|
613
154
|
}
|
|
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
155
|
try {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
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']],
|
|
156
|
+
const profile = taskProfile(root, task, entry)
|
|
157
|
+
report.required = profile.required
|
|
158
|
+
report.applicable = profile.applicable
|
|
159
|
+
report.reason = profile.reason
|
|
160
|
+
if (!profile.applicable) {
|
|
161
|
+
report.status = 'skipped'
|
|
162
|
+
return 0
|
|
669
163
|
}
|
|
670
|
-
const
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
const
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
164
|
+
const before =
|
|
165
|
+
task === 'unit' && policy.coverageMode !== 'off'
|
|
166
|
+
? Object.fromEntries(
|
|
167
|
+
coverageFiles(root, policy).map(({ file, path }) => [file, fingerprint(path)])
|
|
168
|
+
)
|
|
169
|
+
: {}
|
|
170
|
+
const argv = [process.execPath, entry, 'ci', task]
|
|
171
|
+
const result = spawnSync(argv[0], argv.slice(1), { cwd: root, stdio: 'inherit' })
|
|
172
|
+
report.commands.push({
|
|
173
|
+
argv,
|
|
174
|
+
source: profile.source,
|
|
175
|
+
status: result.status,
|
|
176
|
+
signal: result.signal,
|
|
177
|
+
})
|
|
178
|
+
if (result.error) throw result.error
|
|
179
|
+
if (result.status !== 0) {
|
|
180
|
+
report.reason = result.signal
|
|
181
|
+
? `executor terminated by ${result.signal}`
|
|
182
|
+
: `executor exited ${result.status}`
|
|
183
|
+
return result.status ?? 1
|
|
685
184
|
}
|
|
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')
|
|
185
|
+
if (task === 'unit') {
|
|
186
|
+
report.coverage = evaluateCoverage(root, policy, before)
|
|
187
|
+
report.artifacts.push(...report.coverage.artifacts)
|
|
713
188
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
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
|
|
189
|
+
if (task === 'performance') report.artifacts.push('performance-results/summary.json')
|
|
190
|
+
report.status = 'passed'
|
|
191
|
+
return 0
|
|
192
|
+
} catch (error) {
|
|
193
|
+
report.reason = error instanceof Error ? error.message : String(error)
|
|
194
|
+
console.error(report.reason)
|
|
195
|
+
return 1
|
|
196
|
+
} finally {
|
|
197
|
+
report.completedAt = new Date().toISOString()
|
|
198
|
+
saveResult(root, task, report)
|
|
756
199
|
}
|
|
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
200
|
}
|
|
762
201
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
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', '.'])
|
|
202
|
+
if (process.argv[1] && pathToFileURL(resolve(process.argv[1])).href === import.meta.url) {
|
|
203
|
+
try {
|
|
204
|
+
process.exitCode = runRuntime(process.argv.slice(2))
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error(error instanceof Error ? error.message : String(error))
|
|
207
|
+
process.exitCode = 1
|
|
779
208
|
}
|
|
780
209
|
}
|
|
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
|
-
}
|