diff --git a/services/mongodb/drivers/flask.sh b/services/mongodb/drivers/flask.sh index d0dbba7..d44eb84 100644 --- a/services/mongodb/drivers/flask.sh +++ b/services/mongodb/drivers/flask.sh @@ -1,9 +1,5 @@ +# How Flask talks to MongoDB. # shellcheck shell=bash -# ═══════════════════════════════════════════════════════════════════════════ -# Script : services/mongodb/drivers/flask.sh -# Description : How Flask talks to MongoDB. -# Author : ttncode -# ═══════════════════════════════════════════════════════════════════════════ # Self-contained, like services/mongodb/drivers/laravel.sh: a DSN, not # decomposed credentials. Sources services/shared/flask.sh only to reuse # splice_flask_probe, then overrides service_driver_apply, diff --git a/services/mongodb/drivers/laravel.sh b/services/mongodb/drivers/laravel.sh index 149113a..f080738 100644 --- a/services/mongodb/drivers/laravel.sh +++ b/services/mongodb/drivers/laravel.sh @@ -1,9 +1,5 @@ +# How Laravel talks to MongoDB. # shellcheck shell=bash -# ═══════════════════════════════════════════════════════════════════════════ -# Script : services/mongodb/drivers/laravel.sh -# Description : How Laravel talks to MongoDB. -# Author : ttncode -# ═══════════════════════════════════════════════════════════════════════════ # Self-contained rather than sourcing services/shared/laravel.sh: mongodb wires # a DSN and a config/database.php connection instead of the decomposed # DB_HOST/DB_PORT/DB_USERNAME/DB_PASSWORD every relational driver shares. @@ -28,10 +24,13 @@ service_driver_apply() { # empty and laravel refuses to boot. write_env_lines "${SCAFFOLD_PROJECT_ROOT}/example.env" "APP_KEY=changeme" || return 1 - # mongodb has no SQL to run a `select 1` against; ping is what - # laravel-mongodb exposes. The class arrives as a short name with its own - # `use`: pint's fully_qualified_strict_types rejects an inline FQCN once the - # file has imports. + splice_mongodb_probe || return 1 +} + +# mongodb has no SQL to run a `select 1` against; ping is what laravel-mongodb +# exposes. The class arrives as a short name with its own `use`: pint's +# fully_qualified_strict_types rejects an inline FQCN once the file has imports. +splice_mongodb_probe() { sed -i.bak 's|use Illuminate\\Support\\Facades\\Route;|use Illuminate\\Support\\Facades\\DB;\nuse Illuminate\\Support\\Facades\\Route;|' \ routes/health.php || return 1 sed -i.bak 's|// @DB_PROBE@|DB::connection(\x27mongodb\x27)->getMongoDB()->command([\x27ping\x27 => 1]);|' \ diff --git a/services/mongodb/drivers/nest.sh b/services/mongodb/drivers/nest.sh index 0c601d6..855810b 100644 --- a/services/mongodb/drivers/nest.sh +++ b/services/mongodb/drivers/nest.sh @@ -1,9 +1,5 @@ +# MongoDB parameters for the shared Prisma driver. # shellcheck shell=bash -# ═══════════════════════════════════════════════════════════════════════════ -# Script : services/mongodb/drivers/nest.sh -# Description : MongoDB parameters for the shared Prisma driver. -# Author : ttncode -# ═══════════════════════════════════════════════════════════════════════════ # shellcheck disable=SC2034 # read by services/shared/nest.sh, sourced below PRISMA_PROVIDER="mongodb" # authSource=admin because the container creates the user in `admin`, and diff --git a/services/mysql/drivers/flask.sh b/services/mysql/drivers/flask.sh index 70d8b0d..2746fb2 100644 --- a/services/mysql/drivers/flask.sh +++ b/services/mysql/drivers/flask.sh @@ -1,9 +1,5 @@ +# MySQL parameters for the shared Flask driver. # shellcheck shell=bash -# ═══════════════════════════════════════════════════════════════════════════ -# Script : services/mysql/drivers/flask.sh -# Description : MySQL parameters for the shared Flask driver. -# Author : ttncode -# ═══════════════════════════════════════════════════════════════════════════ # shellcheck disable=SC2034 # read by services/shared/flask.sh, sourced below FLASK_DIALECT="mysql+pymysql" # pure python, so no build stage and no system package diff --git a/services/mysql/drivers/laravel.sh b/services/mysql/drivers/laravel.sh index eebd9ec..7d33561 100644 --- a/services/mysql/drivers/laravel.sh +++ b/services/mysql/drivers/laravel.sh @@ -1,9 +1,5 @@ +# MySQL parameters for the shared Laravel driver. # shellcheck shell=bash -# ═══════════════════════════════════════════════════════════════════════════ -# Script : services/mysql/drivers/laravel.sh -# Description : MySQL parameters for the shared Laravel driver. -# Author : ttncode -# ═══════════════════════════════════════════════════════════════════════════ # shellcheck disable=SC2034 # read by services/shared/laravel.sh, sourced below LARAVEL_CONNECTION="mysql" LARAVEL_PORT="3306" diff --git a/services/mysql/drivers/nest.sh b/services/mysql/drivers/nest.sh index abf72f3..08201f5 100644 --- a/services/mysql/drivers/nest.sh +++ b/services/mysql/drivers/nest.sh @@ -1,9 +1,5 @@ +# MySQL parameters for the shared Prisma driver. # shellcheck shell=bash -# ═══════════════════════════════════════════════════════════════════════════ -# Script : services/mysql/drivers/nest.sh -# Description : MySQL parameters for the shared Prisma driver. -# Author : ttncode -# ═══════════════════════════════════════════════════════════════════════════ # shellcheck disable=SC2034 # read by services/shared/nest.sh, sourced below PRISMA_PROVIDER="mysql" PRISMA_URL="mysql://app:app@localhost:3306/app" diff --git a/services/postgres/drivers/flask.sh b/services/postgres/drivers/flask.sh index 84560cf..dd19461 100644 --- a/services/postgres/drivers/flask.sh +++ b/services/postgres/drivers/flask.sh @@ -1,9 +1,5 @@ +# PostgreSQL parameters for the shared Flask driver. # shellcheck shell=bash -# ═══════════════════════════════════════════════════════════════════════════ -# Script : services/postgres/drivers/flask.sh -# Description : PostgreSQL parameters for the shared Flask driver. -# Author : ttncode -# ═══════════════════════════════════════════════════════════════════════════ # shellcheck disable=SC2034 # read by services/shared/flask.sh, sourced below FLASK_DIALECT="postgresql+psycopg" # the [binary] extra ships a wheel with libpq inside, so the image needs no diff --git a/services/postgres/drivers/laravel.sh b/services/postgres/drivers/laravel.sh index b6f217d..c5c3060 100644 --- a/services/postgres/drivers/laravel.sh +++ b/services/postgres/drivers/laravel.sh @@ -1,9 +1,5 @@ +# PostgreSQL parameters for the shared Laravel driver. # shellcheck shell=bash -# ═══════════════════════════════════════════════════════════════════════════ -# Script : services/postgres/drivers/laravel.sh -# Description : PostgreSQL parameters for the shared Laravel driver. -# Author : ttncode -# ═══════════════════════════════════════════════════════════════════════════ # shellcheck disable=SC2034 # read by services/shared/laravel.sh, sourced below LARAVEL_CONNECTION="pgsql" LARAVEL_PORT="5432" diff --git a/services/postgres/drivers/nest.sh b/services/postgres/drivers/nest.sh index eb2b559..38a4e07 100644 --- a/services/postgres/drivers/nest.sh +++ b/services/postgres/drivers/nest.sh @@ -1,9 +1,5 @@ +# PostgreSQL parameters for the shared Prisma driver. # shellcheck shell=bash -# ═══════════════════════════════════════════════════════════════════════════ -# Script : services/postgres/drivers/nest.sh -# Description : PostgreSQL parameters for the shared Prisma driver. -# Author : ttncode -# ═══════════════════════════════════════════════════════════════════════════ # shellcheck disable=SC2034 # read by services/shared/nest.sh, sourced below PRISMA_PROVIDER="postgresql" PRISMA_URL="postgresql://app:app@localhost:5432/app" diff --git a/services/redis/drivers/flask.sh b/services/redis/drivers/flask.sh index f93b8c5..1dbbfd2 100644 --- a/services/redis/drivers/flask.sh +++ b/services/redis/drivers/flask.sh @@ -1,9 +1,5 @@ +# How Flask talks to Redis. # shellcheck shell=bash -# ═══════════════════════════════════════════════════════════════════════════ -# Script : services/redis/drivers/flask.sh -# Description : How Flask talks to Redis. -# Author : ttncode -# ═══════════════════════════════════════════════════════════════════════════ # Self-contained: redis is the only cache, so a shared body would have exactly # one caller. Extract one when a second cache arrives. service_driver_apply() { diff --git a/services/redis/drivers/laravel.sh b/services/redis/drivers/laravel.sh index 042fad8..0042958 100644 --- a/services/redis/drivers/laravel.sh +++ b/services/redis/drivers/laravel.sh @@ -1,14 +1,9 @@ +# How Laravel talks to Redis. # shellcheck shell=bash -# ═══════════════════════════════════════════════════════════════════════════ -# Script : services/redis/drivers/laravel.sh -# Description : How Laravel talks to Redis. -# Author : ttncode -# ═══════════════════════════════════════════════════════════════════════════ # Self-contained: redis is the only cache, so a shared body would have exactly # one caller. Extract one when a second cache arrives. service_driver_apply() { # predis, not the phpredis extension: a composer package needs no build stage. - # composer require predis/predis --no-interaction || return 1 write_env_lines .env.example \ diff --git a/services/redis/drivers/nest.sh b/services/redis/drivers/nest.sh index 511a608..f1fc9f4 100644 --- a/services/redis/drivers/nest.sh +++ b/services/redis/drivers/nest.sh @@ -1,9 +1,5 @@ +# How NestJS talks to Redis. # shellcheck shell=bash -# ═══════════════════════════════════════════════════════════════════════════ -# Script : services/redis/drivers/nest.sh -# Description : How NestJS talks to Redis. -# Author : ttncode -# ═══════════════════════════════════════════════════════════════════════════ # Self-contained: redis is the only cache, so a shared body would have exactly # one caller. Extract one when a second cache arrives. service_driver_apply() { diff --git a/services/shared/flask.sh b/services/shared/flask.sh index b0d6469..c56d0d9 100644 --- a/services/shared/flask.sh +++ b/services/shared/flask.sh @@ -1,9 +1,5 @@ +# The shared Flask SQLAlchemy driver body. # shellcheck shell=bash -# ═══════════════════════════════════════════════════════════════════════════ -# Script : services/shared/flask.sh -# Description : The shared Flask SQLAlchemy driver body. -# Author : ttncode -# ═══════════════════════════════════════════════════════════════════════════ # A service's drivers/flask.sh sets these and sources this. mysql and postgres # only — mongodb is self-contained: pymongo has no SQLAlchemy dialect, so its # driver defines its own service_driver_apply after sourcing this file, kept @@ -77,6 +73,9 @@ splice_flask_probe() { ' "$file" >"${file}.tmp" || return 1 mv "${file}.tmp" "$file" + # Checks that the fallback and both anchors are gone, not that the success + # return exists: live() already returns jsonify(status="ok"), so a presence + # grep for it would pass even when the splice failed. # shellcheck disable=SC2015 # deliberate: die must fire when any grep fails ! grep -q 'no database is configured for this project' "$file" && ! grep -q '@DB_ENGINE@' "$file" && diff --git a/services/shared/laravel.sh b/services/shared/laravel.sh index baea1a6..d75a23b 100644 --- a/services/shared/laravel.sh +++ b/services/shared/laravel.sh @@ -1,9 +1,5 @@ +# The shared Laravel SQL driver body. # shellcheck shell=bash -# ═══════════════════════════════════════════════════════════════════════════ -# Script : services/shared/laravel.sh -# Description : The shared Laravel SQL driver body. -# Author : ttncode -# ═══════════════════════════════════════════════════════════════════════════ # A service's drivers/laravel.sh sets these and sources this. mysql and # postgres only — mongodb is self-contained: a DSN and a config/database.php # edit differ in kind from these decomposed credentials. @@ -16,7 +12,7 @@ # needs beyond DB_CONNECTION, e.g. DB_HOST/DB_PORT service_driver_apply() { - if [ -n "$LARAVEL_PACKAGE" ]; then + if [[ -n "$LARAVEL_PACKAGE" ]]; then composer require "$LARAVEL_PACKAGE" --no-interaction || return 1 fi @@ -36,11 +32,18 @@ service_driver_apply() { # empty and laravel refuses to boot. write_env_lines "${SCAFFOLD_PROJECT_ROOT}/example.env" "APP_KEY=changeme" || return 1 - # Spliced here rather than shipped in the route, so the file carries exactly - # one probe, for the connection this project actually has. - # - # The class arrives as a short name with its own `use`: pint's - # fully_qualified_strict_types rejects an inline FQCN once the file has imports. + splice_laravel_probe || return 1 +} + +service_driver_dockerfile() { + [[ -z "$LARAVEL_SETUP" ]] || printf '%s\n' "$LARAVEL_SETUP" +} + +# Spliced here rather than shipped in the route, so the file carries exactly +# one probe, for the connection this project actually has. The class arrives +# as a short name with its own `use`: pint's fully_qualified_strict_types +# rejects an inline FQCN once the file has imports. +splice_laravel_probe() { sed -i.bak 's|use Illuminate\\Support\\Facades\\Route;|use Illuminate\\Support\\Facades\\DB;\nuse Illuminate\\Support\\Facades\\Route;|' \ routes/health.php || return 1 sed -i.bak 's|// @DB_PROBE@|DB::connection()->select(\x27select 1\x27);|' \ @@ -58,10 +61,6 @@ service_driver_apply() { die "could not splice the database probe into routes/health.php — has the anchor moved?" } -service_driver_dockerfile() { - [ -z "$LARAVEL_SETUP" ] || printf '%s\n' "$LARAVEL_SETUP" -} - # config/database.php defaults to sqlite, so DB_CONNECTION's absence is a # silent wrong answer, not an error. Credentials reach the container through # compose.yaml's env_file already; only what laravel cannot otherwise know goes here. diff --git a/services/shared/nest.sh b/services/shared/nest.sh index 98f3f01..779780e 100644 --- a/services/shared/nest.sh +++ b/services/shared/nest.sh @@ -1,9 +1,5 @@ +# The shared Prisma driver body. # shellcheck shell=bash -# ═══════════════════════════════════════════════════════════════════════════ -# Script : services/shared/nest.sh -# Description : The shared Prisma driver body. -# Author : ttncode -# ═══════════════════════════════════════════════════════════════════════════ # A service's drivers/nest.sh sets the parameters below and sources this. One # client API across every database this toolbox ships is why Prisma was chosen # over TypeORM — the adapter x service matrix collapses to a single code path. @@ -17,9 +13,8 @@ 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 undecided the first `pnpm add` below is refused with + # fallback, and unset 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. if ! yq --inplace \ @@ -54,22 +49,25 @@ EOF write_env_lines .env.example "DATABASE_URL=${PRISMA_URL}" || return 1 - # prisma has no provider-agnostic read: $queryRaw is SQL-only, mongodb needs a - # command, and a generated client only has the one method its provider - # implies — casting to the other fails tsc's "sufficient overlap" check. - # - # Cast to an explicit method signature, not a bare `import(...).then(...)`: - # lint runs before the :prisma task, so the generated client does not exist - # yet and an untyped access to it is `any`, which @typescript-eslint's - # no-unsafe-* rules reject under --max-warnings 0. - # - # The throw is replaced in place, not left below the probe: no-unreachable is - # 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. + splice_nest_probe || return 1 +} + +# prisma has no provider-agnostic read: $queryRaw is SQL-only, mongodb needs a +# command, and a generated client only has the one method its provider +# implies — casting to the other fails tsc's "sufficient overlap" check. Cast +# to an explicit method signature, not a bare `import(...).then(...)`: lint +# runs before the :prisma task, so the generated client does not exist yet and +# an untyped access to it is `any`, which @typescript-eslint's no-unsafe-* +# rules reject under --max-warnings 0. +# +# The throw is replaced in place, not left below the probe: no-unreachable is +# 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. +splice_nest_probe() { local method field preamble probe # shellcheck disable=SC2016 # literal TypeScript spliced into the generated controller case "$PRISMA_PROVIDER" in