From 4959a050adc9dd2756abd57497a1f18c804f590d Mon Sep 17 00:00:00 2001 From: iam-truongtrungnghia Date: Thu, 17 Sep 2026 03:02:17 +0700 Subject: [PATCH 1/4] fix(pnpm): fail when the app directory is missing A failed `cd "$dir"` inside pnpm_install's subshell ran on the left of `||`, where errexit is off, so the install went on to run in the caller's directory and reported success. Guard the cd and add a regression test that stubs pnpm and points pnpm_install at a nonexistent directory. --- lib/pnpm.sh | 2 +- mise.toml | 2 +- tests/pnpm.bats | 29 +++++++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 tests/pnpm.bats diff --git a/lib/pnpm.sh b/lib/pnpm.sh index bf5193e..9c3c996 100644 --- a/lib/pnpm.sh +++ b/lib/pnpm.sh @@ -64,7 +64,7 @@ pnpm_install() { log="$(mktemp)" ( - cd "$dir" + cd "$dir" || exit 1 # This install exists to rewrite the lockfile a generator just produced. mise exec -- pnpm install \ --no-frozen-lockfile \ diff --git a/mise.toml b/mise.toml index 7512142..19967e4 100644 --- a/mise.toml +++ b/mise.toml @@ -27,7 +27,7 @@ run = "bats --jobs $(nproc) --parallel-binary-name rush tests/" description = "the suites with no adapter generator anywhere in setup — genuinely offline" # Listed, not globbed: new-project.bats generates no adapter; provenance.bats # needs an upstream clone (runs in provenance.yml). -run = "bats --jobs $(nproc) --parallel-binary-name rush tests/add-app-errors.bats tests/cli.bats tests/contract.bats tests/documentation.bats tests/install.bats tests/new-project.bats tests/publish.bats tests/service.bats tests/update.bats tests/wizard.bats" +run = "bats --jobs $(nproc) --parallel-binary-name rush tests/add-app-errors.bats tests/cli.bats tests/contract.bats tests/documentation.bats tests/install.bats tests/new-project.bats tests/pnpm.bats tests/publish.bats tests/service.bats tests/update.bats tests/wizard.bats" [tasks."test-integration"] description = "suites that generate a real adapter project as a fixture (not a per-adapter smoke test — see tests/new-*.bats for those)" diff --git a/tests/pnpm.bats b/tests/pnpm.bats new file mode 100644 index 0000000..bf4ddf4 --- /dev/null +++ b/tests/pnpm.bats @@ -0,0 +1,29 @@ +#!/usr/bin/env bats + +setup() { + load 'helpers/setup' + source "${SCAFFOLD_ROOT}/lib/log.sh" + source "${SCAFFOLD_ROOT}/lib/pnpm.sh" + WORKDIR="$(mktemp -d)" +} + +teardown() { + remove_workdir "$WORKDIR" +} + +@test "pnpm_install fails and never invokes pnpm when the target directory is missing" { + local stub_dir="${WORKDIR}/stub" + local ran_marker="${WORKDIR}/pnpm-ran" + mkdir -p "$stub_dir" + cat >"${stub_dir}/pnpm" < Date: Thu, 17 Sep 2026 03:02:35 +0700 Subject: [PATCH 2/4] refactor(service): drop a dead flag apply_service_dockerfile's `found` flag was never read for anything that changed behaviour: `((found == 1)) || return 0` returned 0 either way. --- lib/service.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/service.sh b/lib/service.sh index 91f48df..6c905f9 100644 --- a/lib/service.sh +++ b/lib/service.sh @@ -292,17 +292,14 @@ replace_env_line() { apply_service_dockerfile() { local -r app="$1" local block="$2" - local file found=0 + local file for file in "${app}/Dockerfile" "${app}/Dockerfile.workspace"; do [[ -f "$file" ]] || continue - found=1 grep -q "^${SERVICE_SETUP_ANCHOR}\$" "$file" || die "no @SERVICE_SETUP@ anchor in ${file}" splice_service_setup "$file" "$block" done - - ((found == 1)) || return 0 } # ENVIRON, not -v: -v would consume a backslash in the block as an escape. From 3c97b6c8b83ec9b2595d92b94407f5f82abf4560 Mon Sep 17 00:00:00 2001 From: iam-truongtrungnghia Date: Thu, 17 Sep 2026 03:03:05 +0700 Subject: [PATCH 3/4] docs: fix a stale pointer and the lint gate's description add-an-adapter.md pointed at a comment above `reconcile=` in `scaffold`, which no longer exists; the blind line-delete it describes is restore_pnpm_workspace in lib/pnpm.sh now. CONTRIBUTING.md still described the lint gate as shellcheck alone over a handful of directories; it runs shellcheck + shfmt over every tracked shell file. --- CONTRIBUTING.md | 4 ++-- docs/runbook/add-an-adapter.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4a5ec67..b54b4ce 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,7 +5,7 @@ ```sh mise install # every tool this repository uses, pinned in mise.toml mise exec -- lefthook install # shellcheck, gitleaks, commit-message check -mise run lint # shellcheck over scaffold, lib/, scripts/, common/ +mise run lint # shellcheck + shfmt over every tracked shell file mise run test-unit # the offline suites ``` @@ -22,7 +22,7 @@ anything else, so it needs no wrapper — from the clone, a symlink, or `PATH`. | Task | What it runs | | --- | --- | -| `lint` | shellcheck over every shell file the repository tracks | +| `lint` | shellcheck + shfmt (`-i 2 -ci`) over every shell file the repository tracks | | `test-unit` | the suites that never invoke an adapter's generator — offline and quick | | `test-integration` | the suites that generate a real project as a fixture | | `test` | every suite, including the per-adapter smoke tests | diff --git a/docs/runbook/add-an-adapter.md b/docs/runbook/add-an-adapter.md index 89b191a..a844a0a 100644 --- a/docs/runbook/add-an-adapter.md +++ b/docs/runbook/add-an-adapter.md @@ -77,7 +77,7 @@ still carries a known, open gap. `cmd_add` relaxes `confirmModulesPurge` in `pnpm-workspace.yaml` then strips the line back out by blind text match; if the caller had already added that exact line themselves, on purpose, this silently deletes it -too — known, not fixed, see the comment above `reconcile=` in `scaffold` +too — known, not fixed, see `restore_pnpm_workspace` in `lib/pnpm.sh` and ADR-0017's Consequences): ```bash From 5f9bf0f1d4e061e39b476f134b5c3e2b954f8a2b Mon Sep 17 00:00:00 2001 From: iam-truongtrungnghia Date: Thu, 17 Sep 2026 03:03:31 +0700 Subject: [PATCH 4/4] refactor: rewrite comments that fail the comment gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - services/shared/nest.sh: fix a sentence that no longer parses ("unset the first `pnpm add` below" -> "otherwise the first `pnpm add` below"). - adapters/laravel-{api,inertia}/.env.example: LOG_CHANNEL's comment had the reason backwards — it exists because storage/logs is unreadable in a container, not because the probe logs there. - common/docs/mise.toml, common/mise.root.toml, common/pnpm-workspace.yaml: qualify bare "(ADR-00NN)" references as "(toolbox ADR-00NN)" — a generated project's own docs/decisions/ ships only 0000. - tests/new-laravel-api.bats, tests/new-project.bats, tests/service.bats, tests/wizard.bats, services/shared/nest.sh, services/mongodb/drivers/laravel.sh: rewrite history-shaped comments ("used to", "measured", "before this pin") in present tense as what they guard against. - services/{mongodb,mysql,postgres}/drivers/*.sh and tests/service.bats: give every `shellcheck source=/dev/null` a fixed source= path where the sourced file is constant, or a same-line reason where it varies. --- adapters/laravel-api/.env.example | 3 ++- adapters/laravel-inertia/.env.example | 3 ++- common/docs/mise.toml | 2 +- common/mise.root.toml | 2 +- common/pnpm-workspace.yaml | 2 +- services/mongodb/drivers/flask.sh | 2 +- services/mongodb/drivers/laravel.sh | 5 +++-- services/mongodb/drivers/nest.sh | 2 +- services/mysql/drivers/flask.sh | 2 +- services/mysql/drivers/laravel.sh | 2 +- services/mysql/drivers/nest.sh | 2 +- services/postgres/drivers/flask.sh | 2 +- services/postgres/drivers/laravel.sh | 2 +- services/postgres/drivers/nest.sh | 2 +- services/shared/nest.sh | 10 +++++----- tests/new-laravel-api.bats | 24 ++++++++++-------------- tests/new-project.bats | 20 ++++++++------------ tests/service.bats | 18 ++++++++---------- tests/wizard.bats | 21 ++++++++++----------- 19 files changed, 59 insertions(+), 67 deletions(-) diff --git a/adapters/laravel-api/.env.example b/adapters/laravel-api/.env.example index 2b4c9ef..a3af76d 100644 --- a/adapters/laravel-api/.env.example +++ b/adapters/laravel-api/.env.example @@ -4,5 +4,6 @@ APP_KEY= APP_DEBUG=false APP_URL=http://localhost:8000 # the database variables are written by the selected service's driver -# storage/logs is unreadable in a container, and the readiness probe logs there +# stderr, because nothing reads storage/logs in a container, where the +# readiness probe's failures would vanish LOG_CHANNEL=stderr diff --git a/adapters/laravel-inertia/.env.example b/adapters/laravel-inertia/.env.example index c95f3d7..994f294 100644 --- a/adapters/laravel-inertia/.env.example +++ b/adapters/laravel-inertia/.env.example @@ -5,5 +5,6 @@ APP_DEBUG=false APP_URL=http://localhost:8000 # the database variables are written by the selected service's driver VITE_APP_NAME="${APP_NAME}" -# storage/logs is unreadable in a container, and the readiness probe logs there +# stderr, because nothing reads storage/logs in a container, where the +# readiness probe's failures would vanish LOG_CHANNEL=stderr diff --git a/common/docs/mise.toml b/common/docs/mise.toml index 80571c7..0453dd4 100644 --- a/common/docs/mise.toml +++ b/common/docs/mise.toml @@ -1,5 +1,5 @@ [tasks.install] -# A frozen install that relinks node_modules must not stop to ask (ADR-0017). +# A frozen install that relinks node_modules must not stop to ask (toolbox ADR-0017). env = { npm_config_confirm_modules_purge = "false" } run = "pnpm install --frozen-lockfile" diff --git a/common/mise.root.toml b/common/mise.root.toml index f302810..a58ae20 100644 --- a/common/mise.root.toml +++ b/common/mise.root.toml @@ -1,7 +1,7 @@ monorepo_root = true # Read by `scaffold add` to wire a new application to the same services. -# Editing them migrates nothing (ADR-0019). +# Editing them migrates nothing (toolbox ADR-0019). [vars] database = "@DATABASE@" cache = "@CACHE@" diff --git a/common/pnpm-workspace.yaml b/common/pnpm-workspace.yaml index 0208957..9733a8d 100644 --- a/common/pnpm-workspace.yaml +++ b/common/pnpm-workspace.yaml @@ -2,7 +2,7 @@ packages: - apps/* - packages/* - docs -# pnpm blocks an unreviewed postinstall build, aborting ci-unit (ADR-0017). +# pnpm blocks an unreviewed postinstall build, aborting ci-unit (toolbox ADR-0017). # esbuild fetches the binary vite runs; the others have pure-js fallbacks. allowBuilds: unrs-resolver: false diff --git a/services/mongodb/drivers/flask.sh b/services/mongodb/drivers/flask.sh index d44eb84..f68c10e 100644 --- a/services/mongodb/drivers/flask.sh +++ b/services/mongodb/drivers/flask.sh @@ -5,7 +5,7 @@ # splice_flask_probe, then overrides service_driver_apply, # service_driver_compose_env and service_driver_compose_migrate; no FLASK_* # variables are set. -# shellcheck source=/dev/null +# shellcheck source=services/shared/flask.sh . "${SCAFFOLD_ROOT}/services/shared/flask.sh" service_driver_apply() { diff --git a/services/mongodb/drivers/laravel.sh b/services/mongodb/drivers/laravel.sh index f080738..9620d80 100644 --- a/services/mongodb/drivers/laravel.sh +++ b/services/mongodb/drivers/laravel.sh @@ -54,8 +54,9 @@ service_driver_dockerfile() { # Pinned to 1.21.0, matching platform.ext-mongodb above: mongodb/mongodb's # BSONArray/BSONDocument declare bsonSerialize() against the 1.x signature and # the 2.x extension changed it, so loading those classes is a PHP fatal error, - # not an exception this project's try/catch can see — a 500 on /health/ready - # before this pin. + # not an exception this project's try/catch can see — the readiness probe + # returns a silent 500 on /health/ready if the extension drifts off this + # pin. # shellcheck disable=SC2016,SC1003 # literal Dockerfile RUN text: $PHPIZE_DEPS and the # trailing backslashes are line continuations in the generated file, not shell escapes printf '%s\n' \ diff --git a/services/mongodb/drivers/nest.sh b/services/mongodb/drivers/nest.sh index 855810b..61dc564 100644 --- a/services/mongodb/drivers/nest.sh +++ b/services/mongodb/drivers/nest.sh @@ -9,5 +9,5 @@ PRISMA_PROVIDER="mongodb" PRISMA_URL="mongodb://app:app@localhost:27017/app?authSource=admin&directConnection=true" # shellcheck disable=SC2016 # literal ${...} written into compose.yaml, not expanded here PRISMA_COMPOSE_URL='mongodb://${DB_USERNAME:-app}:${DB_PASSWORD}@database:27017/${DB_DATABASE:-app}?authSource=admin&directConnection=true' -# shellcheck source=/dev/null +# shellcheck source=services/shared/nest.sh . "${SCAFFOLD_ROOT}/services/shared/nest.sh" diff --git a/services/mysql/drivers/flask.sh b/services/mysql/drivers/flask.sh index 2746fb2..038416a 100644 --- a/services/mysql/drivers/flask.sh +++ b/services/mysql/drivers/flask.sh @@ -7,5 +7,5 @@ FLASK_PACKAGE="PyMySQL" FLASK_PORT="3306" # shellcheck disable=SC2016 # literal ${...} written into compose.yaml, not expanded here FLASK_COMPOSE_URL='mysql+pymysql://${DB_USERNAME:-app}:${DB_PASSWORD}@database:3306/${DB_DATABASE:-app}' -# shellcheck source=/dev/null +# shellcheck source=services/shared/flask.sh . "${SCAFFOLD_ROOT}/services/shared/flask.sh" diff --git a/services/mysql/drivers/laravel.sh b/services/mysql/drivers/laravel.sh index 7d33561..9d499f5 100644 --- a/services/mysql/drivers/laravel.sh +++ b/services/mysql/drivers/laravel.sh @@ -9,5 +9,5 @@ LARAVEL_PACKAGE="" LARAVEL_SETUP="RUN docker-php-ext-install pdo_mysql" LARAVEL_COMPOSE_ENV="DB_HOST: database DB_PORT: 3306" -# shellcheck source=/dev/null +# shellcheck source=services/shared/laravel.sh . "${SCAFFOLD_ROOT}/services/shared/laravel.sh" diff --git a/services/mysql/drivers/nest.sh b/services/mysql/drivers/nest.sh index 08201f5..2373c4a 100644 --- a/services/mysql/drivers/nest.sh +++ b/services/mysql/drivers/nest.sh @@ -5,5 +5,5 @@ PRISMA_PROVIDER="mysql" PRISMA_URL="mysql://app:app@localhost:3306/app" # shellcheck disable=SC2016 # literal ${...} written into compose.yaml, not expanded here PRISMA_COMPOSE_URL='mysql://${DB_USERNAME:-app}:${DB_PASSWORD}@database:3306/${DB_DATABASE:-app}' -# shellcheck source=/dev/null +# shellcheck source=services/shared/nest.sh . "${SCAFFOLD_ROOT}/services/shared/nest.sh" diff --git a/services/postgres/drivers/flask.sh b/services/postgres/drivers/flask.sh index dd19461..4380d47 100644 --- a/services/postgres/drivers/flask.sh +++ b/services/postgres/drivers/flask.sh @@ -8,5 +8,5 @@ FLASK_PACKAGE="psycopg[binary]" FLASK_PORT="5432" # shellcheck disable=SC2016 # literal ${...} written into compose.yaml, not expanded here FLASK_COMPOSE_URL='postgresql+psycopg://${DB_USERNAME:-app}:${DB_PASSWORD}@database:5432/${DB_DATABASE:-app}' -# shellcheck source=/dev/null +# shellcheck source=services/shared/flask.sh . "${SCAFFOLD_ROOT}/services/shared/flask.sh" diff --git a/services/postgres/drivers/laravel.sh b/services/postgres/drivers/laravel.sh index c5c3060..10ad322 100644 --- a/services/postgres/drivers/laravel.sh +++ b/services/postgres/drivers/laravel.sh @@ -8,5 +8,5 @@ LARAVEL_SETUP="RUN apk add --no-cache postgresql-dev \\ && docker-php-ext-install pdo_pgsql" LARAVEL_COMPOSE_ENV="DB_HOST: database DB_PORT: 5432" -# shellcheck source=/dev/null +# shellcheck source=services/shared/laravel.sh . "${SCAFFOLD_ROOT}/services/shared/laravel.sh" diff --git a/services/postgres/drivers/nest.sh b/services/postgres/drivers/nest.sh index 38a4e07..b3cdcca 100644 --- a/services/postgres/drivers/nest.sh +++ b/services/postgres/drivers/nest.sh @@ -5,5 +5,5 @@ PRISMA_PROVIDER="postgresql" PRISMA_URL="postgresql://app:app@localhost:5432/app" # shellcheck disable=SC2016 # literal ${...} written into compose.yaml, not expanded here PRISMA_COMPOSE_URL='postgresql://${DB_USERNAME:-app}:${DB_PASSWORD}@database:5432/${DB_DATABASE:-app}' -# shellcheck source=/dev/null +# shellcheck source=services/shared/nest.sh . "${SCAFFOLD_ROOT}/services/shared/nest.sh" diff --git a/services/shared/nest.sh b/services/shared/nest.sh index 779780e..ad91178 100644 --- a/services/shared/nest.sh +++ b/services/shared/nest.sh @@ -13,7 +13,7 @@ service_driver_apply() { # Before the installs, not after: prisma, its engines and its client all place # the query engine binary through an install-time script with no pure-js - # fallback, and unset the first `pnpm add` below is refused with + # fallback, otherwise the first `pnpm add` below is refused with # ERR_PNPM_IGNORED_BUILDS wherever CI=true leaves pnpm no prompt. # SCAFFOLD_PROJECT_ROOT, not a fixed `../..`: cmd_add's app directory is # caller-chosen. @@ -64,9 +64,9 @@ EOF # in eslint's recommended set. A --db none project keeps the throw. # # The client is a field on HealthController, not a local inside ready(): a -# controller is a Nest singleton, and a PrismaClient built per request and -# never closed leaks one connection per poll — measured exhausting Postgres's -# max_connections inside an hour at a 10s probe interval. +# controller is a Nest singleton, so a PrismaClient built per request and +# never closed leaks one connection per poll toward Postgres's +# max_connections. splice_nest_probe() { local method field preamble probe # shellcheck disable=SC2016 # literal TypeScript spliced into the generated controller @@ -135,7 +135,7 @@ service_driver_compose_env() { # one of two locations depending on which Dockerfile shape wins, a decision made # after this driver runs — so the command tries both and `cd`s into whichever # matched, since prisma resolves `./prisma/schema.prisma` from its own working -# directory (measured: `Could not find Prisma Schema` before this `cd`). +# directory, and fails with `Could not find Prisma Schema` otherwise. service_driver_compose_migrate() { local args case "$PRISMA_PROVIDER" in diff --git a/tests/new-laravel-api.bats b/tests/new-laravel-api.bats index 0ea5295..ef3ef76 100644 --- a/tests/new-laravel-api.bats +++ b/tests/new-laravel-api.bats @@ -51,11 +51,9 @@ teardown() { @test "a mixed-language project keeps the supply-chain policy" { scaffold new "$PROJECT" --api laravel-api --web nextjs - # allowBuilds is ADR-0017's policy and applies to any typescript app, but it - # lived in the root pnpm-workspace.yaml, which this branch used to delete - # outright — so one PHP app in the project removed the policy protecting the - # TypeScript one. The packages: list does go, since there is no shared - # workspace without a fully TypeScript project; the settings stay. + # ADR-0017's allowBuilds lives in the root pnpm-workspace.yaml, so a mixed- + # language project keeps the file — deleting it outright would also drop + # the TypeScript app's build policy. [ -f "${PROJECT}/pnpm-workspace.yaml" ] run yq -r '.allowBuilds | keys | .[]' "${PROJECT}/pnpm-workspace.yaml" assert_ok @@ -73,9 +71,9 @@ teardown() { @test "the adapter's docker directory reaches the generated app" { scaffold new "$PROJECT" --api laravel-api - # apply_adapter copies a docker/ subtree separately from the flat files, and - # nothing asserted it: deleting that line left every test green while the - # Dockerfile went on COPYing a file that was no longer there. + # apply_adapter copies a docker/ subtree separately from the flat files; + # this asserts it independently, so a Dockerfile that COPYs a file no + # longer shipped fails here instead of passing silently. [ -f "${PROJECT}/apps/api/docker/opcache.ini" ] run grep -c 'docker/opcache.ini' "${PROJECT}/apps/api/Dockerfile" [ "$output" != "0" ] @@ -101,10 +99,8 @@ teardown() { cd "$PROJECT" # commitlint backs the commit-msg hook and installs from the project root, - # not from an app dir — the one place record_release_age_exceptions used to - # skip in this branch, so a violation among commitlint's own dependencies - # (too fresh at generation time) surfaced only here, minutes later, on the - # first commit. + # not from an app dir, so record_release_age_exceptions must cover the root + # too or a too-fresh commitlint dependency fails the first commit. run mise exec -- pnpm exec commitlint --version assert_ok } @@ -128,8 +124,8 @@ teardown() { @test "a php-only project still ships the build policy" { scaffold new "$PROJECT" --api laravel-api # The root package.json is node tooling — commitlint backs the commit-msg - # hook — so ADR-0017's allowBuilds applies even with no TypeScript app. This - # branch used to delete the file that carries it outright. + # hook — so ADR-0017's allowBuilds applies even to a php-only project; + # pnpm-workspace.yaml must not be deleted for lack of a TypeScript app. [ -f "${PROJECT}/pnpm-workspace.yaml" ] run yq -r '.allowBuilds | keys | .[]' "${PROJECT}/pnpm-workspace.yaml" assert_ok diff --git a/tests/new-project.bats b/tests/new-project.bats index 912d7d6..9adf719 100644 --- a/tests/new-project.bats +++ b/tests/new-project.bats @@ -37,9 +37,8 @@ teardown() { @test "a relative target is created where the command was run" { # scaffold loads its own pinned toolchain through `mise env -C`, which - # prints the environment without moving. `mise exec -C` — what the README - # used to route every invocation through — moves as well, and this project - # then landed inside the toolbox rather than in the caller's directory. + # prints the environment without moving — `mise exec -C` moves as well and + # would generate the project inside the toolbox instead of here. cd "$WORKDIR" run scaffold new demo-relative assert_ok @@ -48,9 +47,8 @@ teardown() { } @test "new reports its steps instead of a package manager's output" { - # It used to hand the terminal several minutes of progress bars, through - # which the one line that mattered — which application is being generated — - # never appeared at all. + # Hides the package manager's own progress output, so the one line that + # matters — which application is being generated — is not buried in it. run scaffold new "$PROJECT" assert_ok [[ "$output" == *"→ "* ]] || { @@ -164,12 +162,10 @@ teardown() { @test "new does not trust a parent config it did not create" { printf 'monorepo_root = true\n\n[monorepo]\nconfig_roots = [\n "x",\n]\n' \ >"${WORKDIR}/mise.toml" - # No skip guard. There used to be one, for the true reason that `CI=true` - # makes mise trust every config it finds — but its only two outcomes were - # "skipped on a runner" and "failed everywhere else", so the property was - # never verified anywhere and the leak it guards against shipped. The state - # directory is the suite's own now (tests/helpers/setup), and CI is scrubbed - # from the run below, so the precondition holds in both environments. + # No CI skip guard: `CI=true` makes mise trust every config it finds, so + # this must run everywhere. The suite's own state directory + # (tests/helpers/setup) and the scrubbed CI below keep the precondition + # true in and out of CI. run env -u CI -u MISE_YES -u GITHUB_ACTIONS mise trust --show -C "$WORKDIR" [[ "$output" == *"${WORKDIR}: untrusted"* ]] || { echo "precondition failed; trust --show reported:" diff --git a/tests/service.bats b/tests/service.bats index f95dfb1..aee5530 100644 --- a/tests/service.bats +++ b/tests/service.bats @@ -183,10 +183,9 @@ setup() { } @test "the nest probe guard rejects a controller whose fallback throw survived" { - # The guard used to check for `return { status: 'ok' };`, which live() already - # returns — so a controller with three splices applied and the fourth missed - # passed it, and the project shipped a readiness route that was permanently - # 503 with nothing said about it. + # assert_nest_probe_spliced must fail on the surviving fallback throw + # alone: checking only for live()'s own `return { status: 'ok' };` would + # pass a controller with the readiness splice missed. source "${SCAFFOLD_ROOT}/services/shared/nest.sh" local file="${BATS_TEST_TMPDIR}/health.controller.ts" @@ -590,9 +589,8 @@ EOF run yq -e '.services.database != null and .services.cache != null' \ "${project}/compose.yaml" assert_ok - # assemble_compose no longer touches an application service — a project has - # one per application now, and add_app_service is what makes each of them - # wait on the services it was generated against (ADR-0022). + # assemble_compose wires only the shared database and cache services; + # add_app_service is what makes each application wait on them (ADR-0022). # Compared as a joined string: yq's `==` on two sequences returns a # sequence of per-element results, not one boolean, so `-e` reads it as no # match and the test fails whatever the keys are. @@ -793,7 +791,7 @@ _password_literal_report() { block="$( . "${SCAFFOLD_ROOT}/lib/service.sh" SERVICE_DIR="$(dirname "$(dirname "$driver")")" - # shellcheck source=/dev/null + # shellcheck source=/dev/null # path varies by driver . "$driver" service_driver_compose_env )" @@ -898,7 +896,7 @@ _password_literal_report() { for service in mysql postgres mongodb; do block="$( . "${SCAFFOLD_ROOT}/lib/service.sh" - # shellcheck source=/dev/null + # shellcheck source=/dev/null # path varies by service . "${SCAFFOLD_ROOT}/services/${service}/drivers/laravel.sh" service_driver_compose_env )" @@ -916,7 +914,7 @@ _password_literal_report() { for service in mysql postgres mongodb; do block="$( . "${SCAFFOLD_ROOT}/lib/service.sh" - # shellcheck source=/dev/null + # shellcheck source=/dev/null # path varies by service . "${SCAFFOLD_ROOT}/services/${service}/drivers/nest.sh" service_driver_compose_env )" diff --git a/tests/wizard.bats b/tests/wizard.bats index a7a007b..fb8d5a7 100644 --- a/tests/wizard.bats +++ b/tests/wizard.bats @@ -219,10 +219,10 @@ strip_ansi() { } @test "every kind wizard_questions yields is accepted by wizard_prompt_for, wizard_options and wizard_new_args" { - # wizard_prompt_for used to live in scaffold, unreachable by any unit test — - # a kind added to wizard_questions and not there died mid-wizard, after the - # name and shape screens were already answered. wizard_shapes' own test - # closed this hole for shapes; this closes it for kinds. + # wizard_prompt_for must handle every kind wizard_questions can yield — a + # kind missing here dies mid-wizard, after the name and shape screens are + # already answered. wizard_shapes' own test covers shapes; this covers + # kinds. local shape kind for shape in $(wizard_shapes | cut -f1); do for kind in $(wizard_questions "$shape"); do @@ -498,13 +498,12 @@ EOF } @test "typing an option's first letter selects it, not whatever Enter would default to" { - # tui_select used to read only arrows and Enter — every typed letter was - # silently discarded, so a first-time user who types the walkthrough's - # answer ("postgres") actually got whatever was already highlighted - # (mysql, the flags' own default) with no error and no sign anything went - # wrong. mysql sorts before postgres in the database menu, so reaching - # postgres here proves typing moved the cursor rather than Enter's default - # winning by coincidence. + # tui_select must accept a typed first letter, not just arrows and Enter, + # or a first-time user who types the walkthrough's answer ("postgres") + # silently gets whatever was already highlighted (mysql, the flags' own + # default) instead. mysql sorts before postgres in the database menu, so + # reaching postgres here proves typing moved the cursor rather than + # Enter's default winning by coincidence. command -v script >/dev/null || skip "script(1) not available" local out="${BATS_TEST_TMPDIR}/session.log"