@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.
- package/README.md +38 -0
- package/package.json +5 -2
- package/src/index.js +142 -12
- package/templates/base-project/.github/copilot-instructions.md +7 -0
- package/templates/base-project/.github/dependabot.yml +12 -0
- package/templates/base-project/.github/skills/project-context/SKILL.md +106 -0
- package/templates/base-project/.github/workflows/ci.yml +97 -0
- package/templates/base-project/.github/workflows/dependabot-auto-merge.yml +22 -0
- package/templates/base-project/.github/workflows/release.yml +38 -0
- package/templates/base-project/apps/web/biome.json +7 -0
- package/templates/base-project/apps/web/jsconfig.json +10 -0
- package/templates/base-project/apps/web/next.config.js +86 -1
- package/templates/base-project/apps/web/package.json +4 -4
- package/templates/base-project/apps/web/src/app/api/auth/register/route.js +9 -9
- package/templates/base-project/apps/web/src/app/api/files/route.js +3 -2
- package/templates/base-project/apps/web/src/app/layout.js +3 -4
- package/templates/base-project/apps/web/src/app/manifest.js +12 -0
- package/templates/base-project/apps/web/src/app/robots.js +21 -0
- package/templates/base-project/apps/web/src/app/sitemap.js +20 -0
- package/templates/base-project/apps/web/src/lib/seo/indexing.js +23 -0
- package/templates/base-project/apps/web/src/lib/seo/site-metadata.js +33 -0
- package/templates/base-project/apps/web/src/proxy.js +1 -2
- package/templates/base-project/apps/worker/package.json +4 -4
- package/templates/base-project/apps/worker/src/index.js +40 -12
- package/templates/base-project/apps/worker/src/index.test.js +296 -15
- package/templates/base-project/biome.json +44 -0
- package/templates/base-project/docker-compose.yml +7 -4
- package/templates/base-project/package.json +1 -1
- package/templates/base-project/packages/db/package.json +1 -1
- package/templates/base-project/packages/db/prisma/migrations/20260202061128_initial/migration.sql +42 -0
- package/templates/base-project/packages/db/prisma/schema.prisma +20 -16
- package/templates/base-project/packages/db/prisma.config.ts +3 -2
- package/templates/base-project/packages/db/scripts/seed.js +117 -30
- package/templates/base-project/packages/db/src/queries.js +58 -68
- package/templates/base-project/packages/db/src/queries.test.js +0 -29
- package/templates/base-project/packages/db/src/schemas.js +4 -10
- package/templates/base-project/pnpm-workspace.yaml +4 -0
- package/templates/base-project/turbo.json +5 -3
- package/templates/config/package.json +2 -0
- package/templates/config/src/environment.js +270 -0
- package/templates/config/src/index.js +10 -18
- package/templates/config/src/load-config.js +135 -0
- package/templates/config/src/validate-env.js +60 -2
- package/templates/jobs/package.json +2 -2
- package/templates/jobs/src/definitions.test.js +34 -0
- package/templates/jobs/src/index.js +1 -1
- package/templates/ui/package.json +4 -4
- package/templates/ui/src/button.test.js +23 -0
- package/templates/ui/src/index.js +1 -3
- package/templates/base-project/apps/web/src/app/api/posts/[id]/route.js +0 -65
- package/templates/base-project/apps/web/src/app/api/posts/route.js +0 -42
- package/templates/ui/src/card.js +0 -14
- 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.
|
|
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": "^
|
|
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
|
-
//
|
|
329
|
+
// Stop any running Docker containers for this project
|
|
282
330
|
try {
|
|
283
|
-
await execa("docker", ["compose", "down"
|
|
331
|
+
await execa("docker", ["compose", "down"], {
|
|
284
332
|
cwd: targetDir,
|
|
285
333
|
stdio: "ignore",
|
|
286
334
|
});
|
|
287
|
-
console.log(chalk.green(" ✓
|
|
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 = `# ---
|
|
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
|
|
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
|
|
480
|
-
#
|
|
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
|
|
551
|
+
# Local storage directory (only when STORAGE_PROVIDER=local)
|
|
483
552
|
# STORAGE_LOCAL_DIR=./uploads
|
|
484
553
|
|
|
485
|
-
# S3 / Cloudflare R2
|
|
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
|
|
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,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 }}
|