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.
Files changed (47) 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/licenses/AGPL-3.0-or-later.txt +661 -0
  6. package/.github/licenses/GPL-3.0-or-later.txt +674 -0
  7. package/.github/licenses/MIT.txt +4 -16
  8. package/.github/workflows/ci.yml +20 -23
  9. package/.github/workflows/ci_self-ci.yml +1 -1
  10. package/.github/workflows/codeql.yml +23 -29
  11. package/.github/workflows/codeql_self-ci.yml +1 -1
  12. package/.github/workflows/draft-pr.yml +2 -2
  13. package/.github/workflows/release-pr.yml +4 -4
  14. package/.github/workflows/release.yml +17 -29
  15. package/.github/workflows/security.yml +24 -19
  16. package/.github/workflows/security_self-ci.yml +1 -1
  17. package/.github/workflows/test.yml +20 -20
  18. package/.github/workflows/test_self-ci.yml +1 -1
  19. package/AGENTS.md +13 -10
  20. package/CHANGELOG.md +28 -0
  21. package/README.md +19 -8
  22. package/docs/CONFIGURATION.md +11 -2
  23. package/docs/INITIALIZATION.md +9 -5
  24. package/docs/RELEASES.md +5 -0
  25. package/docs/WORKFLOWS.md +12 -5
  26. package/package.json +7 -3
  27. package/src/cli.mjs +29 -25
  28. package/src/commands/doctor.mjs +70 -0
  29. package/src/commands/sync.mjs +224 -0
  30. package/src/lib/config.mjs +38 -0
  31. package/src/lib/profile.mjs +114 -0
  32. package/src/runtime.mjs +316 -0
  33. package/.github/scripts/bootstrap.sh +0 -21
  34. package/.github/scripts/changed-files.sh +0 -96
  35. package/.github/scripts/ci.sh +0 -641
  36. package/.github/scripts/codeql-languages.sh +0 -91
  37. package/.github/scripts/doctor.sh +0 -98
  38. package/.github/scripts/format-fast-path.sh +0 -80
  39. package/.github/scripts/init-repo.sh +0 -264
  40. package/.github/scripts/pre-commit.sh +0 -67
  41. package/.github/scripts/profile.sh +0 -186
  42. package/.github/scripts/security.sh +0 -214
  43. package/.github/scripts/sitecustomize.py +0 -17
  44. package/.github/scripts/sync-codeowners.sh +0 -76
  45. package/.github/scripts/sync-protection.sh +0 -138
  46. package/.github/scripts/sync-template.sh +0 -744
  47. package/.github/scripts/turbo-cache-probe.sh +0 -102
