Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e00f809
refactor(template): emit nested copier template payload
rochecompaan Mar 3, 2026
09f8b7d
fix(template): add root guide/workflow and preserve README rendering
rochecompaan Mar 6, 2026
276ccee
fix(template): run first-stage tasks for generated template repo
rochecompaan Mar 7, 2026
037f8f1
fix(tasks): skip starter-stage npm init in nested template
rochecompaan Mar 7, 2026
278de7b
refactor(tasks): split starter and template post-copy scripts
rochecompaan Mar 7, 2026
51ec319
fix(tasks): create initial commit after scaffold init
rochecompaan Mar 7, 2026
ac347a5
refactor(tasks): switch phase post-copy scripts to python
rochecompaan Mar 7, 2026
7f0e1ed
fix(pre-commit): skip templated yaml under template dir
rochecompaan Mar 8, 2026
d186baf
feat(template): add root render-test Makefile for generated template
rochecompaan Mar 8, 2026
fd7409d
refactor(template): use runner template files for stage-1 root tasks
rochecompaan Mar 8, 2026
e9c29a3
fix(pre-commit): scope template yaml exclusion to stage-1 only
rochecompaan Mar 8, 2026
0df770c
feat(template): include license in stage-1 and stage-2 outputs
rochecompaan Mar 8, 2026
6359e2b
fix(ci): skip repository creation during template render tests
rochecompaan Mar 9, 2026
b53224f
fix(ci): pin node 22.14 for template render workflows
rochecompaan Mar 9, 2026
69946d0
fix(template): restore stage-1 semantic workflows and prune correctly
rochecompaan Mar 9, 2026
b601187
chore(ci): use node lts aliases across workflows
rochecompaan Mar 9, 2026
c7e62cb
fix(starter): render root semantic workflows from .scaf assets
rochecompaan Mar 9, 2026
5784650
fix(starter): install pre-commit from project root
rochecompaan Mar 9, 2026
bfa54f7
fix(ci): run starter semantic-release from template dir
rochecompaan Mar 9, 2026
bf2ffc0
fix(starter): generate root semantic-release npm assets
rochecompaan Mar 9, 2026
0bfab28
fix(starter): avoid root/template semantic file collisions
rochecompaan Mar 9, 2026
4d675a0
chore(template): ignore node_modules in starter and nested template
rochecompaan Mar 9, 2026
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
5 changes: 5 additions & 0 deletions .github/workflows/template-render-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ jobs:
with:
python-version: '3.11'

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "lts/*"

- name: Install copier
run: pip install copier

