@usequark/quark-create-app 1.23.11
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/README.md +210 -0
- package/package.json +56 -0
- package/src/dependabot-config.test.js +128 -0
- package/src/deploy/adapters/iac.js +203 -0
- package/src/deploy/adapters/index.js +27 -0
- package/src/deploy/adapters/railway.js +508 -0
- package/src/deploy/adapters/railway.test.js +318 -0
- package/src/deploy/contract.js +59 -0
- package/src/deploy/deploy.integration.test.js +183 -0
- package/src/deploy/deploy.js +430 -0
- package/src/deploy/deploy.test.js +105 -0
- package/src/deploy/discovery.js +115 -0
- package/src/deploy/discovery.test.js +141 -0
- package/src/deploy/index.js +38 -0
- package/src/deploy/inspect.js +130 -0
- package/src/index.js +2920 -0
- package/src/mobile-template.test.js +232 -0
- package/src/prepare-hooks.test.js +535 -0
- package/src/scaffold-guards.test.js +140 -0
- package/src/scaffold-output.test.js +228 -0
- package/src/template-scripts.test.js +160 -0
- package/src/utils.js +77 -0
- package/src/utils.test.js +95 -0
- package/src/worker-dockerfile.test.js +85 -0
- package/templates/base-project/.cursor/rules/quark.mdc +172 -0
- package/templates/base-project/.dockerignore +14 -0
- package/templates/base-project/.env.railway.example +87 -0
- package/templates/base-project/.github/copilot-instructions.md +65 -0
- package/templates/base-project/.github/dependabot.yml +31 -0
- package/templates/base-project/.github/skills/project-context/SKILL.md +109 -0
- package/templates/base-project/.github/workflows/ci.yml +116 -0
- package/templates/base-project/.github/workflows/dependabot-auto-merge.yml +22 -0
- package/templates/base-project/.github/workflows/install-windows.yml +27 -0
- package/templates/base-project/.github/workflows/release.yml +67 -0
- package/templates/base-project/.railway/railway.ts +40 -0
- package/templates/base-project/CLAUDE.md +163 -0
- package/templates/base-project/MAIN.md +21 -0
- package/templates/base-project/README.md +164 -0
- package/templates/base-project/apps/web/.env.example +99 -0
- package/templates/base-project/apps/web/Dockerfile +38 -0
- package/templates/base-project/apps/web/biome.json +8 -0
- package/templates/base-project/apps/web/jsconfig.json +9 -0
- package/templates/base-project/apps/web/next.config.js +154 -0
- package/templates/base-project/apps/web/package.json +42 -0
- package/templates/base-project/apps/web/postcss.config.mjs +7 -0
- package/templates/base-project/apps/web/public/.well-known/security.txt +3 -0
- package/templates/base-project/apps/web/public/quark.svg +47 -0
- package/templates/base-project/apps/web/scripts/prepare-standalone.mjs +71 -0
- package/templates/base-project/apps/web/src/app/_components/AsciiPanel.js +103 -0
- package/templates/base-project/apps/web/src/app/_components/HealthIndicator.js +85 -0
- package/templates/base-project/apps/web/src/app/_components/HomeThemeToggle.js +63 -0
- package/templates/base-project/apps/web/src/app/_components/QuarkAnimation.js +168 -0
- package/templates/base-project/apps/web/src/app/_components/SyncedPanels.js +212 -0
- package/templates/base-project/apps/web/src/app/_components/UmamiReplayRecorder.js +198 -0
- package/templates/base-project/apps/web/src/app/_components/UmamiWebVitals.js +39 -0
- package/templates/base-project/apps/web/src/app/api/auth/[...nextauth]/route.js +7 -0
- package/templates/base-project/apps/web/src/app/api/auth/apple/route.js +82 -0
- package/templates/base-project/apps/web/src/app/api/auth/google/route.js +59 -0
- package/templates/base-project/apps/web/src/app/api/auth/refresh/route.js +48 -0
- package/templates/base-project/apps/web/src/app/api/auth/register/route.js +63 -0
- package/templates/base-project/apps/web/src/app/api/auth/token/route.js +44 -0
- package/templates/base-project/apps/web/src/app/api/csrf/route.js +40 -0
- package/templates/base-project/apps/web/src/app/api/device/register/route.js +57 -0
- package/templates/base-project/apps/web/src/app/api/error-handler.js +23 -0
- package/templates/base-project/apps/web/src/app/api/files/[id]/route.js +89 -0
- package/templates/base-project/apps/web/src/app/api/files/route.js +120 -0
- package/templates/base-project/apps/web/src/app/api/health/route.js +160 -0
- package/templates/base-project/apps/web/src/app/api/users/[id]/route.js +54 -0
- package/templates/base-project/apps/web/src/app/api/users/me/route.js +67 -0
- package/templates/base-project/apps/web/src/app/api/users/route.js +45 -0
- package/templates/base-project/apps/web/src/app/auth/error/page.js +83 -0
- package/templates/base-project/apps/web/src/app/auth/forgot-password/page.js +58 -0
- package/templates/base-project/apps/web/src/app/auth/provider-loading.js +25 -0
- package/templates/base-project/apps/web/src/app/auth/register/RegisterPageClient.js +340 -0
- package/templates/base-project/apps/web/src/app/auth/register/page.js +17 -0
- package/templates/base-project/apps/web/src/app/auth/signin/SignInPageClient.js +230 -0
- package/templates/base-project/apps/web/src/app/auth/signin/page.js +10 -0
- package/templates/base-project/apps/web/src/app/favicon.ico +0 -0
- package/templates/base-project/apps/web/src/app/global-error.js +58 -0
- package/templates/base-project/apps/web/src/app/globals.css +1166 -0
- package/templates/base-project/apps/web/src/app/icon.svg +47 -0
- package/templates/base-project/apps/web/src/app/layout.js +57 -0
- package/templates/base-project/apps/web/src/app/manifest.js +12 -0
- package/templates/base-project/apps/web/src/app/not-found.js +39 -0
- package/templates/base-project/apps/web/src/app/page.js +262 -0
- package/templates/base-project/apps/web/src/app/robots.js +23 -0
- package/templates/base-project/apps/web/src/app/sitemap.js +14 -0
- package/templates/base-project/apps/web/src/lib/analytics/umami-before-send.js +29 -0
- package/templates/base-project/apps/web/src/lib/analytics/umami-config.js +95 -0
- package/templates/base-project/apps/web/src/lib/analytics/umami-csp.js +24 -0
- package/templates/base-project/apps/web/src/lib/analytics/umami-marketing.js +131 -0
- package/templates/base-project/apps/web/src/lib/analytics/umami-replay.js +204 -0
- package/templates/base-project/apps/web/src/lib/analytics/umami-web-vitals.js +237 -0
- package/templates/base-project/apps/web/src/lib/analytics/umami.js +57 -0
- package/templates/base-project/apps/web/src/lib/auth-middleware.js +82 -0
- package/templates/base-project/apps/web/src/lib/auth-signup.js +5 -0
- package/templates/base-project/apps/web/src/lib/auth.js +191 -0
- package/templates/base-project/apps/web/src/lib/csrf-cookie.js +11 -0
- package/templates/base-project/apps/web/src/lib/jwt.js +84 -0
- package/templates/base-project/apps/web/src/lib/load-crm-config.js +35 -0
- package/templates/base-project/apps/web/src/lib/normalize-auth-request.js +33 -0
- package/templates/base-project/apps/web/src/lib/proxy-auth.js +80 -0
- package/templates/base-project/apps/web/src/lib/seo/indexing.js +25 -0
- package/templates/base-project/apps/web/src/lib/seo/site-metadata.js +74 -0
- package/templates/base-project/apps/web/src/lib/sitemap-entries.js +22 -0
- package/templates/base-project/apps/web/src/preflight.js +55 -0
- package/templates/base-project/apps/web/src/proxy.js +328 -0
- package/templates/base-project/biome.json +52 -0
- package/templates/base-project/docker-compose.override.yml +17 -0
- package/templates/base-project/docker-compose.yml +37 -0
- package/templates/base-project/docs/FIRST_FEATURE.md +74 -0
- package/templates/base-project/docs/START_HERE.md +70 -0
- package/templates/base-project/docs/adr/README.md +42 -0
- package/templates/base-project/package.json +58 -0
- package/templates/base-project/packages/db/package.json +38 -0
- package/templates/base-project/packages/db/prisma/migrations/20260202061128_initial/migration.sql +202 -0
- package/templates/base-project/packages/db/prisma/migrations/migration_lock.toml +3 -0
- package/templates/base-project/packages/db/prisma/schema.prisma +161 -0
- package/templates/base-project/packages/db/prisma/seed.js +152 -0
- package/templates/base-project/packages/db/prisma.config.js +27 -0
- package/templates/base-project/packages/db/src/client.js +75 -0
- package/templates/base-project/packages/db/src/connection.js +44 -0
- package/templates/base-project/packages/db/src/context.js +275 -0
- package/templates/base-project/packages/db/src/index.js +5 -0
- package/templates/base-project/packages/db/src/ping.js +66 -0
- package/templates/base-project/packages/db/src/queries.js +292 -0
- package/templates/base-project/packages/db/src/schemas.js +30 -0
- package/templates/base-project/packages/db/src/umami.js +299 -0
- package/templates/base-project/pnpm-workspace.yaml +20 -0
- package/templates/base-project/scripts/check-loading.mjs +49 -0
- package/templates/base-project/scripts/check-standards.mjs +186 -0
- package/templates/base-project/scripts/clean-workspace.mjs +479 -0
- package/templates/base-project/scripts/dev-preflight.mjs +48 -0
- package/templates/base-project/scripts/doctor.js +408 -0
- package/templates/base-project/scripts/prepare.js +490 -0
- package/templates/base-project/scripts/run-tests.mjs +71 -0
- package/templates/base-project/skills/add-dashboard/SKILL.md +28 -0
- package/templates/base-project/skills/add-endpoint/SKILL.md +29 -0
- package/templates/base-project/skills/add-model/SKILL.md +28 -0
- package/templates/base-project/skills/ecommerce/SKILL.md +224 -0
- package/templates/base-project/skills/ecommerce-cart/SKILL.md +193 -0
- package/templates/base-project/skills/ecommerce-catalog/SKILL.md +179 -0
- package/templates/base-project/skills/ecommerce-checkout/SKILL.md +195 -0
- package/templates/base-project/skills/i18n/SKILL.md +211 -0
- package/templates/base-project/skills/payment/SKILL.md +280 -0
- package/templates/base-project/skills/quark-skills/SKILL.md +37 -0
- package/templates/base-project/turbo.json +143 -0
- package/templates/config/package.json +20 -0
- package/templates/config/src/app-url.js +134 -0
- package/templates/config/src/environment.js +284 -0
- package/templates/config/src/index.js +16 -0
- package/templates/config/src/load-config.js +157 -0
- package/templates/config/src/rate-limit.js +136 -0
- package/templates/config/src/redis.js +43 -0
- package/templates/config/src/validate-env.js +532 -0
- package/templates/jobs/README.md +52 -0
- package/templates/jobs/package.json +9 -0
- package/templates/jobs/src/definitions.js +13 -0
- package/templates/jobs/src/index.js +1 -0
- package/templates/mobile/.env.example +16 -0
- package/templates/mobile/CHECKLIST.md +40 -0
- package/templates/mobile/README.md +130 -0
- package/templates/mobile/app/(app)/_layout.tsx +83 -0
- package/templates/mobile/app/(app)/index.tsx +147 -0
- package/templates/mobile/app/(app)/profile.tsx +204 -0
- package/templates/mobile/app/(app)/settings.tsx +80 -0
- package/templates/mobile/app/(auth)/_layout.tsx +10 -0
- package/templates/mobile/app/(auth)/sign-in.tsx +140 -0
- package/templates/mobile/app/(auth)/sign-up.tsx +178 -0
- package/templates/mobile/app/+not-found.tsx +32 -0
- package/templates/mobile/app/_layout.tsx +18 -0
- package/templates/mobile/app.json +47 -0
- package/templates/mobile/assets/adaptive-icon.png +0 -0
- package/templates/mobile/assets/icon.png +0 -0
- package/templates/mobile/assets/notification-icon.png +0 -0
- package/templates/mobile/assets/splash.png +0 -0
- package/templates/mobile/biome.json +27 -0
- package/templates/mobile/components/AuthGate.tsx +19 -0
- package/templates/mobile/components/ThemeProvider.tsx +23 -0
- package/templates/mobile/eas.json +29 -0
- package/templates/mobile/hooks/use-auth.ts +127 -0
- package/templates/mobile/lib/api-client.ts +138 -0
- package/templates/mobile/lib/api.ts +83 -0
- package/templates/mobile/lib/auth-providers-google.ts +74 -0
- package/templates/mobile/lib/auth-providers.ts +74 -0
- package/templates/mobile/lib/auth.ts +100 -0
- package/templates/mobile/lib/config.ts +14 -0
- package/templates/mobile/lib/errors.ts +46 -0
- package/templates/mobile/lib/notifications.ts +77 -0
- package/templates/mobile/lib/storage.ts +25 -0
- package/templates/mobile/lib/theme-tokens.ts +16 -0
- package/templates/mobile/package.json +37 -0
- package/templates/mobile/tsconfig.json +10 -0
- package/templates/pwa/app/_components/PWARegister.js +15 -0
- package/templates/pwa/app/manifest.js +32 -0
- package/templates/pwa/public/sw.js +72 -0
- package/templates/ui/README.md +44 -0
- package/templates/ui/package.json +21 -0
- package/templates/ui/src/badge.js +23 -0
- package/templates/ui/src/button.js +111 -0
- package/templates/ui/src/card.js +128 -0
- package/templates/ui/src/checkbox.js +20 -0
- package/templates/ui/src/container.js +13 -0
- package/templates/ui/src/dialog.js +137 -0
- package/templates/ui/src/error-banner.js +15 -0
- package/templates/ui/src/footer.js +239 -0
- package/templates/ui/src/form-field.integration-test.js +142 -0
- package/templates/ui/src/form-field.js +50 -0
- package/templates/ui/src/index.js +24 -0
- package/templates/ui/src/input.js +11 -0
- package/templates/ui/src/label.js +14 -0
- package/templates/ui/src/lightbox.integration-test.js +167 -0
- package/templates/ui/src/lightbox.js +243 -0
- package/templates/ui/src/logo.js +52 -0
- package/templates/ui/src/navbar.js +602 -0
- package/templates/ui/src/password-input.js +31 -0
- package/templates/ui/src/rich-text.js +514 -0
- package/templates/ui/src/select.js +317 -0
- package/templates/ui/src/skeleton.js +10 -0
- package/templates/ui/src/spinner.js +31 -0
- package/templates/ui/src/table.js +169 -0
- package/templates/ui/src/textarea.js +11 -0
- package/templates/ui/src/theme-constants.js +24 -0
- package/templates/ui/src/theme.js +196 -0
- package/templates/ui/src/toast.js +206 -0
- package/templates/worker/Dockerfile +64 -0
- package/templates/worker/README.md +56 -0
- package/templates/worker/package.json +36 -0
- package/templates/worker/src/config/pricing.js +46 -0
- package/templates/worker/src/handlers/email-job-validation.js +36 -0
- package/templates/worker/src/handlers/email.js +98 -0
- package/templates/worker/src/handlers/files.js +59 -0
- package/templates/worker/src/handlers/index.js +20 -0
- package/templates/worker/src/handlers/push-notification.js +234 -0
- package/templates/worker/src/index.js +505 -0
- package/templates/worker/src/lib/railway.js +438 -0
- package/templates/worker/src/lib/tokens.js +81 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
prerelease:
|
|
7
|
+
description: "Tag as pre-release (staging-only, skips production deploy)"
|
|
8
|
+
required: false
|
|
9
|
+
default: false
|
|
10
|
+
type: boolean
|
|
11
|
+
|
|
12
|
+
concurrency: ${{ github.workflow }}
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: write
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
release:
|
|
19
|
+
name: Tag & Release
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
with:
|
|
24
|
+
ref: main
|
|
25
|
+
fetch-depth: 0
|
|
26
|
+
|
|
27
|
+
- name: Configure git
|
|
28
|
+
run: |
|
|
29
|
+
git config user.name "github-actions[bot]"
|
|
30
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
31
|
+
|
|
32
|
+
- name: Push to production branch
|
|
33
|
+
if: ${{ !inputs.prerelease }}
|
|
34
|
+
run: |
|
|
35
|
+
git fetch origin
|
|
36
|
+
if git ls-remote --exit-code --heads origin production > /dev/null; then
|
|
37
|
+
git checkout production && git merge --ff-only origin/main
|
|
38
|
+
else
|
|
39
|
+
git checkout -b production origin/main
|
|
40
|
+
fi
|
|
41
|
+
git push origin production
|
|
42
|
+
git checkout main
|
|
43
|
+
|
|
44
|
+
- name: Generate tag
|
|
45
|
+
id: tag
|
|
46
|
+
run: |
|
|
47
|
+
BASE="v$(date +%Y.%m.%d)"
|
|
48
|
+
SUFFIX="${{ inputs.prerelease && '-rc' || '' }}"
|
|
49
|
+
# Count only exact-format matches to avoid rc tags polluting production counts
|
|
50
|
+
EXISTING=$(git tag -l | grep -cE "^${BASE}${SUFFIX}(\.[0-9]+)?$" || true)
|
|
51
|
+
if [ "$EXISTING" -eq "0" ]; then
|
|
52
|
+
echo "tag=${BASE}${SUFFIX}" >> "$GITHUB_OUTPUT"
|
|
53
|
+
else
|
|
54
|
+
echo "tag=${BASE}${SUFFIX}.${EXISTING}" >> "$GITHUB_OUTPUT"
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
- name: Tag release
|
|
58
|
+
run: |
|
|
59
|
+
git tag -a "${{ steps.tag.outputs.tag }}" -m "Release ${{ steps.tag.outputs.tag }}"
|
|
60
|
+
git push origin "${{ steps.tag.outputs.tag }}"
|
|
61
|
+
|
|
62
|
+
- name: Create GitHub Release
|
|
63
|
+
uses: softprops/action-gh-release@v2
|
|
64
|
+
with:
|
|
65
|
+
tag_name: ${{ steps.tag.outputs.tag }}
|
|
66
|
+
generate_release_notes: true
|
|
67
|
+
prerelease: ${{ inputs.prerelease }}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { defineRailway, project, service } from "railway/iac";
|
|
2
|
+
|
|
3
|
+
export default defineRailway(() => {
|
|
4
|
+
const web = service("web", {
|
|
5
|
+
build: "pnpm install --frozen-lockfile && pnpm db:generate && pnpm --dir apps/web build:deploy",
|
|
6
|
+
start: "HOSTNAME=0.0.0.0 pnpm --dir apps/web start:deploy",
|
|
7
|
+
preDeploy: "pnpm db:migrate:deploy",
|
|
8
|
+
healthcheck: "/api/health",
|
|
9
|
+
healthcheckTimeout: 120,
|
|
10
|
+
env: {
|
|
11
|
+
DATABASE_URL: "${{Postgres.DATABASE_URL}}",
|
|
12
|
+
REDIS_URL: "${{Redis.REDIS_URL}}",
|
|
13
|
+
NODE_ENV: "production",
|
|
14
|
+
AUTH_SECRET: preserve(),
|
|
15
|
+
NEXTAUTH_SECRET: preserve(),
|
|
16
|
+
STORAGE_PROVIDER: "local",
|
|
17
|
+
AUTH_ALLOW_SIGNUP: "false",
|
|
18
|
+
HOSTNAME: "0.0.0.0",
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const worker = service("worker", {
|
|
23
|
+
build: "pnpm install --frozen-lockfile && pnpm db:generate",
|
|
24
|
+
start: "pnpm --dir apps/worker start:deploy",
|
|
25
|
+
preDeploy: "pnpm db:migrate:deploy",
|
|
26
|
+
env: {
|
|
27
|
+
DATABASE_URL: "${{Postgres.DATABASE_URL}}",
|
|
28
|
+
REDIS_URL: "${{Redis.REDIS_URL}}",
|
|
29
|
+
NODE_ENV: "production",
|
|
30
|
+
AUTH_SECRET: preserve(),
|
|
31
|
+
NEXTAUTH_SECRET: preserve(),
|
|
32
|
+
STORAGE_PROVIDER: "local",
|
|
33
|
+
WORKER_CONCURRENCY: "5",
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return project("__QUARK_PROJECT_NAME__", {
|
|
38
|
+
resources: [web, worker],
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# __QUARK_PROJECT_NAME__ - AI Context
|
|
2
|
+
|
|
3
|
+
> Scaffolded with [Quark](https://github.com/usequark/quark) on __QUARK_SCAFFOLD_DATE__.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
docker compose up -d # PostgreSQL, Redis, Mailpit
|
|
9
|
+
pnpm install
|
|
10
|
+
pnpm db:generate # Generate Prisma client
|
|
11
|
+
pnpm db:migrate # Apply database migrations
|
|
12
|
+
pnpm db:seed # Seed development data
|
|
13
|
+
pnpm dev # Start web + worker
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Project Structure
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
__QUARK_PROJECT_NAME__/
|
|
20
|
+
├── apps/
|
|
21
|
+
│ ├── web/ # Next.js 16 (App Router, Server Actions)
|
|
22
|
+
__QUARK_OPTIONAL_APPS__├── packages/
|
|
23
|
+
│ ├── db/ # Prisma schema, client, query functions
|
|
24
|
+
│ ├── config/ # Environment validation & shared config
|
|
25
|
+
__QUARK_OPTIONAL_PACKAGES__├── docker-compose.yml
|
|
26
|
+
├── .env # Local environment secrets (never commit)
|
|
27
|
+
└── .env.example # Template - copy to .env to get started
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Non-Negotiable Rules
|
|
31
|
+
|
|
32
|
+
- **ESM only** — `import`/`export`. Never `require()` or `module.exports`.
|
|
33
|
+
- **No TypeScript** — `.js` and `.jsx` files only.
|
|
34
|
+
- **Zod required** — all Server Actions and API routes must validate with Zod.
|
|
35
|
+
- **No `throw new Error()`** — use `AppError` / `ValidationError` from `@techstream/quark-core/errors`.
|
|
36
|
+
- **No `console.log`** — use `createLogger(name)` from `@techstream/quark-core`.
|
|
37
|
+
- **DB models** — every Prisma model must include `createdAt DateTime @default(now())` and `updatedAt DateTime @updatedAt`.
|
|
38
|
+
|
|
39
|
+
## Package Imports
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
// Published (from npm) - @techstream/ prefix:
|
|
43
|
+
import { AppError, ValidationError } from "@techstream/quark-core/errors";
|
|
44
|
+
import { createLogger } from "@techstream/quark-core";
|
|
45
|
+
import { getCurrentSession } from "@techstream/quark-core";
|
|
46
|
+
|
|
47
|
+
// Workspace packages - ALWAYS use @__QUARK_SCOPE__/:
|
|
48
|
+
import { prisma } from "@__QUARK_SCOPE__/db";
|
|
49
|
+
import { loadConfig } from "@__QUARK_SCOPE__/config";
|
|
50
|
+
import { Button, Card } from "@__QUARK_SCOPE__/ui";
|
|
51
|
+
import { JOB_NAMES } from "@__QUARK_SCOPE__/jobs";
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## UI Components
|
|
55
|
+
|
|
56
|
+
All components from `@__QUARK_SCOPE__/ui`. Tailwind-only, Server Component-safe.
|
|
57
|
+
|
|
58
|
+
- `Button`, `Input`, `Label`, `Textarea`, `Select`, `Checkbox` — form primitives
|
|
59
|
+
- `Badge`, `Card`/`CardHeader`/`CardTitle`/`CardContent`/`CardFooter`
|
|
60
|
+
- `Table`/`TableHeader`/`TableBody`/`TableRow`/`TableHead`/`TableCell`
|
|
61
|
+
- `Skeleton`, `ErrorBanner`, `Footer`, `Navbar`/`MobileNavbar`
|
|
62
|
+
- `Dialog` *(client)*, `Toast`/`useToast` *(client)*, `ThemeProvider`/`useTheme` *(client)*
|
|
63
|
+
|
|
64
|
+
Import from `@__QUARK_SCOPE__/ui` — never `@/components/ui/*`. Every component accepts `className`.
|
|
65
|
+
|
|
66
|
+
## Server Actions
|
|
67
|
+
|
|
68
|
+
```javascript
|
|
69
|
+
"use server";
|
|
70
|
+
import { z } from "zod";
|
|
71
|
+
import { ValidationError, AppError } from "@techstream/quark-core/errors";
|
|
72
|
+
import { getCurrentSession } from "@techstream/quark-core";
|
|
73
|
+
import { createLogger } from "@techstream/quark-core";
|
|
74
|
+
import { prisma } from "@__QUARK_SCOPE__/db";
|
|
75
|
+
|
|
76
|
+
const log = createLogger("action:example");
|
|
77
|
+
const schema = z.object({ title: z.string().min(1).max(255) });
|
|
78
|
+
|
|
79
|
+
export async function createExample(formData) {
|
|
80
|
+
const session = await getCurrentSession();
|
|
81
|
+
if (!session) throw new AppError("Unauthorized", 401);
|
|
82
|
+
const parsed = schema.safeParse(Object.fromEntries(formData));
|
|
83
|
+
if (!parsed.success) throw new ValidationError(parsed.error.flatten());
|
|
84
|
+
const result = await prisma.example.create({ data: parsed.data });
|
|
85
|
+
log.info("created", { id: result.id });
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## API Routes
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
import { NextResponse } from "next/server";
|
|
94
|
+
import { AppError } from "@techstream/quark-core/errors";
|
|
95
|
+
|
|
96
|
+
export async function GET(request) {
|
|
97
|
+
try {
|
|
98
|
+
return NextResponse.json({ data });
|
|
99
|
+
} catch (error) {
|
|
100
|
+
if (error instanceof AppError) {
|
|
101
|
+
return NextResponse.json({ error: error.message }, { status: error.statusCode });
|
|
102
|
+
}
|
|
103
|
+
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Database
|
|
109
|
+
|
|
110
|
+
- Schema: `packages/db/prisma/schema.prisma`
|
|
111
|
+
- Query helpers: `packages/db/src/queries.js` — do not call `prisma.*` directly in pages/actions.
|
|
112
|
+
- After schema changes: `pnpm db:generate` then `pnpm db:migrate`.
|
|
113
|
+
|
|
114
|
+
## Auth
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
import { getCurrentSession } from "@techstream/quark-core";
|
|
118
|
+
const session = await getCurrentSession();
|
|
119
|
+
if (!session) redirect("/login"); // Server Component
|
|
120
|
+
if (!session) throw new AppError("Unauthorized", 401); // Server Action
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Background Jobs
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
// Dispatch:
|
|
127
|
+
import { createQueue, addJob } from "@techstream/quark-core";
|
|
128
|
+
import { JOB_NAMES } from "@__QUARK_SCOPE__/jobs";
|
|
129
|
+
await addJob(createQueue("default"), JOB_NAMES.SEND_WELCOME_EMAIL, { userId });
|
|
130
|
+
|
|
131
|
+
// Handle in apps/worker/src/handlers/<job-name>.js:
|
|
132
|
+
export async function handleSendEmail({ data }) { /* ... */ }
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Testing
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
docker compose up -d # Required: Postgres + Redis
|
|
139
|
+
pnpm test # Run all tests
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Tests are co-located: `feature.test.js` next to `feature.js`. Uses Node.js built-in `node --test`.
|
|
143
|
+
|
|
144
|
+
## Deployment
|
|
145
|
+
|
|
146
|
+
Railway with two services: **web** (`apps/web`) and **worker** (`apps/worker`). Migrations run automatically on deploy.
|
|
147
|
+
|
|
148
|
+
## Key Files
|
|
149
|
+
|
|
150
|
+
| File | Purpose |
|
|
151
|
+
|---|---|
|
|
152
|
+
| `packages/db/prisma/schema.prisma` | Database schema |
|
|
153
|
+
| `packages/db/src/queries.js` | Query helpers |
|
|
154
|
+
| `packages/config/src/validate-env.js` | Env var validation |
|
|
155
|
+
| `apps/web/src/app/` | Next.js pages and API routes |
|
|
156
|
+
| `apps/web/src/lib/auth.js` | NextAuth configuration |
|
|
157
|
+
| `apps/worker/src/handlers/` | Job handler functions |
|
|
158
|
+
| `apps/web/public/sw.js` | Service worker (if PWA enabled) |
|
|
159
|
+
| `apps/web/src/app/manifest.json` | PWA manifest (if enabled) |
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
*Update this file when you make structural changes: new packages, deployment targets, or major conventions.*
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# __QUARK_PROJECT_NAME__
|
|
2
|
+
|
|
3
|
+
> __QUARK_PROJECT_BRIEF__
|
|
4
|
+
|
|
5
|
+
## Read this first
|
|
6
|
+
|
|
7
|
+
1. `CLAUDE.md` - project rules and conventions (keep this updated)
|
|
8
|
+
2. `docs/` - guides and walkthroughs
|
|
9
|
+
3. `openapi.yaml` - API contract
|
|
10
|
+
4. `.opencode/skills/` - embedded skills (how to build bookings, CRM, CMS, AI, and more on this project)
|
|
11
|
+
|
|
12
|
+
## Quick start
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
docker compose up -d # Start PostgreSQL, Redis, Mailpit
|
|
16
|
+
pnpm install # Install dependencies
|
|
17
|
+
pnpm db:migrate # Apply migrations
|
|
18
|
+
pnpm dev # Start the app (and worker, if included)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Open [http://localhost:3000](http://localhost:3000).
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# __QUARK_PROJECT_NAME__
|
|
2
|
+
|
|
3
|
+
Scaffolded with [Quark](https://github.com/usequark/quark) on __QUARK_SCAFFOLD_DATE__.
|
|
4
|
+
|
|
5
|
+
## Start Here
|
|
6
|
+
|
|
7
|
+
- `docs/START_HERE.md` - current onboarding path for this project
|
|
8
|
+
- `docs/FIRST_FEATURE.md` - add your first domain model, query helper, and page
|
|
9
|
+
- `.opencode/skills/` - embedded skills (add a model, endpoint, dashboard, or build a booking/CRM/CMS/AI system)
|
|
10
|
+
- `CLAUDE.md` - AI tool context for Claude Code, Cursor, Copilot, and others
|
|
11
|
+
|
|
12
|
+
## Local Development
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
docker compose up -d # Start PostgreSQL, Redis, Mailpit
|
|
16
|
+
pnpm install # Install dependencies
|
|
17
|
+
pnpm db:migrate # Apply migrations
|
|
18
|
+
pnpm db:seed # Seed the database (admin user + sample data)
|
|
19
|
+
pnpm dev # Start the app (and worker, if included)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Open [http://localhost:3000](http://localhost:3000)
|
|
23
|
+
|
|
24
|
+
## UI References
|
|
25
|
+
|
|
26
|
+
- `apps/web/src/app/page.js` - production-style public page built from the shared UI package
|
|
27
|
+
- `packages/ui/README.md` - supported component surface and props
|
|
28
|
+
|
|
29
|
+
## Included in This Project
|
|
30
|
+
|
|
31
|
+
| Area | Status | Where to start |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| Web app | Included | `apps/web/src/app/` |
|
|
34
|
+
| Database | Included | `packages/db/prisma/schema.prisma` |
|
|
35
|
+
| Config | Included | `packages/config/src/load-config.js` |
|
|
36
|
+
__QUARK_FEATURE_ROWS__
|
|
37
|
+
|
|
38
|
+
Optional features can be added later with:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx @techstream/quark-create-app add <feature>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### PWA (Optional)
|
|
45
|
+
|
|
46
|
+
When enabled, your app includes:
|
|
47
|
+
- `src/app/manifest.json` — web app manifest for installability (served natively by Next.js)
|
|
48
|
+
- `public/sw.js` — vanilla service worker with cache-first static assets and network-first navigation
|
|
49
|
+
- `src/app/_components/PWARegister.js` — client component that registers the SW in production
|
|
50
|
+
|
|
51
|
+
Edit `manifest.json` to set your app name, theme color, and icons.
|
|
52
|
+
The service worker uses standard Web APIs with no external dependencies.
|
|
53
|
+
|
|
54
|
+
## First Files to Edit
|
|
55
|
+
|
|
56
|
+
- `packages/db/prisma/schema.prisma` - add your domain models
|
|
57
|
+
- `packages/db/src/queries.js` - add reusable query helpers
|
|
58
|
+
- `apps/web/src/app/` - build pages, Server Actions, and route handlers
|
|
59
|
+
__QUARK_FIRST_EDITS__
|
|
60
|
+
|
|
61
|
+
## Feature Guides
|
|
62
|
+
|
|
63
|
+
- `docs/FIRST_FEATURE.md` - the best first walkthrough for product work
|
|
64
|
+
- `packages/db/src/queries.js` - the canonical place for reusable data access
|
|
65
|
+
__QUARK_FEATURE_GUIDES__
|
|
66
|
+
|
|
67
|
+
## Database
|
|
68
|
+
|
|
69
|
+
| Task | Command |
|
|
70
|
+
|------|---------|
|
|
71
|
+
| Run migrations | `pnpm db:migrate` |
|
|
72
|
+
| Push schema (no migration) | `pnpm db:push` |
|
|
73
|
+
| Generate Prisma client | `pnpm db:generate` |
|
|
74
|
+
| Seed database | `pnpm db:seed` |
|
|
75
|
+
| Open Prisma Studio | `pnpm db:studio` |
|
|
76
|
+
|
|
77
|
+
## Other Commands
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pnpm build # Build all packages
|
|
81
|
+
pnpm test # Run all tests (requires Docker)
|
|
82
|
+
pnpm lint # Lint + format check (Biome)
|
|
83
|
+
pnpm clean:check # Review reclaimable build artifacts
|
|
84
|
+
pnpm clean # Remove local build artifacts
|
|
85
|
+
pnpm clean:deep # Also remove repo-local temp workspaces
|
|
86
|
+
QUARK_SKIP_AUTO_CLEAN=1 pnpm dev # Disable dev auto-clean for this run
|
|
87
|
+
npx @techstream/quark-create-app update --scaffold-check # Review scaffold-managed drift
|
|
88
|
+
npx @techstream/quark-create-app update --scaffold-check --fail-on-drift # CI-friendly drift check
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Launch
|
|
92
|
+
|
|
93
|
+
### 1. Push to GitHub
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
gh repo create __QUARK_PROJECT_NAME__ --private --source=. --push
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
No `gh` CLI? Go to [github.com/new](https://github.com/new), create the repo, then:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
git remote add origin https://github.com/<you>/__QUARK_PROJECT_NAME__.git
|
|
103
|
+
git push -u origin main
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
> **Nested inside an existing repository?** GitHub Actions only reads workflow
|
|
107
|
+
> files from the repository root, so this project's `.github/workflows/*.yml`
|
|
108
|
+
> will be ignored. Copy them into the parent repository's
|
|
109
|
+
> `.github/workflows/` and add `working-directory: <path-to-this-project>` to
|
|
110
|
+
> each job's steps (plus path filters so the workflows only run on changes in
|
|
111
|
+
> this directory).
|
|
112
|
+
|
|
113
|
+
### 2. Deploy on Railway
|
|
114
|
+
|
|
115
|
+
> **One-click Railway template coming soon.** For now, follow these steps:
|
|
116
|
+
|
|
117
|
+
1. Go to [railway.app](https://railway.app) → **New Project** → **Deploy from GitHub repo** → select `__QUARK_PROJECT_NAME__`
|
|
118
|
+
2. In the service → **Settings** → **Source**:
|
|
119
|
+
- Set **Root Directory** to `/` (the repo root - Railpack needs the full monorepo context to resolve workspace dependencies)
|
|
120
|
+
3. In the project → **+ New** → **Database** → **Add PostgreSQL**
|
|
121
|
+
4. In the project → **+ New** → **Database** → **Add Redis**
|
|
122
|
+
5. Use `.env.railway.example` as the default Railway variable set:
|
|
123
|
+
- Copy the non-service defaults into Railway shared variables or each service's variables as your starting point
|
|
124
|
+
- Wire `DATABASE_URL` and `REDIS_URL` with Railway service references or paste the managed service connection strings Railway provides
|
|
125
|
+
- Replace `NEXTAUTH_SECRET`, `APP_URL`, and `ADMIN_PASSWORD`
|
|
126
|
+
- `HOSTNAME=0.0.0.0` is included so the standalone Next server binds correctly on Railway
|
|
127
|
+
- Apply `DATABASE_URL`, `REDIS_URL`, `STORAGE_PROVIDER`, and `WORKER_CONCURRENCY` to the worker too if you deploy it
|
|
128
|
+
6. *(If you included the worker)* **+ New** → **GitHub Repo** → same repo → **Root Directory** → `/`
|
|
129
|
+
|
|
130
|
+
Railway auto-deploys on every push to `main`. Migrations run automatically before each deploy via `preDeploy` in `.railway/railway.ts`.
|
|
131
|
+
|
|
132
|
+
If you need to seed Railway manually after the first migration, run `SEED_PROFILE=minimal pnpm db:seed` inside a Railway shell/one-off command, or from a machine using Railway's externally reachable database credentials, and provide the required admin credentials. The seed guard rejects remote seeds without an explicit profile on purpose.
|
|
133
|
+
|
|
134
|
+
> **Important:** Root Directory must be `/` (not `apps/web`) so Railpack can resolve pnpm workspace dependencies. The `.railway/railway.ts` file defines all service configuration via Infrastructure as Code.
|
|
135
|
+
>
|
|
136
|
+
> Railway starts the web service with `HOSTNAME=0.0.0.0 pnpm --dir apps/web start:deploy` (configured in `.railway/railway.ts`), which delegates to the standalone entrypoint `node .next/standalone/apps/web/server.js`. This forces the standalone Next server to bind on Railway's network interface. The `pnpm start` script uses `next start` which requires full `node_modules` - it's for local testing only.
|
|
137
|
+
|
|
138
|
+
## AI-Assisted Development
|
|
139
|
+
|
|
140
|
+
This project ships with pre-loaded context for Claude Code, Cursor, GitHub Copilot, and others - see `CLAUDE.md` for the full reference.
|
|
141
|
+
|
|
142
|
+
**Suggested first prompt:**
|
|
143
|
+
|
|
144
|
+
> I'm building __QUARK_PROJECT_NAME__ - [describe what your app does in one sentence].
|
|
145
|
+
>
|
|
146
|
+
> Start by reviewing `CLAUDE.md` so you understand the full stack, then:
|
|
147
|
+
> 1. Add the Prisma models we need to `packages/db/prisma/schema.prisma`
|
|
148
|
+
> 2. Create the query helpers in `packages/db/src/queries.js`
|
|
149
|
+
> 3. Build the first page in `apps/web/src/app/` using our UI component system
|
|
150
|
+
|
|
151
|
+
Replace that bracketed description with what you're actually building, and the AI has everything it needs.
|
|
152
|
+
|
|
153
|
+
## Structure
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
__QUARK_PROJECT_NAME__/
|
|
157
|
+
├── apps/
|
|
158
|
+
│ ├── web/ # Next.js 16 (App Router, Server Actions)
|
|
159
|
+
__QUARK_OPTIONAL_APPS__├── packages/
|
|
160
|
+
│ ├── db/ # Prisma schema + query helpers
|
|
161
|
+
│ └── config/ # Environment validation
|
|
162
|
+
├── docker-compose.yml
|
|
163
|
+
└── CLAUDE.md # AI tool context - keep this updated
|
|
164
|
+
```
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# --- Database Configuration ---
|
|
2
|
+
# Defaults match docker-compose.yml - change for production
|
|
3
|
+
POSTGRES_HOST=localhost
|
|
4
|
+
POSTGRES_PORT=5430
|
|
5
|
+
POSTGRES_USER=quark_user
|
|
6
|
+
POSTGRES_PASSWORD=quark_password
|
|
7
|
+
POSTGRES_DB=quark_dev
|
|
8
|
+
|
|
9
|
+
# --- Redis Configuration ---
|
|
10
|
+
REDIS_HOST=localhost
|
|
11
|
+
REDIS_PORT=6385
|
|
12
|
+
|
|
13
|
+
# --- Mail Configuration (Mailpit in development) ---
|
|
14
|
+
MAIL_HOST=localhost
|
|
15
|
+
MAIL_SMTP_PORT=1030
|
|
16
|
+
MAIL_UI_PORT=8030
|
|
17
|
+
|
|
18
|
+
# --- Email Provider ---
|
|
19
|
+
# EMAIL_PROVIDER=smtp
|
|
20
|
+
# EMAIL_FROM=YourApp <noreply@yourdomain.com>
|
|
21
|
+
|
|
22
|
+
# --- NextAuth Configuration ---
|
|
23
|
+
# REQUIRED: must match the exact URL the web app runs on (no trailing slash)
|
|
24
|
+
NEXTAUTH_URL=http://localhost:3005
|
|
25
|
+
# Generate a secret: node --input-type=module -e "import { randomBytes } from 'node:crypto'; console.log(randomBytes(32).toString('hex'))"
|
|
26
|
+
NEXTAUTH_SECRET=replace_with_a_long_random_secret
|
|
27
|
+
|
|
28
|
+
# --- OAuth Providers (optional) ---
|
|
29
|
+
# Uncomment and fill in to enable GitHub / Google sign-in
|
|
30
|
+
# GITHUB_ID=your_github_oauth_app_client_id
|
|
31
|
+
# GITHUB_SECRET=your_github_oauth_app_client_secret
|
|
32
|
+
# GOOGLE_CLIENT_ID=your_google_oauth_client_id
|
|
33
|
+
# GOOGLE_CLIENT_SECRET=your_google_oauth_client_secret
|
|
34
|
+
|
|
35
|
+
# --- SEO / Indexing ---
|
|
36
|
+
# Set to "true" ONLY in your production deployment.
|
|
37
|
+
# Staging and preview environments should leave this unset so they are not indexed.
|
|
38
|
+
# ALLOW_INDEXING=true
|
|
39
|
+
|
|
40
|
+
# --- Optional: Umami Analytics ---
|
|
41
|
+
# Leave all three unset to disable analytics cleanly.
|
|
42
|
+
# Set URL + website ID to load Umami script.js in the root layout head.
|
|
43
|
+
# Replay stays off unless NEXT_PUBLIC_UMAMI_REPLAY_ENABLED=true.
|
|
44
|
+
# Umami Links and Pixels are dashboard-generated URLs, so the scaffold does not add extra env vars for them.
|
|
45
|
+
# Use tracked links in campaigns/emails and pixel URLs only in HTML emails or external pages.
|
|
46
|
+
# NEXT_PUBLIC_UMAMI_URL=https://stats.example.com
|
|
47
|
+
# NEXT_PUBLIC_UMAMI_WEBSITE_ID=00000000-0000-0000-0000-000000000000
|
|
48
|
+
# NEXT_PUBLIC_UMAMI_REPLAY_ENABLED=false
|
|
49
|
+
|
|
50
|
+
# --- Web App Configuration ---
|
|
51
|
+
PORT=3005
|
|
52
|
+
# REQUIRED for container deployments (Docker/Railway/Vercel standalone):
|
|
53
|
+
# HOSTNAME=0.0.0.0
|
|
54
|
+
|
|
55
|
+
# --- Booking Slots ---
|
|
56
|
+
# PER_SERVICE (default): each service has its own slot inventory.
|
|
57
|
+
# SHARED: all services share the same slot inventory.
|
|
58
|
+
# BOOKING_SLOT_SCOPE=PER_SERVICE
|
|
59
|
+
|
|
60
|
+
# --- Authentication ---
|
|
61
|
+
# Toggle this during development to enable or disable public self-service signup.
|
|
62
|
+
AUTH_ALLOW_SIGNUP=true
|
|
63
|
+
|
|
64
|
+
# --- Worker Configuration ---
|
|
65
|
+
WORKER_CONCURRENCY=5
|
|
66
|
+
|
|
67
|
+
# --- Admin Seed ---
|
|
68
|
+
# Used only by `pnpm db:seed` to create the initial admin user.
|
|
69
|
+
# ADMIN_PASSWORD is required - there is no default. Generate one:
|
|
70
|
+
# openssl rand -base64 24
|
|
71
|
+
ADMIN_EMAIL=admin@example.com
|
|
72
|
+
ADMIN_PASSWORD=SET_ME
|
|
73
|
+
# ADMIN_NAME=Admin
|
|
74
|
+
|
|
75
|
+
# --- Metrics Scraping ---
|
|
76
|
+
# Set to a long random secret to protect /api/metrics from public access.
|
|
77
|
+
# Prometheus scrapers should pass: Authorization: Bearer <token>
|
|
78
|
+
# Leave unset in development - the endpoint is unprotected by default.
|
|
79
|
+
# METRICS_TOKEN=replace_with_a_long_random_secret
|
|
80
|
+
|
|
81
|
+
# --- File Storage ---
|
|
82
|
+
# Default: local filesystem (./uploads). No further config needed for development.
|
|
83
|
+
STORAGE_PROVIDER=local
|
|
84
|
+
# STORAGE_LOCAL_DIR=./uploads
|
|
85
|
+
|
|
86
|
+
# --- Optional: S3-Compatible Storage (AWS S3, Cloudflare R2, MinIO) ---
|
|
87
|
+
# Only configure when STORAGE_PROVIDER=s3. Not required for local development.
|
|
88
|
+
# Install @aws-sdk/client-s3 and @aws-sdk/s3-request-presigner in the app when enabling S3-compatible storage.
|
|
89
|
+
# S3_BUCKET=my-bucket
|
|
90
|
+
# S3_REGION=auto
|
|
91
|
+
# S3_ENDPOINT=https://<account_id>.r2.cloudflarestorage.com
|
|
92
|
+
# S3_ACCESS_KEY_ID=
|
|
93
|
+
# S3_SECRET_ACCESS_KEY=
|
|
94
|
+
# S3_PUBLIC_URL=https://pub-xxx.r2.dev
|
|
95
|
+
|
|
96
|
+
# --- Optional: CDN ---
|
|
97
|
+
# Provider-agnostic CDN base URL for serving assets (CloudFront, Cloudflare, Bunny, etc.).
|
|
98
|
+
# Falls back to /api/files when unset - works fine without a CDN.
|
|
99
|
+
# ASSET_CDN_URL=https://assets.example.com
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
FROM node:22-alpine@sha256:9385cd9f3001dfc3431e8ead12c43e9e1f87cc1b9b5c6cfd0f73865d405b27c4 AS builder
|
|
2
|
+
|
|
3
|
+
ENV NEXT_TELEMETRY_DISABLED=1
|
|
4
|
+
|
|
5
|
+
WORKDIR /app
|
|
6
|
+
|
|
7
|
+
RUN corepack enable
|
|
8
|
+
|
|
9
|
+
COPY . .
|
|
10
|
+
|
|
11
|
+
RUN pnpm install --frozen-lockfile \
|
|
12
|
+
&& pnpm db:generate \
|
|
13
|
+
&& pnpm --dir apps/web build:deploy
|
|
14
|
+
|
|
15
|
+
FROM node:22-alpine@sha256:9385cd9f3001dfc3431e8ead12c43e9e1f87cc1b9b5c6cfd0f73865d405b27c4 AS runtime
|
|
16
|
+
|
|
17
|
+
RUN apk upgrade --no-cache
|
|
18
|
+
|
|
19
|
+
ENV NODE_ENV=production
|
|
20
|
+
ENV NEXT_TELEMETRY_DISABLED=1
|
|
21
|
+
ENV HOSTNAME=0.0.0.0
|
|
22
|
+
ENV PORT=3000
|
|
23
|
+
|
|
24
|
+
WORKDIR /app
|
|
25
|
+
|
|
26
|
+
RUN rm -rf \
|
|
27
|
+
/usr/local/lib/node_modules/npm \
|
|
28
|
+
/usr/local/bin/npm \
|
|
29
|
+
/usr/local/bin/npx \
|
|
30
|
+
/opt/yarn-v1.22.22 \
|
|
31
|
+
/usr/local/bin/yarn \
|
|
32
|
+
/usr/local/bin/yarnpkg
|
|
33
|
+
|
|
34
|
+
COPY --from=builder /app/apps/web/.next/standalone/apps/web/ ./
|
|
35
|
+
|
|
36
|
+
EXPOSE 3000
|
|
37
|
+
|
|
38
|
+
CMD ["node", "server.js"]
|