code-foundry 0.26.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.
Files changed (44) hide show
  1. package/.githooks/pre-commit +3 -4
  2. package/.github/CONTRIBUTING.md +10 -13
  3. package/.github/actions/setup/action.yml +50 -3
  4. package/.github/code-foundry.yml +6 -1
  5. package/.github/workflows/ci.yml +20 -23
  6. package/.github/workflows/ci_self-ci.yml +1 -1
  7. package/.github/workflows/codeql.yml +23 -29
  8. package/.github/workflows/codeql_self-ci.yml +1 -1
  9. package/.github/workflows/draft-pr.yml +2 -2
  10. package/.github/workflows/release-pr.yml +4 -4
  11. package/.github/workflows/release.yml +17 -29
  12. package/.github/workflows/security.yml +24 -19
  13. package/.github/workflows/security_self-ci.yml +1 -1
  14. package/.github/workflows/test.yml +20 -20
  15. package/.github/workflows/test_self-ci.yml +1 -1
  16. package/AGENTS.md +13 -10
  17. package/CHANGELOG.md +21 -0
  18. package/README.md +16 -6
  19. package/docs/CONFIGURATION.md +10 -1
  20. package/docs/INITIALIZATION.md +6 -5
  21. package/docs/RELEASES.md +5 -0
  22. package/docs/WORKFLOWS.md +12 -5
  23. package/package.json +7 -3
  24. package/src/cli.mjs +29 -25
  25. package/src/commands/doctor.mjs +70 -0
  26. package/src/commands/sync.mjs +224 -0
  27. package/src/lib/config.mjs +38 -0
  28. package/src/lib/profile.mjs +114 -0
  29. package/src/runtime.mjs +316 -0
  30. package/.github/scripts/bootstrap.sh +0 -21
  31. package/.github/scripts/changed-files.sh +0 -96
  32. package/.github/scripts/ci.sh +0 -641
  33. package/.github/scripts/codeql-languages.sh +0 -91
  34. package/.github/scripts/doctor.sh +0 -98
  35. package/.github/scripts/format-fast-path.sh +0 -80
  36. package/.github/scripts/init-repo.sh +0 -268
  37. package/.github/scripts/pre-commit.sh +0 -67
  38. package/.github/scripts/profile.sh +0 -186
  39. package/.github/scripts/security.sh +0 -214
  40. package/.github/scripts/sitecustomize.py +0 -17
  41. package/.github/scripts/sync-codeowners.sh +0 -76
  42. package/.github/scripts/sync-protection.sh +0 -138
  43. package/.github/scripts/sync-template.sh +0 -748
  44. package/.github/scripts/turbo-cache-probe.sh +0 -102