Expand Down
22 changes: 22 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,22 @@ test-template-render-github:
-d copier__ci_provider="github" \
-d copier__enable_semantic_release=false \
-d copier__github_semantic_release_auth="github_token" \
-d copier__create_repo=false \
-d copier__enable_secret_scanning=true \
-d copier__task_runner="task"; \
test -f "$$out_dir/copier.yml"; \
test -f "$$out_dir/Taskfile.yml"; \
test ! -f "$$out_dir/Makefile"; \
test ! -f "$$out_dir/justfile"; \
test -f "$$out_dir/README.md"; \
test -f "$$out_dir/LICENSE"; \
test -f "$$out_dir/.github/workflows/template-correctness.yaml"; \
test ! -f "$$out_dir/.github/workflows/semantic-release.yaml"; \
test ! -f "$$out_dir/.github/workflows/semantic-pull-request.yaml"; \
test -f "$$out_dir/.copier-answers.yml"; \
test -f "$$out_dir/scripts/test-template-render.sh"; \
test -f "$$out_dir/template/README.md"; \
test -f "$$out_dir/template/LICENSE"; \
test -f "$$out_dir/template/.scaf/post-copy.py"; \
test -f "$$out_dir/template/Taskfile.yml"; \
test ! -f "$$out_dir/template/Makefile"; \
Expand All @@ -41,6 +50,7 @@ test-template-render-github:
grep -Fq '{{ copier__project_name }}' "$$out_dir/template/README.md"; \
grep -Fq 'copier copy . /path/to/new-project --trust' "$$out_dir/README.md"; \
grep -Eq '^copier__project_name_raw:' "$$out_dir/copier.yml"; \
(cd "$$out_dir" && bash ./scripts/test-template-render.sh); \
render_dir="$$(mktemp -d /tmp/scaf-template-rendered-gh-XXXXXX)"; \
copier copy "$$out_dir" "$$render_dir" --trust --defaults \
-d copier__configure_repo=false \
Expand All @@ -49,6 +59,7 @@ test-template-render-github:
-d copier__ci_provider="github" \
-d copier__task_runner="task"; \
test -f "$$render_dir/.copier-answers.yml"; \
test -f "$$render_dir/LICENSE"; \
test ! -f "$$render_dir/{{_copier_conf.answers_file}}"; \
rm -rf "$$render_dir"; \
rm -rf "$$out_dir"
Expand All @@ -66,13 +77,22 @@ test-template-render-gitlab:
-d copier__version="0.1.0" \
-d copier__ci_provider="gitlab" \
-d copier__enable_semantic_release=false \
-d copier__create_repo=false \
-d copier__enable_secret_scanning=true \
-d copier__task_runner="just"; \
test -f "$$out_dir/copier.yml"; \
test -f "$$out_dir/justfile"; \
test ! -f "$$out_dir/Makefile"; \
test ! -f "$$out_dir/Taskfile.yml"; \
test -f "$$out_dir/README.md"; \
test -f "$$out_dir/LICENSE"; \
test -f "$$out_dir/.github/workflows/template-correctness.yaml"; \
test ! -f "$$out_dir/.github/workflows/semantic-release.yaml"; \
test ! -f "$$out_dir/.github/workflows/semantic-pull-request.yaml"; \
test -f "$$out_dir/.copier-answers.yml"; \
test -f "$$out_dir/scripts/test-template-render.sh"; \
test -f "$$out_dir/template/README.md"; \
test -f "$$out_dir/template/LICENSE"; \
test -f "$$out_dir/template/.scaf/post-copy.py"; \
test -f "$$out_dir/template/justfile"; \
test ! -f "$$out_dir/template/Makefile"; \
Expand All @@ -86,6 +106,7 @@ test-template-render-gitlab:
grep -Fq '{{ copier__project_name }}' "$$out_dir/template/README.md"; \
grep -Fq 'copier copy . /path/to/new-project --trust' "$$out_dir/README.md"; \
grep -Eq '^copier__project_name_raw:' "$$out_dir/copier.yml"; \
(cd "$$out_dir" && bash ./scripts/test-template-render.sh); \
render_dir="$$(mktemp -d /tmp/scaf-template-rendered-gl-XXXXXX)"; \
copier copy "$$out_dir" "$$render_dir" --trust --defaults \
-d copier__configure_repo=false \
Expand All @@ -94,6 +115,7 @@ test-template-render-gitlab:
-d copier__ci_provider="gitlab" \
-d copier__task_runner="just"; \
test -f "$$render_dir/.copier-answers.yml"; \
test -f "$$render_dir/LICENSE"; \
test ! -f "$$render_dir/{{_copier_conf.answers_file}}"; \
rm -rf "$$render_dir"; \
rm -rf "$$out_dir"
29 changes: 7 additions & 22 deletions template/.github/workflows/template-correctness.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,13 @@ jobs:
with:
python-version: '3.11'

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "lts/*"

- name: Install copier
run: pip install copier

- name: Render sample project
run: |
copier copy . /tmp/sample-project --trust --defaults \
-d copier__project_name_raw="Sample Project" \
-d copier__project_slug="sample_project" \
-d copier__description="Sample generated project" \
-d copier__author_name="Sample Author" \
-d copier__email="sample@example.com" \
-d copier__version="0.1.0" \
-d copier__configure_repo=false \
-d copier__ci_provider="github" \
-d copier__enable_semantic_release=false \
-d copier__enable_secret_scanning=false \
-d copier__task_runner="task"

