@techstream/quark-create-app 1.5.2 → 1.6.0

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.
Files changed (53) hide show
  1. package/README.md +38 -0
  2. package/package.json +5 -2
  3. package/src/index.js +142 -12
  4. package/templates/base-project/.github/copilot-instructions.md +7 -0
  5. package/templates/base-project/.github/dependabot.yml +12 -0
  6. package/templates/base-project/.github/skills/project-context/SKILL.md +106 -0
  7. package/templates/base-project/.github/workflows/ci.yml +97 -0
  8. package/templates/base-project/.github/workflows/dependabot-auto-merge.yml +22 -0
  9. package/templates/base-project/.github/workflows/release.yml +38 -0
  10. package/templates/base-project/apps/web/biome.json +7 -0
  11. package/templates/base-project/apps/web/jsconfig.json +10 -0
  12. package/templates/base-project/apps/web/next.config.js +86 -1
  13. package/templates/base-project/apps/web/package.json +4 -4
  14. package/templates/base-project/apps/web/src/app/api/auth/register/route.js +9 -9
  15. package/templates/base-project/apps/web/src/app/api/files/route.js +3 -2
  16. package/templates/base-project/apps/web/src/app/layout.js +3 -4
  17. package/templates/base-project/apps/web/src/app/manifest.js +12 -0
  18. package/templates/base-project/apps/web/src/app/robots.js +21 -0
  19. package/templates/base-project/apps/web/src/app/sitemap.js +20 -0
  20. package/templates/base-project/apps/web/src/lib/seo/indexing.js +23 -0
  21. package/templates/base-project/apps/web/src/lib/seo/site-metadata.js +33 -0
  22. package/templates/base-project/apps/web/src/proxy.js +1 -2
  23. package/templates/base-project/apps/worker/package.json +4 -4
  24. package/templates/base-project/apps/worker/src/index.js +40 -12
  25. package/templates/base-project/apps/worker/src/index.test.js +296 -15
  26. package/templates/base-project/biome.json +44 -0
  27. package/templates/base-project/docker-compose.yml +7 -4
  28. package/templates/base-project/package.json +1 -1
  29. package/templates/base-project/packages/db/package.json +1 -1
  30. package/templates/base-project/packages/db/prisma/migrations/20260202061128_initial/migration.sql +42 -0
  31. package/templates/base-project/packages/db/prisma/schema.prisma +20 -16
  32. package/templates/base-project/packages/db/prisma.config.ts +3 -2
  33. package/templates/base-project/packages/db/scripts/seed.js +117 -30
  34. package/templates/base-project/packages/db/src/queries.js +58 -68
  35. package/templates/base-project/packages/db/src/queries.test.js +0 -29
  36. package/templates/base-project/packages/db/src/schemas.js +4 -10
  37. package/templates/base-project/pnpm-workspace.yaml +4 -0
  38. package/templates/base-project/turbo.json +5 -3
  39. package/templates/config/package.json +2 -0
  40. package/templates/config/src/environment.js +270 -0
  41. package/templates/config/src/index.js +10 -18
  42. package/templates/config/src/load-config.js +135 -0
  43. package/templates/config/src/validate-env.js +60 -2
  44. package/templates/jobs/package.json +2 -2
  45. package/templates/jobs/src/definitions.test.js +34 -0
  46. package/templates/jobs/src/index.js +1 -1
  47. package/templates/ui/package.json +4 -4
  48. package/templates/ui/src/button.test.js +23 -0
  49. package/templates/ui/src/index.js +1 -3
  50. package/templates/base-project/apps/web/src/app/api/posts/[id]/route.js +0 -65
  51. package/templates/base-project/apps/web/src/app/api/posts/route.js +0 -42
  52. package/templates/ui/src/card.js +0 -14
  53. package/templates/ui/src/input.js +0 -11
package/README.md CHANGED
@@ -24,12 +24,50 @@ pnpm db:migrate
24
24
  pnpm dev
