@techstream/quark-create-app 1.6.0 → 1.8.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 +61 -0
- package/package.json +5 -2
- package/src/index.js +101 -42
- package/templates/base-project/apps/web/next.config.js +4 -0
- package/templates/base-project/apps/web/package.json +6 -6
- package/templates/base-project/apps/web/railway.json +16 -0
- package/templates/base-project/apps/worker/README.md +690 -0
- package/templates/base-project/apps/worker/package.json +4 -3
- package/templates/base-project/apps/worker/railway.json +13 -0
- package/templates/base-project/apps/worker/src/index.js +190 -1
- package/templates/base-project/apps/worker/src/index.test.js +278 -0
- package/templates/base-project/packages/db/package.json +1 -4
- package/templates/base-project/packages/db/prisma/seed.js +119 -0
- package/templates/base-project/packages/db/prisma.config.ts +6 -8
- package/templates/base-project/packages/db/src/client.js +1 -18
- package/templates/base-project/packages/db/src/connection.js +44 -0
- package/templates/base-project/packages/db/src/connection.test.js +119 -0
- package/templates/config/src/index.js +5 -2
- package/templates/config/src/load-config.js +1 -1
- package/templates/config/src/validate-env.js +63 -14
- package/templates/jobs/package.json +1 -1
- package/templates/ui/package.json +3 -3
package/README.md
CHANGED
|
@@ -42,6 +42,67 @@ Aliases:
|
|
|
42
42
|
- `create-quark-app`
|
|
43
43
|
- `quark-update`
|
|
44
44
|
|
|
45
|
+
## Usage with Flags
|
|
46
|
+
|
|
47
|
+
The CLI supports non-interactive mode with custom options for automation and CI/CD workflows.
|
|
48
|
+
|
|
49
|
+
### Non-Interactive Mode
|
|
50
|
+
|
|
51
|
+
Skip all interactive prompts and use defaults:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Create project without prompts
|
|
55
|
+
npx @techstream/quark-create-app my-app --no-prompts
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Custom Features
|
|
59
|
+
|
|
60
|
+
Specify which optional packages to include (default: `ui,jobs`):
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Only include UI package
|
|
64
|
+
npx @techstream/quark-create-app my-app --no-prompts --features ui
|
|
65
|
+
|
|
66
|
+
# Include both UI and Jobs
|
|
67
|
+
npx @techstream/quark-create-app my-app --no-prompts --features ui,jobs
|
|
68
|
+
|
|
69
|
+
# Minimal setup (no optional packages)
|
|
70
|
+
npx @techstream/quark-create-app my-app --no-prompts --features ""
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Skip Installation Steps
|
|
74
|
+
|
|
75
|
+
Create the project structure without running package installation:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Create project but skip pnpm install
|
|
79
|
+
npx @techstream/quark-create-app my-app --no-prompts --skip-install
|
|
80
|
+
|
|
81
|
+
# Useful for CI/CD where you'll install dependencies separately
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Docker Cleanup
|
|
85
|
+
|
|
86
|
+
Control whether to remove Docker volumes from previous cleanup:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# Keep Docker working directories (useful in CI/CD)
|
|
90
|
+
npx @techstream/quark-create-app my-app --no-prompts --skip-docker
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Complete Example: Full Automation
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Create, install, and setup everything automatically
|
|
97
|
+
npx @techstream/quark-create-app my-app \
|
|
98
|
+
--no-prompts \
|
|
99
|
+
--features ui,jobs \
|
|
100
|
+
&& cd my-app \
|
|
101
|
+
&& docker compose up -d \
|
|
102
|
+
&& pnpm db:migrate \
|
|
103
|
+
&& pnpm dev
|
|
104
|
+
```
|
|
105
|
+
|
|
45
106
|
## Common Tasks
|
|
46
107
|
|
|
47
108
|
- **Update Quark packages**: `quark-update` or `pnpm update @techstream/quark-*`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@techstream/quark-create-app",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"quark-create-app": "src/index.js",
|
|
@@ -33,7 +33,10 @@
|
|
|
33
33
|
"test": "node test-cli.js",
|
|
34
34
|
"test:build": "node test-build.js",
|
|
35
35
|
"test:e2e": "node test-e2e.js",
|
|
36
|
+
"test:e2e:full": "node test-e2e-full.js",
|
|
36
37
|
"test:integration": "node test-integration.js",
|
|
37
|
-
"test:
|
|
38
|
+
"test:flags": "node --test test-flags.js",
|
|
39
|
+
"test:all": "node test-all.js",
|
|
40
|
+
"check:perf": "node scripts/check-e2e-perf.js"
|
|
38
41
|
}
|
|
39
42
|
}
|
package/src/index.js
CHANGED
|
@@ -254,7 +254,15 @@ function validateProjectName(name) {
|
|
|
254
254
|
|
|
255
255
|
program
|
|
256
256
|
.argument("<project-name>", "Name of the project to create")
|
|
257
|
-
.
|
|
257
|
+
.option(
|
|
258
|
+
"--no-prompts",
|
|
259
|
+
"Skip interactive prompts and use default/provided values",
|
|
260
|
+
)
|
|
261
|
+
.option(
|
|
262
|
+
"--features <features>",
|
|
263
|
+
"Comma-separated list of optional features to include (ui,jobs)",
|
|
264
|
+
)
|
|
265
|
+
.action(async (projectName, options) => {
|
|
258
266
|
console.log(
|
|
259
267
|
chalk.blue.bold(
|
|
260
268
|
`\n\uD83D\uDE80 Creating your new Quark project: ${projectName}\n`,
|
|
@@ -314,16 +322,25 @@ program
|
|
|
314
322
|
|
|
315
323
|
// Check if directory already exists
|
|
316
324
|
if (await fs.pathExists(targetDir)) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
325
|
+
if (!options.prompts) {
|
|
326
|
+
// In non-interactive mode, automatically remove existing directory
|
|
327
|
+
console.log(
|
|
328
|
+
chalk.yellow(
|
|
329
|
+
` Directory "${projectName}" already exists. Removing... (non-interactive mode)`,
|
|
330
|
+
),
|
|
331
|
+
);
|
|
332
|
+
} else {
|
|
333
|
+
const { overwrite } = await prompts({
|
|
334
|
+
type: "confirm",
|
|
335
|
+
name: "overwrite",
|
|
336
|
+
message: `Directory "${projectName}" already exists. Remove it and recreate?`,
|
|
337
|
+
initial: false,
|
|
338
|
+
});
|
|
323
339
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
340
|
+
if (!overwrite) {
|
|
341
|
+
console.log(chalk.yellow("Aborted."));
|
|
342
|
+
process.exit(1);
|
|
343
|
+
}
|
|
327
344
|
}
|
|
328
345
|
|
|
329
346
|
// Stop any running Docker containers for this project
|
|
@@ -379,35 +396,72 @@ program
|
|
|
379
396
|
}
|
|
380
397
|
|
|
381
398
|
// Step 5: Ask which optional features to eject
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
},
|
|
402
|
-
]);
|
|
399
|
+
let features;
|
|
400
|
+
if (!options.prompts && options.features) {
|
|
401
|
+
// Parse features from CLI flag
|
|
402
|
+
console.log(chalk.cyan("\n 🎯 Configuring optional features..."));
|
|
403
|
+
const validFeatures = ["ui", "jobs"];
|
|
404
|
+
features = options.features
|
|
405
|
+
.split(",")
|
|
406
|
+
.map((f) => f.trim())
|
|
407
|
+
.filter((f) => f.length > 0);
|
|
408
|
+
|
|
409
|
+
// Validate features
|
|
410
|
+
const invalidFeatures = features.filter(
|
|
411
|
+
(f) => !validFeatures.includes(f),
|
|
412
|
+
);
|
|
413
|
+
if (invalidFeatures.length > 0) {
|
|
414
|
+
throw new Error(
|
|
415
|
+
`Invalid features: ${invalidFeatures.join(", ")}. Valid options are: ${validFeatures.join(", ")}`,
|
|
416
|
+
);
|
|
417
|
+
}
|
|
403
418
|
|
|
404
|
-
|
|
419
|
+
console.log(
|
|
420
|
+
chalk.green(
|
|
421
|
+
` Selected features: ${features.join(", ") || "none"} (non-interactive mode)`,
|
|
422
|
+
),
|
|
423
|
+
);
|
|
424
|
+
} else if (!options.prompts) {
|
|
425
|
+
// Use defaults when --no-prompts is set without --features
|
|
426
|
+
console.log(chalk.cyan("\n 🎯 Configuring optional features..."));
|
|
427
|
+
features = ["ui", "jobs"]; // Default to both
|
|
428
|
+
console.log(
|
|
429
|
+
chalk.green(
|
|
430
|
+
` Using default features: ${features.join(", ")} (non-interactive mode)`,
|
|
431
|
+
),
|
|
432
|
+
);
|
|
433
|
+
} else {
|
|
434
|
+
// Interactive prompt
|
|
435
|
+
console.log(chalk.cyan("\n 🎯 Configuring optional features...\n"));
|
|
436
|
+
const response = await prompts([
|
|
437
|
+
{
|
|
438
|
+
type: "multiselect",
|
|
439
|
+
name: "features",
|
|
440
|
+
message: "Which optional packages would you like to include?",
|
|
441
|
+
instructions: false,
|
|
442
|
+
choices: [
|
|
443
|
+
{
|
|
444
|
+
title: "UI Components (packages/ui)",
|
|
445
|
+
value: "ui",
|
|
446
|
+
selected: true,
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
title: "Job Definitions (packages/jobs)",
|
|
450
|
+
value: "jobs",
|
|
451
|
+
selected: true,
|
|
452
|
+
},
|
|
453
|
+
],
|
|
454
|
+
},
|
|
455
|
+
]);
|
|
405
456
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
457
|
+
features = response.features;
|
|
458
|
+
|
|
459
|
+
// Handle prompt cancellation (Ctrl+C)
|
|
460
|
+
if (!features) {
|
|
461
|
+
console.log(chalk.yellow("\n\u26A0\uFE0F Setup cancelled."));
|
|
462
|
+
await fs.remove(targetDir);
|
|
463
|
+
process.exit(0);
|
|
464
|
+
}
|
|
411
465
|
}
|
|
412
466
|
|
|
413
467
|
// Step 6: Copy selected optional packages
|
|
@@ -484,11 +538,11 @@ program
|
|
|
484
538
|
# Generate strong passwords with: openssl rand -base64 32
|
|
485
539
|
POSTGRES_HOST=localhost
|
|
486
540
|
POSTGRES_PORT=5432
|
|
487
|
-
POSTGRES_USER
|
|
541
|
+
POSTGRES_USER=${scope}_user
|
|
488
542
|
POSTGRES_PASSWORD=CHANGE_ME_TO_STRONG_PASSWORD
|
|
489
543
|
POSTGRES_DB=${scope}_dev
|
|
490
544
|
# Optional: Set DATABASE_URL to override the dynamic construction above
|
|
491
|
-
# DATABASE_URL="postgresql
|
|
545
|
+
# DATABASE_URL="postgresql://${scope}_user:CHANGE_ME_TO_STRONG_PASSWORD@localhost:5432/${scope}_dev?schema=public"
|
|
492
546
|
|
|
493
547
|
# --- Redis Configuration ---
|
|
494
548
|
REDIS_HOST=localhost
|
|
@@ -501,8 +555,6 @@ REDIS_PORT=6379
|
|
|
501
555
|
MAIL_HOST=localhost
|
|
502
556
|
MAIL_SMTP_PORT=1025
|
|
503
557
|
MAIL_UI_PORT=8025
|
|
504
|
-
# Optional: Set MAIL_SMTP_URL to override the dynamic construction above
|
|
505
|
-
# MAIL_SMTP_URL="smtp://localhost:1025"
|
|
506
558
|
|
|
507
559
|
# Production SMTP: Set these instead of MAIL_* when using a real SMTP relay
|
|
508
560
|
# SMTP_HOST=smtp.example.com
|
|
@@ -525,6 +577,10 @@ MAIL_UI_PORT=8025
|
|
|
525
577
|
# In production, set this to your real domain:
|
|
526
578
|
# APP_URL=https://yourdomain.com
|
|
527
579
|
|
|
580
|
+
# --- Application Identity ---
|
|
581
|
+
# APP_NAME is used in metadata, emails, and page titles.
|
|
582
|
+
APP_NAME=${projectName}
|
|
583
|
+
|
|
528
584
|
# --- NextAuth Configuration ---
|
|
529
585
|
# ⚠️ CRITICAL: Generate a secure secret with: openssl rand -base64 32
|
|
530
586
|
# This secret is used to encrypt JWT tokens and session data
|
|
@@ -617,7 +673,7 @@ STORAGE_PROVIDER=local
|
|
|
617
673
|
const envContent = `# --- Database Configuration ---
|
|
618
674
|
POSTGRES_HOST=localhost
|
|
619
675
|
POSTGRES_PORT=${postgresPort}
|
|
620
|
-
POSTGRES_USER
|
|
676
|
+
POSTGRES_USER=${scope}_user
|
|
621
677
|
POSTGRES_PASSWORD=${dbPassword}
|
|
622
678
|
POSTGRES_DB=${scope}_dev
|
|
623
679
|
|
|
@@ -630,6 +686,9 @@ MAIL_HOST=localhost
|
|
|
630
686
|
MAIL_SMTP_PORT=${mailSmtpPort}
|
|
631
687
|
MAIL_UI_PORT=${mailUiPort}
|
|
632
688
|
|
|
689
|
+
# --- Application Identity ---
|
|
690
|
+
APP_NAME=${projectName}
|
|
691
|
+
|
|
633
692
|
# --- NextAuth Configuration ---
|
|
634
693
|
NEXTAUTH_SECRET=${nextAuthSecret}
|
|
635
694
|
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
/** @type {import('next').NextConfig} */
|
|
2
2
|
const nextConfig = {
|
|
3
|
+
// Required for Railway deployment — produces a self-contained build
|
|
4
|
+
// at .next/standalone that can run without node_modules.
|
|
5
|
+
output: "standalone",
|
|
6
|
+
|
|
3
7
|
// Support workspace package resolution (including @techstream/quark-db which uses
|
|
4
8
|
// the Prisma driver-adapter pattern — pure JS, no native engine binary)
|
|
5
9
|
transpilePackages: [
|
|
@@ -16,18 +16,18 @@
|
|
|
16
16
|
"@techstream/quark-db": "workspace:*",
|
|
17
17
|
"@techstream/quark-jobs": "workspace:*",
|
|
18
18
|
"@techstream/quark-ui": "workspace:*",
|
|
19
|
-
"@prisma/client": "^7.
|
|
19
|
+
"@prisma/client": "^7.4.0",
|
|
20
20
|
"next": "16.1.6",
|
|
21
21
|
"next-auth": "5.0.0-beta.30",
|
|
22
22
|
"pg": "^8.18.0",
|
|
23
|
-
"react": "19.2.
|
|
24
|
-
"react-dom": "19.2.
|
|
23
|
+
"react": "19.2.4",
|
|
24
|
+
"react-dom": "19.2.4",
|
|
25
25
|
"zod": "^4.3.6"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@tailwindcss/postcss": "^4.
|
|
29
|
-
"@types/node": "^
|
|
30
|
-
"tailwindcss": "^4.
|
|
28
|
+
"@tailwindcss/postcss": "^4.2.0",
|
|
29
|
+
"@types/node": "^25.2.3",
|
|
30
|
+
"tailwindcss": "^4.2.0",
|
|
31
31
|
"@techstream/quark-config": "workspace:*"
|
|
32
32
|
}
|
|
33
33
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://railway.com/railway.schema.json",
|
|
3
|
+
"build": {
|
|
4
|
+
"builder": "RAILPACK",
|
|
5
|
+
"buildCommand": "pnpm install --frozen-lockfile && pnpm db:generate && pnpm build",
|
|
6
|
+
"watchPatterns": ["apps/web/**", "packages/**"]
|
|
7
|
+
},
|
|
8
|
+
"deploy": {
|
|
9
|
+
"releaseCommand": "pnpm --filter @techstream/quark-db exec prisma migrate deploy",
|
|
10
|
+
"startCommand": "node apps/web/.next/standalone/server.js",
|
|
11
|
+
"healthcheckPath": "/api/health",
|
|
12
|
+
"healthcheckTimeout": 30,
|
|
13
|
+
"restartPolicyType": "ON_FAILURE",
|
|
14
|
+
"restartPolicyMaxRetries": 5
|
|
15
|
+
}
|
|
16
|
+
}
|