Skip to content

Commit 95d17e9

Browse files
committed
feat(init): configure reusable workflow repository
1 parent 66d426f commit 95d17e9

7 files changed

Lines changed: 63 additions & 169 deletions

File tree

‎.github/scripts/init-repo.sh‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ force=false
1111
languages="${REPO_FOUNDRY_LANGUAGES:-auto}"
1212
features="${REPO_FOUNDRY_FEATURES:-all}"
1313
package_manager="${REPO_FOUNDRY_PACKAGE_MANAGER:-auto}"
14+
runtime_repository="${REPO_FOUNDRY_RUNTIME_REPOSITORY:-0xPlayerOne/code-foundry}"
1415
bootstrap=true
1516
release_type="${REPO_FOUNDRY_RELEASE_TYPE:-auto}"
1617
npm_publish="${REPO_FOUNDRY_NPM_PUBLISH:-false}"
@@ -37,6 +38,7 @@ Options:
3738
--features LIST all or comma-separated optional features:
3839
ci,codeql,security,test,draft-pr,release-pr,release,dependabot
3940
--package-manager NAME auto, bun, pnpm, yarn, or npm
41+
--runtime-repository OWNER/REPO Reusable workflow runtime repository
4042
--release-type NAME auto, node, python, rust, or simple
4143
--license NAME agpl-3.0-or-later, mit, preserve, or none
4244
--license-file PATH Use an exact custom license file
@@ -63,6 +65,7 @@ while [ "$#" -gt 0 ]; do
6365
--languages) languages="${2:?missing language list}"; shift 2 ;;
6466
--features) features="${2:?missing feature list}"; shift 2 ;;
6567
--package-manager) package_manager="${2:?missing package manager}"; shift 2 ;;
68+
--runtime-repository) runtime_repository="${2:?missing runtime repository}"; shift 2 ;;
6669
--release-type) release_type="${2:?missing release type}"; shift 2 ;;
6770
--license) license="${2:?missing license}"; shift 2 ;;
6871
--license-file) license_file="${2:?missing license file}"; shift 2 ;;
@@ -108,6 +111,7 @@ sync_args=(
108111
--languages "$languages"
109112
--features "$features"
110113
--package-manager "$package_manager"
114+
--runtime-repository "$runtime_repository"
111115
--license "$license"
112116
)
113117
if [ -n "$license_file" ]; then sync_args+=(--license-file "$license_file"); fi
@@ -127,6 +131,7 @@ profile="$(awk -F': ' '/^profile:/ {print $2; exit}' .github/template.yml 2>/dev
127131
languages="$(awk -F': ' '/^languages:/ {print $2; exit}' .github/template.yml 2>/dev/null || true)"
128132
features="$(awk -F': ' '/^features:/ {print $2; exit}' .github/template.yml 2>/dev/null || true)"
129133
package_manager="$(awk -F': ' '/^package_manager:/ {print $2; exit}' .github/template.yml 2>/dev/null || true)"
134+
runtime_repository="$(awk -F': ' '/^runtime_repository:/ {print $2; exit}' .github/template.yml 2>/dev/null || true)"
130135
release_type="$(awk -F': ' '/^release_type:/ {print $2; exit}' .github/template.yml 2>/dev/null || true)"
131136
npm_publish="$(awk -F': ' '/^npm_publish:/ {print $2; exit}' .github/template.yml 2>/dev/null || true)"
132137
license="$(awk -F': ' '/^license:/ {print $2; exit}' .github/template.yml 2>/dev/null || true)"
@@ -139,6 +144,7 @@ license="$(awk -F': ' '/^license:/ {print $2; exit}' .github/template.yml 2>/dev
139144
printf 'languages: %s\n' "$languages"
140145
printf 'features: %s\n' "$features"
141146
printf 'package_manager: %s\n' "$package_manager"
147+
printf 'runtime_repository: %s\n' "$runtime_repository"
142148
printf 'release_type: %s\n' "$release_type"
143149
printf 'npm_publish: %s\n' "$npm_publish"
144150
printf 'license: %s\n' "$license"