25
25
  ```
26
26
 
27
+ ## Commands
28
+
29
+ ```bash
30
+ # Create a new project
31
+ npx @techstream/quark-create-app@latest my-awesome-app
32
+
33
+ # Update Quark core in an existing project
34
+ npx @techstream/quark-create-app update
35
+
36
+ # Check for updates without applying
37
+ npx @techstream/quark-create-app update --check
38
+ ```
39
+
40
+ Aliases:
41
+ - `quark-create-app`
42
+ - `create-quark-app`
43
+ - `quark-update`
44
+
27
45
  ## Common Tasks
28
46
 
29
47
  - **Update Quark packages**: `quark-update` or `pnpm update @techstream/quark-*`
30
48
  - **Check for updates**: `quark-update --check`
31
49
  - **Configure environment**: Edit `.env` file (see `.env.example`)
32
50
 
51
+ ## CLI Testing
52
+
53
+ ```bash
54
+ # Lightweight template checks
55
+ pnpm test
56
+
57
+ # E2E scaffold simulation
58
+ pnpm test:e2e
59
+
60
+ # Full build verification (opt-in)
61
+ QUARK_CLI_BUILD_TEST=1 pnpm test:build
62
+ ```
63
+
64
+ ## Troubleshooting
65
+
66
+ - **pnpm install fails**: Ensure `pnpm` is installed and Node.js >= 22.
67
+ - **Prisma generate fails**: Run `pnpm --filter db db:generate` inside the project.
68
+ - **Docker ports conflict**: The CLI auto-selects free ports. Check `.env` for assigned values.
69
+ - **Missing env vars**: Copy `.env.example` to `.env` and fill required values.
70
+
33
71
  ## Support
34
72
 
35
73
  For issues, questions, and discussions:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@techstream/quark-create-app",
3
- "version": "1.5.2",
3
+ "version": "1.6.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "quark-create-app": "src/index.js",
@@ -14,7 +14,7 @@
14
14
  ],
15
15
  "dependencies": {
16
16
  "chalk": "^5.6.2",
17
- "commander": "^12.1.0",
17
+ "commander": "^14.0.3",
18
18
  "execa": "^9.6.1",
19
19
  "fs-extra": "^11.3.3",
20
20
  "prompts": "^2.4.2"
@@ -28,7 +28,10 @@
28
28
  },
29
29
  "license": "ISC",
30
30
  "scripts": {
31
+ "sync-templates": "node scripts/sync-templates.js",
32
+ "sync-templates:check": "node scripts/sync-templates.js --check",
31
33
  "test": "node test-cli.js",
34
+ "test:build": "node test-build.js",
32
35
  "test:e2e": "node test-e2e.js",
33
36
  "test:integration": "node test-integration.js",
34
37
  "test:all": "node test-all.js"
package/src/index.js CHANGED
@@ -264,6 +264,54 @@ program
264
264
  const targetDir = validateProjectName(projectName);
265
265
  const scope = projectName.toLowerCase().replace(/[^a-z0-9-]/g, "");
266
266
 
267
+ // Clean up orphaned Docker volumes from a previous project with the same name.
268
+ // Docker Compose names volumes as "<project>_postgres_data", "<project>_redis_data".
269
+ // These persist even if the project directory is manually deleted, causing
270
+ // authentication failures when the new project generates different credentials.
271
+ // We also need to stop any running containers that reference these volumes.
272
+ try {
273
+ const volumePrefix = `${projectName}_`;
274
+ const { stdout } = await execa("docker", [
275
+ "volume",
276
+ "ls",
277
+ "--filter",
278
+ `name=${volumePrefix}`,
279
+ "--format",
280
+ "{{.Name}}",
281
+ ]);
282
+ const orphanedVolumes = stdout
283
+ .split("\n")
284
+ .filter((v) => v.startsWith(volumePrefix));
285
+ if (orphanedVolumes.length > 0) {
286
+ // Stop and remove any containers using these volumes first
287
+ const { stdout: containerOut } = await execa("docker", [
288
+ "ps",
289
+ "-a",
290
+ "--filter",
291
+ `name=${projectName}`,
292
+ "--format",
293
+ "{{.ID}}",
294
+ ]);
295
+ const containers = containerOut.split("\n").filter(Boolean);
296
+ if (containers.length > 0) {
297
+ await execa("docker", ["rm", "-f", ...containers]);
298
+ }
299
+ // Remove the Docker network if it exists
300
+ try {
301
+ await execa("docker", ["network", "rm", `${projectName}_default`]);
302
+ } catch {
303
+ // Network may not exist — fine
304
+ }
305
+ // Now remove the orphaned volumes
306
+ for (const vol of orphanedVolumes) {
307
+ await execa("docker", ["volume", "rm", "-f", vol]);
308
+ }
309
+ console.log(chalk.green(" ✓ Cleaned up orphaned Docker volumes"));
310
+ }
311
+ } catch {
312
+ // Docker not available — fine
313
+ }
314
+
267
315
  // Check if directory already exists
268
316
  if (await fs.pathExists(targetDir)) {
269
317
  const { overwrite } = await prompts({
@@ -278,13 +326,13 @@ program
278
326
  process.exit(1);
279
327
  }
280
328
 
281
- // Clean up Docker resources (volumes hold old credentials)
329
+ // Stop any running Docker containers for this project
282
330
  try {
283
- await execa("docker", ["compose", "down", "-v"], {
331
+ await execa("docker", ["compose", "down"], {
284
332
  cwd: targetDir,
285
333
  stdio: "ignore",
286
334
  });
287
- console.log(chalk.green(" ✓ Cleaned up Docker volumes"));
335
+ console.log(chalk.green(" ✓ Stopped existing Docker containers"));
288
336
  } catch {
289
337
  // No docker-compose file or Docker not running — fine
290
338
  }
@@ -426,7 +474,11 @@ program
426
474
 
427
475
  // Step 8: Create .env.example file
428
476
  console.log(chalk.cyan("\n 📋 Creating environment configuration..."));
429
- const envExampleTemplate = `# --- Database Configuration ---
477
+ const envExampleTemplate = `# --- Environment ---
478
+ # Supported: development, test, staging, production (default: development)
479
+ # NODE_ENV=development
480
+
481
+ # --- Database Configuration ---
430
482
  # These map to the service names in docker-compose.yml
431
483
  # ⚠️ SECURITY WARNING: Change these default passwords in production!
432
484
  # Generate strong passwords with: openssl rand -base64 32
@@ -444,13 +496,30 @@ REDIS_PORT=6379
444
496
  # Optional: Set REDIS_URL to override the dynamic construction above
445
497
  # REDIS_URL="redis://localhost:6379"
446
498
 
447
- # --- Mail Configuration (Mailpit in development) ---
499
+ # --- Mail Configuration ---
500
+ # Development: Mailpit local SMTP (defaults below work with docker-compose)
448
501
  MAIL_HOST=localhost
449
502
  MAIL_SMTP_PORT=1025
450
503
  MAIL_UI_PORT=8025
451
504
  # Optional: Set MAIL_SMTP_URL to override the dynamic construction above
452
505
  # MAIL_SMTP_URL="smtp://localhost:1025"
453
506
 
507
+ # Production SMTP: Set these instead of MAIL_* when using a real SMTP relay
508
+ # SMTP_HOST=smtp.example.com
509
+ # SMTP_PORT=587
510
+ # SMTP_SECURE=true
511
+ # SMTP_USER=your_smtp_user
512
+ # SMTP_PASSWORD=your_smtp_password
513
+
514
+ # --- Email Provider ---
515
+ # Provider: "smtp" (default) or "resend"
516
+ # EMAIL_PROVIDER=smtp
517
+ # EMAIL_FROM=App Name <noreply@yourdomain.com>
518
+
519
+ # Resend (only when EMAIL_PROVIDER=resend)
520
+ # Get your API key at: https://resend.com/api-keys
521
+ # RESEND_API_KEY=re_xxxxxxxxxxxxx
522
+
454
523
  # --- Application URL ---
455
524
  # In development, APP_URL is derived automatically from PORT — no need to set it.
456
525
  # In production, set this to your real domain:
@@ -476,23 +545,36 @@ PORT=3000
476
545
  # --- Worker Configuration ---
477
546
  WORKER_CONCURRENCY=5
478
547
 
479
- # --- File Storage Configuration ---
480
- # Storage provider: "local" (default) or "s3" (S3-compatible, e.g. Cloudflare R2)
548
+ # --- File Storage ---
549
+ # Provider: "local" (default) or "s3" (S3-compatible: AWS S3, Cloudflare R2, MinIO)
481
550
  STORAGE_PROVIDER=local
482
- # Local storage directory (only used when STORAGE_PROVIDER=local)
551
+ # Local storage directory (only when STORAGE_PROVIDER=local)
483
552
  # STORAGE_LOCAL_DIR=./uploads
484
553
 
485
- # S3 / Cloudflare R2 Configuration (only used when STORAGE_PROVIDER=s3)
554
+ # S3 / Cloudflare R2 (only when STORAGE_PROVIDER=s3)
486
555
  # S3_BUCKET=your-bucket-name
487
- # S3_REGION=auto
556
+ # S3_REGION=auto # Use "auto" for Cloudflare R2
488
557
  # S3_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com
489
558
  # S3_ACCESS_KEY_ID=your-access-key
490
559
  # S3_SECRET_ACCESS_KEY=your-secret-key
491
560
  # S3_PUBLIC_URL=https://your-public-bucket-domain.com
492
561
 
493
562
  # --- Upload Limits ---
494
- # UPLOAD_MAX_SIZE=10485760
563
+ # UPLOAD_MAX_SIZE=10485760 # Max file size in bytes (default: 10MB)
495
564
  # UPLOAD_ALLOWED_TYPES=image/jpeg,image/png,image/gif,image/webp,image/avif,image/svg+xml,application/pdf
565
+
566
+ # --- Rate Limiting & Security ---
567
+ # RATE_LIMIT_MAX=100 # Max requests per window (default: 1000 dev, 100 prod)
568
+ # RATE_LIMIT_WINDOW_MS=900000 # Window in ms (default: 15 minutes)
569
+ # API_BODY_SIZE_LIMIT=2097152 # 2MB (default)
570
+ # UPLOAD_SIZE_LIMIT=10485760 # 10MB proxy-level limit (default)
571
+
572
+ # --- Logging & Cache ---
573
+ # LOG_LEVEL=debug # debug, info, warn, error (default: debug dev, info prod)
574
+ # CACHE_TTL=60 # Default cache TTL in seconds (default: 60 dev, 600 prod)
575
+
576
+ # --- Database Seeding ---
577
+ # SEED_PROFILE=dev # Options: dev (default, includes audit logs + sample job), minimal (users only)
496
578
  `;
