code-foundry 1.39.1 → 1.39.2
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 +9 -0
- package/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/src/cli.mjs +10 -0
- package/src/commands/doctor.mjs +14 -0
- package/src/commands/sync.mjs +17 -0
- package/src/runtime-core.mjs +45 -13
- package/src/templates/gitignore +3 -0
package/.githooks/pre-commit
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env sh
|
|
2
|
+
# Generated by Code Foundry — see src/commands/sync.mjs.
|
|
2
3
|
set -eu
|
|
3
4
|
|
|
5
|
+
if [ -z "$(git diff --cached --name-only)" ]; then
|
|
6
|
+
exit 0
|
|
7
|
+
fi
|
|
8
|
+
|
|
9
|
+
if command -v npx >/dev/null 2>&1 && npx --yes code-foundry@__VERSION__ pre-commit; then
|
|
10
|
+
exit 0
|
|
11
|
+
fi
|
|
12
|
+
|
|
4
13
|
git diff --cached --check
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.39.2](https://github.com/0xPlayerOne/code-foundry/compare/v1.39.1...v1.39.2) (2026-10-01)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **hooks:** delegate the generated pre-commit hook to the runtime ([#684](https://github.com/0xPlayerOne/code-foundry/issues/684)) ([5d9a571](https://github.com/0xPlayerOne/code-foundry/commit/5d9a5714aeff0d48397e3be51b22e08395bfb5ce))
|
|
9
|
+
|
|
3
10
|
## [1.39.1](https://github.com/0xPlayerOne/code-foundry/compare/v1.39.0...v1.39.1) (2026-10-01)
|
|
4
11
|
|
|
5
12
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-foundry",
|
|
3
|
-
"version": "1.39.
|
|
3
|
+
"version": "1.39.2",
|
|
4
4
|
"description": "A fast, language-aware repository factory for agent-ready workflows, testing, security, and release automation.",
|
|
5
5
|
"homepage": "https://github.com/0xPlayerOne/code-foundry#readme",
|
|
6
6
|
"bugs": {
|
package/src/cli.mjs
CHANGED
|
@@ -271,6 +271,16 @@ async function main() {
|
|
|
271
271
|
} catch (error) {
|
|
272
272
|
fail(error instanceof Error ? error.message : String(error))
|
|
273
273
|
}
|
|
274
|
+
} else if (command === 'pre-commit') {
|
|
275
|
+
// The generated `.githooks/pre-commit` calls this. Without a CLI entry
|
|
276
|
+
// point the language-aware gate in runtime-core is unreachable from a hook.
|
|
277
|
+
try {
|
|
278
|
+
process.chdir(target)
|
|
279
|
+
const { preCommit } = await import('./runtime-core.mjs')
|
|
280
|
+
preCommit()
|
|
281
|
+
} catch (error) {
|
|
282
|
+
fail(error instanceof Error ? error.message : String(error))
|
|
283
|
+
}
|
|
274
284
|
} else fail(`unknown command: ${command}`)
|
|
275
285
|
}
|
|
276
286
|
|
package/src/commands/doctor.mjs
CHANGED
|
@@ -40,6 +40,20 @@ export function doctor(root, options = {}) {
|
|
|
40
40
|
}
|
|
41
41
|
const hooks = git(target, ['config', '--get', 'core.hooksPath'])
|
|
42
42
|
if (hooks !== '.githooks') warn('Git hooks are not enabled; run `npx code-foundry init`')
|
|
43
|
+
// A correctly enabled hook that enforces nothing is the failure this check
|
|
44
|
+
// could not see: the generated template used to be the whitespace guard
|
|
45
|
+
// alone while the real gate sat unused in the runtime. Reading only the
|
|
46
|
+
// hooksPath reports success for a hook that does nothing.
|
|
47
|
+
if (hooks === '.githooks') {
|
|
48
|
+
const hookFile = join(target, '.githooks/pre-commit')
|
|
49
|
+
if (!existsSync(hookFile)) warn('No .githooks/pre-commit found; run `npx code-foundry sync`')
|
|
50
|
+
else if (
|
|
51
|
+
!/code-foundry[@\w./-]*\s+(?:--yes\s+)?pre-commit/.test(readFileSync(hookFile, 'utf8'))
|
|
52
|
+
)
|
|
53
|
+
warn(
|
|
54
|
+
'.githooks/pre-commit does not delegate to the Code Foundry gate; run `npx code-foundry sync`'
|
|
55
|
+
)
|
|
56
|
+
}
|
|
43
57
|
if (
|
|
44
58
|
['rust', 'python', 'solidity'].some((language) =>
|
|
45
59
|
profile.languages.split(',').includes(language)
|
package/src/commands/sync.mjs
CHANGED
|
@@ -260,6 +260,9 @@ function synchronize(options) {
|
|
|
260
260
|
if (file === 'LICENSE' && license !== 'preserve' && license !== 'none') continue
|
|
261
261
|
if (file === '.github/CODEOWNERS' && existsSync(destination)) continue
|
|
262
262
|
let content = readFileSync(sourceFile)
|
|
263
|
+
if (file === '.githooks/pre-commit') {
|
|
264
|
+
content = Buffer.from(renderHook(sourceFile, source))
|
|
265
|
+
}
|
|
263
266
|
if (file === 'release-please-config.json') {
|
|
264
267
|
content = Buffer.from(renderReleaseConfig(target, sourceFile))
|
|
265
268
|
}
|
|
@@ -586,6 +589,20 @@ function renderReleaseConfig(target, sourceFile) {
|
|
|
586
589
|
return `${JSON.stringify(mergeReleaseConfig(target, sourceFile), null, 2)}\n`
|
|
587
590
|
}
|
|
588
591
|
|
|
592
|
+
/**
|
|
593
|
+
* The hook runs `npx code-foundry@<version>` on every commit, so the version
|
|
594
|
+
* is substituted at generation time rather than resolved at run time. A
|
|
595
|
+
* mutable dist-tag there would let any publish under that tag run code on
|
|
596
|
+
* every developer's machine at commit time; pinning means the gate a
|
|
597
|
+
* repository received is the gate that generated it.
|
|
598
|
+
*
|
|
599
|
+
* @param {string} sourceFile the template path
|
|
600
|
+
* @param {string} sourceRoot the package root that carries package.json
|
|
601
|
+
*/
|
|
602
|
+
function renderHook(sourceFile, sourceRoot) {
|
|
603
|
+
return readFileSync(sourceFile, 'utf8').replaceAll('__VERSION__', readPackageVersion(sourceRoot))
|
|
604
|
+
}
|
|
605
|
+
|
|
589
606
|
/** @param {string} file @param {string} languages @param {string} features @param {Record<string, string>} config */
|
|
590
607
|
function shouldInclude(file, languages, features, config) {
|
|
591
608
|
if (file === 'ruff.toml') return includesValue(languages, 'python')
|
package/src/runtime-core.mjs
CHANGED
|
@@ -7,9 +7,11 @@ import {
|
|
|
7
7
|
mkdirSync,
|
|
8
8
|
readdirSync,
|
|
9
9
|
readFileSync,
|
|
10
|
+
realpathSync,
|
|
10
11
|
writeFileSync,
|
|
11
12
|
} from 'node:fs'
|
|
12
13
|
import { resolve } from 'node:path'
|
|
14
|
+
import { fileURLToPath } from 'node:url'
|
|
13
15
|
import { spawnSync } from 'node:child_process'
|
|
14
16
|
import { detectPackageManager, resolveProfile } from './lib/profile.mjs'
|
|
15
17
|
import { configured, readConfig } from './lib/config.mjs'
|
|
@@ -1165,7 +1167,15 @@ function printProfile(command) {
|
|
|
1165
1167
|
writeOutput('npm_publish', config.npm_publish ?? 'false')
|
|
1166
1168
|
}
|
|
1167
1169
|
|
|
1168
|
-
|
|
1170
|
+
/**
|
|
1171
|
+
* The local commit gate.
|
|
1172
|
+
*
|
|
1173
|
+
* Exported so the generated `.githooks/pre-commit` can reach it: the hook is
|
|
1174
|
+
* what turns this into an actual gate, and it used to run the whitespace
|
|
1175
|
+
* check alone. The generated hook is the consumer-visible contract, so keep
|
|
1176
|
+
* the two in step.
|
|
1177
|
+
*/
|
|
1178
|
+
export function preCommit() {
|
|
1169
1179
|
const changed = capture('git', ['diff', '--cached', '--name-only'])
|
|
1170
1180
|
if (!changed) return
|
|
1171
1181
|
const check = spawnSync('git', ['diff', '--cached', '--check'], { cwd: root, stdio: 'inherit' })
|
|
@@ -1173,6 +1183,10 @@ function preCommit() {
|
|
|
1173
1183
|
if (/\.(js|jsx|ts|tsx|json|md|mdx|yml|yaml)$/.test(changed)) {
|
|
1174
1184
|
ci('format')
|
|
1175
1185
|
ci('lint')
|
|
1186
|
+
// Type errors are the fastest check that still needs a full toolchain, and
|
|
1187
|
+
// the most common reason a green-looking commit fails CI later. Run it
|
|
1188
|
+
// here while the dependency graph is warm.
|
|
1189
|
+
ci('type_check')
|
|
1176
1190
|
}
|
|
1177
1191
|
if (/\.rs$|(^|\/)Cargo\.toml$/.test(changed)) {
|
|
1178
1192
|
run('cargo', ['fmt', '--check'])
|
|
@@ -1184,16 +1198,34 @@ function preCommit() {
|
|
|
1184
1198
|
}
|
|
1185
1199
|
}
|
|
1186
1200
|
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1201
|
+
// Only dispatch when this module is the process entry point. The CLI imports
|
|
1202
|
+
// `preCommit` from here, and an unguarded dispatch would read argv and run the
|
|
1203
|
+
// gate a second time on import.
|
|
1204
|
+
const invokedDirectly =
|
|
1205
|
+
process.argv[1] !== undefined &&
|
|
1206
|
+
realpathSafe(process.argv[1]) === realpathSafe(fileURLToPath(import.meta.url))
|
|
1207
|
+
|
|
1208
|
+
/** @param {string} p @returns {string} */
|
|
1209
|
+
function realpathSafe(p) {
|
|
1210
|
+
try {
|
|
1211
|
+
return realpathSync(p)
|
|
1212
|
+
} catch {
|
|
1213
|
+
return p
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
if (invokedDirectly) {
|
|
1218
|
+
const [area, task, ecosystem] = process.argv.slice(2)
|
|
1219
|
+
try {
|
|
1220
|
+
if (area === 'ci') ci(task)
|
|
1221
|
+
else if (area === 'security') security(task, ecosystem)
|
|
1222
|
+
else if (area === 'codeql') codeql()
|
|
1223
|
+
else if (area === 'profile') printProfile(task)
|
|
1224
|
+
else if (area === 'validation') validation(task)
|
|
1225
|
+
else if (area === 'pre-commit') preCommit()
|
|
1226
|
+
else throw new Error(`Unknown runtime command: ${area || '(missing)'}`)
|
|
1227
|
+
} catch (error) {
|
|
1228
|
+
console.error(error instanceof Error ? error.message : String(error))
|
|
1229
|
+
process.exitCode = 1
|
|
1230
|
+
}
|
|
1199
1231
|
}
|