@techstream/quark-create-app 1.7.0 → 1.9.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 +7 -4
- package/src/index.js +130 -44
- package/src/utils.js +36 -0
- package/src/utils.test.js +63 -0
- package/templates/base-project/.github/workflows/release.yml +37 -8
- package/templates/base-project/apps/web/package.json +7 -5
- package/templates/base-project/apps/web/railway.json +1 -0
- package/templates/base-project/apps/web/src/app/api/health/route.js +29 -1
- package/templates/base-project/apps/worker/README.md +690 -0
- package/templates/base-project/apps/worker/package.json +3 -2
- package/templates/base-project/apps/worker/src/index.js +190 -5
- package/templates/base-project/apps/worker/src/index.test.js +278 -0
- package/templates/base-project/package.json +14 -1
- package/templates/base-project/packages/db/package.json +4 -7
- package/templates/base-project/packages/db/prisma/seed.js +119 -0
- package/templates/base-project/packages/db/prisma.config.ts +1 -0
- package/templates/config/src/index.js +2 -4
- package/templates/config/src/validate-env.js +79 -3
- package/templates/jobs/package.json +1 -1
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
export const config = {
|
|
2
2
|
appName: process.env.APP_NAME || "Quark",
|
|
3
|
-
appDescription:
|
|
4
|
-
process.env.APP_DESCRIPTION ||
|
|
5
|
-
"A modern monorepo with Next.js, React, and Prisma",
|
|
3
|
+
appDescription: process.env.APP_DESCRIPTION || "A Quark-powered application",
|
|
6
4
|
};
|
|
7
5
|
|
|
8
6
|
export { getAllowedOrigins, getAppUrl, syncNextAuthUrl } from "./app-url.js";
|
|
@@ -13,4 +11,4 @@ export {
|
|
|
13
11
|
resolveEnvironment,
|
|
14
12
|
} from "./environment.js";
|
|
15
13
|
export { getConfig, loadConfig, resetConfig } from "./load-config.js";
|
|
16
|
-
export { loadEnv
|
|
14
|
+
export { loadEnv } from "./validate-env.js";
|
|
@@ -37,10 +37,16 @@ const envSchema = {
|
|
|
37
37
|
// Email provider
|
|
38
38
|
EMAIL_PROVIDER: {
|
|
39
39
|
required: false,
|
|
40
|
-
description: 'Email provider — "smtp" (default) or "
|
|
40
|
+
description: 'Email provider — "smtp" (default), "resend", or "zeptomail"',
|
|
41
41
|
},
|
|
42
42
|
EMAIL_FROM: { required: false, description: "Sender email address" },
|
|
43
43
|
RESEND_API_KEY: { required: false, description: "Resend API key" },
|
|
44
|
+
ZEPTOMAIL_TOKEN: { required: false, description: "Zeptomail API token" },
|
|
45
|
+
ZEPTOMAIL_URL: { required: false, description: "Zeptomail API base URL" },
|
|
46
|
+
ZEPTOMAIL_BOUNCE_EMAIL: {
|
|
47
|
+
required: false,
|
|
48
|
+
description: "Zeptomail bounce email address",
|
|
49
|
+
},
|
|
44
50
|
|
|
45
51
|
// NextAuth
|
|
46
52
|
NEXTAUTH_SECRET: {
|
|
@@ -58,6 +64,11 @@ const envSchema = {
|
|
|
58
64
|
required: false,
|
|
59
65
|
description: "Application name — used in metadata, emails, and page titles",
|
|
60
66
|
},
|
|
67
|
+
APP_DESCRIPTION: {
|
|
68
|
+
required: false,
|
|
69
|
+
description:
|
|
70
|
+
"Application description — used for SEO metadata and social previews",
|
|
71
|
+
},
|
|
61
72
|
APP_URL: {
|
|
62
73
|
required: false,
|
|
63
74
|
description:
|
|
@@ -69,6 +80,12 @@ const envSchema = {
|
|
|
69
80
|
},
|
|
70
81
|
PORT: { required: false, description: "Web server port" },
|
|
71
82
|
|
|
83
|
+
// Worker
|
|
84
|
+
WORKER_CONCURRENCY: {
|
|
85
|
+
required: false,
|
|
86
|
+
description: "Number of concurrent jobs per queue (default: 5)",
|
|
87
|
+
},
|
|
88
|
+
|
|
72
89
|
// Storage
|
|
73
90
|
STORAGE_PROVIDER: {
|
|
74
91
|
required: false,
|
|
@@ -80,10 +97,19 @@ const envSchema = {
|
|
|
80
97
|
},
|
|
81
98
|
S3_BUCKET: { required: false, description: "S3 bucket name" },
|
|
82
99
|
S3_REGION: { required: false, description: "S3 region" },
|
|
83
|
-
S3_ENDPOINT: {
|
|
100
|
+
S3_ENDPOINT: {
|
|
101
|
+
required: false,
|
|
102
|
+
description:
|
|
103
|
+
"S3-compatible endpoint URL (required for non-AWS providers: R2, MinIO, etc.)",
|
|
104
|
+
},
|
|
84
105
|
S3_ACCESS_KEY_ID: { required: false, description: "S3 access key" },
|
|
85
106
|
S3_SECRET_ACCESS_KEY: { required: false, description: "S3 secret key" },
|
|
86
107
|
S3_PUBLIC_URL: { required: false, description: "S3 public URL prefix" },
|
|
108
|
+
ASSET_CDN_URL: {
|
|
109
|
+
required: false,
|
|
110
|
+
description:
|
|
111
|
+
"Public CDN base URL for asset delivery — provider-agnostic (CloudFront, Cloudflare, Bunny, etc.). Falls back to /api/files when unset.",
|
|
112
|
+
},
|
|
87
113
|
};
|
|
88
114
|
|
|
89
115
|
/**
|
|
@@ -129,6 +155,25 @@ export function validateEnv(service = "web") {
|
|
|
129
155
|
|
|
130
156
|
// --- Cross-field validation ---
|
|
131
157
|
|
|
158
|
+
// Placeholder value security check
|
|
159
|
+
const placeholderPattern = /^CHANGE_ME_/i;
|
|
160
|
+
const criticalKeys = [
|
|
161
|
+
"NEXTAUTH_SECRET",
|
|
162
|
+
"POSTGRES_PASSWORD",
|
|
163
|
+
"RESEND_API_KEY",
|
|
164
|
+
"ZEPTOMAIL_TOKEN",
|
|
165
|
+
"S3_SECRET_ACCESS_KEY",
|
|
166
|
+
"SMTP_PASSWORD",
|
|
167
|
+
];
|
|
168
|
+
for (const key of criticalKeys) {
|
|
169
|
+
const value = process.env[key];
|
|
170
|
+
if (value && placeholderPattern.test(value)) {
|
|
171
|
+
errors.push(
|
|
172
|
+
`${key} contains a placeholder value — replace with a real secret (${envSchema[key]?.description || ""})`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
132
177
|
// Database: either DATABASE_URL or POSTGRES_USER must be set (skip in test)
|
|
133
178
|
if (!isTest) {
|
|
134
179
|
const hasDbUrl = !!process.env.DATABASE_URL;
|
|
@@ -141,16 +186,33 @@ export function validateEnv(service = "web") {
|
|
|
141
186
|
}
|
|
142
187
|
|
|
143
188
|
// Redis: warn if not configured (defaults to localhost in dev, will fail in prod)
|
|
189
|
+
const currentEnv = (process.env.NODE_ENV || "").toLowerCase();
|
|
144
190
|
if (
|
|
145
191
|
!process.env.REDIS_URL &&
|
|
146
192
|
!process.env.REDIS_HOST &&
|
|
147
|
-
|
|
193
|
+
(currentEnv === "production" || currentEnv === "staging")
|
|
148
194
|
) {
|
|
149
195
|
warnings.push(
|
|
150
196
|
"Redis not configured: set REDIS_URL or REDIS_HOST (defaults to localhost)",
|
|
151
197
|
);
|
|
152
198
|
}
|
|
153
199
|
|
|
200
|
+
// SEO metadata: APP_DESCRIPTION should be explicitly set before production
|
|
201
|
+
const isProductionLike =
|
|
202
|
+
currentEnv === "production" || currentEnv === "staging";
|
|
203
|
+
if (!isTest && isProductionLike) {
|
|
204
|
+
const appDescription = process.env.APP_DESCRIPTION;
|
|
205
|
+
if (!appDescription) {
|
|
206
|
+
warnings.push(
|
|
207
|
+
"APP_DESCRIPTION not set: metadata description will fall back to a generic value. Set APP_DESCRIPTION before production.",
|
|
208
|
+
);
|
|
209
|
+
} else if (/^CHANGE_ME_|^TODO_/i.test(appDescription)) {
|
|
210
|
+
warnings.push(
|
|
211
|
+
"APP_DESCRIPTION appears to be a placeholder value. Update it before production.",
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
154
216
|
// Conditional: S3 storage requires bucket + credentials
|
|
155
217
|
if (process.env.STORAGE_PROVIDER === "s3") {
|
|
156
218
|
for (const key of [
|
|
@@ -171,6 +233,20 @@ export function validateEnv(service = "web") {
|
|
|
171
233
|
errors.push("Missing RESEND_API_KEY — required when EMAIL_PROVIDER=resend");
|
|
172
234
|
}
|
|
173
235
|
|
|
236
|
+
// Conditional: Zeptomail provider requires token and URL
|
|
237
|
+
if (process.env.EMAIL_PROVIDER === "zeptomail") {
|
|
238
|
+
if (!process.env.ZEPTOMAIL_TOKEN) {
|
|
239
|
+
errors.push(
|
|
240
|
+
"Missing ZEPTOMAIL_TOKEN — required when EMAIL_PROVIDER=zeptomail",
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
if (!process.env.ZEPTOMAIL_URL) {
|
|
244
|
+
errors.push(
|
|
245
|
+
"Missing ZEPTOMAIL_URL — required when EMAIL_PROVIDER=zeptomail",
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
174
250
|
// Log warnings (non-fatal)
|
|
175
251
|
for (const warning of warnings) {
|
|
176
252
|
console.warn(`[env] ⚠️ ${warning}`);
|