497
579
  await fs.writeFile(
498
580
  path.join(targetDir, ".env.example"),
@@ -561,6 +643,9 @@ WORKER_CONCURRENCY=5
561
643
 
562
644
  # --- File Storage ---
563
645
  STORAGE_PROVIDER=local
646
+
647
+ # --- Database Seeding ---
648
+ # SEED_PROFILE=dev # Options: dev (default), minimal (users only — use for production initial seed)
564
649
  `;
565
650
  await fs.writeFile(path.join(targetDir, ".env"), envContent);
566
651
  console.log(
@@ -581,6 +666,45 @@ STORAGE_PROVIDER=local
581
666
  );
582
667
  console.log(chalk.green(` ✓ .quark-link.json`));
583
668
 
669
+ // Step 10b: Generate project-context skill with actual values
670
+ console.log(chalk.cyan("\n 🤖 Generating project-context skill..."));
671
+ const skillPath = path.join(
672
+ targetDir,
673
+ ".github",
674
+ "skills",
675
+ "project-context",
676
+ "SKILL.md",
677
+ );
678
+ if (await fs.pathExists(skillPath)) {
679
+ let skillContent = await fs.readFile(skillPath, "utf-8");
680
+
681
+ // Build optional packages section
682
+ const optionalLines = features
683
+ .map((f) => {
684
+ const labels = {
685
+ ui: "Shared UI components",
686
+ jobs: "Job queue definitions",
687
+ };
688
+ return `│ ├── ${f}/ # ${labels[f] || f}`;
689
+ })
690
+ .join("\n");
691
+ const optionalBlock = optionalLines ? `${optionalLines}\n` : "";
692
+
693
+ skillContent = skillContent
694
+ .replace(/__QUARK_SCOPE__/g, scope)
695
+ .replace(/__QUARK_PROJECT_NAME__/g, projectName)
696
+ .replace(
697
+ /__QUARK_SCAFFOLD_DATE__/g,
698
+ new Date().toISOString().split("T")[0],
699
+ )
700
+ .replace(/__QUARK_OPTIONAL_PACKAGES__/g, optionalBlock);
701
+
702
+ await fs.writeFile(skillPath, skillContent);
703
+ console.log(
704
+ chalk.green(` ✓ .github/skills/project-context/SKILL.md`),
705
+ );
706
+ }
707
+
584
708
  // Step 11: Initialize git repository
