Skip to content

Commit 12d89e9

Browse files
author
local-scaffold
committed
feat: initial commit (v0.1.0)
0 parents  commit 12d89e9

13 files changed

Lines changed: 916 additions & 0 deletions

File tree

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.DS_Store
2+
node_modules/
3+
*.log
4+
.env
5+
.env.local
6+
dist/
7+
.next/
8+
build/
9+
coverage/
10+
playwright-report/
11+
test-results/

README.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Stack
2+
3+
Tech defaults, configs, and working starters for products built within the [bloom](https://github.com/derrybirkett/bloom) system.
4+
5+
Stack is the answer to "what do I default to when I want to ship quickly without re-deciding the same basics every time."
6+
7+
## Two profiles
8+
9+
Stack exposes two profiles. A product picks one at bootstrap.
10+
11+
| Profile | Thesis | Stage fit |
12+
|---|---|---|
13+
| **lite** (default) | Vercel + Supabase + single Next.js app | Discovery, MVP, validation, single-team |
14+
| **full** | Nx monorepo + NestJS API + Postgres-in-Docker + custom auth | Post-validation, regulated, on-prem, portability needs |
15+
16+
The default contract is in [`stack.yaml`](stack.yaml) — that file IS the lite profile. The full profile lives in [`profiles/full.yaml`](profiles/full.yaml).
17+
18+
Default to lite. Most products will never graduate to full; that's a feature.
19+
20+
## What's in stack
21+
22+
```
23+
stack/
24+
README.md
25+
stack.yaml # the lite contract (canonical default)
26+
profiles/
27+
full.yaml # the full contract (heavyweight thesis from retired hatch)
28+
configs/ # portable config fragments
29+
typescript/tsconfig.base.json
30+
eslint/eslint.config.js
31+
prettier/.prettierrc
32+
github/ci.yml
33+
git/hooks/pre-commit-no-main
34+
templates/
35+
lite/ # working Next.js + Supabase starter (Phase 4)
36+
full/ # working Nx + NestJS starter (Phase 4, from hatch salvage)
37+
docs/
38+
ui-baseline.md # the 5-component baseline
39+
decisions/
40+
0001-vercel-supabase-default.md
41+
```
42+
43+
## How to consume
44+
45+
Three valid modes.
46+
47+
**Reference**: read `stack.yaml`, copy the bits you need into your product manually. Best for products with custom constraints.
48+
49+
**Submodule**: mount stack under `.bloom/stack/` and reference configs by path. Best for normal use — get versioned reuse without copying.
50+
51+
```bash
52+
git submodule add https://github.com/derrybirkett/stack .bloom/stack
53+
ln -s .bloom/stack/configs/typescript/tsconfig.base.json tsconfig.base.json
54+
```
55+
56+
**Selective copy**: bootstrap from a starter template under `templates/`, then diverge. Best when speed matters most.
57+
58+
## When to deviate
59+
60+
A product may deviate from the contract. When it does, record the deviation in the product's own `decisions/NNNN-deviate-from-stack.md` ADR with rationale. Three or more products deviating on the same concern is a signal to update the stack contract — open an ADR here.
61+
62+
## Versioning
63+
64+
Patch bumps for clarifications and config fragment updates. Minor for additions (new profile, new config fragment). Major for breaking semantic changes (changing a default vendor in the lite profile).
65+
66+
## License
67+
68+
MIT

configs/eslint/eslint.config.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Shared ESLint flat config for bloom-built products.
2+
//
3+
// Consumption: products extend this in their own eslint.config.js:
4+
//
5+
// import bloomConfig from '.bloom/stack/configs/eslint/eslint.config.js';
6+
// export default [...bloomConfig, { /* project overrides */ }];
7+
//
8+
// Keep this file minimal. Project-specific rules belong in the consuming
9+
// product, not here.
10+
11+
import js from '@eslint/js';
12+
import tseslint from 'typescript-eslint';
13+
14+
export default [
15+
js.configs.recommended,
16+
...tseslint.configs.recommended,
17+
{
18+
rules: {
19+
// Strict but pragmatic
20+
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
21+
'@typescript-eslint/no-explicit-any': 'warn',
22+
'@typescript-eslint/consistent-type-imports': 'error',
23+
'no-console': ['warn', { allow: ['warn', 'error'] }],
24+
'prefer-const': 'error',
25+
'no-var': 'error',
26+
'eqeqeq': ['error', 'always'],
27+
},
28+
},
29+
{
30+
ignores: ['node_modules/**', 'dist/**', '.next/**', 'build/**', 'coverage/**'],
31+
},
32+
];
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/usr/bin/env bash
2+
#
3+
# pre-commit hook: block direct commits to main.
4+
#
5+
# Install locally:
6+
# cp .bloom/stack/configs/git/hooks/pre-commit-no-main .git/hooks/pre-commit
7+
# chmod +x .git/hooks/pre-commit
8+
#
9+
# Or install globally (one-time):
10+
# mkdir -p ~/.git-hooks
11+
# cp pre-commit-no-main ~/.git-hooks/pre-commit
12+
# chmod +x ~/.git-hooks/pre-commit
13+
# git config --global core.hooksPath ~/.git-hooks
14+
#
15+
# Salvaged from the council repo's "no commits on main" pattern. Applies the
16+
# same rule to every bloom-built product.
17+
18+
set -euo pipefail
19+
20+
branch=$(git symbolic-ref --short HEAD 2>/dev/null || echo "")
21+
22+
if [[ "$branch" == "main" || "$branch" == "master" ]]; then
23+
echo ""
24+
echo "ERROR: direct commits to '$branch' are not allowed."
25+
echo ""
26+
echo "Create a branch instead:"
27+
echo " git checkout -b feat/your-change"
28+
echo " git commit ..."
29+
echo " git push -u origin feat/your-change"
30+
echo ""
31+
echo "Then open a pull request for review and squash-merge."
32+
echo ""
33+
exit 1
34+
fi
35+
36+
exit 0

configs/github/ci.yml

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# GitHub Actions CI workflow for bloom-built products (lite profile).
2+
#
3+
# Copy this into a product's .github/workflows/ci.yml. Adjust the e2e step
4+
# if the product needs a different browser matrix.
5+
#
6+
# Salvaged and refined from the retired hatch repo's CI pattern.
7+
8+
name: CI
9+
10+
on:
11+
pull_request:
12+
branches: [main]
13+
push:
14+
branches: [main]
15+
16+
concurrency:
17+
group: ${{ github.workflow }}-${{ github.ref }}
18+
cancel-in-progress: true
19+
20+
jobs:
21+
ci:
22+
name: Typecheck + Lint + Build + Test
23+
runs-on: ubuntu-latest
24+
timeout-minutes: 15
25+
26+
steps:
27+
- name: Checkout
28+
uses: actions/checkout@v4
29+
30+
- name: Setup pnpm
31+
uses: pnpm/action-setup@v4
32+
33+
- name: Setup Node
34+
uses: actions/setup-node@v4
35+
with:
36+
node-version: '20'
37+
cache: 'pnpm'
38+
39+
- name: Install dependencies
40+
run: pnpm install --frozen-lockfile
41+
42+
- name: Typecheck
43+
run: pnpm typecheck
44+
45+
- name: Lint
46+
run: pnpm lint
47+
48+
- name: Unit tests
49+
run: pnpm test
50+
51+
- name: Build
52+
run: pnpm build
53+
54+
- name: Audit
55+
run: pnpm audit --audit-level=high
56+
continue-on-error: true
57+
58+
e2e:
59+
name: E2E (Playwright)
60+
runs-on: ubuntu-latest
61+
timeout-minutes: 20
62+
needs: ci
63+
64+
steps:
65+
- name: Checkout
66+
uses: actions/checkout@v4
67+
68+
- name: Setup pnpm
69+
uses: pnpm/action-setup@v4
70+
71+
- name: Setup Node
72+
uses: actions/setup-node@v4
73+
with:
74+
node-version: '20'
75+
cache: 'pnpm'
76+
77+
- name: Install dependencies
78+
run: pnpm install --frozen-lockfile
79+
80+
- name: Install Playwright browsers
81+
run: pnpm exec playwright install --with-deps chromium
82+
83+
- name: Run e2e tests
84+
run: pnpm test:e2e
85+
86+
- name: Upload Playwright report
87+
if: always()
88+
uses: actions/upload-artifact@v4
89+
with:
90+
name: playwright-report
91+
path: playwright-report/
92+
retention-days: 7

configs/prettier/.prettierrc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"semi": true,
3+
"trailingComma": "all",
4+
"singleQuote": true,
5+
"printWidth": 100,
6+
"tabWidth": 2,
7+
"useTabs": false,
8+
"arrowParens": "always",
9+
"bracketSpacing": true,
10+
"endOfLine": "lf"
11+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"$schema": "https://json.schemastore.org/tsconfig",
3+
"compilerOptions": {
4+
"target": "ES2022",
5+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
6+
"module": "ESNext",
7+
"moduleResolution": "Bundler",
8+
"jsx": "preserve",
9+
"allowJs": false,
10+
"checkJs": false,
11+
12+
"strict": true,
13+
"noImplicitAny": true,
14+
"strictNullChecks": true,
15+
"strictFunctionTypes": true,
16+
"strictBindCallApply": true,
17+
"strictPropertyInitialization": true,
18+
"noImplicitThis": true,
19+
"alwaysStrict": true,
20+
"noUnusedLocals": true,
21+
"noUnusedParameters": true,
22+
"noFallthroughCasesInSwitch": true,
23+
"noImplicitReturns": true,
24+
"noUncheckedIndexedAccess": true,
25+
26+
"esModuleInterop": true,
27+
"allowSyntheticDefaultImports": true,
28+
"forceConsistentCasingInFileNames": true,
29+
"isolatedModules": true,
30+
"resolveJsonModule": true,
31+
"skipLibCheck": true,
32+
"incremental": true,
33+
34+
"declaration": false,
35+
"sourceMap": true
36+
},
37+
"exclude": ["node_modules", "dist", ".next", "build"]
38+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# ADR-0001: Lite profile default is Vercel + Supabase + single Next.js app
2+
3+
Date: 2026-05-07
4+
Status: Accepted
5+
6+
## Context
7+
8+
The previous bloom-system iteration carried four conflicting "what is the default stack" definitions across `prefs/prefs.yaml`, `stack/docs/stacks/vercel-stack.md`, `skills/CLAUDE.md`, and `skills/skills/vercel-saas-stack/spec.yaml`. They disagreed on package manager (npm vs pnpm), auth + db provider (Supabase vs Clerk + Neon), and monorepo strategy (Nx vs single Next app). New products bootstrapped from "bloom" got incompatible answers depending on which file was read first.
9+
10+
A canonical default must be picked and documented in exactly one place.
11+
12+
## Decision
13+
14+
The lite profile, defined in [`stack.yaml`](../stack.yaml), uses:
15+
16+
- Framework: Next.js (App Router) + TypeScript on Vercel.
17+
- Package manager: pnpm.
18+
- Auth + database: Supabase (via `@supabase/ssr`).
19+
- UI: Tailwind + shadcn/ui (copied into project), monochrome zinc palette.
20+
- Testing: Vitest (unit) + Playwright (e2e), Chromium-only by default.
21+
- Payments: Stripe (Checkout + Customer Portal).
22+
- Email: Resend.
23+
- Analytics: PostHog + Vercel Analytics + Sentry.
24+
25+
A second profile, `full`, is preserved at [`profiles/full.yaml`](../profiles/full.yaml) for products that need ownership/portability (Nx + NestJS + Postgres-in-Docker + custom JWT). Default is lite.
26+
27+
## Consequences
28+
29+
**Positive**:
30+
- Time-to-first-deploy under an hour for the common case.
31+
- Single managed-services vendor for auth + db + storage (Supabase) reduces integration surface.
32+
- shadcn copied-in pattern means no runtime UI dependency.
33+
- pnpm gives strict dep resolution and disk efficiency across many side projects.
34+
- The `full` profile preserves the heavyweight thesis for the rare product that needs it, without forcing it on every bootstrap.
35+
36+
**Negative**:
37+
- Vendor lock-in on Vercel + Supabase + Stripe. Acceptable for validation-stage products; the full profile is the escape hatch when not.
38+
- Single Next.js app is harder to split into multiple deployables if a product grows. Acceptable; that's the graduation signal for full.
39+
- pnpm installation is a one-time tax on a fresh machine.
40+
41+
## Alternatives considered
42+
43+
**Clerk + Neon for auth + db**: Best-in-class auth UX, but adds a second vendor and SSO/B2B-org features that the user does not yet need. Documented as the alternative path when those needs emerge.
44+
45+
**Nx + 3-app monorepo as default**: Solves portability and team-scale problems the user does not have yet. Adds operational complexity (multiple servers, more CI surface) for negligible gain at validation stage. Preserved as the `full` profile.
46+
47+
**npm as the package manager**: Lower mental overhead, every tutorial assumes it. But pnpm's strict resolution and disk savings compound across many side projects; Vercel's docs increasingly default to pnpm. Documented npm as the acceptable fallback when consuming external starters that hard-assume it.
48+
49+
**Drizzle + Neon instead of Supabase native**: Drizzle is a fine ORM but pairs more naturally with Neon. With Supabase, the native client + RLS pattern is the better fit and removes one moving part.

0 commit comments

Comments
 (0)