code-foundry 0.25.0 → 0.27.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/.githooks/pre-commit +3 -4
- package/.github/CONTRIBUTING.md +10 -13
- package/.github/actions/setup/action.yml +50 -3
- package/.github/code-foundry.yml +6 -1
- package/.github/licenses/AGPL-3.0-or-later.txt +661 -0
- package/.github/licenses/GPL-3.0-or-later.txt +674 -0
- package/.github/licenses/MIT.txt +4 -16
- package/.github/workflows/ci.yml +20 -23
- 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 +28 -0
- package/README.md +19 -8
- package/docs/CONFIGURATION.md +11 -2
- package/docs/INITIALIZATION.md +9 -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 +316 -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 -264
- 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 -744
- package/.github/scripts/turbo-cache-probe.sh +0 -102
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
4
|
+
import { dirname, join, resolve } from 'node:path'
|
|
5
|
+
import { spawnSync } from 'node:child_process'
|
|
6
|
+
import { detectLanguages, detectPackageManager, detectProfile } from '../lib/profile.mjs'
|
|
7
|
+
import { configured, includesValue, readConfig } from '../lib/config.mjs'
|
|
8
|
+
|
|
9
|
+
const standardFiles = [
|
|
10
|
+
'.editorconfig', '.gitattributes', '.gitignore', 'release-please-config.json',
|
|
11
|
+
'.githooks/pre-commit', 'AGENTS.md', 'LICENSE', 'NOTICE', 'ruff.toml', '.prettierrc',
|
|
12
|
+
'.github/CODEOWNERS', '.github/CODE_OF_CONDUCT.md', '.github/CONTRIBUTING.md',
|
|
13
|
+
'.github/PULL_REQUEST_TEMPLATE.md', '.github/SECURITY.md', '.github/dependabot.yml',
|
|
14
|
+
'.github/ISSUE_TEMPLATE/bug_report.yml', '.github/ISSUE_TEMPLATE/config.yml',
|
|
15
|
+
'.github/ISSUE_TEMPLATE/feature_request.yml',
|
|
16
|
+
'.github/workflows/ci.yml', '.github/workflows/codeql.yml', '.github/workflows/draft-pr.yml',
|
|
17
|
+
'.github/workflows/release-pr.yml', '.github/workflows/release.yml',
|
|
18
|
+
'.github/workflows/security.yml', '.github/workflows/test.yml',
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
const protectedFiles = new Set([
|
|
22
|
+
'AGENTS.md', '.github/CODE_OF_CONDUCT.md', '.github/CONTRIBUTING.md',
|
|
23
|
+
'.github/PULL_REQUEST_TEMPLATE.md', '.github/SECURITY.md', 'NOTICE',
|
|
24
|
+
])
|
|
25
|
+
|
|
26
|
+
const legacyFiles = [
|
|
27
|
+
'.github/code-foundry.yml.example', '.github/template.yml', '.github/template.yml.example',
|
|
28
|
+
'.github/scripts/bootstrap.sh', '.github/scripts/changed-files.sh', '.github/scripts/ci.sh',
|
|
29
|
+
'.github/scripts/codeql-languages.sh', '.github/scripts/doctor.sh', '.github/scripts/format-fast-path.sh',
|
|
30
|
+
'.github/scripts/init-repo.sh', '.github/scripts/pre-commit.sh', '.github/scripts/profile.sh',
|
|
31
|
+
'.github/scripts/security.sh', '.github/scripts/sitecustomize.py', '.github/scripts/sync-codeowners.sh',
|
|
32
|
+
'.github/scripts/sync-protection.sh', '.github/scripts/sync-template.sh', '.github/scripts/turbo-cache-probe.sh',
|
|
33
|
+
'.github/licenses/MIT.txt',
|
|
34
|
+
'.github/licenses/GPL-3.0-or-later.txt', '.github/licenses/AGPL-3.0-or-later.txt',
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
/** @typedef {{ target: string, source: string, dryRun?: boolean, force?: boolean, init?: boolean }} SyncOptions */
|
|
38
|
+
|
|
39
|
+
/** @param {SyncOptions} options */
|
|
40
|
+
export function syncRepository(options) {
|
|
41
|
+
const target = resolve(options.target)
|
|
42
|
+
const source = resolve(options.source)
|
|
43
|
+
const dryRun = options.dryRun ?? false
|
|
44
|
+
const force = options.force ?? false
|
|
45
|
+
const configPath = join(target, '.github/code-foundry.yml')
|
|
46
|
+
const existingConfig = readConfig(configPath)
|
|
47
|
+
if (!Object.keys(existingConfig).length && !options.init) throw new Error('Missing .github/code-foundry.yml; run init first.')
|
|
48
|
+
const defaults = createDefaultConfig(target, source)
|
|
49
|
+
let config = { ...defaults, ...existingConfig }
|
|
50
|
+
if (!Object.keys(existingConfig).length) {
|
|
51
|
+
writeOrReport(configPath, renderConfig(config), dryRun)
|
|
52
|
+
} else {
|
|
53
|
+
const missing = Object.keys(defaults).filter((key) => !(key in existingConfig))
|
|
54
|
+
if (missing.length) {
|
|
55
|
+
const current = readFileSync(configPath, 'utf8').trimEnd()
|
|
56
|
+
const additions = missing.map((key) => `${key}: ${defaults[key]}`).join('\n')
|
|
57
|
+
writeOrReport(configPath, `${current}\n${additions}\n`, dryRun)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const languages = configured(config.languages, detectLanguages(target).join(','))
|
|
62
|
+
const features = configured(config.features, 'all')
|
|
63
|
+
const runtimeRepository = configured(config.runtime_repository, '0xPlayerOne/code-foundry')
|
|
64
|
+
const runtimeRef = configured(config.runtime_ref, `v${readPackageVersion(source)}`)
|
|
65
|
+
const toolchain = configured(config.toolchain, 'auto')
|
|
66
|
+
if (!['auto', 'native', 'mise'].includes(toolchain)) {
|
|
67
|
+
throw new Error(`Unsupported toolchain: ${toolchain}; use auto, native, or mise.`)
|
|
68
|
+
}
|
|
69
|
+
const license = configured(config.license, existsSync(join(target, 'LICENSE')) ? 'preserve' : 'gpl-3.0-or-later')
|
|
70
|
+
const changed = []
|
|
71
|
+
|
|
72
|
+
for (const file of standardFiles) {
|
|
73
|
+
if (!shouldInclude(file, languages, features)) continue
|
|
74
|
+
const sourceFile = sourcePath(source, file)
|
|
75
|
+
if (!existsSync(sourceFile)) throw new Error(`Template file missing: ${file}`)
|
|
76
|
+
const destination = join(target, file)
|
|
77
|
+
if (!force && protectedFiles.has(file) && existsSync(destination)) {
|
|
78
|
+
const existing = readFileSync(destination, 'utf8')
|
|
79
|
+
if (!isLegacyManagedDoc(file, existing)) continue
|
|
80
|
+
}
|
|
81
|
+
if ((file === 'LICENSE' || file === 'NOTICE') && license === 'preserve' && existsSync(destination)) continue
|
|
82
|
+
if ((file === 'LICENSE' || file === 'NOTICE') && license === 'none') continue
|
|
83
|
+
if (file === '.github/CODEOWNERS' && existsSync(destination)) continue
|
|
84
|
+
let content = readFileSync(sourceFile)
|
|
85
|
+
if (file.endsWith('.yml') && file.startsWith('.github/workflows/')) {
|
|
86
|
+
content = Buffer.from(renderWorkflow(content.toString('utf8'), config, runtimeRepository, runtimeRef))
|
|
87
|
+
}
|
|
88
|
+
if (file === '.gitignore' && existsSync(destination)) {
|
|
89
|
+
content = Buffer.from(mergeGitignore(content.toString('utf8'), readFileSync(destination, 'utf8')))
|
|
90
|
+
}
|
|
91
|
+
if (!existsSync(destination) || !buffersEqual(content, readFileSync(destination))) {
|
|
92
|
+
changed.push(file)
|
|
93
|
+
writeOrReport(destination, content, dryRun)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (license !== 'preserve' && license !== 'none') {
|
|
98
|
+
const licenseFile = license === 'mit' ? 'MIT.txt' : license === 'agpl-3.0-or-later' ? 'AGPL-3.0-or-later.txt' : 'GPL-3.0-or-later.txt'
|
|
99
|
+
const sourceLicense = join(source, '.github/licenses', licenseFile)
|
|
100
|
+
if (!existsSync(sourceLicense)) throw new Error(`License template missing: ${sourceLicense}`)
|
|
101
|
+
writeOrReport(join(target, 'LICENSE'), readFileSync(sourceLicense), dryRun)
|
|
102
|
+
if (!existsSync(join(target, 'NOTICE'))) writeOrReport(join(target, 'NOTICE'), readFileSync(join(source, 'NOTICE')), dryRun)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (const file of legacyFiles) {
|
|
106
|
+
const destination = join(target, file)
|
|
107
|
+
if (existsSync(destination)) {
|
|
108
|
+
changed.push(file)
|
|
109
|
+
if (dryRun) console.log(`Would remove ${file}`)
|
|
110
|
+
else rmSync(destination, { force: true })
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
for (const file of ['ruff.toml', '.prettierrc', '.prettierignore']) {
|
|
114
|
+
const relevant = file === 'ruff.toml' ? includesValue(languages, 'python') : includesValue(languages, 'typescript')
|
|
115
|
+
if (!relevant && existsSync(join(target, file))) {
|
|
116
|
+
changed.push(file)
|
|
117
|
+
if (dryRun) console.log(`Would remove irrelevant language configuration ${file}`)
|
|
118
|
+
else rmSync(join(target, file), { force: true })
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (!dryRun && existsSync(join(target, '.githooks/pre-commit'))) {
|
|
122
|
+
chmodSync(join(target, '.githooks/pre-commit'), 0o755)
|
|
123
|
+
git(target, ['config', 'core.hooksPath', '.githooks'])
|
|
124
|
+
}
|
|
125
|
+
console.log(`${changed.length} baseline file(s) differ.`)
|
|
126
|
+
return { changed, config }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** @param {string} file @param {string} languages @param {string} features */
|
|
130
|
+
function shouldInclude(file, languages, features) {
|
|
131
|
+
if (file === 'ruff.toml') return includesValue(languages, 'python')
|
|
132
|
+
if (file === '.prettierrc') return includesValue(languages, 'typescript')
|
|
133
|
+
if (file === '.github/dependabot.yml') return includesValue(features, 'dependabot')
|
|
134
|
+
const workflow = file.match(/^\.github\/workflows\/([^/]+)\.yml$/)?.[1]
|
|
135
|
+
return !workflow || includesValue(features, workflow)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** @param {string} source @param {string} file */
|
|
139
|
+
function sourcePath(source, file) {
|
|
140
|
+
if (file.startsWith('.github/workflows/')) {
|
|
141
|
+
const name = file.slice('.github/workflows/'.length, -4)
|
|
142
|
+
return join(source, '.github/workflows', `${name}_self-ci.yml`)
|
|
143
|
+
}
|
|
144
|
+
return join(source, file)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** @param {string} content @param {Record<string,string>} config @param {string} repository @param {string} ref */
|
|
148
|
+
function renderWorkflow(content, config, repository, ref) {
|
|
149
|
+
const localPrefix = 'uses: ./.github/workflows/'
|
|
150
|
+
const remotePrefix = `uses: ${repository}/.github/workflows/`
|
|
151
|
+
let rendered = content.replaceAll(localPrefix, remotePrefix)
|
|
152
|
+
rendered = rendered.replace(new RegExp(`${escapeRegExp(remotePrefix)}([^\\s@]+)`, 'g'), `$&@${ref}`)
|
|
153
|
+
/** @type {Record<string, string|undefined>} */
|
|
154
|
+
const runners = {
|
|
155
|
+
ci: config.ci_runner ?? config.runner,
|
|
156
|
+
test: config.test_runner ?? config.runner,
|
|
157
|
+
security: config.security_runner ?? config.runner,
|
|
158
|
+
codeql: config.codeql_runner ?? config.runner,
|
|
159
|
+
'draft-pr': config.pr_runner ?? config.runner,
|
|
160
|
+
'release-pr': config.pr_runner ?? config.runner,
|
|
161
|
+
release: config.release_runner ?? config.runner,
|
|
162
|
+
}
|
|
163
|
+
const workflow = content.match(/\.github\/workflows\/([^/]+)\.yml/)?.[1]
|
|
164
|
+
const runner = workflow ? runners[workflow] : undefined
|
|
165
|
+
if (runner) rendered = rendered.replace(/^(\s+runner:)\s+.*$/m, `$1 ${runner}`)
|
|
166
|
+
return rendered
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** @param {string} value */
|
|
170
|
+
function escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') }
|
|
171
|
+
|
|
172
|
+
/** @param {string} baseline @param {string} existing */
|
|
173
|
+
function mergeGitignore(baseline, existing) {
|
|
174
|
+
const marker = '# Repository-specific rules'
|
|
175
|
+
const custom = existing.includes(marker) ? existing.slice(existing.indexOf(marker) + marker.length).trim() : ''
|
|
176
|
+
return custom ? `${baseline.trimEnd()}\n\n${marker}\n${custom}\n` : baseline
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** @param {string} file @param {string} content */
|
|
180
|
+
function isLegacyManagedDoc(file, content) {
|
|
181
|
+
if (file === 'AGENTS.md') {
|
|
182
|
+
return content.includes('.github/scripts/bootstrap.sh') && content.includes('bash .github/scripts/ci.sh')
|
|
183
|
+
}
|
|
184
|
+
if (file === '.github/CONTRIBUTING.md') {
|
|
185
|
+
return content.includes('.github/scripts/bootstrap.sh') && content.includes('.github/template.yml')
|
|
186
|
+
}
|
|
187
|
+
return false
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** @param {string} target @param {string[]} args */
|
|
191
|
+
function git(target, args) { spawnSync('git', args, { cwd: target, stdio: 'ignore' }) }
|
|
192
|
+
|
|
193
|
+
/** @param {string} file @param {Buffer|string} content @param {boolean} dryRun */
|
|
194
|
+
function writeOrReport(file, content, dryRun) {
|
|
195
|
+
if (dryRun) { console.log(`Would sync ${file}`); return }
|
|
196
|
+
mkdirSync(dirname(file), { recursive: true })
|
|
197
|
+
writeFileSync(file, content)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** @param {Buffer} a @param {Buffer} b */
|
|
201
|
+
function buffersEqual(a, b) { return a.equals(b) }
|
|
202
|
+
|
|
203
|
+
/** @param {string} root @param {string} source @returns {Record<string,string>} */
|
|
204
|
+
function createDefaultConfig(root, source) {
|
|
205
|
+
const languages = detectLanguages(root).join(',')
|
|
206
|
+
const packageManager = detectPackageManager(root)
|
|
207
|
+
return {
|
|
208
|
+
version: '1', profile: detectProfile(root), languages, features: 'all', codeql: 'auto', dependency_review: 'auto', package_manager: packageManager,
|
|
209
|
+
runtime_repository: '0xPlayerOne/code-foundry', runtime_ref: `v${readPackageVersion(source)}`,
|
|
210
|
+
runner: 'ubuntu-latest', unit_runner: 'ubuntu-slim', ci_runner: 'ubuntu-latest', test_runner: 'ubuntu-latest',
|
|
211
|
+
toolchain: 'auto',
|
|
212
|
+
security_runner: 'ubuntu-slim', codeql_runner: 'ubuntu-latest', pr_runner: 'ubuntu-slim', release_runner: 'ubuntu-slim',
|
|
213
|
+
prune_standard: 'false', cache_packages: 'auto', cache_build: 'auto', coverage_minimum: '80', turbo_remote: 'auto',
|
|
214
|
+
release_type: detectPackageManager(root) === 'none' ? 'auto' : 'node', npm_publish: 'false',
|
|
215
|
+
license: existsSync(join(root, 'LICENSE')) ? 'preserve' : 'gpl-3.0-or-later', git_workflow: 'staging-release', merge_strategy: 'rebase',
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** @param {Record<string,string>} config */
|
|
220
|
+
function renderConfig(config) { return `${Object.entries(config).map(([key, value]) => `${key}: ${value}`).join('\n')}\n` }
|
|
221
|
+
/** @param {string} root */
|
|
222
|
+
function readPackageVersion(root) {
|
|
223
|
+
try { return JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).version ?? '0.0.0' } catch { return '0.0.0' }
|
|
224
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Read the intentionally small YAML subset used by code-foundry.yml.
|
|
7
|
+
* Values are scalar strings; this keeps the CLI dependency-free.
|
|
8
|
+
* @param {string} file
|
|
9
|
+
* @returns {Record<string, string>}
|
|
10
|
+
*/
|
|
11
|
+
export function readConfig(file) {
|
|
12
|
+
if (!existsSync(file)) return {}
|
|
13
|
+
|
|
14
|
+
/** @type {Record<string, string>} */
|
|
15
|
+
const config = {}
|
|
16
|
+
for (const line of readFileSync(file, 'utf8').split(/\r?\n/)) {
|
|
17
|
+
const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*?)\s*$/)
|
|
18
|
+
if (!match) continue
|
|
19
|
+
const value = match[2].replace(/\s+#.*$/, '').trim().replace(/^['"]|['"]$/g, '')
|
|
20
|
+
config[match[1]] = value
|
|
21
|
+
}
|
|
22
|
+
return config
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** @param {string} value @param {string} fallback */
|
|
26
|
+
export function configured(value, fallback) {
|
|
27
|
+
return value === undefined || value === '' ? fallback : value
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** @param {string} value @returns {string[]} */
|
|
31
|
+
export function listValue(value) {
|
|
32
|
+
return value.split(',').map((item) => item.trim()).filter(Boolean)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** @param {string} value @param {string} item */
|
|
36
|
+
export function includesValue(value, item) {
|
|
37
|
+
return value === 'all' || listValue(value).includes(item)
|
|
38
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs'
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
import { configured, includesValue, readConfig } from './config.mjs'
|
|
6
|
+
|
|
7
|
+
const ignoredDirectories = new Set([
|
|
8
|
+
'.git',
|
|
9
|
+
'.code-foundry',
|
|
10
|
+
'.kilo',
|
|
11
|
+
'.mise',
|
|
12
|
+
'.next',
|
|
13
|
+
'.nuxt',
|
|
14
|
+
'.venv',
|
|
15
|
+
'node_modules',
|
|
16
|
+
'target',
|
|
17
|
+
'vendor',
|
|
18
|
+
])
|
|
19
|
+
|
|
20
|
+
/** @param {string} root @returns {string[]} */
|
|
21
|
+
function sourceFiles(root) {
|
|
22
|
+
/** @type {string[]} */
|
|
23
|
+
const files = []
|
|
24
|
+
/** @param {string} directory */
|
|
25
|
+
function visit(directory) {
|
|
26
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
27
|
+
if (entry.isDirectory() && ignoredDirectories.has(entry.name)) continue
|
|
28
|
+
const path = join(directory, entry.name)
|
|
29
|
+
if (entry.isDirectory()) visit(path)
|
|
30
|
+
else files.push(path)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
visit(root)
|
|
34
|
+
return files
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** @param {string} root @param {RegExp} pattern */
|
|
38
|
+
function hasSource(root, pattern) {
|
|
39
|
+
return sourceFiles(root).some((file) => pattern.test(file))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** @param {string} root */
|
|
43
|
+
export function detectLanguages(root) {
|
|
44
|
+
const files = sourceFiles(root)
|
|
45
|
+
/** @type {string[]} */
|
|
46
|
+
const languages = []
|
|
47
|
+
if (
|
|
48
|
+
existsSync(join(root, 'package.json')) ||
|
|
49
|
+
files.some((file) => /\.(?:js|jsx|mjs|cjs|ts|tsx|mts|cts)$/.test(file)) ||
|
|
50
|
+
files.some((file) => /(?:bun\.lockb?|pnpm-lock\.yaml|yarn\.lock|package-lock\.json)$/.test(file))
|
|
51
|
+
) languages.push('typescript')
|
|
52
|
+
if (existsSync(join(root, 'Cargo.toml')) || hasSource(root, /\.rs$/)) languages.push('rust')
|
|
53
|
+
if (
|
|
54
|
+
existsSync(join(root, 'pyproject.toml')) ||
|
|
55
|
+
existsSync(join(root, 'requirements.txt')) ||
|
|
56
|
+
existsSync(join(root, 'requirements-dev.txt')) ||
|
|
57
|
+
hasSource(root, /\.py$/)
|
|
58
|
+
) languages.push('python')
|
|
59
|
+
if (files.some((file) => file.endsWith('.sol'))) languages.push('solidity')
|
|
60
|
+
return languages.length ? languages : ['none']
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** @param {string} root */
|
|
64
|
+
export function detectPackageManager(root) {
|
|
65
|
+
if (existsSync(join(root, 'bun.lock')) || existsSync(join(root, 'bun.lockb'))) return 'bun'
|
|
66
|
+
if (existsSync(join(root, 'pnpm-lock.yaml'))) return 'pnpm'
|
|
67
|
+
if (existsSync(join(root, 'yarn.lock'))) return 'yarn'
|
|
68
|
+
if (existsSync(join(root, 'package-lock.json'))) return 'npm'
|
|
69
|
+
if (existsSync(join(root, 'package.json'))) return 'bun'
|
|
70
|
+
return 'none'
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** @param {string} root */
|
|
74
|
+
export function detectProfile(root) {
|
|
75
|
+
if (existsSync(join(root, 'turbo.json')) || existsSync(join(root, 'pnpm-workspace.yaml'))) return 'monorepo'
|
|
76
|
+
if (existsSync(join(root, 'package.json'))) {
|
|
77
|
+
try {
|
|
78
|
+
const packageJson = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
|
|
79
|
+
if (Array.isArray(packageJson.workspaces) || typeof packageJson.workspaces === 'object') return 'monorepo'
|
|
80
|
+
} catch {
|
|
81
|
+
// Doctor reports malformed package.json separately.
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (existsSync(join(root, 'package.json')) || existsSync(join(root, 'pyproject.toml')) || existsSync(join(root, 'Cargo.toml'))) return 'application'
|
|
85
|
+
return 'minimal'
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** @param {string} root */
|
|
89
|
+
export function resolveProfile(root) {
|
|
90
|
+
const config = readConfig(join(root, '.github/code-foundry.yml'))
|
|
91
|
+
const languages = configured(config.languages, 'auto')
|
|
92
|
+
const profile = configured(config.profile, 'auto')
|
|
93
|
+
const packageManager = configured(config.package_manager, 'auto')
|
|
94
|
+
return {
|
|
95
|
+
profile: profile === 'auto' ? detectProfile(root) : profile,
|
|
96
|
+
languages: languages === 'auto' ? detectLanguages(root).join(',') : languages,
|
|
97
|
+
package_manager: packageManager === 'auto' ? detectPackageManager(root) : packageManager,
|
|
98
|
+
features: configured(config.features, 'all'),
|
|
99
|
+
release_type: configured(config.release_type, detectReleaseType(root)),
|
|
100
|
+
npm_publish: configured(config.npm_publish, 'false'),
|
|
101
|
+
runner: configured(config.runner, 'ubuntu-latest'),
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** @param {string} root */
|
|
106
|
+
function detectReleaseType(root) {
|
|
107
|
+
if (existsSync(join(root, 'package.json'))) return 'node'
|
|
108
|
+
if (existsSync(join(root, 'pyproject.toml'))) return 'python'
|
|
109
|
+
if (existsSync(join(root, 'Cargo.toml'))) return 'rust'
|
|
110
|
+
if (existsSync(join(root, 'version.txt'))) return 'simple'
|
|
111
|
+
return 'none'
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export { includesValue }
|