585
709
  console.log(chalk.cyan("\n 📝 Initializing git repository..."));
586
710
  const gitInitialized = await initializeGit(targetDir);
@@ -638,7 +762,13 @@ STORAGE_PROVIDER=local
638
762
  console.log(chalk.white(` 1. cd ${projectName}`));
639
763
  console.log(chalk.white(` 2. docker compose up -d`));
640
764
  console.log(chalk.white(` 3. pnpm db:migrate`));
641
- console.log(chalk.white(` 4. pnpm dev\n`));
765
+ console.log(chalk.white(` 4. pnpm db:seed`));
766
+ console.log(chalk.white(` 5. pnpm dev\n`));
767
+ console.log(
768
+ chalk.dim(
769
+ ` Tip: set SEED_PROFILE=minimal in .env for a lean seed (admin user only)\n`,
770
+ ),
771
+ );
642
772
 
643
773
  console.log(chalk.cyan("Important:"));
644
774
  console.log(
@@ -0,0 +1,7 @@
1
+ <skills>
2
+ <skill>
3
+ <name>project-context</name>
4
+ <description>Project-specific context and conventions. This skill evolves with your project — update it as your architecture grows.</description>
5
+ <file>.github/skills/project-context/SKILL.md</file>
6
+ </skill>
7
+ </skills>
@@ -0,0 +1,12 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: npm
4
+ directory: /
5
+ schedule:
6
+ interval: weekly
7
+ groups:
8
+ production:
9
+ dependency-type: production
10
+ development:
11
+ dependency-type: development
12
+ open-pull-requests-limit: 10
@@ -0,0 +1,106 @@
1
+ ---
2
+ name: project-context
3
+ description: Project-specific context and conventions. This skill evolves with your project — update it as your architecture grows.
4
+ ---
5
+
6
+ # Project Context
7
+
8
+ ## Overview
9
+
10
+ This is a Quark-based full-stack JavaScript application.
11
+
12
+ | Property | Value |
13
+ |---|---|
14
+ | **Scope** | `@__QUARK_SCOPE__` |
15
+ | **Framework** | Quark (scaffolded from `@techstream/quark-create-app`) |
16
+ | **Scaffolded** | __QUARK_SCAFFOLD_DATE__ |
17
+
18
+ ## Project Structure
19
+
20
+ ```
21
+ __QUARK_PROJECT_NAME__/
22
+ ├── apps/
23
+ │ ├── web/ # Next.js (App Router, Server Actions)
24
+ │ └── worker/ # BullMQ background worker
25
+ ├── packages/
26
+ │ ├── db/ # Prisma schema, client, queries
27
+ │ ├── config/ # Environment validation & shared config
28
+ __QUARK_OPTIONAL_PACKAGES__├── docker-compose.yml
29
+ ├── .env # Local environment (git-ignored)
30
+ └── .env.example # Template for environment variables
31
+ ```
32
+
33
+ ## Tech Stack
34
+
35
+ | Layer | Technology |
36
+ |---|---|
37
+ | Runtime | Node.js 24, ES Modules |
38
+ | Package manager | pnpm (workspaces) |
39
+ | Monorepo | Turborepo |
40
+ | Web | Next.js 16 (App Router) |
41
+ | Database | PostgreSQL 16 + Prisma 7 |
42
+ | Queue | BullMQ + Redis 7 |
43
+ | Auth | NextAuth v5 |
44
+ | Validation | Zod 4 |
45
+ | UI | Tailwind CSS + Shadcn |
46
+ | Email | Nodemailer |
47
+ | Linting | Biome |
48
+ | Testing | Node.js built-in test runner |
49
+
50
+ ## Coding Conventions
51
+
52
+ - **ESM only** — use `import`/`export`, never `require`.
53
+ - **No TypeScript** — plain `.js` and `.jsx` files.
54
+ - **Imports:** Use `@techstream/quark-core` for published utilities. Use `@__QUARK_SCOPE__/*` for local packages (db, config, ui, jobs).
55
+ - **Tests:** Co-located `*.test.js` files, run with `node --test`.
56
+ - **Validation:** Zod schemas for all Server Actions and API routes.
57
+ - **Errors:** Use `AppError` / `ValidationError` from `@techstream/quark-core/errors`.
58
+ - **Database models:** Always include `createdAt`/`updatedAt`.
59
+ - **Environment:** All env vars validated in `packages/config/src/validate-env.js`.
60
+
61
+ ## Common Commands
62
+
63
+ ```bash
64
+ pnpm dev # Start all apps in dev mode
65
+ pnpm build # Build everything
66
+ pnpm test # Run all tests
67
+ pnpm lint # Lint with Biome
68
+ pnpm db:generate # Regenerate Prisma client
69
+ pnpm db:push # Push schema changes
70
+ pnpm db:migrate # Run database migrations
71
+ docker compose up -d # Start infrastructure
72
+ ```
73
+
74
+ ## Key Files to Know
75
+
76
+ - `packages/db/prisma/schema.prisma` — Database schema (edit this to add models)
77
+ - `packages/db/src/queries.js` — Database query functions
78
+ - `packages/config/src/validate-env.js` — Environment variable validation
79
+ - `apps/web/src/app/` — Next.js App Router pages and API routes
80
+ - `apps/web/src/lib/auth.js` — Authentication configuration
81
+ - `apps/worker/src/handlers/` — Background job handlers
82
+
83
+ ## Updating Quark Core
84
+
85
+ ```bash
86
+ npx @techstream/quark-create-app update # Update core infrastructure
87
+ pnpm update @techstream/quark-core # Or update directly
88
+ ```
89
+
90
+ ---
91
+
92
+ ## Maintaining This Skill
93
+
94
+ > **Important:** When you make changes that affect this project's architecture,
95
+ > conventions, or structure — such as adding new packages, models, API patterns,
96
+ > environment variables, deployment targets, or team conventions — **update this
97
+ > skill file** to reflect those changes. This ensures future AI interactions
98
+ > always have accurate, up-to-date context.
99
+ >
100
+ > Examples of when to update this file:
101
+ > - Adding a new Prisma model or database table
102
+ > - Introducing a new API route pattern or middleware
103
+ > - Adding or removing a workspace package
104
+ > - Changing deployment infrastructure or CI/CD steps
105
+ > - Establishing new coding conventions or architectural decisions
106
+ > - Adding third-party integrations or services
@@ -0,0 +1,97 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ lint:
11
+ name: Lint
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - uses: pnpm/action-setup@v4
17
+
18
+ - uses: actions/setup-node@v4
19
+ with:
20
+ node-version: 24
21
+ cache: pnpm
22
+
23
+ - run: pnpm install --frozen-lockfile
24
+
25
+ - run: pnpm lint
26
+
27
+ test:
28
+ runs-on: ubuntu-latest
29
+ name: Test
30
+ services:
31
+ postgres:
32
+ image: postgres:16-alpine
33
+ env:
34
+ POSTGRES_USER: postgres
35
+ POSTGRES_PASSWORD: postgres
36
+ POSTGRES_DB: app_test
37
+ ports:
38
+ - 5432:5432
39
+ options: >-
40
+ --health-cmd="pg_isready -U postgres"
41
+ --health-interval=5s
42
+ --health-timeout=5s
43
+ --health-retries=5
44
+ redis:
45
+ image: redis:7-alpine
46
+ ports:
47
+ - 6379:6379
48
+ options: >-
49
+ --health-cmd="redis-cli ping"
50
+ --health-interval=5s
51
+ --health-timeout=5s
52
+ --health-retries=5
53
+ env:
54
+ POSTGRES_USER: postgres
55
+ POSTGRES_PASSWORD: postgres
56
+ POSTGRES_HOST: localhost
57
+ POSTGRES_PORT: "5432"
58
+ POSTGRES_DB: app_test
59
+ REDIS_HOST: localhost
60
+ REDIS_PORT: "6379"
61
+ NEXTAUTH_SECRET: ci-test-secret-must-be-at-least-32-characters-long
62
+ NODE_ENV: test
63
+ steps:
64
+ - uses: actions/checkout@v4
65
+
66
+ - uses: pnpm/action-setup@v4
67
+
68
+ - uses: actions/setup-node@v4
69
+ with:
70
+ node-version: 24
71
+ cache: pnpm
72
+
73
+ - run: pnpm install --frozen-lockfile
74
+
75
+ - run: pnpm db:generate
76
+
77
+ - run: pnpm test
78
+
79
+ build:
80
+ name: Build
81
+ needs: [lint, test]
82
+ runs-on: ubuntu-latest
83
+ steps:
84
+ - uses: actions/checkout@v4
85
+
86
+ - uses: pnpm/action-setup@v4
87
+
88
+ - uses: actions/setup-node@v4
89
+ with:
90
+ node-version: 24
91
+ cache: pnpm
92
+
93
+ - run: pnpm install --frozen-lockfile
94
+
95
+ - run: pnpm db:generate
96
+
97
+ - run: pnpm build
@@ -0,0 +1,22 @@
1
+ name: Auto-merge Dependabot
2
+
3
+ on: pull_request
4
+
5
+ permissions:
6
+ contents: write
7
+ pull-requests: write
8
+
9
+ jobs:
10
+ auto-merge:
11
+ if: github.actor == 'dependabot[bot]'
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: dependabot/fetch-metadata@v2
15
+ id: meta
16
+
17
+ - name: Auto-merge patch updates
18
+ if: steps.meta.outputs.update-type == 'version-update:semver-patch'
19
+ run: gh pr merge "$PR_URL" --auto --squash
20
+ env:
21
+ PR_URL: ${{ github.event.pull_request.html_url }}
22
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,38 @@
1
+ name: Release
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ notes:
7
+ description: "Release notes (optional — auto-generated if blank)"
8
+ required: false
9
+
10
+ permissions:
11
+ contents: write
12
+
13
+ jobs:
14
+ release:
15
+ name: Tag & Release
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ with:
20
+ fetch-depth: 0
21
+
22
+ - name: Generate date-based tag
23
+ id: tag
24
+ run: |
25
+ BASE="v$(date +%Y.%m.%d)"
26
+ EXISTING=$(git tag -l "${BASE}*" | wc -l | tr -d ' ')
27
+ if [ "$EXISTING" -eq "0" ]; then
28
+ echo "tag=${BASE}" >> "$GITHUB_OUTPUT"
29
+ else
30
+ echo "tag=${BASE}.${EXISTING}" >> "$GITHUB_OUTPUT"
31
+ fi
32
+
33
+ - name: Create GitHub Release
34
+ uses: softprops/action-gh-release@v2
35
+ with:
36
+ tag_name: ${{ steps.tag.outputs.tag }}
37
+ generate_release_notes: ${{ github.event.inputs.notes == '' }}
38
+ body: ${{ github.event.inputs.notes }}
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.4.0/schema.json",
3
+ "extends": ["../../biome.json"],
4
+ "files": {
5
+ "includes": ["*.{js,ts,mjs,cjs,json}", "src/**", "scripts/**"]
6
+ }
7
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": "./src",
4
+ "paths": {
5
+ "@/*": ["./*"]
6
+ },
7
+ "jsx": "preserve"
8
+ },
9
+ "include": ["next.config.js"]
10
+ }