‎.github/scripts/sync-template.sh‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Options:
1313
--languages LIST auto or comma-separated: typescript,rust,python,solidity
1414
--features LIST all or comma-separated standard features
1515
--package-manager NAME auto, bun, pnpm, yarn, or npm
16+
--runtime-repository OWNER/REPO Reusable workflow runtime repository
1617
--license NAME preserve, agpl-3.0-or-later, mit, or none
1718
--license-file PATH Use an exact custom license file
1819
--check Preview changes (default)
@@ -40,6 +41,9 @@ features_set=false
4041
package_manager="${REPO_FOUNDRY_PACKAGE_MANAGER:-}"
4142
package_manager_set=false
4243
[ -n "${REPO_FOUNDRY_PACKAGE_MANAGER:-}" ] && package_manager_set=true
44+
runtime_repository="${REPO_FOUNDRY_RUNTIME_REPOSITORY:-}"
45+
runtime_repository_set=false
46+
[ -n "${REPO_FOUNDRY_RUNTIME_REPOSITORY:-}" ] && runtime_repository_set=true
4347
template_ref=""
4448
release_type="${REPO_FOUNDRY_RELEASE_TYPE:-auto}"
4549
npm_publish="${REPO_FOUNDRY_NPM_PUBLISH:-false}"
@@ -104,6 +108,7 @@ while [ "$#" -gt 0 ]; do
104108
--languages) languages="${2:?missing language list}"; languages_set=true; shift 2 ;;
105109
--features) features="${2:?missing feature list}"; features_set=true; shift 2 ;;
106110
--package-manager) package_manager="${2:?missing package manager}"; package_manager_set=true; shift 2 ;;
111+
--runtime-repository) runtime_repository="${2:?missing runtime repository}"; runtime_repository_set=true; shift 2 ;;
107112
--license) license="${2:?missing license}"; license_set=true; shift 2 ;;
108113
--license-file) license_file="${2:?missing license file}"; shift 2 ;;
109114
--check) mode="check"; shift ;;
@@ -132,6 +137,9 @@ if [ -f .github/template.yml ]; then
132137
if [ "$package_manager_set" = false ]; then
133138
package_manager="$(awk -F': ' '/^package_manager:/ {print $2; exit}' .github/template.yml)"
134139
fi
140+
if [ "$runtime_repository_set" = false ]; then
141+
runtime_repository="$(awk -F': ' '/^runtime_repository:/ {print $2; exit}' .github/template.yml)"
142+
fi
135143
template_ref="$(awk -F': ' '/^template:/ {print $2; exit}' .github/template.yml)"
136144
if [ "$release_type_set" != true ]; then
137145
configured_release_type="$(awk -F': ' '/^release_type:/ {print $2; exit}' .github/template.yml)"
@@ -147,6 +155,18 @@ if [ -f .github/template.yml ]; then
147155
fi
148156
fi
149157
[ -n "$package_manager" ] || package_manager=auto
158+
if [ -z "$runtime_repository" ]; then
159+
source_repository="$source"
160+
if [ -d "$source" ]; then
161+
source_repository="$(git -C "$source" remote get-url origin 2>/dev/null || true)"
162+
fi
163+
runtime_repository="$(printf '%s\n' "$source_repository" | sed -nE 's#.*github\.com[:/]([^/]+/[^/.]+)(\.git)?$#\1#p')"
164+
fi
165+
[ -n "$runtime_repository" ] || runtime_repository="0xPlayerOne/code-foundry"
166+
case "$runtime_repository" in
167+
*/*) ;;
168+
*) printf 'Runtime repository must be OWNER/REPO: %s\n' "$runtime_repository" >&2; exit 2 ;;
169+
esac
150170
case "$package_manager" in
151171
auto|bun|pnpm|yarn|npm) ;;
152172
*) printf 'Unsupported package manager: %s\n' "$package_manager" >&2; exit 2 ;;
@@ -185,6 +205,7 @@ if [ -f "$template_root/.github/scripts/profile.sh" ]; then
185205
REPO_FOUNDRY_LANGUAGES="$languages" \
186206
REPO_FOUNDRY_FEATURES="$features" \
187207
REPO_FOUNDRY_PACKAGE_MANAGER="$package_manager" \
208+
REPO_FOUNDRY_RUNTIME_REPOSITORY="$runtime_repository" \
188209
REPO_FOUNDRY_RELEASE_TYPE="$release_type" \
189210
REPO_FOUNDRY_NPM_PUBLISH="$npm_publish" \
190211
bash "$template_root/.github/scripts/profile.sh" detect --root "$PWD"
@@ -354,6 +375,23 @@ for file in "${files[@]}"; do
354375
fi
355376
done
356377

378+
# Reusable workflow callers must use a literal repository/ref. Render the
379+
# selected runtime repository while leaving custom workflows untouched.
380+
for file in .github/workflows/ci.yml .github/workflows/test.yml; do
381+
[ -f "$file" ] || continue
382+
if grep -q '0xPlayerOne/code-foundry' "$file"; then
383+
changed=$((changed + 1))
384+
if [ "$mode" = "check" ]; then
385+
printf 'Would render runtime repository in %s\n' "$file"
386+
else
387+
rendered_workflow="$(mktemp)"
388+
sed "s#0xPlayerOne/code-foundry#$runtime_repository#g" "$file" > "$rendered_workflow"
389+
mv "$rendered_workflow" "$file"
390+
printf 'Rendered runtime repository in %s\n' "$file"
391+
fi
392+
fi
393+
done
394+
357395
write_license() {
358396
local owner license_source
359397
[ "$license" != preserve ] || return 0
@@ -526,6 +564,7 @@ if [ "$mode" = "apply" ]; then
526564
if [ -n "$package_manager" ]; then
527565
printf 'package_manager: %s\n' "$package_manager"
528566
fi
567+
printf 'runtime_repository: %s\n' "$runtime_repository"
529568
printf 'release_type: %s\n' "$release_type"
530569
printf 'npm_publish: %s\n' "$npm_publish"
531570
printf 'license: %s\n' "$license"

‎.github/template.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ profile: auto
44
languages: typescript
55
features: all
66
package_manager: bun
7+
runtime_repository: 0xPlayerOne/code-foundry
78
release_type: node
89
npm_publish: true
910
license: agpl-3.0-or-later

‎.github/template.yml.example‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ profile: auto
55
languages: auto
66
features: all
77
package_manager: auto
8+
runtime_repository: 0xPlayerOne/code-foundry
89
release_type: auto
910
npm_publish: false
1011
license: preserve

‎.github/workflows/test.yml‎

Lines changed: 4 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -10,172 +10,8 @@ on:
1010
permissions:
1111
contents: read
1212

13-
env:
14-
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
15-
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
16-
REPO_FOUNDRY_PROFILE: ${{ vars.REPO_FOUNDRY_PROFILE }}
17-
REPO_FOUNDRY_LANGUAGES: ${{ vars.REPO_FOUNDRY_LANGUAGES }}
18-
REPO_FOUNDRY_FEATURES: ${{ vars.REPO_FOUNDRY_FEATURES }}
19-
REPO_FOUNDRY_PACKAGE_MANAGER: ${{ vars.REPO_FOUNDRY_PACKAGE_MANAGER }}
20-
REPO_FOUNDRY_CACHE_PACKAGES: ${{ vars.REPO_FOUNDRY_CACHE_PACKAGES || 'auto' }}
21-
REPO_FOUNDRY_CACHE_BUILD: ${{ vars.REPO_FOUNDRY_CACHE_BUILD || 'auto' }}
22-
REPO_FOUNDRY_BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before || '' }}
23-
24-
concurrency:
25-
group: ${{ github.workflow }}-${{ github.event.pull_request.head.repo.full_name || github.repository }}-${{ github.event.pull_request.head.ref || github.ref_name }}
26-
cancel-in-progress: true
27-
2813
jobs:
29-
unit:
30-
name: Unit
31-
# Keep ordinary suites on the lean image. Set the repository variable
32-
# REPO_FOUNDRY_UNIT_RUNNER=ubuntu-latest for native/system-dependent tests.
33-
runs-on: ${{ vars.REPO_FOUNDRY_UNIT_RUNNER || 'ubuntu-slim' }}
34-
timeout-minutes: 30
35-
steps:
36-
- name: Checkout
37-
uses: actions/checkout@v7
38-
with:
39-
fetch-depth: 2
40-
- name: Detect
41-
id: applicability
42-
run: bash .github/scripts/ci.sh task_profile unit >> "$GITHUB_OUTPUT"
43-
- name: Setup
44-
if: steps.applicability.outputs.applicable == 'true'
45-
uses: ./.github/actions/setup
46-
with:
47-
install: true
48-
install-javascript: ${{ steps.applicability.outputs.javascript == 'true' }}
49-
install-python: ${{ steps.applicability.outputs.python == 'true' }}
50-
install-rust: ${{ steps.applicability.outputs.rust == 'true' }}
51-
cache-build: false
52-
cache-environment: false
53-
cache-packages: ${{ vars.REPO_FOUNDRY_CACHE_PACKAGES || 'auto' }}
54-
task: unit
55-
task-javascript: ${{ steps.applicability.outputs.javascript }}
56-
task-python: ${{ steps.applicability.outputs.python }}
57-
task-rust: ${{ steps.applicability.outputs.rust }}
58-
- name: Unit
59-
if: steps.applicability.outputs.applicable == 'true'
60-
run: bash .github/scripts/ci.sh unit
61-
- name: Upload coverage
62-
if: >-
63-
always() &&
64-
steps.applicability.outputs.applicable == 'true' &&
65-
hashFiles('coverage/**', 'htmlcov/**', '.coverage*', 'target/llvm-cov/**') != ''
66-
uses: actions/upload-artifact@v7
67-
with:
68-
name: coverage-unit-${{ github.run_id }}
69-
path: |
70-
coverage/**
71-
htmlcov/**
72-
.coverage*
73-
target/llvm-cov/**
74-
compression-level: 1
75-
if-no-files-found: ignore
76-
retention-days: 14
77-
78-
integration:
79-
name: Integration
80-
runs-on: ubuntu-latest
81-
timeout-minutes: 30
82-
steps:
83-
- name: Checkout
84-
uses: actions/checkout@v7
85-
with:
86-
fetch-depth: 2
87-
- name: Detect
88-
id: applicability
89-
run: bash .github/scripts/ci.sh task_profile integration >> "$GITHUB_OUTPUT"
90-
- name: Setup
91-
if: steps.applicability.outputs.applicable == 'true'
92-
uses: ./.github/actions/setup
93-
with:
94-
install: true
95-
install-javascript: ${{ steps.applicability.outputs.javascript == 'true' }}
96-
install-python: ${{ steps.applicability.outputs.python == 'true' }}
97-
install-rust: ${{ steps.applicability.outputs.rust == 'true' }}
98-
cache-build: ${{ steps.applicability.outputs.rust == 'true' }}
99-
cache-environment: false
100-
cache-save: ${{ github.event_name == 'push' }}
101-
task: integration
102-
task-javascript: ${{ steps.applicability.outputs.javascript }}
103-
task-python: ${{ steps.applicability.outputs.python }}
104-
task-rust: ${{ steps.applicability.outputs.rust }}
105-
- name: Integration
106-
if: steps.applicability.outputs.applicable == 'true'
107-
run: bash .github/scripts/ci.sh integration
108-
109-
e2e:
110-
name: E2E
111-
runs-on: ubuntu-latest
112-
timeout-minutes: 45
113-
steps:
114-
- name: Checkout
115-
uses: actions/checkout@v7
116-
with:
117-
fetch-depth: 2
118-
- name: Detect
119-
id: applicability
120-
run: bash .github/scripts/ci.sh task_profile e2e >> "$GITHUB_OUTPUT"
121-
- name: Cache browser binaries
122-
if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.browser == 'true'
123-
uses: ./.github/actions/cache
124-
with:
125-
save: ${{ github.event_name == 'push' }}
126-
path: |
127-
~/.cache/ms-playwright
128-
~/.cache/Cypress
129-
~/.cache/puppeteer
130-
key: ${{ runner.os }}-e2e-browsers-${{ hashFiles('**/package.json', '**/bun.lock', '**/bun.lockb', '**/pnpm-lock.yaml', '**/yarn.lock', '**/package-lock.json', '**/playwright.config.*', '**/cypress.config.*') }}
131-
restore-keys: |
132-
${{ runner.os }}-e2e-browsers-
133-
- name: Setup
134-
if: steps.applicability.outputs.applicable == 'true'
135-
uses: ./.github/actions/setup
136-
with:
137-
install: true
138-
install-javascript: ${{ steps.applicability.outputs.javascript == 'true' }}
139-
install-python: ${{ steps.applicability.outputs.python == 'true' }}
140-
install-rust: ${{ steps.applicability.outputs.rust == 'true' }}
141-
cache-build: false
142-
cache-environment: false
143-
cache-save: ${{ github.event_name == 'push' }}
144-
task: e2e
145-
task-javascript: ${{ steps.applicability.outputs.javascript }}
146-
task-python: ${{ steps.applicability.outputs.python }}
147-
task-rust: ${{ steps.applicability.outputs.rust }}
148-
- name: E2E
149-
if: steps.applicability.outputs.applicable == 'true'
150-
run: bash .github/scripts/ci.sh e2e
151-
152-
smoke:
153-
name: Smoke
154-
runs-on: ubuntu-latest
155-
timeout-minutes: 15
156-
steps:
157-
- name: Checkout
158-
uses: actions/checkout@v7
159-
with:
160-
fetch-depth: 2
161-
- name: Detect
162-
id: applicability
163-
run: bash .github/scripts/ci.sh task_profile smoke >> "$GITHUB_OUTPUT"
164-
- name: Setup
165-
if: steps.applicability.outputs.applicable == 'true'
166-
uses: ./.github/actions/setup
167-
with:
168-
install: true
169-
install-javascript: ${{ steps.applicability.outputs.javascript == 'true' }}
170-
install-python: ${{ steps.applicability.outputs.python == 'true' }}
171-
install-rust: ${{ steps.applicability.outputs.rust == 'true' }}
172-
cache-build: false
173-
cache-environment: false
174-
cache-save: ${{ github.event_name == 'push' }}
175-
task: smoke
176-
task-javascript: ${{ steps.applicability.outputs.javascript }}
177-
task-python: ${{ steps.applicability.outputs.python }}
178-
task-rust: ${{ steps.applicability.outputs.rust }}
179-
- name: Smoke
180-
if: steps.applicability.outputs.applicable == 'true'
181-
run: bash .github/scripts/ci.sh smoke
14+
test:
15+
name: Test
16+
uses: 0xPlayerOne/code-foundry/.github/workflows/reusable-test.yml@v0.8.0
17+
secrets: inherit

