@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.
@@ -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, validateEnv } from "./validate-env.js";
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 "resend"',
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: { required: false, description: "S3-compatible endpoint URL" },
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
- process.env.NODE_ENV === "production"
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}`);
@@ -3,6 +3,6 @@
3
3
  "version": "1.0.0",
4
4
  "type": "module",
5
5
  "dependencies": {
6
- "bullmq": "^5.69.3"
6
+ "bullmq": "^5.70.2"
7
7
  }
8
8
  }