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
31 changes: 10 additions & 21 deletions .github/workflows/adapters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: read
# one full generation per test; new-nextjs/new-nestjs/new-laravel-api.bats
# run 5-6 tests each — bounded well under the ~25 minutes tier b costs.
# one full generation per test, 5-6 tests per adapter
timeout-minutes: 15
strategy:
# a broken adapter must not hide the state of the others
Expand Down Expand Up @@ -145,10 +144,7 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: read
# same per-adapter cost as deploy (generation + image build + container
# start) — tier b's timeout is longer than tier a's elsewhere in this
# file because those jobs run more tests per adapter, not because this
# one does.
# same per-adapter cost as deploy: generation, image build, container start
timeout-minutes: 30
strategy:
fail-fast: false
Expand All @@ -175,11 +171,9 @@ jobs:
run: ./scripts/deploy-check.sh "$ADAPTER"

deploy-multi-app:
# The shape every other deploy job leaves untested: deploy runs one
# adapter at a time, so nothing proves a project with two applications
# actually comes up on two images (ADR-0022). Weekly rather than per
# pull request, like compose below — two generations plus two image
# builds.
# deploy runs one adapter at a time, so nothing else proves a project with
# two applications comes up on two images (ADR-0022). Weekly, like compose
# below: two generations plus two image builds.
if: ${{ github.event.schedule == '23 2 * * 1' || github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-latest
permissions:
Expand Down Expand Up @@ -212,10 +206,8 @@ jobs:
- run: bats tests/compose.bats

services:
# Four databases and two caches across two families is eight combinations
# per adapter, and one tier a generation costs minutes — so the full grid
# is nightly, and every pull request gets the default cell through
# tests/new-*.bats instead.
# Eight combinations per adapter at minutes each, so the full grid is
# nightly; a pull request gets the default cell through tests/new-*.bats.
if: ${{ github.event.schedule == '17 3 * * *' || github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-latest
permissions:
Expand Down Expand Up @@ -247,12 +239,9 @@ jobs:
ADAPTER: ${{ matrix.adapter }}
DB: ${{ matrix.db }}
CACHE: ${{ matrix.cache }}
# resolve_github_owner (lib/project.sh) substitutes this for the
# generated workflows' placeholder `you/` account, and falls back to
# `gh auth login` or git's github.user before giving up — a runner
# has none of the three. scripts/deploy-check.sh and
# tests/helpers/setup.bash each set their own; this job calls
# `scaffold new` directly and so needs its own too.
# resolve_github_owner falls back to `gh auth login` or git's
# github.user before giving up, and a runner has neither. Every other
# caller sets its own; this job calls `scaffold new` directly.
SCAFFOLD_GITHUB_OWNER: ${{ github.repository_owner }}
run: |
work="$(mktemp -d)"
Expand Down
22 changes: 9 additions & 13 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
name: Pull request
# Its own workflow, not a job in ci.yml, because of `edited`: a body that fails
# this check is fixed by editing the body, and ci.yml does not run on that —
# so the check stayed red for a fix that had already been made. Putting
# `edited` in ci.yml instead would re-run the integration lane, twenty
# minutes, every time somebody touched a description.
# Its own workflow, not a job in ci.yml, because of `edited`: this check is
# fixed by editing the body, which ci.yml does not run on — so it stayed red for
# a fix already made. Adding `edited` to ci.yml instead would re-run the
# twenty-minute integration lane on every description tweak.
on:
pull_request:
types: [opened, edited, synchronize, reopened]
Expand All @@ -20,14 +19,11 @@ jobs:
# them, so editing the template changes what is enforced — the convention and
# its enforcement cannot drift apart. Borrowed from immich's auto-close.yml.
#
# The job's name is load-bearing: `main`'s branch protection requires a
# check called `pull-request-body`, and a required check that never reports
# blocks every merge forever. Moving the job to this workflow was safe;
# renaming it to `body` at the same time was not, and blocked the pull
# request that made the change. Renaming it means updating the repository
# setting first — there is no trace of that setting in this repository to
# remind anyone, which is ADR-0004's point about the guardrail that is not
# a file.
# The job's name is load-bearing: `main`'s branch protection requires a check
# called `pull-request-body`, and a required check that never reports blocks
# every merge forever. Rename it here and the repository setting has to change
# first — nothing in this repository records that setting, which is ADR-0004's
# point about the guardrail that is not a file.
pull-request-body:
runs-on: ubuntu-latest
permissions:
Expand Down
15 changes: 9 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
.DS_Store
*.swp

# Neither belongs to the toolbox: a node_modules here is a stray pnpm run,
# and tmp/ is where a probe or a hand-run script lands. Both have been
# committed by a wide `git add -A` before — tmp/ carried a whole generated
# Nest project for a week.
# A node_modules anywhere here is a stray pnpm run, and tmp/ is where a probe
# or a hand-run script lands. Both have been committed by a wide `git add -A`
# before — tmp/ carried a whole generated Nest project for a week.
node_modules/
tmp/
package.json
pnpm-lock.yaml

# Rooted, not bare: four files under common/ carry these names and ship into
# every generated project. Unanchored, the rule matched those too, so a new
# template beside them would be dropped by `git add -A` without a word.
/package.json
/pnpm-lock.yaml
33 changes: 12 additions & 21 deletions adapters/laravel-inertia/mise.toml
Original file line number Diff line number Diff line change
@@ -1,37 +1,28 @@
# composer is pinned the same way as laravel-api's (see the scaffold
# toolbox's ADR-0016 for why php itself is not — not shipped here); a
# fullstack laravel app still needs node, because vite builds the assets —
# the honest consequence of picking laravel to get one app instead of two,
# and someone choosing it to avoid node should learn that here, not in ci.
# php itself is not pinned (ADR-0016). node is here because vite builds the
# assets: picking laravel to get one app instead of two does not avoid node,
# and that is better learned here than in CI.
[tools]
"ubi:composer/composer" = "2.10.2"
node = "24.15.0"

[tasks.install]
# mise installs composer as composer.phar, not composer
# `npm run build` belongs here, not in check or test, and it is not optional.
# Two artifacts it produces are gitignored and generated by nothing else:
# resources/js/{actions,routes}, written by the vite plugin's
# `php artisan wayfinder:generate --with-form`, which types:check compiles
# against; and public/build/manifest.json, without which every test that
# renders an inertia page 500s. Neither is visible locally, because the
# pre-push checklist runs build and leaves both on disk — so the app passed
# every local run and could never pass CI, which checks out clean.
#
# This is the order the starter kit's own CI uses: `composer setup` ends with
# `npm run build`, and only then does `composer ci:check` run types:check and
# the tests.
# `npm run build` belongs here, not in check or test. It produces two
# gitignored artifacts nothing else generates: resources/js/{actions,routes},
# which types:check compiles against, and public/build/manifest.json, without
# which every test rendering an inertia page 500s. Both survive locally from
# the last run, so the app passes every local run and never passes a clean CI
# checkout. Same order the starter kit's own CI uses.
run = [
"php -r 'exit(version_compare(PHP_VERSION, \"8.3.0\", \">=\") ? 0 : 1);' || { echo \"laravel-inertia requires system php >= 8.3.0 (found $(php -r 'echo PHP_VERSION;' 2>/dev/null || echo 'no php on PATH')); install php 8.3 or newer via your OS package manager (e.g. apt install php8.3-cli) or https://php.net, then re-run mise install\" >&2; exit 1; }",
"composer.phar install --no-interaction --prefer-dist",
"npm ci",
"npm run build",
]

# the starter kit's frontend toolchain is vite-plus, whose `vp check` covers
# formatting and linting in one command — there is no separate format:check or
# lint:check script to call. Scoped to resources/ because an unscoped run also
# formats this file, and its idea of toml style is not this toolbox's.
# vite-plus's `vp check` covers formatting and linting in one command; there is
# no separate format:check to call. Scoped to resources/ because an unscoped run
# also reformats this file.
[tasks.format]
run = ["./vendor/bin/pint --test", "npm run check -- resources"]

Expand Down
15 changes: 6 additions & 9 deletions common/.github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,15 @@ concurrency:
permissions: {}
jobs:
build:
# a called workflow cannot request more than its caller was granted, and
# the workflow-level `permissions: {}` above grants nothing — so this is
# the union of what app-build's own jobs ask for, and no more.
# no more than app-build's own jobs ask for; the workflow-level
# `permissions: {}` above grants nothing to inherit.
permissions:
contents: read
packages: write
# `@v1` is a moving tag on purpose (ADR-0005): one fix reaches every
# project at once. It is also a write channel into this repository's CI
# that outlives the engagement — ADR-0005 states that cost in full.
# `@v1` moves on purpose: one fix reaches every project, and that same
# channel outlives the engagement (ADR-0005).
uses: you/.github/.github/workflows/app-build.yml@v1 # zizmor: ignore[unpinned-uses,ref-confusion]
with:
# One entry per application, written by scaffold as each one is
# generated (ADR-0022): {image, context, dockerfile}. A project with no
# application publishes nothing, and this job does not run.
# {image, context, dockerfile} per application, written by scaffold as
# each is generated (ADR-0022). Empty means nothing to publish.
images: "[]"
10 changes: 4 additions & 6 deletions common/.github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,13 @@ concurrency:
permissions: {}
jobs:
ci:
# a called workflow cannot request more than its caller was granted, and
# the workflow-level `permissions: {}` above grants nothing — so this is
# the union of what app-ci's own jobs ask for, and no more.
# no more than app-ci's own jobs ask for; the workflow-level
# `permissions: {}` above grants nothing to inherit.
permissions:
contents: read
pull-requests: read
# `@v1` is a moving tag on purpose (ADR-0005): one fix reaches every
# project at once. It is also a write channel into this repository's CI
# that outlives the engagement — ADR-0005 states that cost in full.
# `@v1` moves on purpose: one fix reaches every project, and that same
# channel outlives the engagement (ADR-0005).
uses: you/.github/.github/workflows/app-ci.yml@v1 # zizmor: ignore[unpinned-uses,ref-confusion]
with:
roots: '["docs"]'
10 changes: 4 additions & 6 deletions common/.github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ on:
permissions: {}
jobs:
docs:
# a called workflow cannot request more than its caller was granted, and
# the workflow-level `permissions: {}` above grants nothing — so this is
# the union of what app-docs's own jobs ask for, and no more.
# no more than app-docs's own jobs ask for; the workflow-level
# `permissions: {}` above grants nothing to inherit.
permissions:
contents: read
# `@v1` is a moving tag on purpose (ADR-0005): one fix reaches every
# project at once. It is also a write channel into this repository's CI
# that outlives the engagement — ADR-0005 states that cost in full.
# `@v1` moves on purpose: one fix reaches every project, and that same
# channel outlives the engagement (ADR-0005).
uses: you/.github/.github/workflows/app-docs.yml@v1 # zizmor: ignore[unpinned-uses,ref-confusion]
26 changes: 10 additions & 16 deletions common/.github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,24 @@ concurrency:
permissions: {}
jobs:
release:
# a called workflow cannot request more than its caller was granted, and
# the workflow-level `permissions: {}` above grants nothing — so this is
# the union of what app-release's own jobs ask for, and no more.
# no more than app-release's own jobs ask for; the workflow-level
# `permissions: {}` above grants nothing to inherit.
permissions:
contents: write
issues: write
packages: write
pull-requests: write
# `@v1` is a moving tag on purpose (ADR-0005): one fix reaches every
# project at once. It is also a write channel into this repository's CI
# that outlives the engagement — ADR-0005 states that cost in full.
# `@v1` moves on purpose: one fix reaches every project, and that same
# channel outlives the engagement (ADR-0005).
uses: you/.github/.github/workflows/app-release.yml@v1 # zizmor: ignore[unpinned-uses,ref-confusion]
# release please opens its pull request as a github app when the repository
# has RELEASE_APP_ID and RELEASE_APP_PRIVATE_KEY, so the checks on that pull
# request run instead of waiting on a manual approval that expires red.
# Without those secrets it falls back to GITHUB_TOKEN and still releases.
# Named rather than `inherit`: a called workflow that inherits everything
# gets secrets it has no business reading, and the two it does need are
# already declared optional on the other side.
# With these, release please opens its pull request as a GitHub app and the
# checks on it run; without them it falls back to GITHUB_TOKEN, whose checks
# sit at "Action required" and expire red. Named rather than `inherit`, so
# the called workflow reads only the two it declared.
secrets:
RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }}
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
with:
# One entry per application, written by scaffold as each one is
# generated (ADR-0022): {image, context, dockerfile}. A project with no
# application publishes nothing, and this job does not run.
# {image, context, dockerfile} per application, written by scaffold as
# each is generated (ADR-0022). Empty means nothing to publish.
images: "[]"
10 changes: 4 additions & 6 deletions common/.github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,13 @@ on:
permissions: {}
jobs:
security:
# a called workflow cannot request more than its caller was granted, and
# the workflow-level `permissions: {}` above grants nothing — so this is
# the union of what app-security's own jobs ask for, and no more.
# no more than app-security's own jobs ask for; the workflow-level
# `permissions: {}` above grants nothing to inherit.
permissions:
contents: read
security-events: write
pull-requests: read
actions: read
# `@v1` is a moving tag on purpose (ADR-0005): one fix reaches every
# project at once. It is also a write channel into this repository's CI
# that outlives the engagement — ADR-0005 states that cost in full.
# `@v1` moves on purpose: one fix reaches every project, and that same
# channel outlives the engagement (ADR-0005).
uses: you/.github/.github/workflows/app-security.yml@v1 # zizmor: ignore[unpinned-uses,ref-confusion]
8 changes: 3 additions & 5 deletions common/compose.dev.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# local development only: throwaway services an app started outside docker
# (mise run dev, etc.) can point at on localhost. never shipped as a release
# asset — see compose.yaml for the stack a client actually runs. the services
# themselves are merged in by scaffold from the selection made at generation
# time.
# Local development only: throwaway services an app started outside docker can
# point at on localhost. Never shipped as a release asset — compose.yaml is the
# stack a client runs. scaffold merges the selected services in.
name: app-dev

services: {}
6 changes: 3 additions & 3 deletions docs/PROVENANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ Excluded, and why:
file-by-file origin and licence — which is what a reader who finds the
directory will actually look at. `docs/public/logo.png` comes from the same
repository and the same `NOTICE` covers it.
- **`docs/decisions/`, `docs/superpowers/`** (this repository's own, at the
root — not `common/docs/decisions/`, which ships and is covered below).
These are this project's planning record, not files copied from upstream.
- **`docs/decisions/`, `docs/superpowers/specs/`** (this repository's own, at
the root — not `common/docs/decisions/`, which ships and is covered below).
These are this project's own record, not files copied from upstream.
- **`lib/*.sh`, `scaffold`, `scripts/*.sh`, `tests/*.bats`, `mise.toml`,
`mise.lock`, `lefthook.yml`, `.github/workflows/*.yml`** (this repository's
own CI and hooks, not `common/.github/workflows/` or `common/lefthook.yml`,
Expand Down
Loading