‎README.md‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ npx code-foundry init --license agpl-3.0-or-later
3636
npx code-foundry init --license-file ./legal/LICENSE.txt
3737
```
3838

39+
Reusable workflow callers default to this package's public runtime. Use
40+
`--runtime-repository OWNER/REPO` when initializing from an organization fork;
41+
the selected repository is saved in `.github/template.yml` and rendered into
42+
the small `ci.yml` and `test.yml` wrappers automatically. The equivalent
43+
environment variable is `REPO_FOUNDRY_RUNTIME_REPOSITORY`.
44+
3945
Initialization defaults to AGPL for new repositories. Synchronization defaults
4046
to `--license preserve`, so an existing repository's license is never replaced
4147
unless you explicitly select a license or provide `--license-file`.
@@ -84,7 +90,7 @@ bash .github/scripts/init-repo.sh \
8490

8591
Supported profiles are `application`, `monorepo`, and `minimal`; use `auto` to detect one. Supported languages are `typescript`, `rust`, `python`, and `solidity`. Supported feature flags are `ci`, `codeql`, `security`, `test`, `draft-pr`, `release-pr`, `release`, and `dependabot`. Use `--prune` only when you explicitly want disabled standard workflows removed; custom workflows are always preserved. The selected profile is stored in `.github/template.yml`, which makes later syncs repeatable and lets workflows consume repository-specific settings without duplicating the template scripts.
8692

87-
Profile precedence is consistent everywhere: explicit CLI flags, then `REPO_FOUNDRY_*` GitHub repository variables/environment values, then `.github/template.yml`, then automatic detection and standard defaults. Useful repository variables include `REPO_FOUNDRY_PROFILE`, `REPO_FOUNDRY_LANGUAGES`, `REPO_FOUNDRY_FEATURES`, `REPO_FOUNDRY_PACKAGE_MANAGER`, `REPO_FOUNDRY_RUNNER`, `REPO_FOUNDRY_UNIT_RUNNER`, `REPO_FOUNDRY_CACHE_PACKAGES`, `REPO_FOUNDRY_CACHE_BUILD`, `REPO_FOUNDRY_COVERAGE_MINIMUM`, and `REPO_FOUNDRY_TURBO_REMOTE`. Use CLI flags for one-off initialization; use repository variables for local overrides that should not be committed.
93+
Profile precedence is consistent everywhere: explicit CLI flags, then `REPO_FOUNDRY_*` GitHub repository variables/environment values, then `.github/template.yml`, then automatic detection and standard defaults. Useful repository variables include `REPO_FOUNDRY_PROFILE`, `REPO_FOUNDRY_LANGUAGES`, `REPO_FOUNDRY_FEATURES`, `REPO_FOUNDRY_PACKAGE_MANAGER`, `REPO_FOUNDRY_RUNTIME_REPOSITORY`, `REPO_FOUNDRY_RUNNER`, `REPO_FOUNDRY_UNIT_RUNNER`, `REPO_FOUNDRY_CACHE_PACKAGES`, `REPO_FOUNDRY_CACHE_BUILD`, `REPO_FOUNDRY_COVERAGE_MINIMUM`, and `REPO_FOUNDRY_TURBO_REMOTE`. Use CLI flags for one-off initialization; use repository variables for local overrides that should not be committed.
8894

8995
Inspect the resolved profile at any time with `bash .github/scripts/profile.sh detect`; use `profile.sh get KEY` when another script needs one setting.
9096

0 commit comments

Comments
 (0)