Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/scripts/init-repo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ force=false
languages="${REPO_FOUNDRY_LANGUAGES:-auto}"
features="${REPO_FOUNDRY_FEATURES:-all}"
package_manager="${REPO_FOUNDRY_PACKAGE_MANAGER:-auto}"
runtime_repository="${REPO_FOUNDRY_RUNTIME_REPOSITORY:-0xPlayerOne/code-foundry}"
bootstrap=true
release_type="${REPO_FOUNDRY_RELEASE_TYPE:-auto}"
npm_publish="${REPO_FOUNDRY_NPM_PUBLISH:-false}"
Expand All @@ -37,6 +38,7 @@ Options:
--features LIST all or comma-separated optional features:
ci,codeql,security,test,draft-pr,release-pr,release,dependabot
--package-manager NAME auto, bun, pnpm, yarn, or npm
--runtime-repository OWNER/REPO Reusable workflow runtime repository
--release-type NAME auto, node, python, rust, or simple
--license NAME agpl-3.0-or-later, mit, preserve, or none
--license-file PATH Use an exact custom license file
Expand All @@ -63,6 +65,7 @@ while [ "$#" -gt 0 ]; do
--languages) languages="${2:?missing language list}"; shift 2 ;;
--features) features="${2:?missing feature list}"; shift 2 ;;
--package-manager) package_manager="${2:?missing package manager}"; shift 2 ;;
--runtime-repository) runtime_repository="${2:?missing runtime repository}"; shift 2 ;;
--release-type) release_type="${2:?missing release type}"; shift 2 ;;
--license) license="${2:?missing license}"; shift 2 ;;
--license-file) license_file="${2:?missing license file}"; shift 2 ;;
Expand Down Expand Up @@ -108,6 +111,7 @@ sync_args=(
--languages "$languages"
--features "$features"
--package-manager "$package_manager"
--runtime-repository "$runtime_repository"
--license "$license"
)
if [ -n "$license_file" ]; then sync_args+=(--license-file "$license_file"); fi
Expand All @@ -127,6 +131,7 @@ profile="$(awk -F': ' '/^profile:/ {print $2; exit}' .github/template.yml 2>/dev
languages="$(awk -F': ' '/^languages:/ {print $2; exit}' .github/template.yml 2>/dev/null || true)"
features="$(awk -F': ' '/^features:/ {print $2; exit}' .github/template.yml 2>/dev/null || true)"
package_manager="$(awk -F': ' '/^package_manager:/ {print $2; exit}' .github/template.yml 2>/dev/null || true)"
runtime_repository="$(awk -F': ' '/^runtime_repository:/ {print $2; exit}' .github/template.yml 2>/dev/null || true)"
release_type="$(awk -F': ' '/^release_type:/ {print $2; exit}' .github/template.yml 2>/dev/null || true)"
npm_publish="$(awk -F': ' '/^npm_publish:/ {print $2; exit}' .github/template.yml 2>/dev/null || true)"
license="$(awk -F': ' '/^license:/ {print $2; exit}' .github/template.yml 2>/dev/null || true)"
Expand All @@ -139,6 +144,7 @@ license="$(awk -F': ' '/^license:/ {print $2; exit}' .github/template.yml 2>/dev
printf 'languages: %s\n' "$languages"
printf 'features: %s\n' "$features"
printf 'package_manager: %s\n' "$package_manager"
printf 'runtime_repository: %s\n' "$runtime_repository"
printf 'release_type: %s\n' "$release_type"
printf 'npm_publish: %s\n' "$npm_publish"
printf 'license: %s\n' "$license"
Expand Down
39 changes: 39 additions & 0 deletions .github/scripts/sync-template.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Options:
--languages LIST auto or comma-separated: typescript,rust,python,solidity
--features LIST all or comma-separated standard features
--package-manager NAME auto, bun, pnpm, yarn, or npm
--runtime-repository OWNER/REPO Reusable workflow runtime repository
--license NAME preserve, agpl-3.0-or-later, mit, or none
--license-file PATH Use an exact custom license file
--check Preview changes (default)
Expand Down Expand Up @@ -40,6 +41,9 @@ features_set=false
package_manager="${REPO_FOUNDRY_PACKAGE_MANAGER:-}"
package_manager_set=false
[ -n "${REPO_FOUNDRY_PACKAGE_MANAGER:-}" ] && package_manager_set=true
runtime_repository="${REPO_FOUNDRY_RUNTIME_REPOSITORY:-}"
runtime_repository_set=false
[ -n "${REPO_FOUNDRY_RUNTIME_REPOSITORY:-}" ] && runtime_repository_set=true
template_ref=""
release_type="${REPO_FOUNDRY_RELEASE_TYPE:-auto}"
npm_publish="${REPO_FOUNDRY_NPM_PUBLISH:-false}"
Expand Down Expand Up @@ -104,6 +108,7 @@ while [ "$#" -gt 0 ]; do
--languages) languages="${2:?missing language list}"; languages_set=true; shift 2 ;;
--features) features="${2:?missing feature list}"; features_set=true; shift 2 ;;
--package-manager) package_manager="${2:?missing package manager}"; package_manager_set=true; shift 2 ;;
--runtime-repository) runtime_repository="${2:?missing runtime repository}"; runtime_repository_set=true; shift 2 ;;
--license) license="${2:?missing license}"; license_set=true; shift 2 ;;
--license-file) license_file="${2:?missing license file}"; shift 2 ;;
--check) mode="check"; shift ;;
Expand Down Expand Up @@ -132,6 +137,9 @@ if [ -f .github/template.yml ]; then
if [ "$package_manager_set" = false ]; then
package_manager="$(awk -F': ' '/^package_manager:/ {print $2; exit}' .github/template.yml)"
fi
if [ "$runtime_repository_set" = false ]; then
runtime_repository="$(awk -F': ' '/^runtime_repository:/ {print $2; exit}' .github/template.yml)"
fi
template_ref="$(awk -F': ' '/^template:/ {print $2; exit}' .github/template.yml)"
if [ "$release_type_set" != true ]; then
configured_release_type="$(awk -F': ' '/^release_type:/ {print $2; exit}' .github/template.yml)"
Expand All @@ -147,6 +155,18 @@ if [ -f .github/template.yml ]; then
fi
fi
[ -n "$package_manager" ] || package_manager=auto
if [ -z "$runtime_repository" ]; then
source_repository="$source"
if [ -d "$source" ]; then
source_repository="$(git -C "$source" remote get-url origin 2>/dev/null || true)"
fi
runtime_repository="$(printf '%s\n' "$source_repository" | sed -nE 's#.*github\.com[:/]([^/]+/[^/.]+)(\.git)?$#\1#p')"
fi
[ -n "$runtime_repository" ] || runtime_repository="0xPlayerOne/code-foundry"
case "$runtime_repository" in
*/*) ;;
*) printf 'Runtime repository must be OWNER/REPO: %s\n' "$runtime_repository" >&2; exit 2 ;;
esac
case "$package_manager" in
auto|bun|pnpm|yarn|npm) ;;
*) printf 'Unsupported package manager: %s\n' "$package_manager" >&2; exit 2 ;;
Expand Down Expand Up @@ -185,6 +205,7 @@ if [ -f "$template_root/.github/scripts/profile.sh" ]; then
REPO_FOUNDRY_LANGUAGES="$languages" \
REPO_FOUNDRY_FEATURES="$features" \
REPO_FOUNDRY_PACKAGE_MANAGER="$package_manager" \
REPO_FOUNDRY_RUNTIME_REPOSITORY="$runtime_repository" \
REPO_FOUNDRY_RELEASE_TYPE="$release_type" \
REPO_FOUNDRY_NPM_PUBLISH="$npm_publish" \
bash "$template_root/.github/scripts/profile.sh" detect --root "$PWD"
Expand Down Expand Up @@ -354,6 +375,23 @@ for file in "${files[@]}"; do
fi
done

# Reusable workflow callers must use a literal repository/ref. Render the
# selected runtime repository while leaving custom workflows untouched.
for file in .github/workflows/ci.yml .github/workflows/test.yml; do
[ -f "$file" ] || continue
if grep -q '0xPlayerOne/code-foundry' "$file"; then
changed=$((changed + 1))
if [ "$mode" = "check" ]; then
printf 'Would render runtime repository in %s\n' "$file"
else
rendered_workflow="$(mktemp)"
sed "s#0xPlayerOne/code-foundry#$runtime_repository#g" "$file" > "$rendered_workflow"
mv "$rendered_workflow" "$file"
printf 'Rendered runtime repository in %s\n' "$file"
fi
fi
done

write_license() {
local owner license_source
[ "$license" != preserve ] || return 0
Expand Down Expand Up @@ -526,6 +564,7 @@ if [ "$mode" = "apply" ]; then
if [ -n "$package_manager" ]; then
printf 'package_manager: %s\n' "$package_manager"
fi
printf 'runtime_repository: %s\n' "$runtime_repository"
printf 'release_type: %s\n' "$release_type"
printf 'npm_publish: %s\n' "$npm_publish"
printf 'license: %s\n' "$license"
Expand Down
1 change: 1 addition & 0 deletions .github/template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ profile: auto
languages: typescript
features: all
package_manager: bun
runtime_repository: 0xPlayerOne/code-foundry
release_type: node
npm_publish: true
license: agpl-3.0-or-later
Expand Down
1 change: 1 addition & 0 deletions .github/template.yml.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ profile: auto
languages: auto
features: all
package_manager: auto
runtime_repository: 0xPlayerOne/code-foundry
release_type: auto
npm_publish: false
license: preserve
Expand Down
172 changes: 4 additions & 168 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,172 +10,8 @@ on:
permissions:
contents: read

env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
REPO_FOUNDRY_PROFILE: ${{ vars.REPO_FOUNDRY_PROFILE }}
REPO_FOUNDRY_LANGUAGES: ${{ vars.REPO_FOUNDRY_LANGUAGES }}
REPO_FOUNDRY_FEATURES: ${{ vars.REPO_FOUNDRY_FEATURES }}
REPO_FOUNDRY_PACKAGE_MANAGER: ${{ vars.REPO_FOUNDRY_PACKAGE_MANAGER }}
REPO_FOUNDRY_CACHE_PACKAGES: ${{ vars.REPO_FOUNDRY_CACHE_PACKAGES || 'auto' }}
REPO_FOUNDRY_CACHE_BUILD: ${{ vars.REPO_FOUNDRY_CACHE_BUILD || 'auto' }}
REPO_FOUNDRY_BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before || '' }}

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.head.repo.full_name || github.repository }}-${{ github.event.pull_request.head.ref || github.ref_name }}
cancel-in-progress: true

jobs:
unit:
name: Unit
# Keep ordinary suites on the lean image. Set the repository variable
# REPO_FOUNDRY_UNIT_RUNNER=ubuntu-latest for native/system-dependent tests.
runs-on: ${{ vars.REPO_FOUNDRY_UNIT_RUNNER || 'ubuntu-slim' }}
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 2
- name: Detect
id: applicability
run: bash .github/scripts/ci.sh task_profile unit >> "$GITHUB_OUTPUT"
- name: Setup
if: steps.applicability.outputs.applicable == 'true'
uses: ./.github/actions/setup
with:
install: true
install-javascript: ${{ steps.applicability.outputs.javascript == 'true' }}
install-python: ${{ steps.applicability.outputs.python == 'true' }}
install-rust: ${{ steps.applicability.outputs.rust == 'true' }}
cache-build: false
cache-environment: false
cache-packages: ${{ vars.REPO_FOUNDRY_CACHE_PACKAGES || 'auto' }}
task: unit
task-javascript: ${{ steps.applicability.outputs.javascript }}
task-python: ${{ steps.applicability.outputs.python }}
task-rust: ${{ steps.applicability.outputs.rust }}
- name: Unit
if: steps.applicability.outputs.applicable == 'true'
run: bash .github/scripts/ci.sh unit
- name: Upload coverage
if: >-
always() &&
steps.applicability.outputs.applicable == 'true' &&
hashFiles('coverage/**', 'htmlcov/**', '.coverage*', 'target/llvm-cov/**') != ''
uses: actions/upload-artifact@v7
with:
name: coverage-unit-${{ github.run_id }}
path: |
coverage/**
htmlcov/**
.coverage*
target/llvm-cov/**
compression-level: 1
if-no-files-found: ignore
retention-days: 14

integration:
name: Integration
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 2
- name: Detect
id: applicability
run: bash .github/scripts/ci.sh task_profile integration >> "$GITHUB_OUTPUT"
- name: Setup
if: steps.applicability.outputs.applicable == 'true'
uses: ./.github/actions/setup
with:
install: true
install-javascript: ${{ steps.applicability.outputs.javascript == 'true' }}
install-python: ${{ steps.applicability.outputs.python == 'true' }}
install-rust: ${{ steps.applicability.outputs.rust == 'true' }}
cache-build: ${{ steps.applicability.outputs.rust == 'true' }}
cache-environment: false
cache-save: ${{ github.event_name == 'push' }}
task: integration
task-javascript: ${{ steps.applicability.outputs.javascript }}
task-python: ${{ steps.applicability.outputs.python }}
task-rust: ${{ steps.applicability.outputs.rust }}
- name: Integration
if: steps.applicability.outputs.applicable == 'true'
run: bash .github/scripts/ci.sh integration

e2e:
name: E2E
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 2
- name: Detect
id: applicability
run: bash .github/scripts/ci.sh task_profile e2e >> "$GITHUB_OUTPUT"
- name: Cache browser binaries
if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.browser == 'true'
uses: ./.github/actions/cache
with:
save: ${{ github.event_name == 'push' }}
path: |
~/.cache/ms-playwright
~/.cache/Cypress
~/.cache/puppeteer
key: ${{ runner.os }}-e2e-browsers-${{ hashFiles('**/package.json', '**/bun.lock', '**/bun.lockb', '**/pnpm-lock.yaml', '**/yarn.lock', '**/package-lock.json', '**/playwright.config.*', '**/cypress.config.*') }}
restore-keys: |
${{ runner.os }}-e2e-browsers-
- name: Setup
if: steps.applicability.outputs.applicable == 'true'
uses: ./.github/actions/setup
with:
install: true
install-javascript: ${{ steps.applicability.outputs.javascript == 'true' }}
install-python: ${{ steps.applicability.outputs.python == 'true' }}
install-rust: ${{ steps.applicability.outputs.rust == 'true' }}
cache-build: false
cache-environment: false
cache-save: ${{ github.event_name == 'push' }}
task: e2e
task-javascript: ${{ steps.applicability.outputs.javascript }}
task-python: ${{ steps.applicability.outputs.python }}
task-rust: ${{ steps.applicability.outputs.rust }}
- name: E2E
if: steps.applicability.outputs.applicable == 'true'
run: bash .github/scripts/ci.sh e2e

