@usequark/quark-create-app 1.23.11

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 (237) hide show
  1. package/README.md +210 -0
  2. package/package.json +56 -0
  3. package/src/dependabot-config.test.js +128 -0
  4. package/src/deploy/adapters/iac.js +203 -0
  5. package/src/deploy/adapters/index.js +27 -0
  6. package/src/deploy/adapters/railway.js +508 -0
  7. package/src/deploy/adapters/railway.test.js +318 -0
  8. package/src/deploy/contract.js +59 -0
  9. package/src/deploy/deploy.integration.test.js +183 -0
  10. package/src/deploy/deploy.js +430 -0
  11. package/src/deploy/deploy.test.js +105 -0
  12. package/src/deploy/discovery.js +115 -0
  13. package/src/deploy/discovery.test.js +141 -0
  14. package/src/deploy/index.js +38 -0
  15. package/src/deploy/inspect.js +130 -0
  16. package/src/index.js +2920 -0
  17. package/src/mobile-template.test.js +232 -0
  18. package/src/prepare-hooks.test.js +535 -0
  19. package/src/scaffold-guards.test.js +140 -0
  20. package/src/scaffold-output.test.js +228 -0
  21. package/src/template-scripts.test.js +160 -0
  22. package/src/utils.js +77 -0
  23. package/src/utils.test.js +95 -0
  24. package/src/worker-dockerfile.test.js +85 -0
  25. package/templates/base-project/.cursor/rules/quark.mdc +172 -0
  26. package/templates/base-project/.dockerignore +14 -0
  27. package/templates/base-project/.env.railway.example +87 -0
  28. package/templates/base-project/.github/copilot-instructions.md +65 -0
  29. package/templates/base-project/.github/dependabot.yml +31 -0
  30. package/templates/base-project/.github/skills/project-context/SKILL.md +109 -0
  31. package/templates/base-project/.github/workflows/ci.yml +116 -0
  32. package/templates/base-project/.github/workflows/dependabot-auto-merge.yml +22 -0
  33. package/templates/base-project/.github/workflows/install-windows.yml +27 -0
  34. package/templates/base-project/.github/workflows/release.yml +67 -0
  35. package/templates/base-project/.railway/railway.ts +40 -0
  36. package/templates/base-project/CLAUDE.md +163 -0
  37. package/templates/base-project/MAIN.md +21 -0
  38. package/templates/base-project/README.md +164 -0
  39. package/templates/base-project/apps/web/.env.example +99 -0
  40. package/templates/base-project/apps/web/Dockerfile +38 -0
  41. package/templates/base-project/apps/web/biome.json +8 -0
  42. package/templates/base-project/apps/web/jsconfig.json +9 -0
  43. package/templates/base-project/apps/web/next.config.js +154 -0
  44. package/templates/base-project/apps/web/package.json +42 -0
  45. package/templates/base-project/apps/web/postcss.config.mjs +7 -0
  46. package/templates/base-project/apps/web/public/.well-known/security.txt +3 -0
  47. package/templates/base-project/apps/web/public/quark.svg +47 -0
  48. package/templates/base-project/apps/web/scripts/prepare-standalone.mjs +71 -0
  49. package/templates/base-project/apps/web/src/app/_components/AsciiPanel.js +103 -0
  50. package/templates/base-project/apps/web/src/app/_components/HealthIndicator.js +85 -0
  51. package/templates/base-project/apps/web/src/app/_components/HomeThemeToggle.js +63 -0
  52. package/templates/base-project/apps/web/src/app/_components/QuarkAnimation.js +168 -0
  53. package/templates/base-project/apps/web/src/app/_components/SyncedPanels.js +212 -0
  54. package/templates/base-project/apps/web/src/app/_components/UmamiReplayRecorder.js +198 -0
  55. package/templates/base-project/apps/web/src/app/_components/UmamiWebVitals.js +39 -0
  56. package/templates/base-project/apps/web/src/app/api/auth/[...nextauth]/route.js +7 -0
  57. package/templates/base-project/apps/web/src/app/api/auth/apple/route.js +82 -0
  58. package/templates/base-project/apps/web/src/app/api/auth/google/route.js +59 -0
  59. package/templates/base-project/apps/web/src/app/api/auth/refresh/route.js +48 -0
  60. package/templates/base-project/apps/web/src/app/api/auth/register/route.js +63 -0
  61. package/templates/base-project/apps/web/src/app/api/auth/token/route.js +44 -0
  62. package/templates/base-project/apps/web/src/app/api/csrf/route.js +40 -0
  63. package/templates/base-project/apps/web/src/app/api/device/register/route.js +57 -0
  64. package/templates/base-project/apps/web/src/app/api/error-handler.js +23 -0
  65. package/templates/base-project/apps/web/src/app/api/files/[id]/route.js +89 -0
  66. package/templates/base-project/apps/web/src/app/api/files/route.js +120 -0
  67. package/templates/base-project/apps/web/src/app/api/health/route.js +160 -0
  68. package/templates/base-project/apps/web/src/app/api/users/[id]/route.js +54 -0
  69. package/templates/base-project/apps/web/src/app/api/users/me/route.js +67 -0
  70. package/templates/base-project/apps/web/src/app/api/users/route.js +45 -0
  71. package/templates/base-project/apps/web/src/app/auth/error/page.js +83 -0
  72. package/templates/base-project/apps/web/src/app/auth/forgot-password/page.js +58 -0
  73. package/templates/base-project/apps/web/src/app/auth/provider-loading.js +25 -0
  74. package/templates/base-project/apps/web/src/app/auth/register/RegisterPageClient.js +340 -0
  75. package/templates/base-project/apps/web/src/app/auth/register/page.js +17 -0
  76. package/templates/base-project/apps/web/src/app/auth/signin/SignInPageClient.js +230 -0
  77. package/templates/base-project/apps/web/src/app/auth/signin/page.js +10 -0
  78. package/templates/base-project/apps/web/src/app/favicon.ico +0 -0
  79. package/templates/base-project/apps/web/src/app/global-error.js +58 -0
  80. package/templates/base-project/apps/web/src/app/globals.css +1166 -0
  81. package/templates/base-project/apps/web/src/app/icon.svg +47 -0
  82. package/templates/base-project/apps/web/src/app/layout.js +57 -0
  83. package/templates/base-project/apps/web/src/app/manifest.js +12 -0
  84. package/templates/base-project/apps/web/src/app/not-found.js +39 -0
  85. package/templates/base-project/apps/web/src/app/page.js +262 -0
  86. package/templates/base-project/apps/web/src/app/robots.js +23 -0
  87. package/templates/base-project/apps/web/src/app/sitemap.js +14 -0
  88. package/templates/base-project/apps/web/src/lib/analytics/umami-before-send.js +29 -0
  89. package/templates/base-project/apps/web/src/lib/analytics/umami-config.js +95 -0
  90. package/templates/base-project/apps/web/src/lib/analytics/umami-csp.js +24 -0
  91. package/templates/base-project/apps/web/src/lib/analytics/umami-marketing.js +131 -0
  92. package/templates/base-project/apps/web/src/lib/analytics/umami-replay.js +204 -0
  93. package/templates/base-project/apps/web/src/lib/analytics/umami-web-vitals.js +237 -0
  94. package/templates/base-project/apps/web/src/lib/analytics/umami.js +57 -0
  95. package/templates/base-project/apps/web/src/lib/auth-middleware.js +82 -0
  96. package/templates/base-project/apps/web/src/lib/auth-signup.js +5 -0
  97. package/templates/base-project/apps/web/src/lib/auth.js +191 -0
  98. package/templates/base-project/apps/web/src/lib/csrf-cookie.js +11 -0
  99. package/templates/base-project/apps/web/src/lib/jwt.js +84 -0
  100. package/templates/base-project/apps/web/src/lib/load-crm-config.js +35 -0
  101. package/templates/base-project/apps/web/src/lib/normalize-auth-request.js +33 -0
  102. package/templates/base-project/apps/web/src/lib/proxy-auth.js +80 -0
  103. package/templates/base-project/apps/web/src/lib/seo/indexing.js +25 -0
  104. package/templates/base-project/apps/web/src/lib/seo/site-metadata.js +74 -0
  105. package/templates/base-project/apps/web/src/lib/sitemap-entries.js +22 -0
  106. package/templates/base-project/apps/web/src/preflight.js +55 -0
  107. package/templates/base-project/apps/web/src/proxy.js +328 -0
  108. package/templates/base-project/biome.json +52 -0
  109. package/templates/base-project/docker-compose.override.yml +17 -0
  110. package/templates/base-project/docker-compose.yml +37 -0
  111. package/templates/base-project/docs/FIRST_FEATURE.md +74 -0
  112. package/templates/base-project/docs/START_HERE.md +70 -0
  113. package/templates/base-project/docs/adr/README.md +42 -0
  114. package/templates/base-project/package.json +58 -0
  115. package/templates/base-project/packages/db/package.json +38 -0
  116. package/templates/base-project/packages/db/prisma/migrations/20260202061128_initial/migration.sql +202 -0
  117. package/templates/base-project/packages/db/prisma/migrations/migration_lock.toml +3 -0
  118. package/templates/base-project/packages/db/prisma/schema.prisma +161 -0
  119. package/templates/base-project/packages/db/prisma/seed.js +152 -0
  120. package/templates/base-project/packages/db/prisma.config.js +27 -0
  121. package/templates/base-project/packages/db/src/client.js +75 -0
  122. package/templates/base-project/packages/db/src/connection.js +44 -0
  123. package/templates/base-project/packages/db/src/context.js +275 -0
  124. package/templates/base-project/packages/db/src/index.js +5 -0
  125. package/templates/base-project/packages/db/src/ping.js +66 -0
  126. package/templates/base-project/packages/db/src/queries.js +292 -0
  127. package/templates/base-project/packages/db/src/schemas.js +30 -0
  128. package/templates/base-project/packages/db/src/umami.js +299 -0
  129. package/templates/base-project/pnpm-workspace.yaml +20 -0
  130. package/templates/base-project/scripts/check-loading.mjs +49 -0
  131. package/templates/base-project/scripts/check-standards.mjs +186 -0
  132. package/templates/base-project/scripts/clean-workspace.mjs +479 -0
  133. package/templates/base-project/scripts/dev-preflight.mjs +48 -0
  134. package/templates/base-project/scripts/doctor.js +408 -0
  135. package/templates/base-project/scripts/prepare.js +490 -0
  136. package/templates/base-project/scripts/run-tests.mjs +71 -0
  137. package/templates/base-project/skills/add-dashboard/SKILL.md +28 -0
  138. package/templates/base-project/skills/add-endpoint/SKILL.md +29 -0
  139. package/templates/base-project/skills/add-model/SKILL.md +28 -0
  140. package/templates/base-project/skills/ecommerce/SKILL.md +224 -0
  141. package/templates/base-project/skills/ecommerce-cart/SKILL.md +193 -0
  142. package/templates/base-project/skills/ecommerce-catalog/SKILL.md +179 -0
  143. package/templates/base-project/skills/ecommerce-checkout/SKILL.md +195 -0
  144. package/templates/base-project/skills/i18n/SKILL.md +211 -0
  145. package/templates/base-project/skills/payment/SKILL.md +280 -0
  146. package/templates/base-project/skills/quark-skills/SKILL.md +37 -0
  147. package/templates/base-project/turbo.json +143 -0
  148. package/templates/config/package.json +20 -0
  149. package/templates/config/src/app-url.js +134 -0
  150. package/templates/config/src/environment.js +284 -0
  151. package/templates/config/src/index.js +16 -0
  152. package/templates/config/src/load-config.js +157 -0
  153. package/templates/config/src/rate-limit.js +136 -0
  154. package/templates/config/src/redis.js +43 -0
  155. package/templates/config/src/validate-env.js +532 -0
  156. package/templates/jobs/README.md +52 -0
  157. package/templates/jobs/package.json +9 -0
  158. package/templates/jobs/src/definitions.js +13 -0
  159. package/templates/jobs/src/index.js +1 -0
  160. package/templates/mobile/.env.example +16 -0
  161. package/templates/mobile/CHECKLIST.md +40 -0
  162. package/templates/mobile/README.md +130 -0
  163. package/templates/mobile/app/(app)/_layout.tsx +83 -0
  164. package/templates/mobile/app/(app)/index.tsx +147 -0
  165. package/templates/mobile/app/(app)/profile.tsx +204 -0
  166. package/templates/mobile/app/(app)/settings.tsx +80 -0
  167. package/templates/mobile/app/(auth)/_layout.tsx +10 -0
  168. package/templates/mobile/app/(auth)/sign-in.tsx +140 -0
  169. package/templates/mobile/app/(auth)/sign-up.tsx +178 -0
  170. package/templates/mobile/app/+not-found.tsx +32 -0
  171. package/templates/mobile/app/_layout.tsx +18 -0
  172. package/templates/mobile/app.json +47 -0
  173. package/templates/mobile/assets/adaptive-icon.png +0 -0
  174. package/templates/mobile/assets/icon.png +0 -0
  175. package/templates/mobile/assets/notification-icon.png +0 -0
  176. package/templates/mobile/assets/splash.png +0 -0
  177. package/templates/mobile/biome.json +27 -0
  178. package/templates/mobile/components/AuthGate.tsx +19 -0
  179. package/templates/mobile/components/ThemeProvider.tsx +23 -0
  180. package/templates/mobile/eas.json +29 -0
  181. package/templates/mobile/hooks/use-auth.ts +127 -0
  182. package/templates/mobile/lib/api-client.ts +138 -0
  183. package/templates/mobile/lib/api.ts +83 -0
  184. package/templates/mobile/lib/auth-providers-google.ts +74 -0
  185. package/templates/mobile/lib/auth-providers.ts +74 -0
  186. package/templates/mobile/lib/auth.ts +100 -0
  187. package/templates/mobile/lib/config.ts +14 -0
  188. package/templates/mobile/lib/errors.ts +46 -0
  189. package/templates/mobile/lib/notifications.ts +77 -0
  190. package/templates/mobile/lib/storage.ts +25 -0
  191. package/templates/mobile/lib/theme-tokens.ts +16 -0
  192. package/templates/mobile/package.json +37 -0
  193. package/templates/mobile/tsconfig.json +10 -0
  194. package/templates/pwa/app/_components/PWARegister.js +15 -0
  195. package/templates/pwa/app/manifest.js +32 -0
  196. package/templates/pwa/public/sw.js +72 -0
  197. package/templates/ui/README.md +44 -0
  198. package/templates/ui/package.json +21 -0
  199. package/templates/ui/src/badge.js +23 -0
  200. package/templates/ui/src/button.js +111 -0
  201. package/templates/ui/src/card.js +128 -0
  202. package/templates/ui/src/checkbox.js +20 -0
  203. package/templates/ui/src/container.js +13 -0
  204. package/templates/ui/src/dialog.js +137 -0
  205. package/templates/ui/src/error-banner.js +15 -0
  206. package/templates/ui/src/footer.js +239 -0
  207. package/templates/ui/src/form-field.integration-test.js +142 -0
  208. package/templates/ui/src/form-field.js +50 -0
  209. package/templates/ui/src/index.js +24 -0
  210. package/templates/ui/src/input.js +11 -0
  211. package/templates/ui/src/label.js +14 -0
  212. package/templates/ui/src/lightbox.integration-test.js +167 -0
  213. package/templates/ui/src/lightbox.js +243 -0
  214. package/templates/ui/src/logo.js +52 -0
  215. package/templates/ui/src/navbar.js +602 -0
  216. package/templates/ui/src/password-input.js +31 -0
  217. package/templates/ui/src/rich-text.js +514 -0
  218. package/templates/ui/src/select.js +317 -0
  219. package/templates/ui/src/skeleton.js +10 -0
  220. package/templates/ui/src/spinner.js +31 -0
  221. package/templates/ui/src/table.js +169 -0
  222. package/templates/ui/src/textarea.js +11 -0
  223. package/templates/ui/src/theme-constants.js +24 -0
  224. package/templates/ui/src/theme.js +196 -0
  225. package/templates/ui/src/toast.js +206 -0
  226. package/templates/worker/Dockerfile +64 -0
  227. package/templates/worker/README.md +56 -0
  228. package/templates/worker/package.json +36 -0
  229. package/templates/worker/src/config/pricing.js +46 -0
  230. package/templates/worker/src/handlers/email-job-validation.js +36 -0
  231. package/templates/worker/src/handlers/email.js +98 -0
  232. package/templates/worker/src/handlers/files.js +59 -0
  233. package/templates/worker/src/handlers/index.js +20 -0
  234. package/templates/worker/src/handlers/push-notification.js +234 -0
  235. package/templates/worker/src/index.js +505 -0
  236. package/templates/worker/src/lib/railway.js +438 -0
  237. package/templates/worker/src/lib/tokens.js +81 -0
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: quark-skills
3
+ description: Index of every skill bundled with this Quark project - what each skill is for and when to load it. Start here when deciding which skill applies to a task.
4
+ ---
5
+
6
+ # Quark Skills Index
7
+
8
+ Every skill below ships with this project and is always available. Skills are abstract and domain-neutral: they describe framework patterns and workflows, not a specific business. Load the relevant `SKILL.md` before starting work in its area.
9
+
10
+ ## Core workflow skills
11
+
12
+ | Skill | Path | Purpose |
13
+ |---|---|---|
14
+ | add-model | `add-model/SKILL.md` | Add a new Prisma model, query helpers, and tests to the db package. |
15
+ | add-endpoint | `add-endpoint/SKILL.md` | Add an API route (or Server Action) with Zod validation, CSRF, and role guards. |
16
+ | add-dashboard | `add-dashboard/SKILL.md` | Add a metrics/overview dashboard page with metric cards and loading states. |
17
+ | admin-dashboard | `admin-dashboard/SKILL.md` | Build a full admin/back-office area: guarded routes, CRUD screens, audit logging. |
18
+
19
+ ## Domain starters
20
+
21
+ These teach the AI to build a complete vertical on demand. No starter code is scaffolded - the AI generates it from the skill.
22
+
23
+ | Skill | Path | Purpose |
24
+ |---|---|---|
25
+ | bookings | `bookings/SKILL.md` | Booking/reservation/appointment/scheduling systems. |
26
+ | crm | `crm/SKILL.md` | Contact/pipeline/customer relationship systems. |
27
+ | cms | `cms/SKILL.md` | Content modeling, authoring, and publishing systems. |
28
+ | ai | `ai/SKILL.md` | AI assistant features (chat, completions) on app data. |
29
+ | payment | `payment/SKILL.md` | Stripe payment integration, checkout, webhooks, fulfillment. |
30
+ | ecommerce | `ecommerce/SKILL.md` | Product catalog, cart, checkout, orders, inventory. Sub-skills: `ecommerce-catalog`, `ecommerce-cart`, `ecommerce-checkout`. |
31
+ | i18n | `i18n/SKILL.md` | Internationalization, locale routing, localized CMS content, hreflang SEO. |
32
+
33
+ ## How to use skills
34
+
35
+ 1. Match the user's request to a skill above; read its `SKILL.md` first.
36
+ 2. Skills assume the Quark conventions: Zod-validated actions/routes, query helpers in `packages/db/src/queries.js`, shared UI imports, `createLogger()` logging, models with `createdAt`/`updatedAt`.
37
+ 3. If no skill matches, follow `CLAUDE.md` / project context and the reference patterns in `apps/web/src/app/api/users/route.js` and `apps/web/src/app/page.js`.
@@ -0,0 +1,143 @@
1
+ {
2
+ "$schema": "https://v2-10-12.turborepo.dev/schema.json",
3
+ "ui": "stream",
4
+ "globalEnv": [
5
+ "GITHUB_OUTPUT",
6
+ "npm_execpath",
7
+ "npm_lifecycle_event",
8
+ "QUARK_SKIP_AUTO_CLEAN",
9
+ "QUARK_SKIP_DEV_PREFLIGHT",
10
+ "QUARK_SKIP_GIT_INIT"
11
+ ],
12
+ "tasks": {
13
+ "clean": {
14
+ "cache": false
15
+ },
16
+ "build": {
17
+ "dependsOn": ["^build", "db:generate"],
18
+ "outputs": ["dist/**", ".next/**"],
19
+ "env": ["NODE_ENV"],
20
+ "passThroughEnv": [
21
+ "DATABASE_URL",
22
+ "POSTGRES_HOST",
23
+ "POSTGRES_PORT",
24
+ "POSTGRES_USER",
25
+ "POSTGRES_PASSWORD",
26
+ "POSTGRES_DB",
27
+ "APP_URL",
28
+ "ALLOW_INDEXING",
29
+ "PORT",
30
+ "NEXTAUTH_SECRET",
31
+ "AUTH_SECRET"
32
+ ]
33
+ },
34
+ "lint": {
35
+ "outputs": []
36
+ },
37
+ "test": {
38
+ "dependsOn": ["^build", "db:generate"],
39
+ "outputs": [],
40
+ "passThroughEnv": [
41
+ "DATABASE_URL",
42
+ "POSTGRES_HOST",
43
+ "POSTGRES_PORT",
44
+ "POSTGRES_USER",
45
+ "POSTGRES_PASSWORD",
46
+ "POSTGRES_DB",
47
+ "REDIS_URL",
48
+ "REDIS_HOST",
49
+ "REDIS_PORT",
50
+ "NEXTAUTH_SECRET",
51
+ "AUTH_SECRET",
52
+ "APP_URL",
53
+ "ALLOW_INDEXING",
54
+ "PORT"
55
+ ]
56
+ },
57
+ "db:generate": {
58
+ "cache": false,
59
+ "outputs": ["node_modules/.prisma/client"],
60
+ "passThroughEnv": [
61
+ "DATABASE_URL",
62
+ "POSTGRES_HOST",
63
+ "POSTGRES_PORT",
64
+ "POSTGRES_USER",
65
+ "POSTGRES_PASSWORD",
66
+ "POSTGRES_DB"
67
+ ]
68
+ },
69
+ "db:migrate:deploy": {
70
+ "cache": false,
71
+ "passThroughEnv": [
72
+ "DATABASE_URL",
73
+ "POSTGRES_HOST",
74
+ "POSTGRES_PORT",
75
+ "POSTGRES_USER",
76
+ "POSTGRES_PASSWORD",
77
+ "POSTGRES_DB"
78
+ ]
79
+ },
80
+ "db:push": {
81
+ "cache": false,
82
+ "passThroughEnv": [
83
+ "DATABASE_URL",
84
+ "POSTGRES_HOST",
85
+ "POSTGRES_PORT",
86
+ "POSTGRES_USER",
87
+ "POSTGRES_PASSWORD",
88
+ "POSTGRES_DB"
89
+ ]
90
+ },
91
+ "db:seed": {
92
+ "cache": false,
93
+ "passThroughEnv": [
94
+ "DATABASE_URL",
95
+ "POSTGRES_HOST",
96
+ "POSTGRES_PORT",
97
+ "POSTGRES_USER",
98
+ "POSTGRES_PASSWORD",
99
+ "POSTGRES_DB",
100
+ "SEED_PROFILE",
101
+ "ADMIN_EMAIL",
102
+ "ADMIN_PASSWORD",
103
+ "ADMIN_NAME"
104
+ ]
105
+ },
106
+ "dev": {
107
+ "cache": false,
108
+ "persistent": true,
109
+ "passThroughEnv": [
110
+ "DATABASE_URL",
111
+ "PORT",
112
+ "POSTGRES_HOST",
113
+ "POSTGRES_PORT",
114
+ "POSTGRES_USER",
115
+ "POSTGRES_PASSWORD",
116
+ "POSTGRES_DB",
117
+ "REDIS_URL",
118
+ "REDIS_HOST",
119
+ "REDIS_PORT",
120
+ "MAIL_HOST",
121
+ "MAIL_SMTP_PORT",
122
+ "MAIL_UI_PORT",
123
+ "NEXTAUTH_URL",
124
+ "NEXTAUTH_SECRET",
125
+ "AUTH_SECRET",
126
+ "APP_URL",
127
+ "ALLOW_INDEXING",
128
+ "WORKER_CONCURRENCY",
129
+ "STORAGE_PROVIDER",
130
+ "STORAGE_LOCAL_DIR",
131
+ "S3_BUCKET",
132
+ "S3_REGION",
133
+ "S3_ENDPOINT",
134
+ "S3_ACCESS_KEY_ID",
135
+ "S3_SECRET_ACCESS_KEY",
136
+ "S3_PUBLIC_URL",
137
+ "UPLOAD_MAX_SIZE",
138
+ "UPLOAD_ALLOWED_TYPES",
139
+ "EXPO_PUBLIC_API_URL"
140
+ ]
141
+ }
142
+ }
143
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@myquark/config",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "src/index.js",
6
+ "exports": {
7
+ ".": "./src/index.js",
8
+ "./rate-limit": "./src/rate-limit.js",
9
+ "./app-url": "./src/app-url.js",
10
+ "./environment": "./src/environment.js",
11
+ "./load-config": "./src/load-config.js",
12
+ "./validate-env": "./src/validate-env.js",
13
+ "./redis": "./src/redis.js"
14
+ },
15
+ "dependencies": {
16
+ "@techstream/quark-core": "^2.0.0",
17
+ "ioredis": "^6.0.0",
18
+ "zod": "^4.6.5"
19
+ }
20
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * APP_URL - Single source of truth for the application's canonical URL.
3
+ *
4
+ * Resolution order:
5
+ * 1. APP_URL (canonical origin - set it in every environment)
6
+ * 2. NEXTAUTH_URL (legacy / backward-compat)
7
+ * 3. http://localhost:${PORT || 3000} (local dev fallback)
8
+ *
9
+ * In development PORT is the default source for the web server port, but
10
+ * APP_URL should still be set explicitly whenever the origin you browse on is
11
+ * not exactly localhost:PORT (127.0.0.1, a LAN IP, a tunnel, another port).
12
+ * Auth.js derives its post-sign-in redirect and its client-side base URL from
13
+ * this value, so a mismatch silently drops the session.
14
+ * In production, set APP_URL explicitly (e.g. https://yourdomain.com).
15
+ *
16
+ * Derived values:
17
+ * - NEXTAUTH_URL - always set equal to the resolved APP_URL so
18
+ * NextAuth works without a separate variable.
19
+ * - allowedOrigins - the resolved APP_URL plus any extra origins
20
+ * listed in ALLOWED_ORIGINS (comma-separated).
21
+ */
22
+
23
+ import { ValidationError } from "@techstream/quark-core/errors";
24
+
25
+ /**
26
+ * Resolves the canonical application URL.
27
+ * @returns {string} The canonical URL (no trailing slash)
28
+ */
29
+ export function getAppUrl() {
30
+ const configuredAppUrl = process.env.APP_URL?.trim();
31
+ if (configuredAppUrl) {
32
+ return normalizeAppUrl(configuredAppUrl, "APP_URL");
33
+ }
34
+
35
+ const configuredNextAuthUrl = process.env.NEXTAUTH_URL?.trim();
36
+ if (configuredNextAuthUrl) {
37
+ return normalizeAppUrl(configuredNextAuthUrl, "NEXTAUTH_URL");
38
+ }
39
+
40
+ const fallbackPort = resolveLocalPort(process.env.PORT);
41
+
42
+ return normalizeAppUrl(`http://localhost:${fallbackPort}`, "derived APP_URL");
43
+ }
44
+
45
+ function resolveLocalPort(value) {
46
+ const parsedPort = Number.parseInt(value, 10);
47
+ if (Number.isInteger(parsedPort) && parsedPort > 0) {
48
+ return String(parsedPort);
49
+ }
50
+
51
+ return "3000";
52
+ }
53
+
54
+ function normalizeAppUrl(rawUrl, label) {
55
+ let parsedUrl;
56
+
57
+ try {
58
+ parsedUrl = new URL(rawUrl);
59
+ } catch {
60
+ throw new ValidationError(
61
+ `${label} must be an absolute http(s) URL. Received: ${rawUrl}`,
62
+ );
63
+ }
64
+
65
+ if (!["http:", "https:"].includes(parsedUrl.protocol)) {
66
+ throw new ValidationError(
67
+ `${label} must use http:// or https://. Received: ${rawUrl}`,
68
+ );
69
+ }
70
+
71
+ return parsedUrl.toString().replace(/\/+$/, "");
72
+ }
73
+
74
+ /**
75
+ * Returns the list of allowed CORS origins.
76
+ *
77
+ * Always includes the canonical APP_URL.
78
+ * If ALLOWED_ORIGINS is set, those are *added* (not replacing) the canonical
79
+ * origin so the primary domain is never accidentally excluded.
80
+ *
81
+ * @returns {string[]} De-duplicated list of allowed origins
82
+ */
83
+ export function getAllowedOrigins() {
84
+ const canonical = getAppUrl();
85
+ const extras = process.env.ALLOWED_ORIGINS
86
+ ? process.env.ALLOWED_ORIGINS.split(",")
87
+ .map((o) => o.trim())
88
+ .filter(Boolean)
89
+ : [];
90
+
91
+ // In development, allow the ports the dev server actually listens on plus
92
+ // the loopback aliases people type by hand. `getEnvironmentConfig()` returns
93
+ // the hard-coded default (3000) and ignores `process.env.PORT`, so reading it
94
+ // here silently drifted from the real port whenever PORT was overridden.
95
+ const isDev = process.env.NODE_ENV !== "production";
96
+ // resolveLocalPort() returns a string - parse before doing arithmetic or
97
+ // "3000" + 1 silently becomes "30001".
98
+ const devPort = Number.parseInt(resolveLocalPort(process.env.PORT), 10);
99
+ const devOrigins = isDev
100
+ ? [
101
+ `http://localhost:${devPort}`,
102
+ `http://localhost:${devPort + 1}`,
103
+ `http://127.0.0.1:${devPort}`,
104
+ `http://127.0.0.1:${devPort + 1}`,
105
+ ]
106
+ : [];
107
+
108
+ // Extra LAN/VPN dev hosts accepted by next.config.js `allowedDevOrigins`,
109
+ // so CORS and the Next.js dev host allow-list stay in agreement.
110
+ const devHostExtras = isDev
111
+ ? ["NEXT_DEV_ALLOWED_ORIGINS", "ALLOWED_DEV_ORIGINS"].flatMap((key) =>
112
+ (process.env[key] ?? "")
113
+ .split(",")
114
+ .map((host) => host.trim())
115
+ .filter(Boolean)
116
+ .map((host) => (host.includes("://") ? host : `http://${host}`)),
117
+ )
118
+ : [];
119
+
120
+ // De-duplicate
121
+ return [...new Set([canonical, ...extras, ...devOrigins, ...devHostExtras])];
122
+ }
123
+
124
+ /**
125
+ * Ensures NEXTAUTH_URL is set in process.env so NextAuth picks it up,
126
+ * even when only APP_URL was configured.
127
+ *
128
+ * Call this once at startup (e.g. in your env validation step).
129
+ */
130
+ export function syncNextAuthUrl() {
131
+ if (!process.env.NEXTAUTH_URL) {
132
+ process.env.NEXTAUTH_URL = getAppUrl();
133
+ }
134
+ }
@@ -0,0 +1,284 @@
1
+ /**
2
+ * @techstream/quark-config - Environment Configuration
3
+ * Provides environment-specific defaults for dev, test, staging, and production.
4
+ * Each environment defines sensible defaults that can be overridden via env vars.
5
+ */
6
+
7
+ /**
8
+ * @typedef {"development" | "test" | "staging" | "production"} Environment
9
+ *
10
+ * @typedef {Object} EnvironmentConfig
11
+ * @property {Environment} environment - Resolved environment name
12
+ * @property {boolean} isProduction - True in production and staging
13
+ * @property {boolean} isDevelopment - True in development
14
+ * @property {boolean} isTest - True in test
15
+ * @property {Object} server - Server configuration
16
+ * @property {number} server.port - HTTP port
17
+ * @property {Object} rateLimit - Rate limiting defaults
18
+ * @property {number} rateLimit.windowMs - Rate limit window in ms
19
+ * @property {number} rateLimit.maxRequests - Max requests per window
20
+ * @property {number} rateLimit.authMaxRequests - Max auth requests per window
21
+ * @property {Object} cache - Cache configuration
22
+ * @property {number} cache.defaultTtl - Default cache TTL in seconds
23
+ * @property {Object} logging - Logging configuration
24
+ * @property {string} logging.level - Minimum log level
25
+ * @property {boolean} logging.json - Use JSON format
26
+ * @property {Object} db - Database configuration
27
+ * @property {number} db.poolMax - Max DB pool connections
28
+ * @property {number} db.poolIdleTimeout - Idle timeout in seconds
29
+ * @property {number} db.connectionTimeout - Connection timeout in seconds
30
+ * @property {Object} email - Email configuration
31
+ * @property {number} email.timeout - SMTP/API timeout in ms
32
+ * @property {Object} security - Security configuration
33
+ * @property {boolean} security.enforceHttps - Require HTTPS
34
+ * @property {boolean} security.trustProxy - Trust proxy headers
35
+ * @property {Object} auth - Authentication configuration
36
+ * @property {boolean} auth.allowSignup - Allow public self-service signup
37
+ * @property {Object} features - Feature flags
38
+ * @property {boolean} features.debugRoutes - Enable debug endpoints
39
+ * @property {boolean} features.seedOnStart - Auto-seed database on startup
40
+ * @property {boolean} features.detailedErrors - Include stack traces in error responses
41
+ */
42
+
43
+ /** @type {Record<Environment, EnvironmentConfig>} */
44
+ const ENVIRONMENT_CONFIGS = {
45
+ development: {
46
+ environment: "development",
47
+ isProduction: false,
48
+ isDevelopment: true,
49
+ isTest: false,
50
+ server: {
51
+ port: 3000,
52
+ },
53
+ rateLimit: {
54
+ windowMs: 15 * 60 * 1000,
55
+ maxRequests: 1000,
56
+ authMaxRequests: 50,
57
+ },
58
+ cache: {
59
+ defaultTtl: 60,
60
+ },
61
+ logging: {
62
+ level: "debug",
63
+ json: false,
64
+ },
65
+ db: {
66
+ poolMax: 5,
67
+ poolIdleTimeout: 30,
68
+ connectionTimeout: 5,
69
+ },
70
+ email: {
71
+ timeout: 10_000,
72
+ },
73
+ security: {
74
+ enforceHttps: false,
75
+ trustProxy: false,
76
+ },
77
+ auth: {
78
+ allowSignup: true,
79
+ },
80
+ features: {
81
+ debugRoutes: true,
82
+ seedOnStart: false,
83
+ detailedErrors: true,
84
+ },
85
+ },
86
+
87
+ test: {
88
+ environment: "test",
89
+ isProduction: false,
90
+ isDevelopment: false,
91
+ isTest: true,
92
+ server: {
93
+ port: 3001,
94
+ },
95
+ rateLimit: {
96
+ windowMs: 15 * 60 * 1000,
97
+ maxRequests: 10_000,
98
+ authMaxRequests: 10_000,
99
+ },
100
+ cache: {
101
+ defaultTtl: 0,
102
+ },
103
+ logging: {
104
+ level: "warn",
105
+ json: false,
106
+ },
107
+ db: {
108
+ poolMax: 3,
109
+ poolIdleTimeout: 10,
110
+ connectionTimeout: 5,
111
+ },
112
+ email: {
113
+ timeout: 5_000,
114
+ },
115
+ security: {
116
+ enforceHttps: false,
117
+ trustProxy: false,
118
+ },
119
+ auth: {
120
+ allowSignup: true,
121
+ },
122
+ features: {
123
+ debugRoutes: true,
124
+ seedOnStart: false,
125
+ detailedErrors: true,
126
+ },
127
+ },
128
+
129
+ staging: {
130
+ environment: "staging",
131
+ isProduction: true,
132
+ isDevelopment: false,
133
+ isTest: false,
134
+ server: {
135
+ port: 3000,
136
+ },
137
+ rateLimit: {
138
+ windowMs: 15 * 60 * 1000,
139
+ maxRequests: 100,
140
+ authMaxRequests: 5,
141
+ },
142
+ cache: {
143
+ defaultTtl: 300,
144
+ },
145
+ logging: {
146
+ level: "info",
147
+ json: true,
148
+ },
149
+ db: {
150
+ poolMax: 10,
151
+ poolIdleTimeout: 30,
152
+ connectionTimeout: 5,
153
+ },
154
+ email: {
155
+ timeout: 10_000,
156
+ },
157
+ security: {
158
+ enforceHttps: true,
159
+ trustProxy: true,
160
+ },
161
+ auth: {
162
+ allowSignup: true,
163
+ },
164
+ features: {
165
+ debugRoutes: false,
166
+ seedOnStart: false,
167
+ detailedErrors: false,
168
+ },
169
+ },
170
+
171
+ production: {
172
+ environment: "production",
173
+ isProduction: true,
174
+ isDevelopment: false,
175
+ isTest: false,
176
+ server: {
177
+ port: 3000,
178
+ },
179
+ rateLimit: {
180
+ windowMs: 15 * 60 * 1000,
181
+ maxRequests: 100,
182
+ authMaxRequests: 5,
183
+ },
184
+ cache: {
185
+ defaultTtl: 600,
186
+ },
187
+ logging: {
188
+ level: "info",
189
+ json: true,
190
+ },
191
+ db: {
192
+ poolMax: 10,
193
+ poolIdleTimeout: 30,
194
+ connectionTimeout: 5,
195
+ },
196
+ email: {
197
+ timeout: 10_000,
198
+ },
199
+ security: {
200
+ enforceHttps: true,
201
+ trustProxy: true,
202
+ },
203
+ auth: {
204
+ allowSignup: true,
205
+ },
206
+ features: {
207
+ debugRoutes: false,
208
+ seedOnStart: false,
209
+ detailedErrors: false,
210
+ },
211
+ },
212
+ };
213
+
214
+ /** Valid environment names */
215
+ export const ENVIRONMENTS = /** @type {const} */ ([
216
+ "development",
217
+ "test",
218
+ "staging",
219
+ "production",
220
+ ]);
221
+
222
+ /**
223
+ * Resolves the current environment from NODE_ENV.
224
+ * Maps common aliases (e.g. "dev" → "development", "prod" → "production").
225
+ * Defaults to "development" if unset or unrecognized.
226
+ *
227
+ * @param {string} [nodeEnv] - Override for NODE_ENV (defaults to process.env.NODE_ENV)
228
+ * @returns {Environment}
229
+ */
230
+ export function resolveEnvironment(nodeEnv) {
231
+ const raw = (nodeEnv ?? process.env.NODE_ENV ?? "").toLowerCase().trim();
232
+
233
+ const aliases = {
234
+ dev: "development",
235
+ development: "development",
236
+ test: "test",
237
+ testing: "test",
238
+ staging: "staging",
239
+ stage: "staging",
240
+ prod: "production",
241
+ production: "production",
242
+ };
243
+
244
+ return aliases[raw] || "development";
245
+ }
246
+
247
+ /**
248
+ * Returns the full environment configuration for a given environment.
249
+ * Unknown environments fall back to development.
250
+ *
251
+ * @param {string} [nodeEnv] - Override for NODE_ENV
252
+ * @returns {EnvironmentConfig}
253
+ */
254
+ export function getEnvironmentConfig(nodeEnv) {
255
+ const env = resolveEnvironment(nodeEnv);
256
+ return { ...ENVIRONMENT_CONFIGS[env] };
257
+ }
258
+
259
+ /**
260
+ * Shallow-merges environment config with user-provided overrides (one level deep).
261
+ * Top-level scalars are replaced; top-level objects are spread-merged.
262
+ * Useful when downstream apps need to adjust defaults per-environment.
263
+ *
264
+ * @param {EnvironmentConfig} base - Base environment config
265
+ * @param {Record<string, unknown>} overrides - Partial overrides to merge
266
+ * @returns {EnvironmentConfig}
267
+ */
268
+ export function mergeConfig(base, overrides) {
269
+ const result = { ...base };
270
+ for (const [key, value] of Object.entries(overrides)) {
271
+ if (
272
+ value != null &&
273
+ typeof value === "object" &&
274
+ !Array.isArray(value) &&
275
+ typeof result[key] === "object" &&
276
+ result[key] != null
277
+ ) {
278
+ result[key] = { ...result[key], ...value };
279
+ } else {
280
+ result[key] = value;
281
+ }
282
+ }
283
+ return result;
284
+ }
@@ -0,0 +1,16 @@
1
+ export const config = {
2
+ appName: process.env.APP_NAME || "Quark",
3
+ appDescription: process.env.APP_DESCRIPTION || "A Quark-powered application",
4
+ };
5
+
6
+ export { getAllowedOrigins, getAppUrl, syncNextAuthUrl } from "./app-url.js";
7
+ export {
8
+ ENVIRONMENTS,
9
+ getEnvironmentConfig,
10
+ mergeConfig,
11
+ resolveEnvironment,
12
+ } from "./environment.js";
13
+ export { getConfig, loadConfig, resetConfig } from "./load-config.js";
14
+ export { applyRateLimit, rateLimit } from "./rate-limit.js";
15
+ export { closeSharedRedisClient, getSharedRedisClient } from "./redis.js";
16
+ export { loadEnv } from "./validate-env.js";