- name: Validate generated project files
run: |
test -f /tmp/sample-project/README.md
test -f /tmp/sample-project/.copier-answers.yml
test -f /tmp/sample-project/Taskfile.yml
test ! -f /tmp/sample-project/Makefile
test ! -f /tmp/sample-project/justfile
- name: Run render tests
run: bash ./scripts/test-template-render.sh
1 change: 1 addition & 0 deletions template/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
12 changes: 12 additions & 0 deletions template/.pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: check-case-conflict
- id: check-merge-conflict
- id: detect-private-key
- id: end-of-file-fixer
- id: trailing-whitespace
- id: check-yaml
args: ['--unsafe']
exclude: '^template/'
14 changes: 14 additions & 0 deletions template/.scaf/semantic-assets/.releaserc.json.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"branches": ["main"],
"plugins": [
["@semantic-release/commit-analyzer", { "preset": "conventionalcommits" }],
["@semantic-release/release-notes-generator", { "preset": "conventionalcommits" }],
["@semantic-release/changelog", { "changelogFile": "CHANGELOG.md", "changelogTitle": "# Changelog" }],
{% if copier__ci_provider == "github" %}
"@semantic-release/github",
{% else %}
"@semantic-release/gitlab",
{% endif %}
["@semantic-release/git", { "assets": ["package.json", "CHANGELOG.md"], "message": "chore(release): ${nextRelease.version} [skip ci]\\n\\n${nextRelease.notes}" }]
]
}
1 change: 1 addition & 0 deletions template/.scaf/semantic-assets/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Changelog
11 changes: 11 additions & 0 deletions template/.scaf/semantic-assets/dependencies-dev-init.txt.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
semantic-release
@semantic-release/commit-analyzer
@semantic-release/release-notes-generator
@semantic-release/changelog
@semantic-release/git
conventional-changelog-conventionalcommits
{% if copier__ci_provider == "github" %}
@semantic-release/github
{% else %}
@semantic-release/gitlab
{% endif %}
Empty file.
5 changes: 5 additions & 0 deletions template/.scaf/semantic-assets/package.json.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "{{ copier__project_dash }}",
"version": "{{ copier__version }}",
"private": true
}
92 changes: 82 additions & 10 deletions template/.scaf/starter-post-copy.py.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,9 @@ def install_semantic_release_deps(init_file: pathlib.Path, dev_file: pathlib.Pat

if shutil.which("npm"):
if init_packages:
run(["npm", "install", *init_packages], cwd=TEMPLATE_ROOT)
run(["npm", "install", *init_packages], cwd=PROJECT_ROOT)
if dev_packages:
run(["npm", "install", "--save-dev", *dev_packages], cwd=TEMPLATE_ROOT)
run(["npm", "install", "--save-dev", *dev_packages], cwd=PROJECT_ROOT)
return

if shutil.which("docker"):
Expand All @@ -175,7 +175,7 @@ def install_semantic_release_deps(init_file: pathlib.Path, dev_file: pathlib.Pat
"-w",
"/app",
"-v",
f"{TEMPLATE_ROOT}:/app",
f"{PROJECT_ROOT}:/app",
"-e",
"npm_config_cache=/tmp/.npm",
"node:lts",
Expand All @@ -186,27 +186,27 @@ def install_semantic_release_deps(init_file: pathlib.Path, dev_file: pathlib.Pat
f"if [ -s {dev_file.name!r} ]; then xargs npm install --save-dev < {dev_file.name!r}; fi"
),
],
cwd=TEMPLATE_ROOT,
cwd=PROJECT_ROOT,
)
return

print(WARNING + "docker and npm not found; skipping semantic-release dependency install" + TERMINATOR)


def run_template_init() -> None:
if shutil.which("pre-commit") and try_run(["git", "rev-parse", "--is-inside-work-tree"], cwd=TEMPLATE_ROOT):
if shutil.which("pre-commit") and try_run(["git", "rev-parse", "--is-inside-work-tree"], cwd=PROJECT_ROOT):
try:
run(["pre-commit", "install"], cwd=TEMPLATE_ROOT)
run(["pre-commit", "install"], cwd=PROJECT_ROOT)
except subprocess.CalledProcessError:
print("pre-commit install failed; continuing")

if SEMANTIC_RELEASE:
init_deps = TEMPLATE_ROOT / "dependencies-init.txt"
dev_deps = TEMPLATE_ROOT / "dependencies-dev-init.txt"
init_deps = PROJECT_ROOT / "dependencies-init.txt"
dev_deps = PROJECT_ROOT / "dependencies-dev-init.txt"

if has_unrendered_jinja(init_deps) or has_unrendered_jinja(dev_deps):
with tempfile.NamedTemporaryFile(
mode="w", dir=TEMPLATE_ROOT, prefix=".scaf-deps-init.", delete=False
mode="w", dir=PROJECT_ROOT, prefix=".scaf-deps-init.", delete=False
) as init_tmp:
for line in read_packages(init_deps):
if ("{" + "{") in line or ("{" + "%") in line:
Expand All @@ -215,7 +215,7 @@ def run_template_init() -> None:
init_tmp_path = pathlib.Path(init_tmp.name)

