@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
package/README.md ADDED
@@ -0,0 +1,210 @@
1
+ # @techstream/quark-create-app CLI
2
+
3
+ Scaffold a new Quark project with sensible defaults for full-stack JavaScript development.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ # Interactive — prompts for project name, description, and options
9
+ npx @techstream/quark-create-app@latest
10
+
11
+ # Non-interactive — provide project name as argument
12
+ npx @techstream/quark-create-app@latest my-awesome-app
13
+ ```
14
+
15
+ The CLI scaffolds a complete project structure with:
16
+ - **Next.js** web application
17
+ - **Prisma** database schema and migrations
18
+ - **BullMQ** job queues
19
+ - **Docker Compose** setup (PostgreSQL, Redis, Mailpit)
20
+ - **JavaScript** monorepo with `pnpm` workspaces
21
+
22
+ ## Quick Setup
23
+
24
+ ```bash
25
+ cd my-awesome-app
26
+ docker compose up -d
27
+ pnpm db:migrate
28
+ pnpm dev
29
+ ```
30
+
31
+ ## Scaffolding into an existing repository
32
+
33
+ The scaffolder creates a new project folder and initialises a git repository inside it. Run it from inside an existing git repository and the project lands one level below that repository's root — where pnpm workspaces, turbo and GitHub Actions cannot see `package.json`, breaking installs and CI.
34
+
35
+ The CLI detects this, prints a warning, and skips git initialisation so a nested repository is never created. Recommended alternatives:
36
+
37
+ - Scaffold outside the repository, then push the project as its own repository.
38
+ - Scaffold into a temporary folder, then move the files to the repository root before the first commit.
39
+
40
+ ## Commands
41
+
42
+ ```bash
43
+ # Create a new project (interactive or with name argument)
44
+ npx @techstream/quark-create-app@latest
45
+ npx @techstream/quark-create-app@latest my-app
46
+
47
+ # Add a package to an existing project
48
+ npx @techstream/quark-create-app add jobs
49
+
50
+ # Print an embedded skill for a feature
51
+ npx @techstream/quark-create-app skill bookings
52
+
53
+ # Update Quark packages in an existing project
54
+ npx @techstream/quark-create-app update
55
+
56
+ # Check for available updates without applying them
57
+ npx @techstream/quark-create-app update --check
58
+
59
+ # Report drift in scaffold-managed files without overwriting anything
60
+ npx @techstream/quark-create-app update --scaffold-check
61
+
62
+ # Fail CI when scaffold drift is detected
63
+ npx @techstream/quark-create-app update --scaffold-check --fail-on-drift
64
+ ```
65
+
66
+ Aliases:
67
+ - `quark-create-app`
68
+ - `create-quark-app`
69
+ - `quark-update`
70
+
71
+ ## Interactive Mode
72
+
73
+ When run without arguments, the CLI prompts for:
74
+
75
+ 1. **Project name** — becomes the directory name, npm scope, and env vars
76
+ 2. **Description** — seeds `MAIN.md` and `.env` `APP_NAME`/`APP_DESCRIPTION`
77
+ 3. **Background jobs** — include BullMQ worker for emails, webhooks, scheduled tasks
78
+ 4. **Public signup** — allow self-service user registration
79
+
80
+ ```bash
81
+ npx @techstream/quark-create-app@latest
82
+ # ? Project name: my-app
83
+ # ? Describe your app: A platform for booking salon appointments
84
+ # ? Include background jobs (emails, webhooks, scheduled tasks)? Yes
85
+ # ? Allow public self-service signup? Yes
86
+ ```
87
+
88
+ ## Non-Interactive Mode
89
+
90
+ Skip all prompts with flags:
91
+
92
+ ```bash
93
+ npx @techstream/quark-create-app@latest my-app --no-prompts
94
+ ```
95
+
96
+ ### Packages
97
+
98
+ Specify which optional packages to include (default: `ui,jobs`; valid: `ui`, `jobs`):
99
+
100
+ ```bash
101
+ # Only include UI package
102
+ npx @techstream/quark-create-app@latest my-app --no-prompts --packages ui
103
+
104
+ # Include both UI and Jobs
105
+ npx @techstream/quark-create-app@latest my-app --no-prompts --packages ui,jobs
106
+
107
+ # Minimal setup (no optional packages)
108
+ npx @techstream/quark-create-app@latest my-app --no-prompts --packages ""
109
+ ```
110
+
111
+ `ui` is always included automatically as a required dependency.
112
+
113
+ ### Product brief
114
+
115
+ Seed the `MAIN.md` brief from a flag:
116
+
117
+ ```bash
118
+ npx @techstream/quark-create-app@latest my-app --no-prompts --prompt "A booking platform for salons"
119
+ ```
120
+
121
+ ### Signup
122
+
123
+ Control public self-service signup (default: `enabled`):
124
+
125
+ ```bash
126
+ npx @techstream/quark-create-app@latest my-app --no-prompts --signup disabled
127
+ ```
128
+
129
+ ### skill command
130
+
131
+ Print an embedded skill for a feature:
132
+
133
+ ```bash
134
+ # Print the bookings skill
135
+ npx @techstream/quark-create-app@latest skill bookings
136
+
137
+ # Print a core skill (model, endpoint, dashboard)
138
+ npx @techstream/quark-create-app@latest skill model
139
+ npx @techstream/quark-create-app@latest skill endpoint
140
+ npx @techstream/quark-create-app@latest skill dashboard
141
+ ```
142
+
143
+ Available skills: `model`, `endpoint`, `dashboard`, `bookings`, `crm`, `cms`, `ai`.
144
+
145
+ ### Skip Installation Steps
146
+
147
+ Create the project structure without running package installation:
148
+
149
+ ```bash
150
+ # Create project but skip pnpm install
151
+ npx @techstream/quark-create-app@latest my-app --no-prompts --skip-install
152
+
153
+ # Useful for CI/CD where you'll install dependencies separately
154
+ ```
155
+
156
+ ### Docker Cleanup
157
+
158
+ Control whether to remove Docker volumes from previous cleanup:
159
+
160
+ ```bash
161
+ # Keep Docker working directories (useful in CI/CD)
162
+ npx @techstream/quark-create-app@latest my-app --no-prompts --skip-docker
163
+ ```
164
+
165
+ ### Complete Example: Full Automation
166
+
167
+ ```bash
168
+ # Create, install, and setup everything automatically
169
+ npx @techstream/quark-create-app@latest my-app \
170
+ --no-prompts \
171
+ --packages ui,jobs \
172
+ && cd my-app \
173
+ && docker compose up -d \
174
+ && pnpm db:migrate \
175
+ && pnpm dev
176
+ ```
177
+
178
+ ## Common Tasks
179
+
180
+ - **Update Quark packages**: `quark-update` or `pnpm update @techstream/quark-*`
181
+ - **Check for updates**: `quark-update --check`
182
+ - **Review scaffold drift**: `quark-update --scaffold-check`
183
+ - **Fail CI on scaffold drift**: `quark-update --scaffold-check --fail-on-drift`
184
+ - **Configure environment**: Edit `.env` file (see `.env.example`)
185
+
186
+ ## CLI Testing
187
+
188
+ ```bash
189
+ # Lightweight template checks
190
+ pnpm test
191
+
192
+ # E2E scaffold simulation
193
+ pnpm test:e2e
194
+
195
+ # Full build verification (opt-in)
196
+ QUARK_CLI_BUILD_TEST=1 pnpm test:build
197
+ ```
198
+
199
+ ## Troubleshooting
200
+
201
+ - **pnpm install fails**: Ensure `pnpm` is installed and Node.js >= 22.
202
+ - **Prisma generate fails**: Run `pnpm --filter db db:generate` inside the project.
203
+ - **Docker ports conflict**: The CLI auto-selects free ports. Check `.env` for assigned values.
204
+ - **Missing env vars**: Copy `.env.example` to `.env` and fill required values.
205
+
206
+ ## Support
207
+
208
+ For issues, questions, and discussions:
209
+ - [Issue Tracker](https://github.com/usequark/quark/issues)
210
+ - [Discussions](https://github.com/usequark/quark/discussions)
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@usequark/quark-create-app",
3
+ "version": "1.23.11",
4
+ "description": "CLI to scaffold new Quark projects with Next.js, Prisma, BullMQ, and Railway deployment",
5
+ "type": "module",
6
+ "homepage": "https://github.com/usequark/quark#readme",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/usequark/quark.git",
10
+ "directory": "packages/cli"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/usequark/quark/issues"
14
+ },
15
+ "author": "Quark Contributors",
16
+ "bin": {
17
+ "quark": "src/index.js",
18
+ "quark-create-app": "src/index.js",
19
+ "create-quark-app": "src/index.js",
20
+ "quark-update": "src/index.js"
21
+ },
22
+ "files": [
23
+ "src",
24
+ "templates",
25
+ "README.md"
26
+ ],
27
+ "scripts": {
28
+ "sync-templates": "node scripts/sync-templates.js",
29
+ "sync-templates:check": "node scripts/sync-templates.js --check",
30
+ "regen-migration": "node scripts/regenerate-template-migration.js",
31
+ "smoke:published": "node scripts/smoke-published.js",
32
+ "test": "node test-cli.js && node --test src/utils.test.js && node --test src/prepare-hooks.test.js && node --test src/scaffold-output.test.js && node --test src/template-scripts.test.js && node --test src/scaffold-guards.test.js && node --test src/worker-dockerfile.test.js && node --test src/dependabot-config.test.js && node --test 'src/deploy/**/*.test.js' && node --test --test-concurrency=1 'src/deploy/**/*.integration.test.js'",
33
+ "test:build": "node test-build.js",
34
+ "test:e2e": "node test-e2e.js",
35
+ "test:e2e:full": "node test-e2e-full.js",
36
+ "test:integration": "node test-integration.js",
37
+ "test:flags": "node --test test-flags.js",
38
+ "test:all": "node test-all.js",
39
+ "check:perf": "node scripts/check-e2e-perf.js"
40
+ },
41
+ "dependencies": {
42
+ "chalk": "^6.0.0",
43
+ "commander": "^15.0.0",
44
+ "execa": "^10.0.1",
45
+ "fs-extra": "^11.4.1",
46
+ "prompts": "^2.4.2"
47
+ },
48
+ "publishConfig": {
49
+ "registry": "https://registry.npmjs.org",
50
+ "access": "public"
51
+ },
52
+ "engines": {
53
+ "node": ">=22.13.0"
54
+ },
55
+ "license": "MIT"
56
+ }
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Guard the scaffold template's dependabot config.
5
+ *
6
+ * The template used to group by `dependency-type` alone, which produced one
7
+ * PR bundling patch, minor and major bumps together. That is how several
8
+ * breaking changes reached this repo unnoticed - bullmq 6 removed
9
+ * `queue.client` and shipped two silent production regressions inside a
10
+ * 35-package diff.
11
+ *
12
+ * This checks the shipped template still separates majors, so a scaffolded
13
+ * project does not inherit the problem.
14
+ *
15
+ * Run with: node --test packages/cli/src/dependabot-config.test.js
16
+ */
17
+
18
+ import assert from "node:assert/strict";
19
+ import fs from "node:fs";
20
+ import path from "node:path";
21
+ import { describe, it } from "node:test";
22
+ import { fileURLToPath } from "node:url";
23
+
24
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
25
+ const REPO_ROOT = path.resolve(__dirname, "../../..");
26
+
27
+ const CONFIGS = [
28
+ ["monorepo", ".github/dependabot.yml"],
29
+ [
30
+ "scaffold template",
31
+ "packages/cli/templates/base-project/.github/dependabot.yml",
32
+ ],
33
+ ];
34
+
35
+ function readConfig(relativePath) {
36
+ return fs.readFileSync(path.join(REPO_ROOT, relativePath), "utf8");
37
+ }
38
+
39
+ describe("dependabot config", () => {
40
+ for (const [label, relativePath] of CONFIGS) {
41
+ it(`${label} isolates major bumps into their own group`, () => {
42
+ const text = readConfig(relativePath);
43
+
44
+ assert.match(
45
+ text,
46
+ /production-majors:[\s\S]*?dependency-type:\s*production[\s\S]*?update-types:\s*\["major"\]/,
47
+ "production majors must be grouped separately so a breaking change is reviewed alone",
48
+ );
49
+ assert.match(
50
+ text,
51
+ /development-majors:[\s\S]*?dependency-type:\s*development[\s\S]*?update-types:\s*\["major"\]/,
52
+ "development majors must be grouped separately too",
53
+ );
54
+ });
55
+
56
+ it(`${label} does not lump every production bump into one group`, () => {
57
+ const text = readConfig(relativePath);
58
+
59
+ // A bare `production:` group with no update-types collects majors,
60
+ // minors and patches into a single reviewable-looking PR.
61
+ assert.doesNotMatch(
62
+ text,
63
+ /\n {6}production:\n {8}dependency-type:\s*production\n/,
64
+ "a catch-all production group re-creates the 35-package PR problem",
65
+ );
66
+ });
67
+
68
+ it(`${label} keeps a PR limit that can hold the extra groups`, () => {
69
+ const text = readConfig(relativePath);
70
+ const limit = Number(
71
+ (text.match(/open-pull-requests-limit:\s*(\d+)/) || [])[1],
72
+ );
73
+
74
+ assert.ok(
75
+ Number.isInteger(limit) && limit >= 6,
76
+ `open-pull-requests-limit should be at least 6 for the split groups (got ${limit})`,
77
+ );
78
+ });
79
+ }
80
+ });
81
+
82
+ /**
83
+ * The auto-merge workflow is the other half of the split, and the only part
84
+ * that actually merges anything.
85
+ *
86
+ * Splitting dependabot into per-severity groups changes what a human sees. It
87
+ * does not change what merges unattended - that is gated solely by
88
+ * `update-type == 'version-update:semver-patch'`. A dependency that ships a
89
+ * breaking change in a *patch* passes straight through, and that is exactly how
90
+ * bullmq 6's `queue.client` removal reached this repo. Majors already need a
91
+ * human by virtue of not matching; this test keeps it that way.
92
+ *
93
+ * The monorepo runs this workflow; the template ships an equivalent one, so a
94
+ * scaffolded project inherits the same gate.
95
+ */
96
+ const AUTO_MERGE_WORKFLOWS = [
97
+ ["monorepo", ".github/workflows/dependabot-auto-merge.yml"],
98
+ [
99
+ "scaffold template",
100
+ "packages/cli/templates/base-project/.github/workflows/dependabot-auto-merge.yml",
101
+ ],
102
+ ];
103
+
104
+ describe("dependabot auto-merge gate", () => {
105
+ for (const [label, relativePath] of AUTO_MERGE_WORKFLOWS) {
106
+ it(`${label} gates auto-merge on the semver update type`, () => {
107
+ const text = readConfig(relativePath);
108
+
109
+ assert.match(
110
+ text,
111
+ /version-update:semver-patch/,
112
+ "the auto-merge gate must key on the semver patch update type, so minors and majors still need a human",
113
+ );
114
+ });
115
+
116
+ it(`${label} does not merge every dependabot update`, () => {
117
+ const text = readConfig(relativePath);
118
+
119
+ // An unconditional `gh pr merge` would auto-merge majors unattended,
120
+ // which is precisely how the bullmq break nearly landed.
121
+ assert.doesNotMatch(
122
+ text,
123
+ /if:\s*github\.actor\s*==\s*'dependabot\[bot\]'\n\s*steps:/,
124
+ "auto-merge must be gated on the update type, not just the actor",
125
+ );
126
+ });
127
+ }
128
+ });
@@ -0,0 +1,203 @@
1
+ import { execa } from "execa";
2
+
3
+ const RAILWAY = "railway";
4
+
5
+ /**
6
+ * Escapes a string for safe embedding in a TypeScript string literal.
7
+ * Handles backslashes, quotes, newlines, and template literal interpolation.
8
+ *
9
+ * @param {string} value
10
+ * @returns {string} The escaped string (without surrounding quotes)
11
+ */
12
+ export function escapeTsString(value) {
13
+ return String(value)
14
+ .replace(/\\/g, "\\\\")
15
+ .replace(/"/g, '\\"')
16
+ .replace(/\n/g, "\\n")
17
+ .replace(/\r/g, "\\r")
18
+ .replace(/\$/g, "\\$");
19
+ }
20
+
21
+ /**
22
+ * Converts an arbitrary string into a valid JavaScript identifier.
23
+ * Replaces non-alphanumeric characters with underscores, prefixes
24
+ * with `_` if it starts with a digit, and deduplicates underscores.
25
+ *
26
+ * @param {string} value
27
+ * @returns {string} A valid JS identifier (e.g. `my__web__app`)
28
+ */
29
+ function toIdentifier(value) {
30
+ return (
31
+ String(value)
32
+ .replace(/[^a-zA-Z0-9]/g, "_")
33
+ .replace(/_+/g, "_")
34
+ .replace(/^(\d)/, "_$1") || "_service"
35
+ );
36
+ }
37
+
38
+ /**
39
+ * Generates a Railway IaC file (.railway/railway.ts) from service contracts.
40
+ *
41
+ * Uses `preserve()` for secrets so Railway keeps existing values.
42
+ * Uses `${{ServiceName.VARIABLE}}` references for provisioned databases.
43
+ * Omits `source` so the CLI deploy flow (railway config apply) manages
44
+ * settings without overwriting a linked GitHub/Docker source.
45
+ *
46
+ * All user-supplied strings (service names, project names, command strings,
47
+ * variable values) are escaped before embedding in the generated TypeScript.
48
+ *
49
+ * @param {object} options
50
+ * @param {string} options.iacPath - Absolute path to .railway/railway.ts
51
+ * @param {Array<object>} options.services - Discovered services from contract.js
52
+ * @param {object} options.secrets - Map of secret key → preserve() or literal value
53
+ * @param {object} options.variableRefs - Map of variable key → Railway reference string
54
+ * @param {object} options.serviceVars - Map of service name → extra env vars
55
+ * @param {string} [options.projectName] - Railway project name (defaults to "quark-app")
56
+ */
57
+ export async function generateIacFile({
58
+ iacPath,
59
+ services,
60
+ secrets = {},
61
+ variableRefs = {},
62
+ serviceVars = {},
63
+ projectName = "quark-app",
64
+ }) {
65
+ const { default: fs } = await import("fs-extra");
66
+ const { dirname } = await import("node:path");
67
+
68
+ const imports = new Set();
69
+ imports.add(`import { defineRailway, project, service } from "railway/iac";`);
70
+
71
+ const resourceLines = [];
72
+
73
+ for (const svc of services) {
74
+ const svcIdentifier = toIdentifier(svc.name);
75
+ const svcLabel = escapeTsString(svc.name);
76
+
77
+ // Build config
78
+ const buildCmd =
79
+ svc.kind === "web"
80
+ ? "pnpm install --frozen-lockfile && pnpm db:generate && pnpm --dir apps/web build:deploy"
81
+ : "pnpm install --frozen-lockfile && pnpm db:generate";
82
+
83
+ // Deploy config
84
+ const startCmd =
85
+ svc.kind === "web"
86
+ ? "HOSTNAME=0.0.0.0 pnpm --dir apps/web start:deploy"
87
+ : "pnpm --dir apps/worker start:deploy";
88
+
89
+ const preDeploy = "pnpm db:migrate:deploy";
90
+
91
+ // Healthcheck (web only)
92
+ const healthcheck =
93
+ svc.kind === "web"
94
+ ? `\n\t\thealthcheck: "/api/health",\n\t\thealthcheckTimeout: 120,`
95
+ : "";
96
+
97
+ // Build env vars — escape all string values
98
+ const extraVars = serviceVars[svc.name] || {};
99
+ const envEntries = [
100
+ ...Object.entries(variableRefs),
101
+ ...Object.entries(secrets),
102
+ ...Object.entries(extraVars),
103
+ ];
104
+
105
+ const envBlock =
106
+ envEntries.length > 0
107
+ ? `,\n\t\tenv: {\n${envEntries.map(([k, v]) => `\t\t\t${escapeTsString(k)}: ${v},`).join("\n")}\n\t\t}`
108
+ : "";
109
+
110
+ resourceLines.push(`\tconst ${svcIdentifier} = service("${svcLabel}", {
111
+ \t\tbuild: "${escapeTsString(buildCmd)}",
112
+ \t\tstart: "${escapeTsString(startCmd)}",
113
+ \t\tpreDeploy: "${escapeTsString(preDeploy)}",${healthcheck}${envBlock}
114
+ \t});`);
115
+ }
116
+
117
+ const resources = services.map((s) => toIdentifier(s.name)).join(", ");
118
+
119
+ const content = `${Array.from(imports).join("\n")}
120
+
121
+ export default defineRailway(() => {
122
+ ${resourceLines.join("\n\n")}
123
+
124
+ \treturn project("${escapeTsString(projectName)}", {
125
+ \t\tresources: [${resources}],
126
+ \t});
127
+ });
128
+ `;
129
+
130
+ await fs.ensureDir(dirname(iacPath));
131
+ await fs.writeFile(iacPath, content, "utf8");
132
+ }
133
+
134
+ /**
135
+ * Runs `railway config apply --yes` to apply IaC configuration.
136
+ *
137
+ * @param {object} options
138
+ * @param {string} options.cwd - Project root directory
139
+ * @param {number} [options.timeout=120000] - Timeout in ms
140
+ * @returns {Promise<{success: boolean, output: string}>}
141
+ */
142
+ export async function applyIacConfig({ cwd, timeout = 120_000 } = {}) {
143
+ try {
144
+ const { stdout, stderr } = await execa(
145
+ RAILWAY,
146
+ ["config", "apply", "--yes"],
147
+ { cwd, timeout, reject: true },
148
+ );
149
+ return { success: true, output: stdout || stderr };
150
+ } catch (error) {
151
+ const output = error.stdout || error.stderr || error.message;
152
+ return { success: false, output };
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Installs the Railway SDK (required for TypeScript IaC evaluation).
158
+ *
159
+ * @param {object} options
160
+ * @param {string} options.cwd - Project root directory
161
+ * @param {string} [options.packageManager="pnpm"] - Package manager to use
162
+ */
163
+ export async function installRailwaySdk({ cwd, packageManager = "pnpm" } = {}) {
164
+ await execa(packageManager, ["add", "-D", "railway"], {
165
+ cwd,
166
+ timeout: 60_000,
167
+ });
168
+ }
169
+
170
+ /**
171
+ * Checks the latest deployment status for a service.
172
+ *
173
+ * @param {string} serviceName
174
+ * @param {object} options
175
+ * @param {string} options.cwd - Project root directory
176
+ * @param {string} [options.environment] - Railway environment name
177
+ * @returns {Promise<{status: string|null, deploymentId: string|null}>}
178
+ */
179
+ export async function getDeploymentStatus(
180
+ serviceName,
181
+ { cwd, environment } = {},
182
+ ) {
183
+ const args = ["deployment", "list", "--json", "--service", serviceName];
184
+ if (environment) {
185
+ args.push("--environment", environment);
186
+ }
187
+
188
+ try {
189
+ const { stdout } = await execa(RAILWAY, args, {
190
+ cwd,
191
+ timeout: 15_000,
192
+ });
193
+ const deployments = JSON.parse(stdout);
194
+ const latest = Array.isArray(deployments) ? deployments[0] : null;
195
+ if (!latest) return { status: null, deploymentId: null };
196
+ return {
197
+ status: (latest.status || "").toUpperCase(),
198
+ deploymentId: latest.id || null,
199
+ };
200
+ } catch {
201
+ return { status: null, deploymentId: null };
202
+ }
203
+ }
@@ -0,0 +1,27 @@
1
+ export {
2
+ applyIacConfig,
3
+ escapeTsString,
4
+ generateIacFile,
5
+ getDeploymentStatus,
6
+ installRailwaySdk,
7
+ } from "./iac.js";
8
+ export {
9
+ checkRailwayCLI,
10
+ checkRailwayLogin,
11
+ DIAGNOSTIC_CODES as RAILWAY_DIAGNOSTIC_CODES,
12
+ deleteService,
13
+ deployService,
14
+ ensurePlugin,
15
+ ensureRailwayProject,
16
+ ensureService,
17
+ getExistingVariable,
18
+ getProjectDomains,
19
+ getServiceUrl,
20
+ isProjectLinked,
21
+ listProjects,
22
+ RAILWAY,
23
+ RailwayError,
24
+ removeServiceDomain,
25
+ setProjectVariables,
26
+ tryLinkProject,
27
+ } from "./railway.js";