@@ -1,744 +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, 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|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
- )
375
- for file in "${removed_files[@]}"; do
376
- if [ -e "$file" ]; then
377
- changed=$((changed + 1))
378
- if [ "$mode" = "check" ]; then
379
- printf 'Would remove legacy managed file %s\n' "$file"
380
- else
381
- rm -f "$file"
382
- printf 'Removed legacy managed file %s\n' "$file"
383
- fi
384
- fi
385
- done
386
-
387
- # Workflows outside the standard baseline are repository-owned extensions.
388
- # The sync operation never deletes or replaces them; surface them explicitly
389
- # so maintainers can verify custom deployment, indexing, or security flows.
390
- standard_workflow() {
391
- case "$1" in
392
- ci.yml|codeql.yml|draft-pr.yml|release-pr.yml|release.yml|security.yml|test.yml) return 0 ;;
393
- *_self-ci.yml) return 0 ;;
394
- *) return 1 ;;
395
- esac
396
- }
397
-
398
- protected_standard_file() {
399
- case "$1" in
400
- AGENTS.md|.github/CODE_OF_CONDUCT.md|.github/CONTRIBUTING.md|.github/PULL_REQUEST_TEMPLATE.md|.github/SECURITY.md|.github/ISSUE_TEMPLATE/*)
401
- return 0 ;;
402
- *) return 1 ;;
403
- esac
404
- }
405
-
406
- custom_workflows=()
407
- if [ -d .github/workflows ]; then
408
- while IFS= read -r workflow; do
409
- workflow="${workflow#./}"
410
- workflow="${workflow#.github/workflows/}"
411
- standard_workflow "$workflow" || custom_workflows+=("$workflow")
412
- done < <(find .github/workflows -maxdepth 1 -type f -print | sort)
413
- fi
414
- if [ "${#custom_workflows[@]}" -gt 0 ]; then
415
- printf 'Preserving custom workflows: %s\n' "${custom_workflows[*]}"
416
- fi
417
-
418
- for file in "${files[@]}"; do
419
- template_file="$template_root/$file"
420
- case "$file" in
421
- .github/workflows/*.yml)
422
- workflow_name="${file##*/}"
423
- workflow_name="${workflow_name%.yml}"
424
- template_file="$template_root/.github/workflows/${workflow_name}_self-ci.yml"
425
- ;;
426
- esac
427
- # npm renames .gitignore to .npmignore when installing a package. Treat the
428
- # renamed file as the same template asset so packaged initialization works.
429
- if [ "$file" = .gitignore ] && [ ! -f "$template_file" ] && [ -f "$template_root/.npmignore" ]; then
430
- template_file="$template_root/.npmignore"
431
- fi
432
- if [ ! -f "$template_file" ]; then
433
- echo "Template file missing: $file" >&2
434
- exit 1
435
- fi
436
- if [ "$force" != true ] && [ -f "$file" ] && protected_standard_file "$file"; then
437
- printf 'Preserving existing authored file %s (use --force to refresh).\n' "$file"
438
- continue
439
- fi
440
- if { [ "$file" = LICENSE ] || [ "$file" = NOTICE ]; } && [ "$license" = preserve ] && [ -f "$file" ]; then
441
- continue
442
- fi
443
- if { [ "$file" = LICENSE ] || [ "$file" = NOTICE ]; } && [ "$license" = none ]; then
444
- continue
445
- fi
446
- if [ "$file" = .github/CODEOWNERS ] && [ -f "$file" ]; then
447
- continue
448
- fi
449
- if [ ! -f "$file" ] || ! cmp -s "$template_file" "$file"; then
450
- changed=$((changed + 1))
451
- if [ "$mode" = "check" ]; then
452
- printf 'Would sync %s\n' "$file"
453
- else
454
- mkdir -p "$(dirname "$file")"
455
- if [ "$file" = .gitignore ] && [ -f "$file" ]; then
456
- # Keep repository-specific ignore rules while refreshing the shared
457
- # baseline. This prevents a template sync from hiding generated files
458
- # or local tooling that only one project uses.
459
- gitignore_backup="$(mktemp)"
460
- custom_ignores="$(mktemp)"
461
- cp "$file" "$gitignore_backup"
462
- cp "$template_file" "$file"
463
- awk 'NR == FNR { seen[$0] = 1; next } /^# Repository-specific rules$/ { next } NF && !seen[$0] { print }' \
464
- "$template_file" "$gitignore_backup" > "$custom_ignores"
465
- if [ -s "$custom_ignores" ]; then
466
- {
467
- printf '\n# Repository-specific rules\n'
468
- cat "$custom_ignores"
469
- } >> "$file"
470
- fi
471
- rm -f "$gitignore_backup" "$custom_ignores"
472
- gitignore_backup=""
473
- custom_ignores=""
474
- else
475
- cp "$template_file" "$file"
476
- fi
477
- case "$file" in
478
- .githooks/*|.github/scripts/*) chmod +x "$file" ;;
479
- esac
480
- printf 'Synced %s\n' "$file"
481
- fi
482
- fi
483
- done
484
-
485
- # Reusable workflow callers must use a literal repository/ref. Render the
486
- # selected runtime repository while leaving custom workflows untouched. Read
487
- # the source's configured runtime first so forks remain self-contained.
488
- source_runtime_repository=""
489
- source_runtime_ref=""
490
- source_config="$template_root/.github/code-foundry.yml"
491
- if [ -f "$source_config" ]; then
492
- source_runtime_repository="$(awk -F': ' '/^runtime_repository:/ {print $2; exit}' "$source_config")"
493
- source_runtime_ref="$(awk -F': ' '/^runtime_ref:/ {print $2; exit}' "$source_config")"
494
- fi
495
- [ -n "$source_runtime_repository" ] || source_runtime_repository="0xPlayerOne/code-foundry"
496
- [ -n "$source_runtime_ref" ] || source_runtime_ref="$runtime_ref"
497
- 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
498
- [ -f "$file" ] || continue
499
- if grep -qF "$source_runtime_repository@" "$file" || grep -q 'runtime-ref:' "$file" || grep -qF 'uses: ./.github/workflows/' "$file"; then
500
- changed=$((changed + 1))
501
- if [ "$mode" = "check" ]; then
502
- printf 'Would render runtime repository in %s\n' "$file"
503
- else
504
- runner_value="$runner"
505
- case "$file" in
506
- .github/workflows/ci.yml) runner_value="$ci_runner" ;;
507
- .github/workflows/test.yml) runner_value="$test_runner" ;;
508
- .github/workflows/security.yml) runner_value="$security_runner" ;;
509
- .github/workflows/codeql.yml) runner_value="$codeql_runner" ;;
510
- .github/workflows/draft-pr.yml|.github/workflows/release-pr.yml) runner_value="$pr_runner" ;;
511
- .github/workflows/release.yml) runner_value="$release_runner" ;;
512
- esac
513
- rendered_workflow="$(mktemp)"
514
- sed -E \
515
- -e "s#${source_runtime_repository}@[^[:space:]]+#${runtime_repository}@${runtime_ref}#g" \
516
- -e "s#uses: \.\/.github/workflows/([^[:space:]]+)#uses: ${runtime_repository}/.github/workflows/\\1@${runtime_ref}#g" \
517
- -e "s#runtime-ref: [^[:space:]]+#runtime-ref: ${runtime_ref}#g" \
518
- -e "s#^ runner: [^[:space:]]+# runner: ${runner_value}#" \
519
- -e "s#^ unit-runner: [^[:space:]]+# unit-runner: ${unit_runner}#" \
520
- "$file" > "$rendered_workflow"
521
- mv "$rendered_workflow" "$file"
522
- printf 'Rendered runtime repository in %s\n' "$file"
523
- fi
524
- fi
525
- done
526
-
527
- write_license() {
528
- local owner license_source
529
- [ "$license" != preserve ] || return 0
530
- [ "$license" != none ] || return 0
531
- if [ -n "$license_file" ]; then
532
- [ -f "$license_file" ] || { printf 'License file not found: %s\n' "$license_file" >&2; exit 1; }
533
- license_source="$license_file"
534
- else
535
- case "$license" in
536
- agpl-3.0-or-later) license_source="$template_root/LICENSE" ;;
537
- mit) license_source="$template_root/.github/licenses/MIT.txt" ;;
538
- esac
539
- [ -f "$license_source" ] || { printf 'License template missing: %s\n' "$license_source" >&2; exit 1; }
540
- fi
541
- changed=$((changed + 1))
542
- if [ "$mode" = check ]; then
543
- printf 'Would generate LICENSE (%s)\n' "$license"
544
- return
545
- fi
546
- owner="${REPO_FOUNDRY_LICENSE_OWNER:-$(git config user.name 2>/dev/null || true)}"
547
- [ -n "$owner" ] || owner="Project contributors"
548
- sed "s/PROJECT_OWNER/$owner/g; s/CURRENT_YEAR/$(date +%Y)/g" "$license_source" > LICENSE
549
- {
550
- printf 'Copyright (C) %s %s\n\n' "$(date +%Y)" "$owner"
551
- printf 'This repository is distributed under %s.\n' "$license"
552
- } > NOTICE
553
- printf 'Generated LICENSE and NOTICE for %s.\n' "$license"
554
- }
555
-
556
- write_license
557
-
558
- # Release Please owns changelog updates after initialization. Preserve an
559
- # existing project history, but give new repositories the expected file.
560
- if [ ! -f CHANGELOG.md ]; then
561
- changed=$((changed + 1))
562
- if [ "$mode" = "check" ]; then
563
- printf '%s\n' 'Would initialize CHANGELOG.md'
564
- else
565
- cat > CHANGELOG.md <<'EOF'
566
- # Changelog
567
-
568
- All notable changes to this project are documented here.
569
- EOF
570
- printf '%s\n' 'Initialized CHANGELOG.md'
571
- fi
572
- fi
573
-
574
- detect_languages() {
575
- local detected=()
576
- if [ -f package.json ] || git ls-files -- '*.js' '*.jsx' '*.ts' '*.tsx' | grep -q .; then
577
- detected+=(typescript)
578
- fi
579
- if [ -f Cargo.toml ] || git ls-files -- '*.rs' | grep -q .; then
580
- detected+=(rust)
581
- fi
582
- if [ -f pyproject.toml ] || [ -f requirements.txt ] || [ -f requirements-dev.txt ] ||
583
- git ls-files -- '*.py' ':!.github/**' | grep -q .; then
584
- detected+=(python)
585
- fi
586
- if find . -path './.git' -prune -o -type f -name '*.sol' -print | grep -q .; then
587
- detected+=(solidity)
588
- fi
589
- printf '%s\n' "${detected[*]}"
590
- }
591
-
592
- initialize_mise() {
593
- local selected_languages="$1"
594
- local language selected_package_manager
595
- if [ -f .mise.toml ]; then
596
- return
597
- fi
598
- if [ "$selected_languages" = auto ]; then
599
- selected_languages="$(detect_languages)"
600
- fi
601
- selected_package_manager="$package_manager"
602
- if [ "$selected_package_manager" = auto ]; then
603
- if [ -f bun.lock ] || [ -f bun.lockb ]; then selected_package_manager=bun
604
- elif [ -f pnpm-lock.yaml ]; then selected_package_manager=pnpm
605
- elif [ -f yarn.lock ]; then selected_package_manager=yarn
606
- elif [ -f package-lock.json ]; then selected_package_manager=npm
607
- else selected_package_manager=bun
608
- fi
609
- fi
610
- changed=$((changed + 1))
611
- if [ "$mode" = check ]; then
612
- printf '%s\n' 'Would initialize .mise.toml'
613
- return
614
- fi
615
- {
616
- printf '%s\n' '[tools]'
617
- for language in $(normalize_csv "$selected_languages"); do
618
- case "$language" in
619
- typescript|solidity)
620
- printf 'node = "%s"\n' "$node_version"
621
- if [ "$selected_package_manager" = bun ]; then
622
- printf 'bun = "%s"\n' "$bun_version"
623
- fi
624
- ;;
625
- python)
626
- printf 'python = "%s"\n' "$python_version"
627
- printf 'uv = "%s"\n' "$uv_version"
628
- printf 'ruff = "%s"\n' "$ruff_version"
629
- ;;
630
- rust)
631
- printf 'rust = { version = "%s", components = ["rustfmt", "clippy"] }\n' "$rust_version"
632
- ;;
633
- esac
634
- done | awk '!seen[$0]++'
635
- printf '\n%s\n' '[settings]'
636
- printf '%s\n' 'experimental = true'
637
- } > .mise.toml
638
- printf '%s\n' 'Initialized .mise.toml'
639
- }
640
-
641
- initialize_mise "$languages"
642
-
643
- initialize_mise_lock() {
644
- if [ ! -f .mise.toml ] || [ -f mise.lock ]; then
645
- return
646
- fi
647
- if ! command -v mise >/dev/null 2>&1; then
648
- return
649
- fi
650
- changed=$((changed + 1))
651
- if [ "$mode" = check ]; then
652
- printf '%s\n' 'Would initialize mise.lock'
653
- return
654
- fi
655
- if MISE_TRUSTED_CONFIG_PATHS="$PWD" mise lock >/dev/null 2>&1; then
656
- printf '%s\n' 'Initialized mise.lock'
657
- else
658
- printf '%s\n' 'Warning: mise.lock could not be generated; CI will resolve pinned tools normally.' >&2
659
- fi
660
- }
661
-
662
- initialize_mise_lock
663
-
664
- if [ -x "$template_root/.github/scripts/sync-codeowners.sh" ]; then
665
- if [ "$mode" = "check" ]; then
666
- bash "$template_root/.github/scripts/sync-codeowners.sh" --check
667
- else
668
- bash "$template_root/.github/scripts/sync-codeowners.sh" --apply
669
- fi
670
- fi
671
-
672
- # Release Please's simple strategy needs a version file. Initialize one only
673
- # for Solidity-only repositories, where no package manifest owns the version.
674
- if [ ! -f version.txt ] && find . -path './.git' -prune -o -type f -name '*.sol' -print | grep -q . &&
675
- [ ! -f package.json ] && [ ! -f Cargo.toml ] && [ ! -f pyproject.toml ]; then
676
- changed=$((changed + 1))
677
- if [ "$mode" = "check" ]; then
678
- printf '%s\n' 'Would initialize version.txt'
679
- else
680
- printf '%s\n' '0.1.0' > version.txt
681
- printf '%s\n' 'Initialized version.txt'
682
- fi
683
- fi
684
-
685
- if [ "$mode" = "apply" ]; then
686
- git config core.hooksPath .githooks
687
- mkdir -p .github
688
- {
689
- printf 'version: 1\n'
690
- printf 'profile: %s\n' "$profile"
691
- printf 'languages: %s\n' "$languages"
692
- printf 'features: %s\n' "$features"
693
- if [ -n "$package_manager" ]; then
694
- printf 'package_manager: %s\n' "$package_manager"
695
- fi
696
- printf 'runtime_repository: %s\n' "$runtime_repository"
697
- printf 'runtime_ref: %s\n' "$runtime_ref"
698
- printf 'runner: %s\n' "$runner"
699
- printf 'unit_runner: %s\n' "$unit_runner"
700
- printf 'ci_runner: %s\n' "$ci_runner"
701
- printf 'test_runner: %s\n' "$test_runner"
702
- printf 'security_runner: %s\n' "$security_runner"
703
- printf 'codeql_runner: %s\n' "$codeql_runner"
704
- printf 'pr_runner: %s\n' "$pr_runner"
705
- printf 'release_runner: %s\n' "$release_runner"
706
- printf 'prune_standard: %s\n' "$prune_standard"
707
- printf 'cache_packages: %s\n' "$cache_packages"
708
- printf 'cache_build: %s\n' "$cache_build"
709
- printf 'coverage_minimum: %s\n' "$coverage_minimum"
710
- printf 'turbo_remote: %s\n' "$turbo_remote"
711
- printf 'release_type: %s\n' "$release_type"
712
- printf 'npm_publish: %s\n' "$npm_publish"
713
- printf 'license: %s\n' "$license"
714
- if [ -n "$license_file" ]; then
715
- printf 'license_file: %s\n' "$license_file"
716
- fi
717
- } > .github/code-foundry.yml
718
- fi
719
-
720
- if [ "$prune" = true ]; then
721
- for workflow in ci codeql security test draft-pr release-pr release; do
722
- file=".github/workflows/$workflow.yml"
723
- if [ -f "$file" ] && ! workflow_enabled "$workflow"; then
724
- changed=$((changed + 1))
725
- if [ "$mode" = "check" ]; then
726
- printf 'Would remove disabled standard workflow %s\n' "$file"
727
- else
728
- rm "$file"
729
- printf 'Removed disabled standard workflow %s\n' "$file"
730
- fi
731
- fi
732
- done
733
- if [ -f .github/dependabot.yml ] && ! workflow_enabled dependabot; then
734
- changed=$((changed + 1))
735
- if [ "$mode" = "check" ]; then
736
- printf 'Would remove disabled standard configuration .github/dependabot.yml\n'
737
- else
738
- rm .github/dependabot.yml
739
- printf '%s\n' 'Removed disabled standard configuration .github/dependabot.yml'
740
- fi
741
- fi
742
- fi
743
-
744
- printf '%s\n' "$changed baseline file(s) differ."