with tempfile.NamedTemporaryFile(
mode="w", dir=TEMPLATE_ROOT, prefix=".scaf-deps-dev.", delete=False
mode="w", dir=PROJECT_ROOT, prefix=".scaf-deps-dev.", delete=False
) as dev_tmp:
for line in read_packages(dev_deps):
if ("{" + "{") in line or ("{" + "%") in line:
Expand All @@ -238,7 +238,73 @@ def run_template_init() -> None:
print("Local development setup complete.")


def render_root_task_runner() -> None:
runner_templates = PROJECT_ROOT / ".scaf" / "task-runners"
by_runner = {
"make": "Makefile",
"task": "Taskfile.yml",
"just": "justfile",
}
selected = by_runner.get(TASK_RUNNER)

for runner_file in by_runner.values():
target = PROJECT_ROOT / runner_file
source = runner_templates / runner_file
if runner_file == selected and source.exists():
shutil.copyfile(source, target)
else:
remove(target)

remove(runner_templates)


def render_root_semantic_workflows() -> None:
workflow_templates = PROJECT_ROOT / ".scaf" / "workflows"
root_workflows = PROJECT_ROOT / ".github" / "workflows"
root_workflows.mkdir(parents=True, exist_ok=True)

workflow_files = [
"semantic-release.yaml",
"semantic-pull-request.yaml",
]

for workflow_file in workflow_files:
source = workflow_templates / workflow_file
target = root_workflows / workflow_file

if CI_PROVIDER == "github" and SEMANTIC_RELEASE and source.exists():
shutil.copyfile(source, target)
else:
remove(target)

remove(workflow_templates)


def render_root_semantic_assets() -> None:
semantic_assets = PROJECT_ROOT / ".scaf" / "semantic-assets"
asset_files = [
"package.json",
"dependencies-init.txt",
"dependencies-dev-init.txt",
".releaserc.json",
"CHANGELOG.md",
]

for asset_file in asset_files:
source = semantic_assets / asset_file
target = PROJECT_ROOT / asset_file
if SEMANTIC_RELEASE and source.exists():
shutil.copyfile(source, target)
else:
remove(target)

remove(semantic_assets)


def prune_starter_template() -> None:
render_root_task_runner()
render_root_semantic_workflows()

if TASK_RUNNER != "make":
remove(TEMPLATE_ROOT / "Makefile")
if TASK_RUNNER != "task":
Expand All @@ -252,6 +318,11 @@ def prune_starter_template() -> None:
remove(TEMPLATE_ROOT / ".gitlab-ci.yml")

if not SEMANTIC_RELEASE:
remove(PROJECT_ROOT / "package.json")
remove(PROJECT_ROOT / "dependencies-init.txt")
remove(PROJECT_ROOT / "dependencies-dev-init.txt")
remove(PROJECT_ROOT / ".releaserc.json")
remove(PROJECT_ROOT / "CHANGELOG.md")
remove(TEMPLATE_ROOT / "package.json")
remove(TEMPLATE_ROOT / "dependencies-init.txt")
remove(TEMPLATE_ROOT / "dependencies-dev-init.txt")
Expand Down Expand Up @@ -303,6 +374,7 @@ def main() -> None:
initialized_repo = init_git_repo()
maybe_create_repo()
configure_git_remote()
render_root_semantic_assets()
run_template_init()
prune_starter_template()
remove(PROJECT_ROOT / ".scaf/starter-post-copy.py")
Expand Down
6 changes: 6 additions & 0 deletions template/.scaf/task-runners/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
SHELL := bash

.PHONY: test-template-render

test-template-render:
bash ./scripts/test-template-render.sh
7 changes: 7 additions & 0 deletions template/.scaf/task-runners/Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
version: "3"

tasks:
test-template-render:
desc: Validate template rendering output
cmds:
- bash ./scripts/test-template-render.sh
2 changes: 2 additions & 0 deletions template/.scaf/task-runners/justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
test-template-render:
bash ./scripts/test-template-render.sh
17 changes: 17 additions & 0 deletions template/.scaf/workflows/semantic-pull-request.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: Lint PR

on:
pull_request_target:
types: [opened, edited, synchronize, reopened]

permissions:
pull-requests: read

jobs:
main:
name: Validate PR title
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Loading
Loading