@@ -1,91 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- languages=()
5
- changed_files=""
6
-
7
- # Scheduled and manually dispatched scans are always full scans. For pushes
8
- # and pull requests, a shallow commit diff lets the matrix skip analyzers for
9
- # languages untouched by the change while keeping every matrix check visible.
10
- case "${GITHUB_EVENT_NAME:-}" in
11
- schedule|workflow_dispatch) ;;
12
- *)
13
- base_sha="${CODEQL_BASE_SHA:-}"
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 || true
17
- fi
18
- changed_files="$(git diff --name-only "$base_sha" "${GITHUB_SHA:-HEAD}" 2>/dev/null || true)"
19
- fi
20
- [ -n "$changed_files" ] || changed_files="$(git diff --name-only HEAD^ HEAD 2>/dev/null || true)"
21
- ;;
22
- esac
23
-
24
- if find .github/workflows -type f \( -name '*.yml' -o -name '*.yaml' \) -print -quit | grep -q .; then languages+=(actions); fi
25
- configured=""
26
- if [ -x .github/scripts/profile.sh ]; then
27
- configured="$(bash .github/scripts/profile.sh get languages 2>/dev/null || true)"
28
- else
29
- config_file=.github/code-foundry.yml
30
- if [ -f "$config_file" ]; then
31
- configured="$(awk -F': ' '/^languages:/ {print $2; exit}' "$config_file")"
32
- fi
33
- fi
34
-
35
- if [ "$configured" = auto ] || [ "$configured" = all ] || [ -z "$configured" ]; then
36
- tracked_files="$(git ls-files)"
37
- if printf '%s\n' "$tracked_files" | grep -Eq '(^|/)([^/]+\.(ts|tsx|js|jsx)|package\.json|tsconfig[^/]*\.json)$'; then
38
- languages+=(javascript-typescript)
39
- fi
40
- if printf '%s\n' "$tracked_files" | awk '!/^\.github\// && /(^|\/)([^\/]+\.py|pyproject\.toml|requirements[^\/]*\.txt|setup\.py)$/ { found=1; exit } END { exit !found }'; then
41
- languages+=(python)
42
- fi
43
- if printf '%s\n' "$tracked_files" | grep -Eq '(^|/)([^/]+\.rs|Cargo\.toml|Cargo\.lock)$'; then
44
- languages+=(rust)
45
- fi
46
- else
47
- case ",$configured," in *,typescript,*) languages+=(javascript-typescript) ;; esac
48
- case ",$configured," in *,python,*) languages+=(python) ;; esac
49
- case ",$configured," in *,rust,*) languages+=(rust) ;; esac
50
- fi
51
-
52
- changed_json="$(printf '%s\n' "$changed_files" | jq -Rsc 'split("\n") | map(select(length > 0))')"
53
- json=$(printf '%s\n' "${languages[@]}" | jq -Rsc --argjson changed "$changed_json" '
54
- split("\n")
55
- | map(select(length > 0) | {
56
- language: .,
57
- name: (if . == "javascript-typescript" then "TypeScript" elif . == "actions" then "Actions" else (. | ascii_upcase[0:1] + .[1:]) end),
58
- "build-mode": "none",
59
- changed: (
60
- if ($changed | length) == 0 then true
61
- elif . == "actions" then any($changed[]; test("(^|/)(\\.github/workflows/|action\\.yml$)"))
62
- elif . == "javascript-typescript" then any($changed[]; test("(^|/)(.*\\.(js|jsx|mjs|cjs|ts|tsx|mts|cts)|package\\.json|tsconfig[^/]*\\.json|((bun|pnpm|yarn|package-lock)\\.lock))$"))
63
- elif . == "python" then any($changed[]; test("(^|/)(.*\\.py|pyproject\\.toml|requirements[^/]*\\.txt|setup\\.py|Pipfile\\.lock|poetry\\.lock)$"))
64
- elif . == "rust" then any($changed[]; test("(^|/)(.*\\.rs|Cargo\\.toml|Cargo\\.lock)$"))
65
- else true
66
- end
67
- )
68
- })
69
- ')
70
- printf 'languages=%s\n' "$json" >> "$GITHUB_OUTPUT"
71
-
72
- # Expose stable, shell-friendly outputs for explicit analyzer jobs. Keeping
73
- # these separate from the JSON matrix lets workflow-level conditions avoid
74
- # allocating a runner for languages that are absent or unchanged.
75
- for codeql_language in actions javascript-typescript python rust; do
76
- output_prefix="$codeql_language"
77
- case "$codeql_language" in
78
- javascript-typescript) output_prefix="javascript" ;;
79
- esac
80
-
81
- entry="$(jq -c --arg language "$codeql_language" '.[] | select(.language == $language)' <<< "$json")"
82
- if [ -n "$entry" ]; then
83
- printf '%s_available=true\n' "$output_prefix" >> "$GITHUB_OUTPUT"
84
- printf '%s_changed=%s\n' "$output_prefix" "$(jq -r '.changed' <<< "$entry")" >> "$GITHUB_OUTPUT"
85
- printf '%s_build_mode=%s\n' "$output_prefix" "$(jq -r '."build-mode"' <<< "$entry")" >> "$GITHUB_OUTPUT"
86
- else
87
- printf '%s_available=false\n' "$output_prefix" >> "$GITHUB_OUTPUT"
88
- printf '%s_changed=false\n' "$output_prefix" >> "$GITHUB_OUTPUT"
89
- printf '%s_build_mode=none\n' "$output_prefix" >> "$GITHUB_OUTPUT"
90
- fi
91
- done
@@ -1,98 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- errors=0
5
-
6
- configured_features="all"
7
- config_file=.github/code-foundry.yml
8
- if [ -f "$config_file" ]; then
9
- configured_features="$(awk -F': ' '/^features:/ {print $2; exit}' "$config_file")"
10
- [ -n "$configured_features" ] || configured_features="all"
11
- fi
12
-
13
- feature_enabled() {
14
- [ "$configured_features" = all ] && return 0
15
- case " $(printf '%s' "$configured_features" | tr ',' ' ') " in
16
- *" $1 "*) return 0 ;;
17
- *) return 1 ;;
18
- esac
19
- }
20
-
21
- error() {
22
- printf 'ERROR: %s\n' "$1" >&2
23
- errors=$((errors + 1))
24
- }
25
-
26
- warn() {
27
- printf 'WARN: %s\n' "$1" >&2
28
- }
29
-
30
- if [ ! -f .mise.toml ]; then
31
- error ".mise.toml is missing"
32
- fi
33
-
34
- if [ "$(git config --get core.hooksPath || true)" != ".githooks" ]; then
35
- warn "Git hooks are not enabled; run bash .github/scripts/bootstrap.sh"
36
- fi
37
-
38
- if [ -f package.json ]; then
39
- node -e 'JSON.parse(require("fs").readFileSync("package.json", "utf8"))'
40
- lockfiles=0
41
- for lockfile in bun.lock bun.lockb pnpm-lock.yaml yarn.lock package-lock.json; do
42
- if [ -f "$lockfile" ]; then lockfiles=$((lockfiles + 1)); fi
43
- done
44
- if [ "$lockfiles" -eq 0 ]; then
45
- if node -e 'const p=require("./package.json"); const groups=[p.dependencies,p.devDependencies,p.optionalDependencies,p.peerDependencies]; process.exit(groups.some((g)=>g && Object.keys(g).length) ? 0 : 1)' 2>/dev/null; then
46
- error "package.json exists but no supported lockfile was found"
47
- else
48
- printf '%s\n' "INFO: package.json has no dependencies; a lockfile is optional"
49
- fi
50
- elif [ "$lockfiles" -gt 1 ]; then
51
- error "multiple JavaScript lockfiles found; keep one package manager"
52
- fi
53
- if node -e 'const p=require("./package.json"); process.exit(p.packageManager ? 0 : 1)' 2>/dev/null; then
54
- declared="$(node -p 'require("./package.json").packageManager.split("@")[0]')"
55
- actual=""
56
- [ -f bun.lock ] || [ -f bun.lockb ] && actual="bun"
57
- [ -f pnpm-lock.yaml ] && actual="pnpm"
58
- [ -f yarn.lock ] && actual="yarn"
59
- [ -f package-lock.json ] && actual="npm"
60
- [ -n "$actual" ] && [ "$declared" != "$actual" ] && error "packageManager ($declared) does not match $actual lockfile"
61
- fi
62
- if git ls-files -- '*.ts' '*.tsx' '*.js' '*.jsx' | grep -q .; then
63
- if ! node -e 'const p=require("./package.json"); const s=p.scripts||{}; process.exit(s.test||s["test:unit"]||s["test:integration"] ? 0 : 1)' 2>/dev/null; then
64
- warn "JavaScript/TypeScript sources found but no test or test:unit/test:integration script is defined"
65
- fi
66
- fi
67
- if [ -f bunfig.toml ] && node -e 'const p=require("./package.json"); process.exit(p.scripts?.["test:coverage"] ? 0 : 1)' 2>/dev/null; then
68
- if ! grep -q 'coverageThreshold' bunfig.toml; then
69
- printf '%s\n' "INFO: shared CI enforces the Bun aggregate coverage threshold"
70
- fi
71
- fi
72
- fi
73
-
74
- if [ -f Cargo.toml ]; then
75
- command -v cargo >/dev/null 2>&1 || error "Cargo is required for this repository"
76
- cargo metadata --no-deps --format-version 1 >/dev/null
77
- fi
78
-
79
- if [ -f pyproject.toml ] || [ -f requirements.txt ] || [ -f requirements-dev.txt ]; then
80
- if ! command -v python >/dev/null 2>&1 && [ ! -x .venv/bin/python ]; then
81
- error "Python is required for this repository"
82
- fi
83
- fi
84
-
85
- for workflow in ci codeql security test draft-pr release-pr release; do
86
- if feature_enabled "$workflow"; then
87
- [ -f ".github/workflows/$workflow.yml" ] || error "missing enabled workflow: $workflow.yml"
88
- fi
89
- done
90
-
91
- printf '%s\n' 'Remote CI, Test, Security, CodeQL, and release runtimes are loaded by reusable workflow wrappers.'
92
-
93
- if [ "$errors" -gt 0 ]; then
94
- printf '%s\n' "Repository doctor found $errors error(s)." >&2
95
- exit 1
96
- fi
97
-
98
- printf '%s\n' "Repository doctor passed."
@@ -1,80 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- # Bootstrap only the formatter for Bun projects whose repository-owned format
5
- # script directly invokes Prettier. The workflow keeps a full-install fallback
6
- # because formatter configs may import arbitrary repository dependencies.
7
-
8
- set_output() {
9
- printf '%s=%s\n' "$1" "$2" >> "${GITHUB_OUTPUT:-/dev/null}"
10
- }
11
-
12
- set_output hit false
13
-
14
- [ -f package.json ] || exit 0
15
- [ -f bun.lock ] || [ -f bun.lockb ] || exit 0
16
-
17
- project_profile="$(node <<'NODE'
18
- const fs = require('node:fs');
19
- const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
20
- process.stdout.write(Array.isArray(packageJson.workspaces) ||
21
- (packageJson.workspaces && typeof packageJson.workspaces === 'object') ? 'workspace' : 'standalone');
22
- NODE
23
- )"
24
- lock_file='bun.lock'
25
- [ -f "$lock_file" ] || lock_file='bun.lockb'
26
- lock_bytes="$(wc -c < "$lock_file")"
27
- # A temporary npm formatter install has fixed overhead. For small standalone
28
- # projects, the normal Bun install is usually faster; workspace graphs and
29
- # larger lockfiles still benefit materially from avoiding their full tree.
30
- if [ "$project_profile" = standalone ] && [ "$lock_bytes" -le 524288 ]; then
31
- exit 0
32
- fi
33
-
34
- format_script="$(node <<'NODE'
35
- const fs = require('node:fs');
36
- const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
37
- console.log(packageJson.scripts?.['format:check'] || packageJson.scripts?.format || '');
38
- NODE
39
- )"
40
- if ! grep -Eiq '(^|[[:space:];&|])prettier([[:space:]]|$)' <<<"$format_script" ||
41
- grep -Eiq '(^|[[:space:];&|])turbo([[:space:]]|$)' <<<"$format_script"; then
42
- exit 0
43
- fi
44
-
45
- prettier_version="$(node <<'NODE'
46
- const fs = require('node:fs');
47
- const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
48
- for (const group of [packageJson.devDependencies, packageJson.dependencies, packageJson.optionalDependencies]) {
49
- const value = group?.prettier;
50
- const match = typeof value === 'string' && value.match(/(\d+\.\d+\.\d+)/);
51
- if (match) {
52
- console.log(match[1]);
53
- break;
54
- }
55
- }
56
- NODE
57
- )"
58
- [ -n "$prettier_version" ] || exit 0
59
-
60
- tool_home="${RUNNER_TEMP:-/tmp}/repo-foundry-format"
61
- install_log="$(mktemp)"
62
- cleanup() { rm -f "$install_log"; }
63
- trap cleanup EXIT
64
-
65
- if ! npm install --prefix "$tool_home" --no-save --ignore-scripts --no-audit --no-fund \
66
- "prettier@${prettier_version}" >"$install_log" 2>&1; then
67
- echo 'Format Probe: formatter bootstrap unavailable; using full install'
68
- exit 0
69
- fi
70
-
71
- tool_bin="$tool_home/node_modules/.bin"
72
- if [ ! -x "$tool_bin/prettier" ]; then
73
- echo 'Format Probe: formatter binary unavailable; using full install'
74
- exit 0
75
- fi
76
-
77
- echo "$tool_bin" >> "${GITHUB_PATH:-/dev/null}"
78
- printf 'REPO_FOUNDRY_FORMAT_FAST=true\n' >> "${GITHUB_ENV:-/dev/null}"
79
- echo "Format Probe: using Prettier ${prettier_version} without project install"
80
- set_output hit true
@@ -1,268 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- source="${REPO_FOUNDRY_SOURCE:-https://github.com/${GITHUB_REPOSITORY_OWNER:-OWNER}/code-foundry.git}"
5
- ref="main"
6
- profile="${REPO_FOUNDRY_PROFILE:-auto}"
7
- protection=false
8
- dry_run=false
9
- prune=false
10
- force=false
11
- languages="${REPO_FOUNDRY_LANGUAGES:-auto}"
12
- features="${REPO_FOUNDRY_FEATURES:-all}"
13
- package_manager="${REPO_FOUNDRY_PACKAGE_MANAGER:-auto}"
14
- runtime_repository="${REPO_FOUNDRY_RUNTIME_REPOSITORY:-}"
15
- runtime_ref="${REPO_FOUNDRY_RUNTIME_REF:-}"
16
- bootstrap=true
17
- release_type="${REPO_FOUNDRY_RELEASE_TYPE:-auto}"
18
- npm_publish="${REPO_FOUNDRY_NPM_PUBLISH:-false}"
19
- prune_standard="${REPO_FOUNDRY_PRUNE_STANDARD:-false}"
20
- license="${REPO_FOUNDRY_LICENSE:-gpl-3.0-or-later}"
21
- license_file="${REPO_FOUNDRY_LICENSE_FILE:-}"
22
- runner="${REPO_FOUNDRY_RUNNER:-ubuntu-latest}"
23
- unit_runner="${REPO_FOUNDRY_UNIT_RUNNER:-ubuntu-slim}"
24
- ci_runner="${REPO_FOUNDRY_CI_RUNNER:-ubuntu-latest}"
25
- test_runner="${REPO_FOUNDRY_TEST_RUNNER:-ubuntu-latest}"
26
- security_runner="${REPO_FOUNDRY_SECURITY_RUNNER:-ubuntu-slim}"
27
- codeql_runner="${REPO_FOUNDRY_CODEQL_RUNNER:-ubuntu-latest}"
28
- pr_runner="${REPO_FOUNDRY_PR_RUNNER:-ubuntu-slim}"
29
- release_runner="${REPO_FOUNDRY_RELEASE_RUNNER:-ubuntu-slim}"
30
- cache_packages="${REPO_FOUNDRY_CACHE_PACKAGES:-auto}"
31
- cache_build="${REPO_FOUNDRY_CACHE_BUILD:-auto}"
32
- coverage_minimum="${REPO_FOUNDRY_COVERAGE_MINIMUM:-80}"
33
- turbo_remote="${REPO_FOUNDRY_TURBO_REMOTE:-auto}"
34
-
35
- profile_set=false
36
- languages_set=false
37
- features_set=false
38
- package_manager_set=false
39
- runtime_repository_set=false
40
- runtime_ref_set=false
41
- release_type_set=false
42
- npm_publish_set=false
43
- license_set=false
44
- [ -n "${REPO_FOUNDRY_PROFILE:-}" ] && profile_set=true
45
- [ -n "${REPO_FOUNDRY_LANGUAGES:-}" ] && languages_set=true
46
- [ -n "${REPO_FOUNDRY_FEATURES:-}" ] && features_set=true
47
- [ -n "${REPO_FOUNDRY_PACKAGE_MANAGER:-}" ] && package_manager_set=true
48
- [ -n "${REPO_FOUNDRY_RUNTIME_REPOSITORY:-}" ] && runtime_repository_set=true
49
- [ -n "${REPO_FOUNDRY_RUNTIME_REF:-}" ] && runtime_ref_set=true
50
- [ -n "${REPO_FOUNDRY_RELEASE_TYPE:-}" ] && release_type_set=true
51
- [ -n "${REPO_FOUNDRY_NPM_PUBLISH:-}" ] && npm_publish_set=true
52
- [ -n "${REPO_FOUNDRY_LICENSE:-}" ] && license_set=true
53
- tool_dir=""
54
-
55
- cleanup() {
56
- if [ -n "$tool_dir" ]; then rm -rf "$tool_dir"; fi
57
- }
58
- trap cleanup EXIT
59
-
60
- usage() {
61
- cat <<'EOF'
62
- Usage: init-repo.sh [options]
63
-
64
- Initialize or synchronize a repository from the shared baseline.
65
-
66
- Options:
67
- --source PATH_OR_URL Template source (default: REPO_FOUNDRY_SOURCE or GitHub owner)
68
- --ref REF Template branch or tag (default: main)
69
- --profile NAME auto, application, monorepo, or minimal
70
- --languages LIST auto or comma-separated: typescript,rust,python,solidity
71
- --features LIST all or comma-separated optional features:
72
- ci,codeql,security,test,draft-pr,release-pr,release,dependabot
73
- --package-manager NAME auto, bun, pnpm, yarn, or npm
74
- --runtime-repository OWNER/REPO Reusable workflow runtime repository (auto from source)
75
- --runtime-ref REF Reusable workflow runtime tag or branch
76
- --release-type NAME auto, node, python, rust, or simple
77
- --license NAME gpl-3.0-or-later, agpl-3.0-or-later, mit, preserve, or none
78
- --license-file PATH Use an exact custom license file
79
- --npm-publish Enable npm publication in the release workflow
80
- --dry-run Preview changes without writing files
81
- --force Replace protected standard docs/templates
82
- --prune Remove disabled standard workflows (never custom workflows)
83
- --protection Synchronize main branch required checks
84
- --no-bootstrap Skip mise/hooks/doctor bootstrap after init
85
- -h, --help Show this help
86
-
87
- Examples:
88
- bash .github/scripts/init-repo.sh --languages typescript,python
89
- bash .github/scripts/init-repo.sh --languages rust --features ci,codeql,security,test
90
- bash .github/scripts/init-repo.sh --features all --dry-run
91
- EOF
92
- }
93
-
94
- while [ "$#" -gt 0 ]; do
95
- case "$1" in
96
- --source) source="${2:?missing source path or URL}"; shift 2 ;;
97
- --ref) ref="${2:?missing ref}"; shift 2 ;;
98
- --profile) profile="${2:?missing profile}"; profile_set=true; shift 2 ;;
99
- --languages) languages="${2:?missing language list}"; languages_set=true; shift 2 ;;
100
- --features) features="${2:?missing feature list}"; features_set=true; shift 2 ;;
101
- --package-manager) package_manager="${2:?missing package manager}"; package_manager_set=true; shift 2 ;;
102
- --runtime-repository) runtime_repository="${2:?missing runtime repository}"; runtime_repository_set=true; shift 2 ;;
103
- --runtime-ref) runtime_ref="${2:?missing runtime ref}"; runtime_ref_set=true; shift 2 ;;
104
- --release-type) release_type="${2:?missing release type}"; release_type_set=true; shift 2 ;;
105
- --license) license="${2:?missing license}"; license_set=true; shift 2 ;;
106
- --license-file) license_file="${2:?missing license file}"; shift 2 ;;
107
- --npm-publish) npm_publish=true; shift ;;
108
- --dry-run) dry_run=true; shift ;;
109
- --force) force=true; shift ;;
110
- --prune) prune=true; shift ;;
111
- --protection) protection=true; shift ;;
112
- --no-bootstrap) bootstrap=false; shift ;;
113
- -h|--help) usage; exit 0 ;;
114
- *)
115
- printf 'Unknown option: %s\n' "$1" >&2
116
- exit 2
117
- ;;
118
- esac
119
- done
120
-
121
- config_path=.github/code-foundry.yml
122
- if [ -f "$config_path" ]; then
123
- config_value() {
124
- awk -F': ' -v key="$1" '$1 == key { value=$2; sub(/[[:space:]]+#.*/, "", value); gsub(/^[[:space:]]+|[[:space:]]+$/, "", value); print value; exit }' "$config_path"
125
- }
126
- if [ "$profile_set" = false ]; then profile="$(config_value profile)"; fi
127
- if [ "$languages_set" = false ]; then languages="$(config_value languages)"; fi
128
- if [ "$features_set" = false ]; then features="$(config_value features)"; fi
129
- if [ "$package_manager_set" = false ]; then package_manager="$(config_value package_manager)"; fi
130
- if [ "$runtime_repository_set" = false ]; then runtime_repository="$(config_value runtime_repository)"; fi
131
- if [ "$runtime_ref_set" = false ]; then runtime_ref="$(config_value runtime_ref)"; fi
132
- if [ "$release_type_set" = false ]; then release_type="$(config_value release_type)"; fi
133
- if [ "$npm_publish_set" = false ]; then npm_publish="$(config_value npm_publish)"; fi
134
- if [ "$license_set" = false ]; then license="$(config_value license)"; fi
135
- [ -n "$license_file" ] || license_file="$(config_value license_file)"
136
- prune_standard="$(config_value prune_standard)"
137
- [ -n "${REPO_FOUNDRY_RUNNER:-}" ] || runner="$(config_value runner)"
138
- [ -n "${REPO_FOUNDRY_UNIT_RUNNER:-}" ] || unit_runner="$(config_value unit_runner)"
139
- [ -n "${REPO_FOUNDRY_CI_RUNNER:-}" ] || ci_runner="$(config_value ci_runner)"
140
- [ -n "${REPO_FOUNDRY_TEST_RUNNER:-}" ] || test_runner="$(config_value test_runner)"
141
- [ -n "${REPO_FOUNDRY_SECURITY_RUNNER:-}" ] || security_runner="$(config_value security_runner)"
142
- [ -n "${REPO_FOUNDRY_CODEQL_RUNNER:-}" ] || codeql_runner="$(config_value codeql_runner)"
143
- [ -n "${REPO_FOUNDRY_PR_RUNNER:-}" ] || pr_runner="$(config_value pr_runner)"
144
- [ -n "${REPO_FOUNDRY_RELEASE_RUNNER:-}" ] || release_runner="$(config_value release_runner)"
145
- [ -n "${REPO_FOUNDRY_CACHE_PACKAGES:-}" ] || cache_packages="$(config_value cache_packages)"
146
- [ -n "${REPO_FOUNDRY_CACHE_BUILD:-}" ] || cache_build="$(config_value cache_build)"
147
- [ -n "${REPO_FOUNDRY_COVERAGE_MINIMUM:-}" ] || coverage_minimum="$(config_value coverage_minimum)"
148
- [ -n "${REPO_FOUNDRY_TURBO_REMOTE:-}" ] || turbo_remote="$(config_value turbo_remote)"
149
- [ -n "$runner" ] || runner=ubuntu-latest
150
- [ -n "$unit_runner" ] || unit_runner=ubuntu-slim
151
- [ -n "$ci_runner" ] || ci_runner="$runner"
152
- [ -n "$test_runner" ] || test_runner="$runner"
153
- [ -n "$security_runner" ] || security_runner=ubuntu-slim
154
- [ -n "$codeql_runner" ] || codeql_runner="$runner"
155
- [ -n "$pr_runner" ] || pr_runner=ubuntu-slim
156
- [ -n "$release_runner" ] || release_runner=ubuntu-slim
157
- [ -n "$cache_packages" ] || cache_packages=auto
158
- [ -n "$cache_build" ] || cache_build=auto
159
- [ -n "$coverage_minimum" ] || coverage_minimum=80
160
- [ -n "$turbo_remote" ] || turbo_remote=auto
161
- [ -n "$prune_standard" ] || prune_standard=false
162
- [ "$prune_standard" = true ] && prune=true
163
- elif [ -f LICENSE ] && [ -z "${REPO_FOUNDRY_LICENSE:-}" ] && [ "$license_set" = false ]; then
164
- # Existing projects keep their authored license unless the user explicitly
165
- # selects a replacement in the generated configuration.
166
- license=preserve
167
- fi
168
-
169
- case "$package_manager" in
170
- auto|none|bun|pnpm|yarn|npm) ;;
171
- *) printf 'Unsupported package manager: %s\n' "$package_manager" >&2; exit 2 ;;
172
- esac
173
- case "$release_type" in
174
- auto|node|python|rust|simple|none) ;;
175
- *) printf 'Unsupported release type: %s\n' "$release_type" >&2; exit 2 ;;
176
- esac
177
-
178
- script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" 2>/dev/null && pwd || true)"
179
- if [ -f "$script_dir/sync-template.sh" ]; then
180
- sync_script="$script_dir/sync-template.sh"
181
- elif [ -d "$source" ] && [ -f "$source/.github/scripts/sync-template.sh" ]; then
182
- sync_script="$source/.github/scripts/sync-template.sh"
183
- else
184
- command -v git >/dev/null 2>&1 || { echo "git is required" >&2; exit 1; }
185
- tool_dir="$(mktemp -d)"
186
- git clone --quiet --depth 1 --branch "$ref" "$source" "$tool_dir/template-repo"
187
- sync_script="$tool_dir/template-repo/.github/scripts/sync-template.sh"
188
- fi
189
-
190
- sync_args=(
191
- --source "$source"
192
- --ref "$ref"
193
- --profile "$profile"
194
- --languages "$languages"
195
- --features "$features"
196
- --package-manager "$package_manager"
197
- --license "$license"
198
- )
199
- if [ -n "$runtime_repository" ]; then sync_args+=(--runtime-repository "$runtime_repository"); fi
200
- if [ -n "$runtime_ref" ]; then sync_args+=(--runtime-ref "$runtime_ref"); fi
201
- if [ -n "$license_file" ]; then sync_args+=(--license-file "$license_file"); fi
202
- if [ "$dry_run" = true ]; then sync_args+=(--check); else sync_args+=(--apply); fi
203
- if [ "$prune" = true ]; then sync_args+=(--prune); fi
204
- if [ "$force" = true ]; then sync_args+=(--force); fi
205
- REPO_FOUNDRY_INIT=true bash "$sync_script" "${sync_args[@]}"
206
-
207
- if [ "$dry_run" = true ]; then
208
- printf '%s\n' 'Dry run complete; no files were changed.'
209
- exit 0
210
- fi
211
-
212
- mkdir -p .github
213
- config_value() {
214
- awk -F': ' -v key="$1" '$1 == key { value=$2; sub(/[[:space:]]+#.*/, "", value); gsub(/^[[:space:]]+|[[:space:]]+$/, "", value); print value; exit }' .github/code-foundry.yml
215
- }
216
- profile="$(config_value profile 2>/dev/null || true)"
217
- languages="$(config_value languages 2>/dev/null || true)"
218
- features="$(config_value features 2>/dev/null || true)"
219
- package_manager="$(config_value package_manager 2>/dev/null || true)"
220
- runtime_repository="$(config_value runtime_repository 2>/dev/null || true)"
221
- runtime_ref="$(config_value runtime_ref 2>/dev/null || true)"
222
- release_type="$(config_value release_type 2>/dev/null || true)"
223
- npm_publish="$(config_value npm_publish 2>/dev/null || true)"
224
- license="$(config_value license 2>/dev/null || true)"
225
- {
226
- printf 'version: 1\n'
227
- printf 'profile: %s\n' "$profile"
228
- printf 'languages: %s\n' "$languages"
229
- printf 'features: %s\n' "$features"
230
- printf 'package_manager: %s\n' "$package_manager"
231
- printf 'runtime_repository: %s\n' "$runtime_repository"
232
- printf 'runtime_ref: %s\n' "$runtime_ref"
233
- printf 'runner: %s\n' "$runner"
234
- printf 'unit_runner: %s\n' "$unit_runner"
235
- printf 'ci_runner: %s\n' "$ci_runner"
236
- printf 'test_runner: %s\n' "$test_runner"
237
- printf 'security_runner: %s\n' "$security_runner"
238
- printf 'codeql_runner: %s\n' "$codeql_runner"
239
- printf 'pr_runner: %s\n' "$pr_runner"
240
- printf 'release_runner: %s\n' "$release_runner"
241
- printf 'release_type: %s\n' "$release_type"
242
- printf 'npm_publish: %s\n' "$npm_publish"
243
- printf 'license: %s\n' "$license"
244
- if [ -n "$license_file" ]; then
245
- printf 'license_file: %s\n' "$license_file"
246
- fi
247
- printf 'prune_standard: %s\n' "$prune_standard"
248
- printf 'cache_packages: %s\n' "$cache_packages"
249
- printf 'cache_build: %s\n' "$cache_build"
250
- printf 'coverage_minimum: %s\n' "$coverage_minimum"
251
- printf 'turbo_remote: %s\n' "$turbo_remote"
252
- } > .github/code-foundry.yml
253
-
254
- if [ "$bootstrap" = false ]; then
255
- printf '%s\n' 'Bootstrap skipped.'
256
- exit 0
257
- fi
258
-
259
- bootstrap_script="$script_dir/bootstrap.sh"
260
- [ -x "$bootstrap_script" ] || { echo "Package is missing bootstrap.sh" >&2; exit 1; }
261
- bash "$bootstrap_script"
262
-
263
- if [ "$protection" = true ]; then
264
- remote="$(git remote get-url origin 2>/dev/null || true)"
265
- repo="$(printf '%s\n' "$remote" | sed -E 's#.*github.com[:/]([^/]+/[^/.]+)(\.git)?$#\1#')"
266
- [ -n "$repo" ] || { echo 'Could not determine GitHub repository from origin' >&2; exit 1; }
267
- bash .github/scripts/sync-protection.sh --repo "$repo" --apply
268
- fi
@@ -1,67 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- git diff --cached --check
5
- changed=$(git diff --cached --name-only)
6
-
7
- has_script() {
8
- [ -f package.json ] && node -e 'const p=require("./package.json"); process.exit(p.scripts?.[process.argv[1]] ? 0 : 1)' "$1"
9
- }
10
-
11
- package_manager() {
12
- if [ -f bun.lock ] || [ -f bun.lockb ]; then echo bun
13
- elif [ -f pnpm-lock.yaml ]; then echo pnpm
14
- elif [ -f yarn.lock ]; then echo yarn
15
- else echo npm
16
- fi
17
- }
18
-
19
- run_lint_staged() {
20
- if ! has_script lint-staged; then return 1; fi
21
- case "$(package_manager)" in
22
- bun) bun run lint-staged ;;
23
- pnpm) corepack pnpm run lint-staged ;;
24
- yarn) corepack yarn run lint-staged ;;
25
- npm) npm run lint-staged ;;
26
- esac
27
- }
28
-
29
- if printf '%s\n' "$changed" | grep -Eq '\.(js|jsx|ts|tsx|json|md|mdx|yml|yaml)$'; then
30
- if ! run_lint_staged; then
31
- bash .github/scripts/ci.sh format
32
- bash .github/scripts/ci.sh lint
33
- fi
34
- fi
35
-
36
- if printf '%s\n' "$changed" | grep -Eq '\.rs$|(^|/)Cargo\.toml$'; then
37
- cargo fmt --check
38
- cargo clippy --all-targets -- -D warnings
39
- fi
40
-
41
- python_files=()
42
- python_config=false
43
- while IFS= read -r file; do
44
- case "$file" in
45
- *.py)
46
- [ -f "$file" ] && python_files+=("$file")
47
- ;;
48
- pyproject.toml|requirements.txt|requirements-dev.txt)
49
- python_config=true
50
- ;;
51
- esac
52
- done <<< "$changed"
53
-
54
- if [ "$python_config" = true ] || [ "${#python_files[@]}" -gt 0 ]; then
55
- ruff_targets=(.)
56
- [ "$python_config" = true ] || ruff_targets=("${python_files[@]}")
57
- if [ -x .venv/bin/ruff ]; then
58
- .venv/bin/ruff format --check "${ruff_targets[@]}"
59
- .venv/bin/ruff check "${ruff_targets[@]}"
60
- elif command -v ruff >/dev/null 2>&1; then
61
- ruff format --check "${ruff_targets[@]}"
62
- ruff check "${ruff_targets[@]}"
63
- else
64
- echo "Python files changed but Ruff is not installed; run the template setup first." >&2
65
- exit 1
66
- fi
67
- fi