smoke:
name: Smoke
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 2
- name: Detect
id: applicability
run: bash .github/scripts/ci.sh task_profile smoke >> "$GITHUB_OUTPUT"
- name: Setup
if: steps.applicability.outputs.applicable == 'true'
uses: ./.github/actions/setup
with:
install: true
install-javascript: ${{ steps.applicability.outputs.javascript == 'true' }}
install-python: ${{ steps.applicability.outputs.python == 'true' }}
install-rust: ${{ steps.applicability.outputs.rust == 'true' }}
cache-build: false
cache-environment: false
cache-save: ${{ github.event_name == 'push' }}
task: smoke
task-javascript: ${{ steps.applicability.outputs.javascript }}
task-python: ${{ steps.applicability.outputs.python }}
task-rust: ${{ steps.applicability.outputs.rust }}
- name: Smoke
if: steps.applicability.outputs.applicable == 'true'
run: bash .github/scripts/ci.sh smoke
test:
name: Test
uses: 0xPlayerOne/code-foundry/.github/workflows/reusable-test.yml@v0.8.0
secrets: inherit
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ npx code-foundry init --license agpl-3.0-or-later
npx code-foundry init --license-file ./legal/LICENSE.txt
```

Reusable workflow callers default to this package's public runtime. Use
`--runtime-repository OWNER/REPO` when initializing from an organization fork;
the selected repository is saved in `.github/template.yml` and rendered into
the small `ci.yml` and `test.yml` wrappers automatically. The equivalent
environment variable is `REPO_FOUNDRY_RUNTIME_REPOSITORY`.

Initialization defaults to AGPL for new repositories. Synchronization defaults
to `--license preserve`, so an existing repository's license is never replaced
unless you explicitly select a license or provide `--license-file`.
Expand Down Expand Up @@ -84,7 +90,7 @@ bash .github/scripts/init-repo.sh \

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.

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.
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.

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.

Expand Down
Loading