@webjsdev/cli 0.10.0 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/webjs.js CHANGED
@@ -148,17 +148,31 @@ async function main() {
148
148
  const { readdir } = await import('node:fs/promises');
149
149
  const testFiles = [];
150
150
 
151
- for (const dir of ['test/server', 'test/unit', 'test']) {
152
- const fullDir = join(cwd, dir);
153
- if (!existsSync(fullDir)) continue;
154
- const files = await readdir(fullDir);
155
- for (const f of files) {
156
- if (/\.test\.(js|ts|mjs|mts)$/.test(f)) {
157
- const full = join(fullDir, f);
151
+ // Walk test/ recursively so the documented feature-folder layout
152
+ // (test/<feature>/<name>.test.ts) is discovered, not just files
153
+ // sitting directly in test/. Two kinds are NOT run here:
154
+ // - **/browser/** → real-browser tests, owned by WTR below.
155
+ // - **/e2e/** → full-app boot, opt-in via WEBJS_E2E=1 (the
156
+ // documented "WEBJS_E2E=1 webjs test adds the
157
+ // e2e tests" semantics).
158
+ const runE2E = !!process.env.WEBJS_E2E;
159
+ const walk = async (dir, segments) => {
160
+ let entries;
161
+ try { entries = await readdir(dir, { withFileTypes: true }); }
162
+ catch { return; }
163
+ for (const ent of entries) {
164
+ if (ent.name === 'node_modules') continue;
165
+ const full = join(dir, ent.name);
166
+ if (ent.isDirectory()) {
167
+ if (ent.name === 'browser') continue;
168
+ if (ent.name === 'e2e' && !runE2E) continue;
169
+ await walk(full, [...segments, ent.name]);
170
+ } else if (/\.test\.(js|ts|mjs|mts)$/.test(ent.name)) {
158
171
  if (!testFiles.includes(full)) testFiles.push(full);
159
172
  }
160
173
  }
161
- }
174
+ };
175
+ await walk(join(cwd, 'test'), []);
162
176
 
163
177
  if (testFiles.length > 0) {
164
178
  console.log(`webjs test: running ${testFiles.length} server test file(s)…\n`);
package/lib/create.js CHANGED
@@ -373,6 +373,7 @@ export async function scaffoldApp(name, cwd, opts = {}) {
373
373
  '.claude/hooks/block-prose-punctuation.sh',
374
374
  '.claude/hooks/guard-branch-context.sh',
375
375
  '.claude/hooks/nudge-uncommitted.sh',
376
+ '.claude/hooks/require-tests-with-src.sh',
376
377
  // Gemini CLI config + hooks
377
378
  '.gemini/settings.json',
378
379
  '.gemini/hooks/nudge-uncommitted.sh',
@@ -391,7 +392,16 @@ export async function scaffoldApp(name, cwd, opts = {}) {
391
392
  '.cursorrules',
392
393
  '.github/copilot-instructions.md',
393
394
  '.github/pull_request_template.md',
395
+ // CI is the test gate (the pre-commit hook only blocks main). Runs
396
+ // webjs check + the unit / browser / e2e layers on every PR and push
397
+ // to main, mirroring the webjs framework's own CI.
398
+ '.github/workflows/ci.yml',
394
399
  '.editorconfig',
400
+ // Production / deploy scaffolding. `docker compose up --build` runs
401
+ // the app locally with the same Dockerfile production builds from.
402
+ 'Dockerfile',
403
+ 'compose.yaml',
404
+ '.dockerignore',
395
405
  ];
396
406
  for (const f of templateFiles) {
397
407
  const src = join(TEMPLATES, f);
@@ -405,7 +415,7 @@ export async function scaffoldApp(name, cwd, opts = {}) {
405
415
 
406
416
  // Make hook scripts executable
407
417
  const { chmod } = await import('node:fs/promises');
408
- for (const hook of ['block-prose-punctuation.sh', 'guard-branch-context.sh', 'nudge-uncommitted.sh']) {
418
+ for (const hook of ['block-prose-punctuation.sh', 'guard-branch-context.sh', 'nudge-uncommitted.sh', 'require-tests-with-src.sh']) {
409
419
  const hookPath = join(appDir, '.claude', 'hooks', hook);
410
420
  if (existsSync(hookPath)) await chmod(hookPath, 0o755);
411
421
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webjsdev/cli",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
4
4
  "type": "module",
5
5
  "description": "webjs CLI - dev, start, create, db",
6
6
  "bin": {
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # PreToolUse hook (scaffolded by `webjs create`): block a `git commit`
4
+ # that adds or changes application code without any accompanying test.
5
+ #
6
+ # webjs is AI-first: most apps are built with an AI agent, and the
7
+ # easiest corner to cut is shipping a feature with no test. This gate
8
+ # makes "every change ships with a test" a hard floor, not a suggestion.
9
+ #
10
+ # What a hook CANNOT do: judge WHICH test layer a change needs (a unit
11
+ # test vs a browser/e2e test is a judgement call). So it enforces the
12
+ # floor (some real test must accompany app code) and reminds you to add
13
+ # browser/e2e coverage for interactive surfaces. `webjs test` runs the
14
+ # actual suite in the commit hook.
15
+ #
16
+ # Scope: fires only on `git commit`. Inspects the STAGED diff.
17
+ #
18
+ # Blocks (exit 2) when the staged diff changes app code (app/, modules/,
19
+ # components/, lib/) but stages no test (test/** or *.test.* / *.spec.*).
20
+ # Allowed: commits with no app-code change, commits that stage a test
21
+ # alongside, and WEBJS_NO_TEST_GATE=1 for a genuine non-code commit.
22
+ #
23
+ # Bypass (humans, emergencies): git commit --no-verify.
24
+
25
+ set -euo pipefail
26
+
27
+ if [ "${WEBJS_NO_TEST_GATE:-}" = "1" ]; then
28
+ exit 0
29
+ fi
30
+
31
+ payload=$(cat)
32
+ cmd=$(printf '%s' "$payload" | jq -r '.tool_input.command // empty' 2>/dev/null || true)
33
+ if [ -z "$cmd" ]; then exit 0; fi
34
+ # Match `git commit` as a whole word so sibling subcommands
35
+ # (git commit-graph, git commit-tree) and string mentions do not trip it.
36
+ if ! printf '%s' "$cmd" | grep -Eq '(^|[^[:alnum:]-])git commit([^[:alnum:]-]|$)'; then
37
+ exit 0
38
+ fi
39
+
40
+ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then exit 0; fi
41
+
42
+ staged=$(git diff --cached --name-only 2>/dev/null || true)
43
+ if [ -z "$staged" ]; then exit 0; fi
44
+
45
+ # App code lives under app/, modules/, components/, lib/. A `.server.*`
46
+ # file is still app code. Match source extensions only (skip .css, .md).
47
+ app_code=$(printf '%s\n' "$staged" \
48
+ | grep -E '^(app|modules|components|lib)/.*\.([mc]?[jt]sx?)$' || true)
49
+ if [ -z "$app_code" ]; then exit 0; fi
50
+
51
+ test_staged=$(printf '%s\n' "$staged" \
52
+ | grep -E '(^|/)test/|\.test\.[mc]?[jt]sx?$|\.spec\.[mc]?[jt]sx?$' || true)
53
+
54
+ if [ -z "$test_staged" ]; then
55
+ cat >&2 <<'EOF'
56
+ BLOCKED: this commit changes app code but stages no test.
57
+
58
+ You staged application code (app/, modules/, components/, lib/) with no
59
+ accompanying test. Every change ships with a test. Add or update the test
60
+ that proves the new behaviour, then `git add` it.
61
+
62
+ Pick the layer the change needs (a unit test is not always enough):
63
+ - logic / actions / queries / utils -> a unit test
64
+ - a component, hydration, a server action called from the client, the
65
+ router, anything interactive -> a browser or e2e test that asserts the
66
+ real behaviour in a browser, not just the function in isolation.
67
+
68
+ See `webjs test` and the testing guide. Genuine non-code commit (docs,
69
+ config) that needs no test? Re-run with WEBJS_NO_TEST_GATE=1.
70
+
71
+ Hook: .claude/hooks/require-tests-with-src.sh
72
+ EOF
73
+ exit 2
74
+ fi
75
+
76
+ # Reminder for interactive surfaces: a unit test alone rarely covers them.
77
+ interactive=$(printf '%s\n' "$app_code" | grep -E '^components/|/components/' || true)
78
+ if [ -n "$interactive" ]; then
79
+ jq -n --arg ctx "Reminder: this commit changes component code. A unit test alone usually is not enough for an interactive component; add a browser test (webjs test --browser) that asserts the rendered/hydrated behaviour." '{
80
+ hookSpecificOutput: { hookEventName: "PreToolUse", additionalContext: $ctx }
81
+ }'
82
+ fi
83
+ exit 0
@@ -18,6 +18,15 @@
18
18
  "command": ".claude/hooks/guard-branch-context.sh"
19
19
  }
20
20
  ]
21
+ },
22
+ {
23
+ "matcher": "Bash",
24
+ "hooks": [
25
+ {
26
+ "type": "command",
27
+ "command": ".claude/hooks/require-tests-with-src.sh"
28
+ }
29
+ ]
21
30
  }
22
31
  ],
23
32
  "PostToolUse": [
@@ -0,0 +1,43 @@
1
+ .git
2
+ .github
3
+ node_modules
4
+ **/node_modules
5
+
6
+ # `.webjs/` is ignored EXCEPT for `.webjs/vendor/`, which holds the committed
7
+ # importmap manifest (and optionally downloaded bundle bytes) the server needs
8
+ # at boot without reaching api.jspm.io. DO NOT collapse to `**/.webjs`: parent
9
+ # exclusion blocks child negations and the vendor files would never ship.
10
+ .webjs/*
11
+ !.webjs/vendor/
12
+ !.webjs/vendor/**
13
+
14
+ dist
15
+ build
16
+ out
17
+ .cache
18
+
19
+ # Local env files. The container gets its env from compose / uncloud, not a
20
+ # committed file. Keep the example for reference.
21
+ .env
22
+ !.env.example
23
+
24
+ # Prisma local SQLite. Schema + migrations ship; the runtime volume owns the db.
25
+ dev.db
26
+ dev.db-journal
27
+ prisma/dev.db
28
+ prisma/dev.db-journal
29
+
30
+ # logs
31
+ *.log
32
+ npm-debug.log*
33
+
34
+ # editors / OS
35
+ .vscode
36
+ .idea
37
+ .DS_Store
38
+ Thumbs.db
39
+
40
+ # tests aren't needed in the production image
41
+ coverage/
42
+ test/
43
+ **/test/
@@ -0,0 +1,105 @@
1
+ name: CI
2
+
3
+ # The test gate for {{APP_NAME}}. Runs the full test pyramid on every PR
4
+ # into main and on every push to main. This is the gate the local
5
+ # pre-commit hook deliberately leaves out, so `git commit` stays fast and
6
+ # the test gate runs in one authoritative place a local --no-verify cannot
7
+ # skip. Same posture as the webjs framework's own CI.
8
+ #
9
+ # The four layers run as separate jobs so a failure names the layer that
10
+ # broke. Mark all four as required status checks in the branch-protection
11
+ # rule for main so a PR can only merge when every layer is green. Free on
12
+ # public repos (ubuntu-latest has unlimited Actions minutes).
13
+
14
+ on:
15
+ pull_request:
16
+ branches: [main]
17
+ push:
18
+ branches: [main]
19
+
20
+ # A newer push to the same branch cancels the older in-flight run.
21
+ concurrency:
22
+ group: ci-${{ github.ref }}
23
+ cancel-in-progress: true
24
+
25
+ jobs:
26
+ conventions:
27
+ name: Conventions (webjs check)
28
+ runs-on: ubuntu-latest
29
+ steps:
30
+ - uses: actions/checkout@v6
31
+ - uses: actions/setup-node@v6
32
+ with:
33
+ node-version: '24'
34
+ cache: npm
35
+ - run: npm ci
36
+ - run: npm run check
37
+
38
+ unit:
39
+ name: Unit + integration (node --test)
40
+ runs-on: ubuntu-latest
41
+ steps:
42
+ - uses: actions/checkout@v6
43
+ - uses: actions/setup-node@v6
44
+ with:
45
+ node-version: '24'
46
+ cache: npm
47
+ - run: npm ci
48
+ - run: npx prisma generate
49
+ - name: Apply migrations to the test database
50
+ run: npx prisma migrate deploy
51
+ env:
52
+ DATABASE_URL: file:./ci.db
53
+ # --server keeps this job to node:test (the browser layer is its own
54
+ # job below). Without WEBJS_E2E the e2e folders are skipped too.
55
+ - run: npm run test:server
56
+ env:
57
+ DATABASE_URL: file:./ci.db
58
+
59
+ browser:
60
+ name: Browser (web-test-runner / Playwright)
61
+ runs-on: ubuntu-latest
62
+ steps:
63
+ - uses: actions/checkout@v6
64
+ - uses: actions/setup-node@v6
65
+ with:
66
+ node-version: '24'
67
+ cache: npm
68
+ - run: npm ci
69
+ - run: npx prisma generate
70
+ - name: Install Playwright Chromium
71
+ run: npx playwright install --with-deps chromium
72
+ - run: npm run test:browser
73
+
74
+ e2e:
75
+ name: E2E (full app boot)
76
+ runs-on: ubuntu-latest
77
+ steps:
78
+ - uses: actions/checkout@v6
79
+ - uses: actions/setup-node@v6
80
+ with:
81
+ node-version: '24'
82
+ cache: npm
83
+ - run: npm ci
84
+ - run: npx prisma generate
85
+ - name: Apply migrations to the test database
86
+ run: npx prisma migrate deploy
87
+ env:
88
+ DATABASE_URL: file:./ci.db
89
+ # The scaffold's e2e test (test/hello/e2e/) drives a real browser
90
+ # via puppeteer-core, which is not a default dependency (the test
91
+ # skips when it is absent). Install it and Chromium so the e2e
92
+ # layer actually runs in CI rather than skipping silently.
93
+ - name: Install puppeteer-core + Chromium
94
+ run: |
95
+ npm install --no-save puppeteer-core
96
+ npx playwright install --with-deps chromium
97
+ - name: Resolve the Chromium binary path
98
+ run: echo "CHROMIUM_PATH=$(node -e "console.log(require('playwright-core').chromium.executablePath())")" >> "$GITHUB_ENV"
99
+ # --server with WEBJS_E2E=1 runs node:test including the e2e folders
100
+ # (the runner gates them on that env var) and skips the browser layer.
101
+ - name: Run e2e
102
+ env:
103
+ WEBJS_E2E: '1'
104
+ DATABASE_URL: file:./ci.db
105
+ run: npm run test:server
@@ -1,11 +1,16 @@
1
1
  #!/bin/bash
2
2
  #
3
- # pre-commit hook - blocks commits on main/master.
3
+ # pre-commit hook. Blocks commits on main/master.
4
4
  #
5
5
  # No AI agent, no editor, no human can commit to main directly.
6
6
  # Create a feature branch first. This is git-level enforcement.
7
7
  #
8
8
  # To bypass in emergencies: git commit --no-verify
9
+ #
10
+ # Tests and convention checks run in CI (.github/workflows/ci.yml), not
11
+ # here, so a commit stays fast and the test gate cannot be skipped by a
12
+ # local --no-verify. The CI workflow runs `webjs check` + `webjs test`
13
+ # on every push and pull request.
9
14
 
10
15
  BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null)
11
16
 
@@ -21,28 +26,4 @@ if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
21
26
  exit 1
22
27
  fi
23
28
 
24
- # webjs test + webjs check on every commit. Tool-agnostic enforcement:
25
- # fires regardless of which agent (Claude, Cursor, Antigravity, Copilot,
26
- # human) is making the commit. Skipped if the CLI is not yet installed
27
- # (fresh clone before npm install).
28
- if command -v webjs >/dev/null 2>&1 || [ -x "node_modules/.bin/webjs" ]; then
29
- echo "Running webjs test..."
30
- if ! npx --no-install webjs test; then
31
- echo ""
32
- echo "ERROR: webjs test failed. Fix tests before committing."
33
- echo "To bypass (emergencies only): git commit --no-verify"
34
- echo ""
35
- exit 1
36
- fi
37
-
38
- echo "Running webjs check..."
39
- if ! npx --no-install webjs check; then
40
- echo ""
41
- echo "ERROR: webjs check failed. Fix convention violations before committing."
42
- echo "To bypass (emergencies only): git commit --no-verify"
43
- echo ""
44
- exit 1
45
- fi
46
- fi
47
-
48
29
  exit 0
@@ -301,6 +301,16 @@ In Docker / Railway, prefer `npm start` (or `node node_modules/.bin/npm
301
301
  start`) as the CMD over `node ... webjs.js start ...`. The npm form
302
302
  fires `prestart`; the direct binary form skips it.
303
303
 
304
+ **Containerized deploy ships with the scaffold.** `Dockerfile`,
305
+ `compose.yaml`, and `.dockerignore` are scaffolded at the app root. The
306
+ Dockerfile pins `node:24-alpine` (the same Node major CI uses), installs
307
+ deps, runs `prisma generate`, and starts via `npm start` so `prestart`
308
+ applies migrations. Run it locally with `docker compose up --build` (the
309
+ app comes up on http://localhost:8080 against a SQLite file on a named
310
+ volume). For production, point `DATABASE_URL` at managed Postgres and set
311
+ `AUTH_SECRET`. The `.dockerignore` keeps the `.webjs/vendor/` importmap in
312
+ the image while excluding `node_modules`, tests, and local state.
313
+
304
314
  **Health and readiness probes.** Every webjs server answers two endpoints:
305
315
  `/__webjs/health` (liveness, 200 once the process is listening) and
306
316
  `/__webjs/ready` (readiness, 503 until the instance is fully warm, then 200).
@@ -883,8 +893,17 @@ composition, so a nested shell ends up dropped by the HTML parser.
883
893
  ## Workflow expectations for AI agents
884
894
 
885
895
  1. Branch before editing. Never push to `main` directly.
886
- 2. Every code change comes with: unit test(s), AGENTS.md / docs updates if
887
- the feature surface changed, `webjs check` passing.
896
+ 2. Every code change comes with a test, AGENTS.md / docs updates if the
897
+ feature surface changed, `webjs check` passing. A unit test is not
898
+ always enough: a component, hydration, the client router, or a server
899
+ action called from the client needs a browser test
900
+ (`webjs test --browser`) asserting the behaviour in a real browser. A
901
+ commit that stages app code (`app/`, `modules/`, `components/`, `lib/`)
902
+ with no test is blocked for Claude Code by
903
+ `.claude/hooks/require-tests-with-src.sh`. The test suite itself runs in
904
+ CI (`.github/workflows/ci.yml`), not in the pre-commit hook, so `git
905
+ commit` stays fast and the gate cannot be skipped with a local
906
+ `--no-verify`.
888
907
  3. Commit and push **per logical unit**, not at the end. A logical unit is one
889
908
  feature, one fix, one rename, one doc rewrite. If you have 5+ unstaged files
890
909
  spanning different concerns, commit the current group before continuing.
@@ -900,9 +919,10 @@ composition, so a nested shell ends up dropped by the HTML parser.
900
919
  | Antigravity (Google) | text rule only (post-write hooks not yet exposed) | `.agents/rules/workflow.md` |
901
920
  | GitHub Copilot | text rule only (no hooks API) | `.github/copilot-instructions.md` |
902
921
 
903
- Tool-agnostic fallback: `.hooks/pre-commit` runs `webjs test` + `webjs check`
904
- on every commit, regardless of which agent (or human) made it. No AI
905
- attribution trailers in commit messages.
922
+ The `.hooks/pre-commit` hook blocks commits to main and nothing else;
923
+ `webjs test` + `webjs check` run in CI (`.github/workflows/ci.yml`) on
924
+ every PR and push to main, regardless of which agent (or human) made
925
+ the commit. No AI attribution trailers in commit messages.
906
926
  4. Run the **pre-merge self-review loop** before signaling the PR is
907
927
  ready. After committing the work, trigger a fresh-context review
908
928
  pass (a new chat / composer tab / subagent / Cascade thread
@@ -451,6 +451,15 @@ test/
451
451
  - `webjs test --browser` (or `npx wtr`) runs the browser tests.
452
452
  - `WEBJS_E2E=1 webjs test` adds the e2e tests.
453
453
 
454
+ **Every change ships with a test.** For Claude Code, a commit that
455
+ stages app code (`app/`, `modules/`, `components/`, `lib/`) without
456
+ staging a test is blocked by `.claude/hooks/require-tests-with-src.sh`.
457
+ A unit test alone is not enough for interactive or component code: add
458
+ the browser test that asserts the rendered/hydrated behaviour. The test
459
+ suite itself runs in CI (`.github/workflows/ci.yml`) on every PR and
460
+ push to main, not in the local pre-commit hook, so `git commit` stays
461
+ fast and the gate cannot be skipped with a local `--no-verify`.
462
+
454
463
  ### Choosing a feature folder
455
464
 
456
465
  Use the same name as the matching module folder when one exists:
@@ -994,10 +1003,19 @@ This project enforces a git workflow via agent-specific config files
994
1003
  Other agents enforce this via `.cursorrules`, `.agents/rules/workflow.md`,
995
1004
  `.github/copilot-instructions.md`.
996
1005
 
997
- **Pre-commit checks:**
998
- - `webjs test` must pass
999
- - `webjs check` must pass
1000
- - No unrelated files in the commit
1006
+ **Pre-commit hook (`.hooks/pre-commit`):**
1007
+ - Blocks commits to `main` / `master`. Nothing else runs locally, so
1008
+ `git commit` stays fast. Keeping commits to one logical unit (no
1009
+ unrelated files) is your discipline plus the `nudge-uncommitted`
1010
+ hooks, not something this hook enforces.
1011
+
1012
+ **CI gate (`.github/workflows/ci.yml`), on every PR and push to main:**
1013
+ - `webjs check` (conventions) must pass
1014
+ - `webjs test` (unit + integration), the browser layer, and the e2e
1015
+ layer must pass
1016
+ - Mark these as required status checks in the branch-protection rule for
1017
+ main so a PR can only merge when the gate is green. The gate lives in
1018
+ CI, where a local `--no-verify` cannot skip it.
1001
1019
 
1002
1020
  ---
1003
1021
 
@@ -0,0 +1,38 @@
1
+ # Production image for the {{APP_NAME}} webjs app.
2
+ #
3
+ # Works with a plain `docker build` / `docker compose up`, and is the same
4
+ # artifact the webdeploy hosting tool (ubicloud + uncloud) builds and ships.
5
+ #
6
+ # webjs serves .ts directly via Node's built-in type-stripping, so there is
7
+ # NO JavaScript build step. **Node 24+ is REQUIRED**: on older Node the runtime
8
+ # falls back to esbuild, whose class-declaration transform breaks webjs's SSR
9
+ # walker for multi-class component files. Do not lower this base image
10
+ # below 24 (the same version the CI workflow and the framework pin).
11
+ FROM node:24-alpine
12
+
13
+ # openssl + ca-certificates are required by Prisma's query engine at runtime.
14
+ RUN apk add --no-cache openssl ca-certificates
15
+
16
+ WORKDIR /app
17
+
18
+ # Install deps first so this layer is cached unless the manifests change.
19
+ # package-lock.json is optional (it's absent when the app was scaffolded with
20
+ # --no-install); the glob keeps the COPY working with or without it.
21
+ COPY package.json package-lock.json* ./
22
+ RUN npm install --no-audit --no-fund
23
+
24
+ # App source. node_modules and local state are excluded via .dockerignore.
25
+ COPY . .
26
+
27
+ # Generate the Prisma client at build time (every scaffold ships a
28
+ # prisma/schema.prisma). If you remove Prisma from the app, delete this line.
29
+ RUN npx prisma generate
30
+
31
+ ENV NODE_ENV=production
32
+ # webjs start reads $PORT (default 8080). compose / uncloud / Railway set it.
33
+ ENV PORT=8080
34
+ EXPOSE 8080
35
+
36
+ # `npm start` runs `prestart: prisma migrate deploy` (idempotent, a no-op when
37
+ # there are no migrations yet) and then `webjs start`, which serves on $PORT.
38
+ CMD ["npm", "start"]
@@ -0,0 +1,28 @@
1
+ # {{APP_NAME}} local parity with production, using the same Dockerfile, one service.
2
+ #
3
+ # docker compose up --build → http://localhost:8080
4
+ #
5
+ # In production the webdeploy tool (ubicloud + uncloud) provisions a managed
6
+ # Postgres and injects DATABASE_URL + AUTH_SECRET for you. Locally this uses
7
+ # the scaffold's SQLite file on a named volume so data survives `compose down`.
8
+ services:
9
+ app:
10
+ build: .
11
+ ports:
12
+ - "8080:8080"
13
+ environment:
14
+ PORT: 8080
15
+ # SQLite on a volume for local dev. For production, point DATABASE_URL at
16
+ # your managed Postgres and switch prisma/schema.prisma's provider to
17
+ # "postgresql".
18
+ DATABASE_URL: file:/data/dev.db
19
+ # Generate: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
20
+ AUTH_SECRET: ${AUTH_SECRET:-change-me-please-at-least-32-characters!}
21
+ # Uncomment to back cache / sessions / rate-limiting / pub-sub with Redis
22
+ # instead of the in-memory defaults.
23
+ # REDIS_URL: redis://redis:6379
24
+ volumes:
25
+ - app-data:/data
26
+
27
+ volumes:
28
+ app-data: