code-foundry 0.26.0 → 0.27.1
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/.githooks/pre-commit +3 -4
- package/.github/CONTRIBUTING.md +10 -13
- package/.github/actions/setup/action.yml +68 -3
- package/.github/code-foundry.yml +6 -1
- package/.github/workflows/ci.yml +29 -24
- package/.github/workflows/ci_self-ci.yml +1 -1
- package/.github/workflows/codeql.yml +23 -29
- package/.github/workflows/codeql_self-ci.yml +1 -1
- package/.github/workflows/draft-pr.yml +2 -2
- package/.github/workflows/release-pr.yml +4 -4
- package/.github/workflows/release.yml +17 -29
- package/.github/workflows/security.yml +24 -19
- package/.github/workflows/security_self-ci.yml +1 -1
- package/.github/workflows/test.yml +20 -20
- package/.github/workflows/test_self-ci.yml +1 -1
- package/AGENTS.md +13 -10
- package/CHANGELOG.md +29 -0
- package/README.md +16 -6
- package/docs/CONFIGURATION.md +10 -1
- package/docs/INITIALIZATION.md +6 -5
- package/docs/RELEASES.md +5 -0
- package/docs/WORKFLOWS.md +12 -5
- package/package.json +7 -3
- package/src/cli.mjs +29 -25
- package/src/commands/doctor.mjs +70 -0
- package/src/commands/sync.mjs +224 -0
- package/src/lib/config.mjs +38 -0
- package/src/lib/profile.mjs +114 -0
- package/src/runtime.mjs +321 -0
- package/.github/scripts/bootstrap.sh +0 -21
- package/.github/scripts/changed-files.sh +0 -96
- package/.github/scripts/ci.sh +0 -641
- package/.github/scripts/codeql-languages.sh +0 -91
- package/.github/scripts/doctor.sh +0 -98
- package/.github/scripts/format-fast-path.sh +0 -80
- package/.github/scripts/init-repo.sh +0 -268
- package/.github/scripts/pre-commit.sh +0 -67
- package/.github/scripts/profile.sh +0 -186
- package/.github/scripts/security.sh +0 -214
- package/.github/scripts/sitecustomize.py +0 -17
- package/.github/scripts/sync-codeowners.sh +0 -76
- package/.github/scripts/sync-protection.sh +0 -138
- package/.github/scripts/sync-template.sh +0 -748
- package/.github/scripts/turbo-cache-probe.sh +0 -102
package/src/runtime.mjs
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-check
|
|
3
|
+
|
|
4
|
+
import { appendFileSync, existsSync, readFileSync } 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
|
+
|
|
10
|
+
const root = process.cwd()
|
|
11
|
+
const config = readConfig(resolve(root, '.github/code-foundry.yml'))
|
|
12
|
+
const repoProfile = resolveProfile(root)
|
|
13
|
+
/** @type {string[]} */
|
|
14
|
+
const languages = configured(config.languages, repoProfile.languages).split(',').filter(Boolean)
|
|
15
|
+
const packageManager = configured(config.package_manager, detectPackageManager(root))
|
|
16
|
+
const output = process.env.GITHUB_OUTPUT
|
|
17
|
+
|
|
18
|
+
/** @param {string} language */
|
|
19
|
+
function hasLanguage(language) {
|
|
20
|
+
return languages.includes(language) || languages.includes('all')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** @param {string} name */
|
|
24
|
+
function hasScript(name) {
|
|
25
|
+
return readPackage()?.scripts?.[name] !== undefined
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function readPackage() {
|
|
29
|
+
try { return JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf8')) } catch { return null }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** @param {string} key @param {unknown} value */
|
|
33
|
+
function writeOutput(key, value) {
|
|
34
|
+
const line = `${key}=${typeof value === 'string' ? value : JSON.stringify(value)}\n`
|
|
35
|
+
if (output) requireWrite(output, line)
|
|
36
|
+
else console.log(line.trimEnd())
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** @param {string} file @param {string} content */
|
|
40
|
+
function requireWrite(file, content) {
|
|
41
|
+
appendFileSync(file, content)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** @param {string} command */
|
|
45
|
+
function commandExists(command) {
|
|
46
|
+
return spawnSync(command, ['--version'], { stdio: 'ignore' }).status === 0
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** @param {string} command @param {string[]} [args] @param {Record<string, unknown>} [options] */
|
|
50
|
+
function run(command, args = [], options = {}) {
|
|
51
|
+
const result = spawnSync(command, args, { cwd: root, stdio: 'inherit', env: process.env, ...options })
|
|
52
|
+
if (result.error) throw result.error
|
|
53
|
+
if (result.status !== 0) process.exit(result.status ?? 1)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** @param {string} command @param {string[]} [args] */
|
|
57
|
+
function capture(command, args = []) {
|
|
58
|
+
const result = spawnSync(command, args, { cwd: root, encoding: 'utf8', env: process.env })
|
|
59
|
+
return result.status === 0 ? result.stdout.trim() : ''
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** @param {string[]} args @returns {[string|null, string[]]} */
|
|
63
|
+
function packageCommand(args) {
|
|
64
|
+
switch (packageManager) {
|
|
65
|
+
case 'bun': return ['bun', args]
|
|
66
|
+
case 'pnpm': return ['pnpm', args]
|
|
67
|
+
case 'yarn': return ['yarn', args]
|
|
68
|
+
case 'npm': return ['npm', args]
|
|
69
|
+
default: return [null, args]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** @param {string[]} names */
|
|
74
|
+
function runScript(names) {
|
|
75
|
+
const name = names.find((candidate) => hasScript(candidate))
|
|
76
|
+
if (!name) return false
|
|
77
|
+
const [manager, args] = packageCommand(['run', name])
|
|
78
|
+
if (!manager) return false
|
|
79
|
+
run(manager, args)
|
|
80
|
+
return true
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** @param {string} tool @param {string[]} [args] */
|
|
84
|
+
function runTool(tool, args = []) {
|
|
85
|
+
const [manager] = packageCommand([])
|
|
86
|
+
if (manager === 'bun') return run('bunx', ['--no-install', tool, ...args])
|
|
87
|
+
if (manager === 'pnpm') return run('pnpm', ['exec', tool, ...args])
|
|
88
|
+
if (manager === 'yarn') return run('yarn', ['exec', tool, ...args])
|
|
89
|
+
if (manager === 'npm') return run('npx', ['--no-install', tool, ...args])
|
|
90
|
+
return run(tool, args)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function install() {
|
|
94
|
+
if (existsSync(resolve(root, 'package.json'))) {
|
|
95
|
+
const lock = ['bun.lock', 'bun.lockb', 'pnpm-lock.yaml', 'yarn.lock', 'package-lock.json'].find((file) => existsSync(resolve(root, file)))
|
|
96
|
+
if (lock) {
|
|
97
|
+
/** @type {Record<string, [string, string[]]>} */
|
|
98
|
+
const commands = {
|
|
99
|
+
bun: ['bun', ['install', '--frozen-lockfile']],
|
|
100
|
+
pnpm: ['pnpm', ['install', '--frozen-lockfile', '--prefer-offline']],
|
|
101
|
+
yarn: ['yarn', ['install', '--immutable']],
|
|
102
|
+
npm: ['npm', ['ci', '--prefer-offline', '--no-audit', '--fund=false']],
|
|
103
|
+
}
|
|
104
|
+
const command = commands[packageManager]
|
|
105
|
+
if (command) run(command[0], command[1])
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (hasLanguage('python') && (existsSync(resolve(root, 'pyproject.toml')) || existsSync(resolve(root, 'requirements.txt')))) {
|
|
109
|
+
if (commandExists('uv') && existsSync(resolve(root, 'uv.lock'))) run('uv', ['sync', '--frozen'])
|
|
110
|
+
else {
|
|
111
|
+
if (!existsSync(resolve(root, '.venv'))) run('python', ['-m', 'venv', '.venv'])
|
|
112
|
+
const python = resolve(root, '.venv/bin/python')
|
|
113
|
+
if (existsSync(resolve(root, 'requirements.txt'))) run(python, ['-m', 'pip', 'install', '--disable-pip-version-check', '-r', 'requirements.txt'])
|
|
114
|
+
if (existsSync(resolve(root, 'requirements-dev.txt'))) run(python, ['-m', 'pip', 'install', '--disable-pip-version-check', '-r', 'requirements-dev.txt'])
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (hasLanguage('rust') && existsSync(resolve(root, 'Cargo.toml'))) run('cargo', ['fetch'])
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** @param {string} task */
|
|
121
|
+
function relevant(task) {
|
|
122
|
+
const js = hasLanguage('typescript') || hasLanguage('javascript')
|
|
123
|
+
const python = hasLanguage('python')
|
|
124
|
+
const rust = hasLanguage('rust')
|
|
125
|
+
if (task === 'format' || task === 'lint' || task === 'type_check' || task === 'build') return js || python || rust
|
|
126
|
+
if (['unit', 'integration', 'e2e', 'smoke'].includes(task)) return js || python || rust || hasLanguage('solidity')
|
|
127
|
+
return true
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** @param {string} ecosystem */
|
|
131
|
+
function hasDependencyManifest(ecosystem) {
|
|
132
|
+
if (ecosystem === 'javascript') {
|
|
133
|
+
const packageJson = readPackage()
|
|
134
|
+
return ['bun.lock', 'bun.lockb', 'pnpm-lock.yaml', 'yarn.lock', 'package-lock.json'].some((file) => existsSync(resolve(root, file))) ||
|
|
135
|
+
Boolean(packageJson && ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'].some((group) => Object.keys(packageJson[group] ?? {}).length))
|
|
136
|
+
}
|
|
137
|
+
if (ecosystem === 'rust') return existsSync(resolve(root, 'Cargo.toml'))
|
|
138
|
+
if (ecosystem === 'python') return existsSync(resolve(root, 'pyproject.toml')) || existsSync(resolve(root, 'uv.lock')) || capture('git', ['ls-files', '*requirements*.txt']) !== ''
|
|
139
|
+
return false
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** @param {string} task */
|
|
143
|
+
function ci(task) {
|
|
144
|
+
if (task === 'install') return install()
|
|
145
|
+
if (task === 'should_run' || task === 'task_profile') {
|
|
146
|
+
const selected = process.argv[3]
|
|
147
|
+
writeOutput('applicable', relevant(selected) ? 'true' : 'false')
|
|
148
|
+
writeOutput('javascript', (hasLanguage('typescript') || hasLanguage('javascript')) ? 'true' : 'false')
|
|
149
|
+
writeOutput('python', hasLanguage('python') ? 'true' : 'false')
|
|
150
|
+
writeOutput('rust', hasLanguage('rust') ? 'true' : 'false')
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
if (task === 'format') {
|
|
154
|
+
const scripted = runScript(['format:check', 'format', 'fmt'])
|
|
155
|
+
if (!scripted && (hasLanguage('typescript') || hasLanguage('javascript'))) runTool('prettier', ['--check', '.'])
|
|
156
|
+
if (hasLanguage('python')) runTool('ruff', ['format', '--check', '.'])
|
|
157
|
+
if (hasLanguage('rust')) run('cargo', ['fmt', '--check'])
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
if (task === 'lint') {
|
|
161
|
+
const scripted = runScript(['lint'])
|
|
162
|
+
if (!scripted && (hasLanguage('typescript') || hasLanguage('javascript'))) runTool('eslint', ['.'])
|
|
163
|
+
if (hasLanguage('python')) runTool('ruff', ['check', '.'])
|
|
164
|
+
if (hasLanguage('rust')) run('cargo', ['clippy', '--all-targets', '--', '-D', 'warnings'])
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
if (task === 'type_check') {
|
|
168
|
+
const scripted = runScript(['type-check', 'typecheck', 'type:check'])
|
|
169
|
+
if (!scripted && existsSync(resolve(root, 'tsconfig.json'))) runTool('tsc', ['--noEmit'])
|
|
170
|
+
if (hasLanguage('rust')) run('cargo', ['check', '--all-targets'])
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
if (task === 'build') {
|
|
174
|
+
const scripted = runScript(['build'])
|
|
175
|
+
if (!scripted && hasLanguage('rust')) run('cargo', ['build', '--all-targets'])
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
/** @type {Record<string, string[]>} */
|
|
179
|
+
const scriptsByTask = {
|
|
180
|
+
unit: ['test:unit', 'test:coverage', 'test'],
|
|
181
|
+
integration: ['test:integration'],
|
|
182
|
+
e2e: ['test:e2e', 'e2e'],
|
|
183
|
+
smoke: ['test:smoke', 'smoke'],
|
|
184
|
+
}
|
|
185
|
+
const scripts = scriptsByTask[task]
|
|
186
|
+
if (scripts) runScript(scripts)
|
|
187
|
+
if (hasLanguage('rust')) run('cargo', ['test'])
|
|
188
|
+
if (hasLanguage('python')) {
|
|
189
|
+
const python = existsSync(resolve(root, '.venv/bin/python')) ? resolve(root, '.venv/bin/python') : 'python'
|
|
190
|
+
run(python, ['-m', 'pytest', ...(task === 'integration' ? ['tests/integration'] : [])])
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** @param {string} task @param {string} ecosystem */
|
|
195
|
+
function security(task, ecosystem) {
|
|
196
|
+
if (task === 'profile') {
|
|
197
|
+
writeOutput('javascript', hasDependencyManifest('javascript') ? 'true' : 'false')
|
|
198
|
+
writeOutput('rust', hasDependencyManifest('rust') ? 'true' : 'false')
|
|
199
|
+
writeOutput('python', hasDependencyManifest('python') ? 'true' : 'false')
|
|
200
|
+
const requirements = capture('git', ['ls-files', '*requirements*.txt']).split('\n').filter(Boolean)
|
|
201
|
+
writeOutput('python_requirements', hasDependencyManifest('python') ? (requirements.length ? requirements : ['project']) : ['none'])
|
|
202
|
+
writeOutput('dependency_review', featureEnabled('dependency_review') ? 'true' : 'false')
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
if (task === 'should_run') {
|
|
206
|
+
writeOutput('applicable', hasDependencyManifest(ecosystem) ? 'true' : 'false')
|
|
207
|
+
return
|
|
208
|
+
}
|
|
209
|
+
if (ecosystem === 'javascript' && existsSync(resolve(root, 'package.json'))) {
|
|
210
|
+
const ignores = existsSync(resolve(root, '.github/security-audit-allowlist.txt'))
|
|
211
|
+
? readFileSync(resolve(root, '.github/security-audit-allowlist.txt'), 'utf8').split(/\r?\n/).filter((line) => line && !line.startsWith('#'))
|
|
212
|
+
: []
|
|
213
|
+
/** @type {Record<string, [string, string[]]>} */
|
|
214
|
+
const commands = {
|
|
215
|
+
bun: ['bun', ['audit', ...ignores.flatMap((advisory) => ['--ignore', advisory])]],
|
|
216
|
+
pnpm: ['pnpm', ['audit', '--audit-level', 'high']],
|
|
217
|
+
yarn: ['yarn', ['npm', 'audit', '--all', '--recursive']],
|
|
218
|
+
npm: ['npm', ['audit', '--audit-level=high']],
|
|
219
|
+
}
|
|
220
|
+
const command = commands[packageManager]
|
|
221
|
+
if (command) run(command[0], command[1])
|
|
222
|
+
} else if (ecosystem === 'rust' && existsSync(resolve(root, 'Cargo.toml'))) {
|
|
223
|
+
if (!commandExists('cargo-audit')) run('cargo', ['install', 'cargo-audit', '--locked', '--quiet'])
|
|
224
|
+
run('cargo', ['audit'])
|
|
225
|
+
} else if (ecosystem === 'python' && hasDependencyManifest('python')) {
|
|
226
|
+
const requirement = process.env.REPO_FOUNDRY_PYTHON_REQUIREMENT
|
|
227
|
+
const auditArgs = requirement && !['project', 'none'].includes(requirement) ? ['-r', requirement] : []
|
|
228
|
+
if (commandExists('uv')) run('uv', ['tool', 'run', '--from', 'pip-audit==2.10.1', 'pip-audit', ...auditArgs])
|
|
229
|
+
else {
|
|
230
|
+
run('python', ['-m', 'pip', 'install', '--quiet', 'pip-audit==2.10.1'])
|
|
231
|
+
run('python', ['-m', 'pip_audit', ...auditArgs])
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** @param {string} key */
|
|
237
|
+
function featureEnabled(key) {
|
|
238
|
+
const policy = config[key] ?? 'auto'
|
|
239
|
+
if (policy === 'false') return false
|
|
240
|
+
if (policy === 'true') return true
|
|
241
|
+
return isPublicRepository()
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function isPublicRepository() {
|
|
245
|
+
const visibility = process.env.REPO_FOUNDRY_VISIBILITY
|
|
246
|
+
return visibility ? visibility === 'public' : process.env.REPO_FOUNDRY_PRIVATE !== 'true'
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function codeql() {
|
|
250
|
+
const enabled = featureEnabled('codeql') &&
|
|
251
|
+
(isPublicRepository() || process.env.REPO_FOUNDRY_CODE_SECURITY === 'enabled')
|
|
252
|
+
writeOutput('enabled', enabled ? 'true' : 'false')
|
|
253
|
+
if (!enabled) {
|
|
254
|
+
writeOutput('languages', [])
|
|
255
|
+
for (const language of ['actions', 'javascript', 'python', 'rust']) {
|
|
256
|
+
writeOutput(`${language}_available`, 'false')
|
|
257
|
+
writeOutput(`${language}_changed`, 'false')
|
|
258
|
+
writeOutput(`${language}_build_mode`, 'none')
|
|
259
|
+
}
|
|
260
|
+
return
|
|
261
|
+
}
|
|
262
|
+
const available = []
|
|
263
|
+
if (existsSync(resolve(root, '.github/workflows'))) available.push('actions')
|
|
264
|
+
if (hasLanguage('typescript') || hasLanguage('javascript')) available.push('javascript-typescript')
|
|
265
|
+
if (hasLanguage('python')) available.push('python')
|
|
266
|
+
if (hasLanguage('rust')) available.push('rust')
|
|
267
|
+
const changed = capture('git', ['diff', '--name-only', process.env.CODEQL_BASE_SHA || 'HEAD^', process.env.GITHUB_SHA || 'HEAD']).split('\n').filter(Boolean)
|
|
268
|
+
const languagesJson = available.map((language) => ({ language, name: language === 'javascript-typescript' ? 'TypeScript' : language[0].toUpperCase() + language.slice(1), 'build-mode': 'none', changed: changed.length === 0 || changed.some((file) => fileMatches(language, file)) }))
|
|
269
|
+
writeOutput('languages', languagesJson)
|
|
270
|
+
for (const language of ['actions', 'javascript-typescript', 'python', 'rust']) {
|
|
271
|
+
const entry = languagesJson.find((item) => item.language === language)
|
|
272
|
+
const prefix = language === 'javascript-typescript' ? 'javascript' : language
|
|
273
|
+
writeOutput(`${prefix}_available`, entry ? 'true' : 'false')
|
|
274
|
+
writeOutput(`${prefix}_changed`, entry?.changed ? 'true' : 'false')
|
|
275
|
+
writeOutput(`${prefix}_build_mode`, entry?.['build-mode'] ?? 'none')
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** @param {string} language @param {string} file */
|
|
280
|
+
function fileMatches(language, file) {
|
|
281
|
+
if (language === 'actions') return file.startsWith('.github/workflows/') || file.endsWith('action.yml')
|
|
282
|
+
if (language === 'javascript-typescript') return /\.(js|jsx|mjs|cjs|ts|tsx|mts|cts)$/.test(file) || /(^|\/)(package\.json|tsconfig[^/]*\.json|(?:bun|pnpm|yarn|package-lock)\.lock)$/.test(file)
|
|
283
|
+
if (language === 'python') return /\.py$|(^|\/)(pyproject\.toml|requirements[^/]*\.txt|setup\.py|Pipfile\.lock|poetry\.lock)$/.test(file)
|
|
284
|
+
return /\.rs$|(^|\/)Cargo\.(toml|lock)$/.test(file)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** @param {string} command */
|
|
288
|
+
function printProfile(command) {
|
|
289
|
+
if (command === 'get') {
|
|
290
|
+
const key = process.argv[3]
|
|
291
|
+
console.log(config[key] ?? (key === 'languages' ? languages.join(',') : key === 'package_manager' ? packageManager : ''))
|
|
292
|
+
return
|
|
293
|
+
}
|
|
294
|
+
writeOutput('languages', languages.join(','))
|
|
295
|
+
writeOutput('package_manager', packageManager)
|
|
296
|
+
writeOutput('release_type', config.release_type ?? 'auto')
|
|
297
|
+
writeOutput('npm_publish', config.npm_publish ?? 'false')
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function preCommit() {
|
|
301
|
+
const changed = capture('git', ['diff', '--cached', '--name-only'])
|
|
302
|
+
if (!changed) return
|
|
303
|
+
const check = spawnSync('git', ['diff', '--cached', '--check'], { cwd: root, stdio: 'inherit' })
|
|
304
|
+
if (check.status !== 0) process.exit(check.status ?? 1)
|
|
305
|
+
if (/\.(js|jsx|ts|tsx|json|md|mdx|yml|yaml)$/.test(changed)) { ci('format'); ci('lint') }
|
|
306
|
+
if (/\.rs$|(^|\/)Cargo\.toml$/.test(changed)) { run('cargo', ['fmt', '--check']); run('cargo', ['clippy', '--all-targets', '--', '-D', 'warnings']) }
|
|
307
|
+
if (/\.py$|(^|\/)(pyproject\.toml|requirements[^/]*\.txt)$/.test(changed)) { runTool('ruff', ['format', '--check', '.']); runTool('ruff', ['check', '.']) }
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const [area, task, ecosystem] = process.argv.slice(2)
|
|
311
|
+
try {
|
|
312
|
+
if (area === 'ci') ci(task)
|
|
313
|
+
else if (area === 'security') security(task, ecosystem)
|
|
314
|
+
else if (area === 'codeql') codeql()
|
|
315
|
+
else if (area === 'profile') printProfile(task)
|
|
316
|
+
else if (area === 'pre-commit') preCommit()
|
|
317
|
+
else throw new Error(`Unknown runtime command: ${area || '(missing)'}`)
|
|
318
|
+
} catch (error) {
|
|
319
|
+
console.error(error instanceof Error ? error.message : String(error))
|
|
320
|
+
process.exitCode = 1
|
|
321
|
+
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
git config core.hooksPath .githooks
|
|
5
|
-
|
|
6
|
-
if command -v mise >/dev/null 2>&1; then
|
|
7
|
-
mise trust --yes .mise.toml >/dev/null 2>&1 || true
|
|
8
|
-
if [ -f .mise.toml ] && [ ! -f mise.lock ]; then
|
|
9
|
-
if MISE_TRUSTED_CONFIG_PATHS="$PWD" mise lock >/dev/null 2>&1; then
|
|
10
|
-
printf '%s\n' 'Initialized mise.lock for deterministic CI tool installs.'
|
|
11
|
-
else
|
|
12
|
-
printf '%s\n' 'Warning: mise.lock could not be generated; continuing with pinned tool resolution.' >&2
|
|
13
|
-
fi
|
|
14
|
-
fi
|
|
15
|
-
mise install
|
|
16
|
-
else
|
|
17
|
-
printf '%s\n' "mise is not installed; install it from https://mise.jdx.dev/ and rerun this script." >&2
|
|
18
|
-
exit 1
|
|
19
|
-
fi
|
|
20
|
-
|
|
21
|
-
bash .github/scripts/doctor.sh
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
|
|
3
|
-
# Shared pull-request change detection. A failed or incomplete diff must be
|
|
4
|
-
# treated as relevant so checks never become silently optional.
|
|
5
|
-
repo_foundry_changed_files() {
|
|
6
|
-
if [ "${REPO_FOUNDRY_CHANGED_FILES_READY:-false}" = true ]; then
|
|
7
|
-
return 0
|
|
8
|
-
fi
|
|
9
|
-
|
|
10
|
-
local base_sha="${REPO_FOUNDRY_BASE_SHA:-}"
|
|
11
|
-
local head_sha="${GITHUB_SHA:-HEAD}"
|
|
12
|
-
local changed_files=""
|
|
13
|
-
|
|
14
|
-
if [ -n "$base_sha" ] && [ "$base_sha" != "0000000000000000000000000000000000000000" ]; then
|
|
15
|
-
if ! git cat-file -e "$base_sha^{commit}" 2>/dev/null; then
|
|
16
|
-
git fetch --no-tags --filter=blob:none --depth=1 origin "$base_sha" >/dev/null 2>&1 || return 1
|
|
17
|
-
fi
|
|
18
|
-
changed_files="$(git diff --name-only "$base_sha" "$head_sha" 2>/dev/null || true)"
|
|
19
|
-
else
|
|
20
|
-
changed_files="$(git diff --name-only HEAD^ HEAD 2>/dev/null || true)"
|
|
21
|
-
fi
|
|
22
|
-
|
|
23
|
-
[ -n "$changed_files" ] || return 1
|
|
24
|
-
REPO_FOUNDRY_CHANGED_FILES="$changed_files"
|
|
25
|
-
REPO_FOUNDRY_CHANGED_FILES_READY=true
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
repo_foundry_governance_only() {
|
|
29
|
-
case "${GITHUB_EVENT_NAME:-}" in
|
|
30
|
-
pull_request|push) ;;
|
|
31
|
-
*) return 1 ;;
|
|
32
|
-
esac
|
|
33
|
-
|
|
34
|
-
local changed_files=""
|
|
35
|
-
|
|
36
|
-
repo_foundry_changed_files || return 1
|
|
37
|
-
changed_files="$REPO_FOUNDRY_CHANGED_FILES"
|
|
38
|
-
|
|
39
|
-
# An unavailable or empty diff is not evidence that a PR is documentation
|
|
40
|
-
# only. Continue with the full check in that case.
|
|
41
|
-
while IFS= read -r file; do
|
|
42
|
-
case "$file" in
|
|
43
|
-
README|README.*|LICENSE|NOTICE|.github/CODEOWNERS|.github/CODE_OF_CONDUCT.md|\
|
|
44
|
-
.github/CONTRIBUTING.md|.github/PULL_REQUEST_TEMPLATE.md|.github/SECURITY.md|\
|
|
45
|
-
.github/ISSUE_TEMPLATE/*)
|
|
46
|
-
;;
|
|
47
|
-
*) return 1 ;;
|
|
48
|
-
esac
|
|
49
|
-
done <<< "$changed_files"
|
|
50
|
-
|
|
51
|
-
return 0
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
repo_foundry_pr_docs_only() {
|
|
55
|
-
repo_foundry_governance_only
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
# Return success when an ordinary push or pull request changed no dependency or
|
|
59
|
-
# audit inputs for the requested ecosystem. Scheduled and manual runs, plus
|
|
60
|
-
# unknown or unavailable diffs, fail open to a full security check.
|
|
61
|
-
repo_foundry_pr_dependencies_unchanged() {
|
|
62
|
-
local ecosystem="${1:-all}"
|
|
63
|
-
case "${GITHUB_EVENT_NAME:-}" in
|
|
64
|
-
pull_request|push) ;;
|
|
65
|
-
*) return 1 ;;
|
|
66
|
-
esac
|
|
67
|
-
|
|
68
|
-
local changed_files=""
|
|
69
|
-
repo_foundry_changed_files || return 1
|
|
70
|
-
changed_files="$REPO_FOUNDRY_CHANGED_FILES"
|
|
71
|
-
|
|
72
|
-
while IFS= read -r file; do
|
|
73
|
-
case "$file" in
|
|
74
|
-
.github/workflows/security.yml|.github/scripts/security.sh|.github/scripts/changed-files.sh|\
|
|
75
|
-
.github/actions/setup/action.yml|.github/code-foundry.yml|.mise.toml|mise.lock|\
|
|
76
|
-
.github/security-audit-allowlist.txt)
|
|
77
|
-
return 1
|
|
78
|
-
;;
|
|
79
|
-
package.json|*/package.json|bun.lock|bun.lockb|*/bun.lock|*/bun.lockb|\
|
|
80
|
-
pnpm-lock.yaml|*/pnpm-lock.yaml|yarn.lock|*/yarn.lock|package-lock.json|*/package-lock.json|\
|
|
81
|
-
.npmrc|*/.npmrc|.yarnrc*|*/.yarnrc*|.pnpmfile.cjs|*/.pnpmfile.cjs)
|
|
82
|
-
case "$ecosystem" in javascript|all) return 1 ;; esac
|
|
83
|
-
;;
|
|
84
|
-
Cargo.toml|*/Cargo.toml|Cargo.lock|*/Cargo.lock|.cargo/*|*/.cargo/*)
|
|
85
|
-
case "$ecosystem" in rust|all) return 1 ;; esac
|
|
86
|
-
;;
|
|
87
|
-
pyproject.toml|*/pyproject.toml|requirements*.txt|*/requirements*.txt|\
|
|
88
|
-
setup.py|*/setup.py|setup.cfg|*/setup.cfg|Pipfile|*/Pipfile|Pipfile.lock|*/Pipfile.lock|\
|
|
89
|
-
poetry.lock|*/poetry.lock|uv.lock|*/uv.lock)
|
|
90
|
-
case "$ecosystem" in python|all) return 1 ;; esac
|
|
91
|
-
;;
|
|
92
|
-
esac
|
|
93
|
-
done <<< "$changed_files"
|
|
94
|
-
|
|
95
|
-
return 0
|
|
96
|
-
}
|