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.
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 +68 -3
  4. package/.github/code-foundry.yml +6 -1
  5. package/.github/workflows/ci.yml +29 -24
  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 +29 -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 +321 -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,748 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- usage() {
5
- cat <<'EOF'
6
- Usage: sync-template.sh --source PATH_OR_URL [options]
7
-
8
- Synchronize the repository-owned baseline without replacing project-specific
9
- README files, mise tool selections, or additional workflows.
10
-
11
- Options:
12
- --profile NAME auto, application, monorepo, or minimal
13
- --languages LIST auto or comma-separated: typescript,rust,python,solidity
14
- --features LIST all or comma-separated standard features
15
- --package-manager NAME auto, bun, pnpm, yarn, or npm
16
- --runtime-repository OWNER/REPO Reusable workflow runtime repository
17
- --runtime-ref REF Reusable workflow runtime tag or branch
18
- --license NAME preserve, gpl-3.0-or-later, agpl-3.0-or-later, mit, or none
19
- --license-file PATH Use an exact custom license file
20
- --check Preview changes (default)
21
- --apply Apply changes
22
- --prune Remove disabled standard workflows
23
- --force Replace protected standard docs/templates
24
- EOF
25
- }
26
-
27
- source_ref="main"
28
- mode="check"
29
- source=""
30
- temp_dir=""
31
- profile="${REPO_FOUNDRY_PROFILE:-auto}"
32
- languages="${REPO_FOUNDRY_LANGUAGES:-auto}"
33
- features="${REPO_FOUNDRY_FEATURES:-all}"
34
- prune=false
35
- prune_set=false
36
- force=false
37
- languages_set=false
38
- profile_set=false
39
- features_set=false
40
- [ -n "${REPO_FOUNDRY_LANGUAGES:-}" ] && languages_set=true
41
- [ -n "${REPO_FOUNDRY_PROFILE:-}" ] && profile_set=true
42
- [ -n "${REPO_FOUNDRY_FEATURES:-}" ] && features_set=true
43
- package_manager="${REPO_FOUNDRY_PACKAGE_MANAGER:-}"
44
- package_manager_set=false
45
- [ -n "${REPO_FOUNDRY_PACKAGE_MANAGER:-}" ] && package_manager_set=true
46
- runtime_repository="${REPO_FOUNDRY_RUNTIME_REPOSITORY:-}"
47
- runtime_ref="${REPO_FOUNDRY_RUNTIME_REF:-}"
48
- runtime_repository_set=false
49
- [ -n "${REPO_FOUNDRY_RUNTIME_REPOSITORY:-}" ] && runtime_repository_set=true
50
- runtime_ref_set=false
51
- [ -n "${REPO_FOUNDRY_RUNTIME_REF:-}" ] && runtime_ref_set=true
52
- release_type="${REPO_FOUNDRY_RELEASE_TYPE:-auto}"
53
- npm_publish="${REPO_FOUNDRY_NPM_PUBLISH:-false}"
54
- prune_standard="${REPO_FOUNDRY_PRUNE_STANDARD:-false}"
55
- runner="${REPO_FOUNDRY_RUNNER:-}"
56
- unit_runner="${REPO_FOUNDRY_UNIT_RUNNER:-}"
57
- ci_runner="${REPO_FOUNDRY_CI_RUNNER:-}"
58
- test_runner="${REPO_FOUNDRY_TEST_RUNNER:-}"
59
- security_runner="${REPO_FOUNDRY_SECURITY_RUNNER:-}"
60
- codeql_runner="${REPO_FOUNDRY_CODEQL_RUNNER:-}"
61
- pr_runner="${REPO_FOUNDRY_PR_RUNNER:-}"
62
- release_runner="${REPO_FOUNDRY_RELEASE_RUNNER:-}"
63
- cache_packages="${REPO_FOUNDRY_CACHE_PACKAGES:-}"
64
- cache_build="${REPO_FOUNDRY_CACHE_BUILD:-}"
65
- coverage_minimum="${REPO_FOUNDRY_COVERAGE_MINIMUM:-}"
66
- turbo_remote="${REPO_FOUNDRY_TURBO_REMOTE:-}"
67
- license="${REPO_FOUNDRY_LICENSE:-preserve}"
68
- license_file="${REPO_FOUNDRY_LICENSE_FILE:-}"
69
- release_type_set=false
70
- npm_publish_set=false
71
- license_set=false
72
- license_file_set=false
73
- [ -n "${REPO_FOUNDRY_RELEASE_TYPE:-}" ] && release_type_set=true
74
- [ -n "${REPO_FOUNDRY_NPM_PUBLISH:-}" ] && npm_publish_set=true
75
- [ -n "${REPO_FOUNDRY_PRUNE_STANDARD:-}" ] && prune_set=true
76
- [ -n "${REPO_FOUNDRY_LICENSE:-}" ] && license_set=true
77
- [ -n "${REPO_FOUNDRY_LICENSE_FILE:-}" ] && license_file_set=true
78
- node_version="24.18.0"
79
- bun_version="1.3.14"
80
- python_version="3.13"
81
- rust_version="1.97.1"
82
- uv_version="0.11.32"
83
- ruff_version="0.16.0"
84
- gitignore_backup=""
85
- custom_ignores=""
86
-
87
- valid_languages="typescript rust python solidity"
88
- valid_features="ci codeql security test draft-pr release-pr release dependabot"
89
- valid_profiles="auto application monorepo minimal"
90
- valid_release_types="auto node python rust simple none"
91
-
92
- contains_word() {
93
- case " $1 " in *" $2 "*) return 0 ;; *) return 1 ;; esac
94
- }
95
-
96
- normalize_csv() {
97
- printf '%s' "$1" | tr ',' ' ' | awk '{$1=$1; print}'
98
- }
99
-
100
- validate_list() {
101
- local kind="$1" value="$2" valid="$3" item
102
- [ "$value" = auto ] || [ "$value" = all ] || {
103
- for item in $(normalize_csv "$value"); do
104
- contains_word "$valid" "$item" || { echo "Unsupported $kind: $item" >&2; exit 2; }
105
- done
106
- }
107
- }
108
-
109
- workflow_enabled() {
110
- local workflow="$1" list
111
- [ "$features" = all ] && return 0
112
- list="$(normalize_csv "$features")"
113
- contains_word "$list" "$workflow"
114
- }
115
-
116
- cleanup() {
117
- if [ -n "$temp_dir" ]; then rm -rf "$temp_dir"; fi
118
- if [ -n "$gitignore_backup" ]; then rm -f "$gitignore_backup"; fi
119
- if [ -n "$custom_ignores" ]; then rm -f "$custom_ignores"; fi
120
- }
121
- trap cleanup EXIT
122
-
123
- while [ "$#" -gt 0 ]; do
124
- case "$1" in
125
- --source) source="${2:?missing source path or URL}"; shift 2 ;;
126
- --ref) source_ref="${2:?missing ref}"; shift 2 ;;
127
- --profile) profile="${2:?missing profile}"; profile_set=true; shift 2 ;;
128
- --languages) languages="${2:?missing language list}"; languages_set=true; shift 2 ;;
129
- --features) features="${2:?missing feature list}"; features_set=true; shift 2 ;;
130
- --package-manager) package_manager="${2:?missing package manager}"; package_manager_set=true; shift 2 ;;
131
- --runtime-repository) runtime_repository="${2:?missing runtime repository}"; runtime_repository_set=true; shift 2 ;;
132
- --runtime-ref) runtime_ref="${2:?missing runtime ref}"; runtime_ref_set=true; shift 2 ;;
133
- --license) license="${2:?missing license}"; license_set=true; shift 2 ;;
134
- --license-file) license_file="${2:?missing license file}"; license_file_set=true; shift 2 ;;
135
- --check) mode="check"; shift ;;
136
- --apply) mode="apply"; shift ;;
137
- --prune) prune=true; prune_set=true; shift ;;
138
- --force) force=true; shift ;;
139
- -h|--help) usage; exit 0 ;;
140
- *) usage >&2; exit 2 ;;
141
- esac
142
- done
143
-
144
- [ -n "$source" ] || { usage >&2; exit 2; }
145
- config_path=.github/code-foundry.yml
146
- [ -f "$config_path" ] || [ "${REPO_FOUNDRY_INIT:-false}" = true ] || {
147
- printf '%s\n' 'Missing .github/code-foundry.yml; run `npx code-foundry init` first.' >&2
148
- exit 1
149
- }
150
- if [ -f "$config_path" ]; then
151
- config_value() {
152
- awk -F': ' -v key="$1" '$1 == key { value=$2; sub(/[[:space:]]+#.*/, "", value); gsub(/^[[:space:]]+|[[:space:]]+$/, "", value); print value; exit }' "$config_path"
153
- }
154
- if [ "$profile_set" = false ]; then
155
- configured_profile="$(config_value profile)"
156
- [ -n "$configured_profile" ] && profile="$configured_profile"
157
- fi
158
- if [ "$languages_set" = false ]; then
159
- configured_languages="$(config_value languages)"
160
- [ -n "$configured_languages" ] && languages="$configured_languages"
161
- fi
162
- if [ "$features_set" = false ]; then
163
- configured_features="$(config_value features)"
164
- [ -n "$configured_features" ] && features="$configured_features"
165
- fi
166
- if [ "$package_manager_set" = false ]; then
167
- package_manager="$(config_value package_manager)"
168
- fi
169
- if [ "$runtime_repository_set" = false ]; then
170
- runtime_repository="$(config_value runtime_repository)"
171
- fi
172
- if [ "$runtime_ref_set" = false ]; then
173
- runtime_ref="$(config_value runtime_ref)"
174
- fi
175
- if [ "$release_type_set" != true ]; then
176
- configured_release_type="$(config_value release_type)"
177
- [ -n "$configured_release_type" ] && release_type="$configured_release_type"
178
- fi
179
- if [ "$npm_publish_set" != true ]; then
180
- configured_npm_publish="$(config_value npm_publish)"
181
- [ -n "$configured_npm_publish" ] && npm_publish="$configured_npm_publish"
182
- fi
183
- if [ "$license_set" != true ]; then
184
- configured_license="$(config_value license)"
185
- [ -n "$configured_license" ] && license="$configured_license"
186
- fi
187
- if [ "$license_file_set" = false ]; then
188
- license_file="$(config_value license_file)"
189
- fi
190
- if [ "$prune_set" = false ]; then
191
- configured_prune="$(config_value prune_standard)"
192
- [ -n "$configured_prune" ] && prune_standard="$configured_prune"
193
- fi
194
- [ -n "$runner" ] || runner="$(config_value runner)"
195
- [ -n "$unit_runner" ] || unit_runner="$(config_value unit_runner)"
196
- [ -n "$ci_runner" ] || ci_runner="$(config_value ci_runner)"
197
- [ -n "$test_runner" ] || test_runner="$(config_value test_runner)"
198
- [ -n "$security_runner" ] || security_runner="$(config_value security_runner)"
199
- [ -n "$codeql_runner" ] || codeql_runner="$(config_value codeql_runner)"
200
- [ -n "$pr_runner" ] || pr_runner="$(config_value pr_runner)"
201
- [ -n "$release_runner" ] || release_runner="$(config_value release_runner)"
202
- [ -n "$cache_packages" ] || cache_packages="$(config_value cache_packages)"
203
- [ -n "$cache_build" ] || cache_build="$(config_value cache_build)"
204
- [ -n "$coverage_minimum" ] || coverage_minimum="$(config_value coverage_minimum)"
205
- [ -n "$turbo_remote" ] || turbo_remote="$(config_value turbo_remote)"
206
- fi
207
- [ -n "$package_manager" ] || package_manager=auto
208
- [ -n "$runner" ] || runner=ubuntu-latest
209
- [ -n "$unit_runner" ] || unit_runner=ubuntu-slim
210
- [ -n "$ci_runner" ] || ci_runner="$runner"
211
- [ -n "$test_runner" ] || test_runner="$runner"
212
- [ -n "$security_runner" ] || security_runner=ubuntu-slim
213
- [ -n "$codeql_runner" ] || codeql_runner="$runner"
214
- [ -n "$pr_runner" ] || pr_runner=ubuntu-slim
215
- [ -n "$release_runner" ] || release_runner=ubuntu-slim
216
- [ -n "$cache_packages" ] || cache_packages=auto
217
- [ -n "$cache_build" ] || cache_build=auto
218
- [ -n "$coverage_minimum" ] || coverage_minimum=80
219
- [ -n "$turbo_remote" ] || turbo_remote=auto
220
- [ "$prune_standard" = true ] || [ "$prune_standard" = false ] || {
221
- printf 'prune_standard must be true or false: %s\n' "$prune_standard" >&2
222
- exit 2
223
- }
224
- [ "$prune_standard" = true ] && prune=true
225
- if [ -z "$runtime_repository" ]; then
226
- source_repository="$source"
227
- if [ -d "$source" ]; then
228
- source_repository="$(git -C "$source" remote get-url origin 2>/dev/null || true)"
229
- fi
230
- runtime_repository="$(printf '%s\n' "$source_repository" | sed -nE 's#.*github\.com[:/]([^/]+/[^/.]+)(\.git)?$#\1#p')"
231
- fi
232
- [ -n "$runtime_repository" ] || runtime_repository="0xPlayerOne/code-foundry"
233
- case "$runtime_repository" in
234
- */*) ;;
235
- *) printf 'Runtime repository must be OWNER/REPO: %s\n' "$runtime_repository" >&2; exit 2 ;;
236
- esac
237
- case "$package_manager" in
238
- auto|none|bun|pnpm|yarn|npm) ;;
239
- *) printf 'Unsupported package manager: %s\n' "$package_manager" >&2; exit 2 ;;
240
- esac
241
- validate_list language "$languages" "$valid_languages"
242
- validate_list feature "$features" "$valid_features"
243
- contains_word "$valid_profiles" "$profile" || {
244
- printf 'Unsupported profile: %s\n' "$profile" >&2
245
- exit 2
246
- }
247
- contains_word "$valid_release_types" "$release_type" || {
248
- printf 'Unsupported release type: %s\n' "$release_type" >&2
249
- exit 2
250
- }
251
- case "$license" in
252
- preserve|gpl-3.0-or-later|agpl-3.0-or-later|mit|custom|none) ;;
253
- *) printf 'Unsupported license: %s\n' "$license" >&2; exit 2 ;;
254
- esac
255
- [ -z "$license_file" ] || license=custom
256
- for configured_runner in "$runner" "$unit_runner" "$ci_runner" "$test_runner" "$security_runner" "$codeql_runner" "$pr_runner" "$release_runner"; do
257
- case "$configured_runner" in
258
- ''|*[!A-Za-z0-9._/-]*) printf 'Runner contains unsupported characters: %s\n' "$configured_runner" >&2; exit 2 ;;
259
- esac
260
- done
261
-
262
- if [ -d "$source" ] && [ -f "$source/.github/scripts/sync-template.sh" ]; then
263
- template_root="$source"
264
- else
265
- command -v git >/dev/null 2>&1 || { echo "git is required" >&2; exit 1; }
266
- temp_dir="$(mktemp -d)"
267
- git clone --quiet --depth 1 --branch "$source_ref" "$source" "$temp_dir/template-repo"
268
- template_root="$temp_dir/template-repo"
269
- fi
270
-
271
- # Prefer an explicit CLI/environment value, then the target's saved contract,
272
- # then the source template's contract, and finally the stable public runtime.
273
- source_config="$template_root/.github/code-foundry.yml"
274
- if [ -z "$runtime_ref" ] && [ -f "$source_config" ]; then
275
- runtime_ref="$(awk -F': ' '/^runtime_ref:/ {print $2; exit}' "$source_config")"
276
- fi
277
- [ -n "$runtime_ref" ] || runtime_ref="v0.20.2"
278
- case "$runtime_ref" in
279
- ''|*[!A-Za-z0-9._/-]*) printf 'Runtime ref contains unsupported characters: %s\n' "$runtime_ref" >&2; exit 2 ;;
280
- esac
281
-
282
- # Resolve the effective profile after the template is available. This gives
283
- # CLI flags and GitHub repository variables priority over template defaults,
284
- # while making automatic detection concrete in the generated profile.
285
- if [ -f "$template_root/.github/scripts/profile.sh" ]; then
286
- profile_output="$(
287
- REPO_FOUNDRY_PROFILE="$profile" \
288
- REPO_FOUNDRY_LANGUAGES="$languages" \
289
- REPO_FOUNDRY_FEATURES="$features" \
290
- REPO_FOUNDRY_PACKAGE_MANAGER="$package_manager" \
291
- REPO_FOUNDRY_RUNTIME_REPOSITORY="$runtime_repository" \
292
- REPO_FOUNDRY_RUNTIME_REF="$runtime_ref" \
293
- REPO_FOUNDRY_RELEASE_TYPE="$release_type" \
294
- REPO_FOUNDRY_NPM_PUBLISH="$npm_publish" \
295
- bash "$template_root/.github/scripts/profile.sh" detect --root "$PWD"
296
- )"
297
- profile="$(printf '%s\n' "$profile_output" | awk -F= '$1 == "profile" {print substr($0, index($0, "=") + 1)}')"
298
- languages="$(printf '%s\n' "$profile_output" | awk -F= '$1 == "languages" {print substr($0, index($0, "=") + 1)}')"
299
- package_manager="$(printf '%s\n' "$profile_output" | awk -F= '$1 == "package_manager" {print substr($0, index($0, "=") + 1)}')"
300
- release_type="$(printf '%s\n' "$profile_output" | awk -F= '$1 == "release_type" {print substr($0, index($0, "=") + 1)}')"
301
- npm_publish="$(printf '%s\n' "$profile_output" | awk -F= '$1 == "npm_publish" {print substr($0, index($0, "=") + 1)}')"
302
- fi
303
-
304
- files=(
305
- .editorconfig
306
- .gitattributes
307
- .gitignore
308
- release-please-config.json
309
- .githooks/pre-commit
310
- AGENTS.md
311
- LICENSE
312
- NOTICE
313
- ruff.toml
314
- .prettierrc
315
- .github/CODEOWNERS
316
- .github/CODE_OF_CONDUCT.md
317
- .github/CONTRIBUTING.md
318
- .github/PULL_REQUEST_TEMPLATE.md
319
- .github/SECURITY.md
320
- .github/dependabot.yml
321
- .github/ISSUE_TEMPLATE/bug_report.yml
322
- .github/ISSUE_TEMPLATE/config.yml
323
- .github/ISSUE_TEMPLATE/feature_request.yml
324
- # Keep only the small local hook runner and its language-aware formatter.
325
- # Full CI, security, CodeQL, and release implementations are loaded from
326
- # the reusable runtime and are not copied into consumer repositories.
327
- .github/scripts/profile.sh
328
- .github/scripts/changed-files.sh
329
- .github/scripts/ci.sh
330
- .github/scripts/pre-commit.sh
331
- .github/workflows/ci.yml
332
- .github/workflows/codeql.yml
333
- .github/workflows/draft-pr.yml
334
- .github/workflows/release-pr.yml
335
- .github/workflows/release.yml
336
- .github/workflows/security.yml
337
- .github/workflows/test.yml
338
- )
339
-
340
- filtered_files=()
341
- for file in "${files[@]}"; do
342
- case "$file" in
343
- .github/dependabot.yml) workflow_enabled dependabot && filtered_files+=("$file") ;;
344
- .github/workflows/ci.yml) workflow_enabled ci && filtered_files+=("$file") ;;
345
- .github/workflows/codeql.yml) workflow_enabled codeql && filtered_files+=("$file") ;;
346
- .github/workflows/security.yml) workflow_enabled security && filtered_files+=("$file") ;;
347
- .github/workflows/test.yml) workflow_enabled test && filtered_files+=("$file") ;;
348
- .github/workflows/draft-pr.yml) workflow_enabled draft-pr && filtered_files+=("$file") ;;
349
- .github/workflows/release-pr.yml) workflow_enabled release-pr && filtered_files+=("$file") ;;
350
- .github/workflows/release.yml) workflow_enabled release && filtered_files+=("$file") ;;
351
- *) filtered_files+=("$file") ;;
352
- esac
353
- done
354
- files=("${filtered_files[@]}")
355
- changed=0
356
-
357
- # These files belonged to older Code Foundry layouts. They are intentionally
358
- # removed now that the npm CLI owns initialization and synchronization.
359
- removed_files=(
360
- .github/code-foundry.yml.example
361
- .github/template.yml
362
- .github/template.yml.example
363
- .github/scripts/bootstrap.sh
364
- .github/scripts/codeql-languages.sh
365
- .github/scripts/doctor.sh
366
- .github/scripts/format-fast-path.sh
367
- .github/scripts/init-repo.sh
368
- .github/scripts/security.sh
369
- .github/scripts/sync-codeowners.sh
370
- .github/scripts/sync-protection.sh
371
- .github/scripts/sync-template.sh
372
- .github/scripts/sitecustomize.py
373
- .github/scripts/turbo-cache-probe.sh
374
- .github/licenses/MIT.txt
375
- .github/licenses/GPL-3.0-or-later.txt
376
- .github/licenses/AGPL-3.0-or-later.txt
377
- )
378
- for file in "${removed_files[@]}"; do
379
- if [ -e "$file" ]; then
380
- changed=$((changed + 1))
381
- if [ "$mode" = "check" ]; then
382
- printf 'Would remove legacy managed file %s\n' "$file"
383
- else
384
- rm -f "$file"
385
- printf 'Removed legacy managed file %s\n' "$file"
386
- fi
387
- fi
388
- done
389
-
390
- # Workflows outside the standard baseline are repository-owned extensions.
391
- # The sync operation never deletes or replaces them; surface them explicitly
392
- # so maintainers can verify custom deployment, indexing, or security flows.
393
- standard_workflow() {
394
- case "$1" in
395
- ci.yml|codeql.yml|draft-pr.yml|release-pr.yml|release.yml|security.yml|test.yml) return 0 ;;
396
- *_self-ci.yml) return 0 ;;
397
- *) return 1 ;;
398
- esac
399
- }
400
-
401
- protected_standard_file() {
402
- case "$1" in
403
- AGENTS.md|.github/CODE_OF_CONDUCT.md|.github/CONTRIBUTING.md|.github/PULL_REQUEST_TEMPLATE.md|.github/SECURITY.md|.github/ISSUE_TEMPLATE/*)
404
- return 0 ;;
405
- *) return 1 ;;
406
- esac
407
- }
408
-
409
- custom_workflows=()
410
- if [ -d .github/workflows ]; then
411
- while IFS= read -r workflow; do
412
- workflow="${workflow#./}"
413
- workflow="${workflow#.github/workflows/}"
414
- standard_workflow "$workflow" || custom_workflows+=("$workflow")
415
- done < <(find .github/workflows -maxdepth 1 -type f -print | sort)
416
- fi
417
- if [ "${#custom_workflows[@]}" -gt 0 ]; then
418
- printf 'Preserving custom workflows: %s\n' "${custom_workflows[*]}"
419
- fi
420
-
421
- for file in "${files[@]}"; do
422
- template_file="$template_root/$file"
423
- case "$file" in
424
- .github/workflows/*.yml)
425
- workflow_name="${file##*/}"
426
- workflow_name="${workflow_name%.yml}"
427
- template_file="$template_root/.github/workflows/${workflow_name}_self-ci.yml"
428
- ;;
429
- esac
430
- # npm renames .gitignore to .npmignore when installing a package. Treat the
431
- # renamed file as the same template asset so packaged initialization works.
432
- if [ "$file" = .gitignore ] && [ ! -f "$template_file" ] && [ -f "$template_root/.npmignore" ]; then
433
- template_file="$template_root/.npmignore"
434
- fi
435
- if [ ! -f "$template_file" ]; then
436
- echo "Template file missing: $file" >&2
437
- exit 1
438
- fi
439
- if [ "$force" != true ] && [ -f "$file" ] && protected_standard_file "$file"; then
440
- printf 'Preserving existing authored file %s (use --force to refresh).\n' "$file"
441
- continue
442
- fi
443
- if { [ "$file" = LICENSE ] || [ "$file" = NOTICE ]; } && [ "$license" = preserve ] && [ -f "$file" ]; then
444
- continue
445
- fi
446
- if { [ "$file" = LICENSE ] || [ "$file" = NOTICE ]; } && [ "$license" = none ]; then
447
- continue
448
- fi
449
- if [ "$file" = .github/CODEOWNERS ] && [ -f "$file" ]; then
450
- continue
451
- fi
452
- if [ ! -f "$file" ] || ! cmp -s "$template_file" "$file"; then
453
- changed=$((changed + 1))
454
- if [ "$mode" = "check" ]; then
455
- printf 'Would sync %s\n' "$file"
456
- else
457
- mkdir -p "$(dirname "$file")"
458
- if [ "$file" = .gitignore ] && [ -f "$file" ]; then
459
- # Keep repository-specific ignore rules while refreshing the shared
460
- # baseline. This prevents a template sync from hiding generated files
461
- # or local tooling that only one project uses.
462
- gitignore_backup="$(mktemp)"
463
- custom_ignores="$(mktemp)"
464
- cp "$file" "$gitignore_backup"
465
- cp "$template_file" "$file"
466
- awk 'NR == FNR { seen[$0] = 1; next } /^# Repository-specific rules$/ { next } NF && !seen[$0] { print }' \
467
- "$template_file" "$gitignore_backup" > "$custom_ignores"
468
- if [ -s "$custom_ignores" ]; then
469
- {
470
- printf '\n# Repository-specific rules\n'
471
- cat "$custom_ignores"
472
- } >> "$file"
473
- fi
474
- rm -f "$gitignore_backup" "$custom_ignores"
475
- gitignore_backup=""
476
- custom_ignores=""
477
- else
478
- cp "$template_file" "$file"
479
- fi
480
- case "$file" in
481
- .githooks/*|.github/scripts/*) chmod +x "$file" ;;
482
- esac
483
- printf 'Synced %s\n' "$file"
484
- fi
485
- fi
486
- done
487
-
488
- # Reusable workflow callers must use a literal repository/ref. Render the
489
- # selected runtime repository while leaving custom workflows untouched. Read
490
- # the source's configured runtime first so forks remain self-contained.
491
- source_runtime_repository=""
492
- source_runtime_ref=""
493
- source_config="$template_root/.github/code-foundry.yml"
494
- if [ -f "$source_config" ]; then
495
- source_runtime_repository="$(awk -F': ' '/^runtime_repository:/ {print $2; exit}' "$source_config")"
496
- source_runtime_ref="$(awk -F': ' '/^runtime_ref:/ {print $2; exit}' "$source_config")"
497
- fi
498
- [ -n "$source_runtime_repository" ] || source_runtime_repository="0xPlayerOne/code-foundry"
499
- [ -n "$source_runtime_ref" ] || source_runtime_ref="$runtime_ref"
500
- for file in .github/workflows/ci.yml .github/workflows/test.yml .github/workflows/security.yml .github/workflows/codeql.yml .github/workflows/draft-pr.yml .github/workflows/release-pr.yml .github/workflows/release.yml; do
501
- [ -f "$file" ] || continue
502
- if grep -qF "$source_runtime_repository@" "$file" || grep -q 'runtime-ref:' "$file" || grep -qF 'uses: ./.github/workflows/' "$file"; then
503
- changed=$((changed + 1))
504
- if [ "$mode" = "check" ]; then
505
- printf 'Would render runtime repository in %s\n' "$file"
506
- else
507
- runner_value="$runner"
508
- case "$file" in
509
- .github/workflows/ci.yml) runner_value="$ci_runner" ;;
510
- .github/workflows/test.yml) runner_value="$test_runner" ;;
511
- .github/workflows/security.yml) runner_value="$security_runner" ;;
512
- .github/workflows/codeql.yml) runner_value="$codeql_runner" ;;
513
- .github/workflows/draft-pr.yml|.github/workflows/release-pr.yml) runner_value="$pr_runner" ;;
514
- .github/workflows/release.yml) runner_value="$release_runner" ;;
515
- esac
516
- rendered_workflow="$(mktemp)"
517
- sed -E \
518
- -e "s#${source_runtime_repository}@[^[:space:]]+#${runtime_repository}@${runtime_ref}#g" \
519
- -e "s#uses: \.\/.github/workflows/([^[:space:]]+)#uses: ${runtime_repository}/.github/workflows/\\1@${runtime_ref}#g" \
520
- -e "s#runtime-ref: [^[:space:]]+#runtime-ref: ${runtime_ref}#g" \
521
- -e "s#^ runner: [^[:space:]]+# runner: ${runner_value}#" \
522
- -e "s#^ unit-runner: [^[:space:]]+# unit-runner: ${unit_runner}#" \
523
- "$file" > "$rendered_workflow"
524
- mv "$rendered_workflow" "$file"
525
- printf 'Rendered runtime repository in %s\n' "$file"
526
- fi
527
- fi
528
- done
529
-
530
- write_license() {
531
- local owner license_source
532
- [ "$license" != preserve ] || return 0
533
- [ "$license" != none ] || return 0
534
- if [ -n "$license_file" ]; then
535
- [ -f "$license_file" ] || { printf 'License file not found: %s\n' "$license_file" >&2; exit 1; }
536
- license_source="$license_file"
537
- else
538
- case "$license" in
539
- gpl-3.0-or-later) license_source="$template_root/.github/licenses/GPL-3.0-or-later.txt" ;;
540
- agpl-3.0-or-later) license_source="$template_root/.github/licenses/AGPL-3.0-or-later.txt" ;;
541
- mit) license_source="$template_root/.github/licenses/MIT.txt" ;;
542
- esac
543
- [ -f "$license_source" ] || { printf 'License template missing: %s\n' "$license_source" >&2; exit 1; }
544
- fi
545
- changed=$((changed + 1))
546
- if [ "$mode" = check ]; then
547
- printf 'Would generate LICENSE (%s)\n' "$license"
548
- return
549
- fi
550
- owner="${REPO_FOUNDRY_LICENSE_OWNER:-$(git config user.name 2>/dev/null || true)}"
551
- [ -n "$owner" ] || owner="Project contributors"
552
- sed "s/PROJECT_OWNER/$owner/g; s/CURRENT_YEAR/$(date +%Y)/g" "$license_source" > LICENSE
553
- {
554
- printf 'Copyright (C) %s %s\n\n' "$(date +%Y)" "$owner"
555
- printf 'This repository is distributed under %s.\n' "$license"
556
- } > NOTICE
557
- printf 'Generated LICENSE and NOTICE for %s.\n' "$license"
558
- }
559
-
560
- write_license
561
-
562
- # Release Please owns changelog updates after initialization. Preserve an
563
- # existing project history, but give new repositories the expected file.
564
- if [ ! -f CHANGELOG.md ]; then
565
- changed=$((changed + 1))
566
- if [ "$mode" = "check" ]; then
567
- printf '%s\n' 'Would initialize CHANGELOG.md'
568
- else
569
- cat > CHANGELOG.md <<'EOF'
570
- # Changelog
571
-
572
- All notable changes to this project are documented here.
573
- EOF
574
- printf '%s\n' 'Initialized CHANGELOG.md'
575
- fi
576
- fi
577
-
578
- detect_languages() {
579
- local detected=()
580
- if [ -f package.json ] || git ls-files -- '*.js' '*.jsx' '*.ts' '*.tsx' | grep -q .; then
581
- detected+=(typescript)
582
- fi
583
- if [ -f Cargo.toml ] || git ls-files -- '*.rs' | grep -q .; then
584
- detected+=(rust)
585
- fi
586
- if [ -f pyproject.toml ] || [ -f requirements.txt ] || [ -f requirements-dev.txt ] ||
587
- git ls-files -- '*.py' ':!.github/**' | grep -q .; then
588
- detected+=(python)
589
- fi
590
- if find . -path './.git' -prune -o -type f -name '*.sol' -print | grep -q .; then
591
- detected+=(solidity)
592
- fi
593
- printf '%s\n' "${detected[*]}"
594
- }
595
-
596
- initialize_mise() {
597
- local selected_languages="$1"
598
- local language selected_package_manager
599
- if [ -f .mise.toml ]; then
600
- return
601
- fi
602
- if [ "$selected_languages" = auto ]; then
603
- selected_languages="$(detect_languages)"
604
- fi
605
- selected_package_manager="$package_manager"
606
- if [ "$selected_package_manager" = auto ]; then
607
- if [ -f bun.lock ] || [ -f bun.lockb ]; then selected_package_manager=bun
608
- elif [ -f pnpm-lock.yaml ]; then selected_package_manager=pnpm
609
- elif [ -f yarn.lock ]; then selected_package_manager=yarn
610
- elif [ -f package-lock.json ]; then selected_package_manager=npm
611
- else selected_package_manager=bun
612
- fi
613
- fi
614
- changed=$((changed + 1))
615
- if [ "$mode" = check ]; then
616
- printf '%s\n' 'Would initialize .mise.toml'
617
- return
618
- fi
619
- {
620
- printf '%s\n' '[tools]'
621
- for language in $(normalize_csv "$selected_languages"); do
622
- case "$language" in
623
- typescript|solidity)
624
- printf 'node = "%s"\n' "$node_version"
625
- if [ "$selected_package_manager" = bun ]; then
626
- printf 'bun = "%s"\n' "$bun_version"
627
- fi
628
- ;;
629
- python)
630
- printf 'python = "%s"\n' "$python_version"
631
- printf 'uv = "%s"\n' "$uv_version"
632
- printf 'ruff = "%s"\n' "$ruff_version"
633
- ;;
634
- rust)
635
- printf 'rust = { version = "%s", components = ["rustfmt", "clippy"] }\n' "$rust_version"
636
- ;;
637
- esac
638
- done | awk '!seen[$0]++'
639
- printf '\n%s\n' '[settings]'
640
- printf '%s\n' 'experimental = true'
641
- } > .mise.toml
642
- printf '%s\n' 'Initialized .mise.toml'
643
- }
644
-
645
- initialize_mise "$languages"
646
-
647
- initialize_mise_lock() {
648
- if [ ! -f .mise.toml ] || [ -f mise.lock ]; then
649
- return
650
- fi
651
- if ! command -v mise >/dev/null 2>&1; then
652
- return
653
- fi
654
- changed=$((changed + 1))
655
- if [ "$mode" = check ]; then
656
- printf '%s\n' 'Would initialize mise.lock'
657
- return
658
- fi
659
- if MISE_TRUSTED_CONFIG_PATHS="$PWD" mise lock >/dev/null 2>&1; then
660
- printf '%s\n' 'Initialized mise.lock'
661
- else
662
- printf '%s\n' 'Warning: mise.lock could not be generated; CI will resolve pinned tools normally.' >&2
663
- fi
664
- }
665
-
666
- initialize_mise_lock
667
-
668
- if [ -x "$template_root/.github/scripts/sync-codeowners.sh" ]; then
669
- if [ "$mode" = "check" ]; then
670
- bash "$template_root/.github/scripts/sync-codeowners.sh" --check
671
- else
672
- bash "$template_root/.github/scripts/sync-codeowners.sh" --apply
673
- fi
674
- fi
675
-
676
- # Release Please's simple strategy needs a version file. Initialize one only
677
- # for Solidity-only repositories, where no package manifest owns the version.
678
- if [ ! -f version.txt ] && find . -path './.git' -prune -o -type f -name '*.sol' -print | grep -q . &&
679
- [ ! -f package.json ] && [ ! -f Cargo.toml ] && [ ! -f pyproject.toml ]; then
680
- changed=$((changed + 1))
681
- if [ "$mode" = "check" ]; then
682
- printf '%s\n' 'Would initialize version.txt'
683
- else
684
- printf '%s\n' '0.1.0' > version.txt
685
- printf '%s\n' 'Initialized version.txt'
686
- fi
687
- fi
688
-
689
- if [ "$mode" = "apply" ]; then
690
- git config core.hooksPath .githooks
691
- mkdir -p .github
692
- {
693
- printf 'version: 1\n'
694
- printf 'profile: %s\n' "$profile"
695
- printf 'languages: %s\n' "$languages"
696
- printf 'features: %s\n' "$features"
697
- if [ -n "$package_manager" ]; then
698
- printf 'package_manager: %s\n' "$package_manager"
699
- fi
700
- printf 'runtime_repository: %s\n' "$runtime_repository"
701
- printf 'runtime_ref: %s\n' "$runtime_ref"
702
- printf 'runner: %s\n' "$runner"
703
- printf 'unit_runner: %s\n' "$unit_runner"
704
- printf 'ci_runner: %s\n' "$ci_runner"
705
- printf 'test_runner: %s\n' "$test_runner"
706
- printf 'security_runner: %s\n' "$security_runner"
707
- printf 'codeql_runner: %s\n' "$codeql_runner"
708
- printf 'pr_runner: %s\n' "$pr_runner"
709
- printf 'release_runner: %s\n' "$release_runner"
710
- printf 'prune_standard: %s\n' "$prune_standard"
711
- printf 'cache_packages: %s\n' "$cache_packages"
712
- printf 'cache_build: %s\n' "$cache_build"
713
- printf 'coverage_minimum: %s\n' "$coverage_minimum"
714
- printf 'turbo_remote: %s\n' "$turbo_remote"
715
- printf 'release_type: %s\n' "$release_type"
716
- printf 'npm_publish: %s\n' "$npm_publish"
717
- printf 'license: %s\n' "$license"
718
- if [ -n "$license_file" ]; then
719
- printf 'license_file: %s\n' "$license_file"
720
- fi
721
- } > .github/code-foundry.yml
722
- fi
723
-
724
- if [ "$prune" = true ]; then
725
- for workflow in ci codeql security test draft-pr release-pr release; do
726
- file=".github/workflows/$workflow.yml"
727
- if [ -f "$file" ] && ! workflow_enabled "$workflow"; then
728
- changed=$((changed + 1))
729
- if [ "$mode" = "check" ]; then
730
- printf 'Would remove disabled standard workflow %s\n' "$file"
731
- else
732
- rm "$file"
733
- printf 'Removed disabled standard workflow %s\n' "$file"
734
- fi
735
- fi
736
- done
737
- if [ -f .github/dependabot.yml ] && ! workflow_enabled dependabot; then
738
- changed=$((changed + 1))
739
- if [ "$mode" = "check" ]; then
740
- printf 'Would remove disabled standard configuration .github/dependabot.yml\n'
741
- else
742
- rm .github/dependabot.yml
743
- printf '%s\n' 'Removed disabled standard configuration .github/dependabot.yml'
744
- fi
745
- fi
746
- fi
747
-
748
- printf '%s\n' "$changed baseline file(s) differ."