create-skit 0.1.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.
Files changed (195) hide show
  1. package/README.md +36 -0
  2. package/bin/create-skit.mjs +1064 -0
  3. package/lib/module-application.mjs +281 -0
  4. package/lib/module-resolver.mjs +179 -0
  5. package/modules/README.md +22 -0
  6. package/modules/ai-dx/files/AGENTS.md +116 -0
  7. package/modules/ai-dx/files/ARCHITECTURE.md +103 -0
  8. package/modules/ai-dx/module.json +14 -0
  9. package/modules/ai-dx-claude/files/CLAUDE.md +8 -0
  10. package/modules/ai-dx-claude/module.json +13 -0
  11. package/modules/ai-dx-cursor/files/.cursor/rules/auth.mdc +53 -0
  12. package/modules/ai-dx-cursor/files/.cursor/rules/database.mdc +48 -0
  13. package/modules/ai-dx-cursor/files/.cursor/rules/env.mdc +43 -0
  14. package/modules/ai-dx-cursor/files/.cursor/rules/nextjs.mdc +58 -0
  15. package/modules/ai-dx-cursor/files/.cursor/rules/project.mdc +33 -0
  16. package/modules/ai-dx-cursor/files/.cursor/rules/testing.mdc +55 -0
  17. package/modules/ai-dx-cursor/module.json +18 -0
  18. package/modules/ai-dx-gemini/files/.gemini/GEMINI.md +5 -0
  19. package/modules/ai-dx-gemini/module.json +13 -0
  20. package/modules/auth-core/module.json +8 -0
  21. package/modules/auth-github/module.json +20 -0
  22. package/modules/billing-polar/module.json +20 -0
  23. package/modules/billing-stripe/module.json +23 -0
  24. package/modules/dashboard-shell/files/src/app/globals.css +756 -0
  25. package/modules/dashboard-shell/files/src/app/settings/page.tsx +67 -0
  26. package/modules/dashboard-shell/module.json +11 -0
  27. package/modules/db-pg/module.json +21 -0
  28. package/modules/db-postgresjs/module.json +21 -0
  29. package/modules/deploy-docker/files/.dockerignore +19 -0
  30. package/modules/deploy-docker/files/Dockerfile +25 -0
  31. package/modules/deploy-docker/module.json +11 -0
  32. package/modules/email-resend/module.json +21 -0
  33. package/modules/quality-baseline/module.json +8 -0
  34. package/modules/testing-baseline/module.json +8 -0
  35. package/package.json +40 -0
  36. package/presets/README.md +12 -0
  37. package/presets/blank.json +67 -0
  38. package/presets/dashboard.json +67 -0
  39. package/templates/base-web/.env.example +17 -0
  40. package/templates/base-web/.github/workflows/ci.yml +34 -0
  41. package/templates/base-web/.husky/pre-commit +3 -0
  42. package/templates/base-web/.husky/pre-push +3 -0
  43. package/templates/base-web/.prettierignore +3 -0
  44. package/templates/base-web/README.md +42 -0
  45. package/templates/base-web/drizzle.config.ts +16 -0
  46. package/templates/base-web/eslint.config.mjs +127 -0
  47. package/templates/base-web/manifest.json +5 -0
  48. package/templates/base-web/next-env.d.ts +4 -0
  49. package/templates/base-web/next.config.ts +5 -0
  50. package/templates/base-web/package.json +75 -0
  51. package/templates/base-web/playwright.config.ts +21 -0
  52. package/templates/base-web/prettier.config.mjs +9 -0
  53. package/templates/base-web/proxy.ts +23 -0
  54. package/templates/base-web/src/app/api/auth/[...all]/route.ts +5 -0
  55. package/templates/base-web/src/app/api/billing/checkout/route.ts +26 -0
  56. package/templates/base-web/src/app/api/billing/portal/route.ts +25 -0
  57. package/templates/base-web/src/app/api/email/test/route.ts +28 -0
  58. package/templates/base-web/src/app/api/webhooks/polar/route.ts +5 -0
  59. package/templates/base-web/src/app/api/webhooks/stripe/route.ts +5 -0
  60. package/templates/base-web/src/app/billing/page.tsx +55 -0
  61. package/templates/base-web/src/app/dashboard/page.tsx +15 -0
  62. package/templates/base-web/src/app/email/page.tsx +46 -0
  63. package/templates/base-web/src/app/error.tsx +27 -0
  64. package/templates/base-web/src/app/globals.css +534 -0
  65. package/templates/base-web/src/app/layout.tsx +19 -0
  66. package/templates/base-web/src/app/llms-full.txt/route.ts +158 -0
  67. package/templates/base-web/src/app/llms.txt/route.ts +59 -0
  68. package/templates/base-web/src/app/loading.tsx +24 -0
  69. package/templates/base-web/src/app/not-found.tsx +16 -0
  70. package/templates/base-web/src/app/page.tsx +5 -0
  71. package/templates/base-web/src/app/sign-in/page.tsx +14 -0
  72. package/templates/base-web/src/app/sign-up/page.tsx +14 -0
  73. package/templates/base-web/src/components/auth/email-auth-form.test.tsx +40 -0
  74. package/templates/base-web/src/components/auth/email-auth-form.tsx +128 -0
  75. package/templates/base-web/src/components/auth/sign-out-button.tsx +29 -0
  76. package/templates/base-web/src/db/index.ts +16 -0
  77. package/templates/base-web/src/db/schema/auth.ts +4 -0
  78. package/templates/base-web/src/db/schema/index.ts +2 -0
  79. package/templates/base-web/src/db/schema/projects.ts +17 -0
  80. package/templates/base-web/src/db/seeds/index.ts +32 -0
  81. package/templates/base-web/src/lib/auth-client.ts +5 -0
  82. package/templates/base-web/src/lib/auth-session.ts +21 -0
  83. package/templates/base-web/src/lib/auth.ts +23 -0
  84. package/templates/base-web/src/lib/billing/index.ts +37 -0
  85. package/templates/base-web/src/lib/billing/providers/polar.ts +80 -0
  86. package/templates/base-web/src/lib/billing/providers/stripe.ts +77 -0
  87. package/templates/base-web/src/lib/billing/types.ts +25 -0
  88. package/templates/base-web/src/lib/email/index.ts +19 -0
  89. package/templates/base-web/src/lib/email/templates.test.ts +12 -0
  90. package/templates/base-web/src/lib/email/templates.ts +40 -0
  91. package/templates/base-web/src/lib/env.ts +83 -0
  92. package/templates/base-web/tests/e2e/home.spec.ts +8 -0
  93. package/templates/base-web/tsconfig.json +34 -0
  94. package/templates/base-web/vitest.config.ts +19 -0
  95. package/templates/blank/.env.example +16 -0
  96. package/templates/blank/.github/workflows/ci.yml +34 -0
  97. package/templates/blank/.husky/pre-commit +3 -0
  98. package/templates/blank/.husky/pre-push +3 -0
  99. package/templates/blank/.prettierignore +3 -0
  100. package/templates/blank/drizzle.config.ts +16 -0
  101. package/templates/blank/eslint.config.mjs +127 -0
  102. package/templates/blank/next-env.d.ts +4 -0
  103. package/templates/blank/next.config.ts +5 -0
  104. package/templates/blank/package.json +75 -0
  105. package/templates/blank/playwright.config.ts +21 -0
  106. package/templates/blank/prettier.config.mjs +9 -0
  107. package/templates/blank/proxy.ts +28 -0
  108. package/templates/blank/src/app/api/auth/[...all]/route.ts +5 -0
  109. package/templates/blank/src/app/api/billing/checkout/route.ts +26 -0
  110. package/templates/blank/src/app/api/billing/portal/route.ts +25 -0
  111. package/templates/blank/src/app/api/email/test/route.ts +28 -0
  112. package/templates/blank/src/app/api/webhooks/polar/route.ts +5 -0
  113. package/templates/blank/src/app/api/webhooks/stripe/route.ts +5 -0
  114. package/templates/blank/src/app/billing/page.tsx +70 -0
  115. package/templates/blank/src/app/email/page.tsx +46 -0
  116. package/templates/blank/src/app/globals.css +394 -0
  117. package/templates/blank/src/app/layout.tsx +19 -0
  118. package/templates/blank/src/app/page.tsx +23 -0
  119. package/templates/blank/src/app/sign-in/page.tsx +18 -0
  120. package/templates/blank/src/app/sign-up/page.tsx +18 -0
  121. package/templates/blank/src/components/auth/email-auth-form.test.tsx +39 -0
  122. package/templates/blank/src/components/auth/email-auth-form.tsx +109 -0
  123. package/templates/blank/src/components/auth/sign-out-button.tsx +29 -0
  124. package/templates/blank/src/db/index.ts +16 -0
  125. package/templates/blank/src/db/schema/auth.ts +4 -0
  126. package/templates/blank/src/db/schema/index.ts +2 -0
  127. package/templates/blank/src/db/schema/projects.ts +17 -0
  128. package/templates/blank/src/db/seeds/index.ts +28 -0
  129. package/templates/blank/src/lib/auth-client.ts +5 -0
  130. package/templates/blank/src/lib/auth-session.ts +11 -0
  131. package/templates/blank/src/lib/auth.ts +23 -0
  132. package/templates/blank/src/lib/billing/index.ts +37 -0
  133. package/templates/blank/src/lib/billing/providers/polar.ts +80 -0
  134. package/templates/blank/src/lib/billing/providers/stripe.ts +77 -0
  135. package/templates/blank/src/lib/billing/types.ts +25 -0
  136. package/templates/blank/src/lib/email/index.ts +19 -0
  137. package/templates/blank/src/lib/email/templates.test.ts +15 -0
  138. package/templates/blank/src/lib/email/templates.ts +40 -0
  139. package/templates/blank/src/lib/env.ts +80 -0
  140. package/templates/blank/tsconfig.json +34 -0
  141. package/templates/blank/vitest.config.ts +19 -0
  142. package/templates/dashboard/.env.example +16 -0
  143. package/templates/dashboard/.github/workflows/ci.yml +34 -0
  144. package/templates/dashboard/.husky/pre-commit +3 -0
  145. package/templates/dashboard/.husky/pre-push +3 -0
  146. package/templates/dashboard/.prettierignore +3 -0
  147. package/templates/dashboard/drizzle.config.ts +16 -0
  148. package/templates/dashboard/eslint.config.mjs +127 -0
  149. package/templates/dashboard/next-env.d.ts +4 -0
  150. package/templates/dashboard/next.config.ts +5 -0
  151. package/templates/dashboard/package.json +75 -0
  152. package/templates/dashboard/playwright.config.ts +21 -0
  153. package/templates/dashboard/prettier.config.mjs +9 -0
  154. package/templates/dashboard/proxy.ts +36 -0
  155. package/templates/dashboard/src/app/api/auth/[...all]/route.ts +5 -0
  156. package/templates/dashboard/src/app/api/billing/checkout/route.ts +26 -0
  157. package/templates/dashboard/src/app/api/billing/portal/route.ts +25 -0
  158. package/templates/dashboard/src/app/api/email/test/route.ts +28 -0
  159. package/templates/dashboard/src/app/api/webhooks/polar/route.ts +5 -0
  160. package/templates/dashboard/src/app/api/webhooks/stripe/route.ts +5 -0
  161. package/templates/dashboard/src/app/billing/layout.tsx +22 -0
  162. package/templates/dashboard/src/app/billing/page.tsx +73 -0
  163. package/templates/dashboard/src/app/dashboard/layout.tsx +22 -0
  164. package/templates/dashboard/src/app/dashboard/page.tsx +104 -0
  165. package/templates/dashboard/src/app/email/layout.tsx +22 -0
  166. package/templates/dashboard/src/app/email/page.tsx +54 -0
  167. package/templates/dashboard/src/app/globals.css +1357 -0
  168. package/templates/dashboard/src/app/layout.tsx +25 -0
  169. package/templates/dashboard/src/app/page.tsx +154 -0
  170. package/templates/dashboard/src/app/settings/layout.tsx +22 -0
  171. package/templates/dashboard/src/app/settings/page.tsx +85 -0
  172. package/templates/dashboard/src/app/sign-in/page.tsx +47 -0
  173. package/templates/dashboard/src/app/sign-up/page.tsx +47 -0
  174. package/templates/dashboard/src/components/auth/email-auth-form.test.tsx +39 -0
  175. package/templates/dashboard/src/components/auth/email-auth-form.tsx +160 -0
  176. package/templates/dashboard/src/components/auth/sign-out-button.tsx +29 -0
  177. package/templates/dashboard/src/components/dashboard/shell.tsx +158 -0
  178. package/templates/dashboard/src/db/index.ts +16 -0
  179. package/templates/dashboard/src/db/schema/auth.ts +4 -0
  180. package/templates/dashboard/src/db/schema/index.ts +2 -0
  181. package/templates/dashboard/src/db/schema/projects.ts +17 -0
  182. package/templates/dashboard/src/db/seeds/index.ts +28 -0
  183. package/templates/dashboard/src/lib/auth-client.ts +5 -0
  184. package/templates/dashboard/src/lib/auth-session.ts +11 -0
  185. package/templates/dashboard/src/lib/auth.ts +41 -0
  186. package/templates/dashboard/src/lib/billing/index.ts +37 -0
  187. package/templates/dashboard/src/lib/billing/providers/polar.ts +80 -0
  188. package/templates/dashboard/src/lib/billing/providers/stripe.ts +77 -0
  189. package/templates/dashboard/src/lib/billing/types.ts +25 -0
  190. package/templates/dashboard/src/lib/email/index.ts +19 -0
  191. package/templates/dashboard/src/lib/email/templates.test.ts +15 -0
  192. package/templates/dashboard/src/lib/email/templates.ts +40 -0
  193. package/templates/dashboard/src/lib/env.ts +88 -0
  194. package/templates/dashboard/tsconfig.json +34 -0
  195. package/templates/dashboard/vitest.config.ts +19 -0
@@ -0,0 +1,281 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ async function readJson(filePath) {
5
+ const content = await readFile(filePath, "utf8");
6
+ return JSON.parse(content);
7
+ }
8
+
9
+ async function loadModule(repoRoot, moduleName) {
10
+ const modulePath = path.join(repoRoot, "modules", moduleName, "module.json");
11
+ return readJson(modulePath);
12
+ }
13
+
14
+ function getBillingProviderFromDefinitions(moduleDefinitions) {
15
+ const providers = new Set();
16
+
17
+ for (const moduleDefinition of moduleDefinitions) {
18
+ for (const provider of moduleDefinition.template?.billingProviders ?? []) {
19
+ providers.add(provider);
20
+ }
21
+ }
22
+
23
+ if (providers.has("stripe") && providers.has("polar")) {
24
+ return "both";
25
+ }
26
+
27
+ if (providers.has("stripe")) {
28
+ return "stripe";
29
+ }
30
+
31
+ if (providers.has("polar")) {
32
+ return "polar";
33
+ }
34
+
35
+ return "none";
36
+ }
37
+
38
+ function getEmailProviderFromDefinitions(moduleDefinitions) {
39
+ for (const moduleDefinition of moduleDefinitions) {
40
+ if (moduleDefinition.template?.emailProvider) {
41
+ return moduleDefinition.template.emailProvider;
42
+ }
43
+ }
44
+
45
+ return "none";
46
+ }
47
+
48
+ function getPackageDependenciesFromDefinitions(moduleDefinitions) {
49
+ const dependencyLines = [];
50
+
51
+ for (const moduleDefinition of moduleDefinitions) {
52
+ dependencyLines.push(...(moduleDefinition.template?.packageDependencies ?? []));
53
+ }
54
+
55
+ return dependencyLines.join("\n");
56
+ }
57
+
58
+ function getBillingImportsFromDefinitions(moduleDefinitions) {
59
+ const lines = [];
60
+
61
+ for (const moduleDefinition of moduleDefinitions) {
62
+ lines.push(...(moduleDefinition.template?.billing?.imports ?? []));
63
+ }
64
+
65
+ return lines.join("\n");
66
+ }
67
+
68
+ function getBillingFactoriesFromDefinitions(moduleDefinitions) {
69
+ const lines = [];
70
+
71
+ for (const moduleDefinition of moduleDefinitions) {
72
+ lines.push(...(moduleDefinition.template?.billing?.factories ?? []));
73
+ }
74
+
75
+ return lines.join("\n");
76
+ }
77
+
78
+ function getStripeWebhookRouteBody(moduleDefinitions) {
79
+ for (const moduleDefinition of moduleDefinitions) {
80
+ if (moduleDefinition.template?.billing?.stripeWebhookRouteBody) {
81
+ return moduleDefinition.template.billing.stripeWebhookRouteBody;
82
+ }
83
+ }
84
+
85
+ return " return new Response(\"Stripe billing is not enabled for this project.\", { status: 404 });";
86
+ }
87
+
88
+ function getPolarWebhookRouteBody(moduleDefinitions) {
89
+ for (const moduleDefinition of moduleDefinitions) {
90
+ if (moduleDefinition.template?.billing?.polarWebhookRouteBody) {
91
+ return moduleDefinition.template.billing.polarWebhookRouteBody;
92
+ }
93
+ }
94
+
95
+ return " return new Response(\"Polar billing is not enabled for this project.\", { status: 404 });";
96
+ }
97
+
98
+ function getStripeWebhookRouteImport(moduleDefinitions) {
99
+ for (const moduleDefinition of moduleDefinitions) {
100
+ if (moduleDefinition.template?.billing?.stripeWebhookRouteBody) {
101
+ return 'import { getBillingProvider } from "@/lib/billing";';
102
+ }
103
+ }
104
+
105
+ return "";
106
+ }
107
+
108
+ function getStripeWebhookRouteSignature(moduleDefinitions) {
109
+ for (const moduleDefinition of moduleDefinitions) {
110
+ if (moduleDefinition.template?.billing?.stripeWebhookRouteBody) {
111
+ return "request: Request";
112
+ }
113
+ }
114
+
115
+ return "_request: Request";
116
+ }
117
+
118
+ function getPolarWebhookRouteImport(moduleDefinitions) {
119
+ for (const moduleDefinition of moduleDefinitions) {
120
+ if (moduleDefinition.template?.billing?.polarWebhookRouteBody) {
121
+ return 'import { getBillingProvider } from "@/lib/billing";';
122
+ }
123
+ }
124
+
125
+ return "";
126
+ }
127
+
128
+ function getPolarWebhookRouteSignature(moduleDefinitions) {
129
+ for (const moduleDefinition of moduleDefinitions) {
130
+ if (moduleDefinition.template?.billing?.polarWebhookRouteBody) {
131
+ return "request: Request";
132
+ }
133
+ }
134
+
135
+ return "_request: Request";
136
+ }
137
+
138
+ function getEmailImportsFromDefinitions(moduleDefinitions) {
139
+ const lines = [];
140
+
141
+ for (const moduleDefinition of moduleDefinitions) {
142
+ lines.push(...(moduleDefinition.template?.email?.imports ?? []));
143
+ }
144
+
145
+ return lines.join("\n");
146
+ }
147
+
148
+ function getEmailEnvImport(moduleDefinitions) {
149
+ for (const moduleDefinition of moduleDefinitions) {
150
+ if (moduleDefinition.template?.email?.setup) {
151
+ return 'import { env } from "@/lib/env";';
152
+ }
153
+ }
154
+
155
+ return "";
156
+ }
157
+
158
+ function getEmailSetupFromDefinitions(moduleDefinitions) {
159
+ for (const moduleDefinition of moduleDefinitions) {
160
+ if (moduleDefinition.template?.email?.setup) {
161
+ return moduleDefinition.template.email.setup;
162
+ }
163
+ }
164
+
165
+ return "";
166
+ }
167
+
168
+ function getEmailFromAddressHelper(moduleDefinitions) {
169
+ for (const moduleDefinition of moduleDefinitions) {
170
+ if (moduleDefinition.template?.email?.setup) {
171
+ return `function getFromAddress() {
172
+ if (!env.EMAIL_FROM) {
173
+ throw new Error("EMAIL_FROM is required for email sending.");
174
+ }
175
+
176
+ return env.EMAIL_FROM;
177
+ }`;
178
+ }
179
+ }
180
+
181
+ return "";
182
+ }
183
+
184
+ function getEmailSendImplementation(moduleDefinitions) {
185
+ for (const moduleDefinition of moduleDefinitions) {
186
+ if (moduleDefinition.template?.email?.sendImplementation) {
187
+ return moduleDefinition.template.email.sendImplementation;
188
+ }
189
+ }
190
+
191
+ return ' return { skipped: true as const, provider: "none" as const };';
192
+ }
193
+
194
+ function getEmailSendSignature(moduleDefinitions) {
195
+ for (const moduleDefinition of moduleDefinitions) {
196
+ if (moduleDefinition.template?.email?.sendImplementation) {
197
+ return "input";
198
+ }
199
+ }
200
+
201
+ return "_input";
202
+ }
203
+
204
+ export async function buildModuleTokenReplacements(repoRoot, scaffoldPlan) {
205
+ const replacements = {};
206
+ const moduleDefinitions = [];
207
+
208
+ for (const moduleName of scaffoldPlan.modules) {
209
+ const moduleDefinition = await loadModule(repoRoot, moduleName);
210
+ moduleDefinitions.push(moduleDefinition);
211
+
212
+ for (const [token, value] of Object.entries(
213
+ moduleDefinition.template?.tokenReplacements ?? {}
214
+ )) {
215
+ replacements[token] = value;
216
+ }
217
+ }
218
+
219
+ replacements.__BILLING_PROVIDER__ = getBillingProviderFromDefinitions(moduleDefinitions);
220
+ replacements.__EMAIL_PROVIDER__ = getEmailProviderFromDefinitions(moduleDefinitions);
221
+ replacements.__PROVIDER_PACKAGE_DEPENDENCIES__ =
222
+ getPackageDependenciesFromDefinitions(moduleDefinitions);
223
+ replacements.__BILLING_PROVIDER_IMPORTS__ = getBillingImportsFromDefinitions(moduleDefinitions);
224
+ replacements.__BILLING_PROVIDER_FACTORIES__ =
225
+ getBillingFactoriesFromDefinitions(moduleDefinitions);
226
+ replacements.__STRIPE_WEBHOOK_ROUTE_IMPORT__ =
227
+ getStripeWebhookRouteImport(moduleDefinitions);
228
+ replacements.__STRIPE_WEBHOOK_ROUTE_SIGNATURE__ =
229
+ getStripeWebhookRouteSignature(moduleDefinitions);
230
+ replacements.__STRIPE_WEBHOOK_ROUTE_BODY__ =
231
+ getStripeWebhookRouteBody(moduleDefinitions);
232
+ replacements.__POLAR_WEBHOOK_ROUTE_IMPORT__ =
233
+ getPolarWebhookRouteImport(moduleDefinitions);
234
+ replacements.__POLAR_WEBHOOK_ROUTE_SIGNATURE__ =
235
+ getPolarWebhookRouteSignature(moduleDefinitions);
236
+ replacements.__POLAR_WEBHOOK_ROUTE_BODY__ = getPolarWebhookRouteBody(moduleDefinitions);
237
+ replacements.__EMAIL_PROVIDER_IMPORTS__ = getEmailImportsFromDefinitions(moduleDefinitions);
238
+ replacements.__EMAIL_ENV_IMPORT__ = getEmailEnvImport(moduleDefinitions);
239
+ replacements.__EMAIL_PROVIDER_SETUP__ = getEmailSetupFromDefinitions(moduleDefinitions);
240
+ replacements.__EMAIL_FROM_ADDRESS_HELPER__ =
241
+ getEmailFromAddressHelper(moduleDefinitions);
242
+ replacements.__EMAIL_SEND_SIGNATURE__ = getEmailSendSignature(moduleDefinitions);
243
+ replacements.__EMAIL_SEND_IMPLEMENTATION__ =
244
+ getEmailSendImplementation(moduleDefinitions);
245
+
246
+ return replacements;
247
+ }
248
+
249
+ export async function buildModuleFileOverlays(repoRoot, scaffoldPlan) {
250
+ const overlays = [];
251
+
252
+ for (const moduleName of scaffoldPlan.modules) {
253
+ const moduleDefinition = await loadModule(repoRoot, moduleName);
254
+
255
+ for (const relativeFilePath of moduleDefinition.template?.copyFiles ?? []) {
256
+ overlays.push({
257
+ source: path.join(repoRoot, "modules", moduleName, "files", relativeFilePath),
258
+ destination: relativeFilePath
259
+ });
260
+ }
261
+ }
262
+
263
+ return overlays;
264
+ }
265
+
266
+ export function buildModulePruneList(scaffoldPlan) {
267
+ const filesToRemove = [];
268
+ const moduleSet = new Set(scaffoldPlan.modules);
269
+
270
+ if (!moduleSet.has("billing-stripe")) {
271
+ filesToRemove.push("src/lib/billing/providers/stripe.ts");
272
+ filesToRemove.push("src/app/api/webhooks/stripe/route.ts");
273
+ }
274
+
275
+ if (!moduleSet.has("billing-polar")) {
276
+ filesToRemove.push("src/lib/billing/providers/polar.ts");
277
+ filesToRemove.push("src/app/api/webhooks/polar/route.ts");
278
+ }
279
+
280
+ return filesToRemove;
281
+ }
@@ -0,0 +1,179 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ async function readJson(filePath) {
5
+ const content = await readFile(filePath, "utf8");
6
+ return JSON.parse(content);
7
+ }
8
+
9
+ async function loadPreset(repoRoot, presetName) {
10
+ const presetPath = path.join(repoRoot, "presets", `${presetName}.json`);
11
+ return readJson(presetPath);
12
+ }
13
+
14
+ async function loadModule(repoRoot, moduleName) {
15
+ const modulePath = path.join(repoRoot, "modules", moduleName, "module.json");
16
+ return readJson(modulePath);
17
+ }
18
+
19
+ function mapDatabaseModule(databaseDriver) {
20
+ return databaseDriver === "postgres.js" ? "db-postgresjs" : "db-pg";
21
+ }
22
+
23
+ function mapBillingModules(billingProvider) {
24
+ if (billingProvider === "stripe") {
25
+ return ["billing-stripe"];
26
+ }
27
+
28
+ if (billingProvider === "polar") {
29
+ return ["billing-polar"];
30
+ }
31
+
32
+ if (billingProvider === "both") {
33
+ return ["billing-stripe", "billing-polar"];
34
+ }
35
+
36
+ return [];
37
+ }
38
+
39
+ function mapEmailModules(emailProvider) {
40
+ if (emailProvider === "resend") {
41
+ return ["email-resend"];
42
+ }
43
+
44
+ return [];
45
+ }
46
+
47
+ function mapAuthModules(authMode) {
48
+ if (authMode === "email-password+github") {
49
+ return ["auth-github"];
50
+ }
51
+
52
+ if (authMode === "email-password+google") {
53
+ return ["auth-google"];
54
+ }
55
+
56
+ if (authMode === "email-password+github+google") {
57
+ return ["auth-github", "auth-google"];
58
+ }
59
+
60
+ return [];
61
+ }
62
+
63
+ function mapDeployModules(deployTarget) {
64
+ if (deployTarget === "docker") {
65
+ return ["deploy-docker"];
66
+ }
67
+
68
+ return [];
69
+ }
70
+
71
+ function mapAiDxModules(aiTools) {
72
+ if (!aiTools || aiTools.length === 0) {
73
+ return [];
74
+ }
75
+
76
+ const modules = ["ai-dx"];
77
+ const toolToModule = {
78
+ cursor: "ai-dx-cursor",
79
+ claude: "ai-dx-claude",
80
+ gemini: "ai-dx-gemini"
81
+ };
82
+
83
+ for (const tool of aiTools) {
84
+ if (toolToModule[tool]) {
85
+ modules.push(toolToModule[tool]);
86
+ }
87
+ }
88
+
89
+ return modules;
90
+ }
91
+
92
+ async function collectModuleGraph(repoRoot, initialModuleNames) {
93
+ const visited = new Set();
94
+ const modules = new Map();
95
+
96
+ async function visit(moduleName) {
97
+ if (visited.has(moduleName)) {
98
+ return;
99
+ }
100
+
101
+ visited.add(moduleName);
102
+ const moduleDefinition = await loadModule(repoRoot, moduleName);
103
+ modules.set(moduleName, moduleDefinition);
104
+
105
+ for (const dependency of moduleDefinition.dependsOn ?? []) {
106
+ await visit(dependency);
107
+ }
108
+ }
109
+
110
+ for (const moduleName of initialModuleNames) {
111
+ await visit(moduleName);
112
+ }
113
+
114
+ return modules;
115
+ }
116
+
117
+ function validateConflicts(modules) {
118
+ for (const moduleDefinition of modules.values()) {
119
+ for (const conflict of moduleDefinition.conflictsWith ?? []) {
120
+ if (modules.has(conflict)) {
121
+ throw new Error(
122
+ `Module conflict: ${moduleDefinition.name} cannot be combined with ${conflict}.`
123
+ );
124
+ }
125
+ }
126
+ }
127
+ }
128
+
129
+ function sortModules(modules) {
130
+ const sorted = [];
131
+ const visited = new Set();
132
+
133
+ function visit(moduleName) {
134
+ if (visited.has(moduleName)) {
135
+ return;
136
+ }
137
+
138
+ visited.add(moduleName);
139
+ const moduleDefinition = modules.get(moduleName);
140
+
141
+ for (const dependency of moduleDefinition.dependsOn ?? []) {
142
+ visit(dependency);
143
+ }
144
+
145
+ sorted.push(moduleName);
146
+ }
147
+
148
+ for (const moduleName of modules.keys()) {
149
+ visit(moduleName);
150
+ }
151
+
152
+ return sorted;
153
+ }
154
+
155
+ export async function resolveScaffoldPlan(repoRoot, options) {
156
+ const preset = await loadPreset(repoRoot, options.template);
157
+ const initialModuleNames = [
158
+ ...(preset.modules ?? []),
159
+ mapDatabaseModule(options.databaseDriver),
160
+ ...mapAuthModules(options.authMode),
161
+ ...mapBillingModules(options.billingProvider),
162
+ ...mapEmailModules(options.emailProvider),
163
+ ...mapDeployModules(options.deployTarget),
164
+ ...mapAiDxModules(options.aiTools)
165
+ ];
166
+ const uniqueModuleNames = [...new Set(initialModuleNames)];
167
+ const modules = await collectModuleGraph(repoRoot, uniqueModuleNames);
168
+
169
+ validateConflicts(modules);
170
+
171
+ return {
172
+ base: preset.base,
173
+ preset: preset.name,
174
+ template: preset.template,
175
+ overrideFiles: preset.overrideFiles ?? [],
176
+ tokenReplacements: preset.tokenReplacements ?? {},
177
+ modules: sortModules(modules)
178
+ };
179
+ }
@@ -0,0 +1,22 @@
1
+ # Modules
2
+
3
+ This directory contains the first module registry for Skit.
4
+
5
+ Current status:
6
+
7
+ - modules are resolved and written into `skit.json`
8
+ - generation still uses the verified template flow
9
+ - modules can now contribute token replacements, pruning rules, and file overlays
10
+ - `dashboard-shell` is the first module that contributes actual app files, currently `/settings` and its dashboard visual theme layer
11
+
12
+ The first module slice is intentionally small:
13
+
14
+ - `quality-baseline`
15
+ - `testing-baseline`
16
+ - `auth-core`
17
+ - `db-pg`
18
+ - `db-postgresjs`
19
+ - `billing-stripe`
20
+ - `billing-polar`
21
+ - `email-resend`
22
+ - `dashboard-shell`
@@ -0,0 +1,116 @@
1
+ # __PROJECT_NAME__
2
+
3
+ > Generated by Skit. Customize freely — this file is yours.
4
+
5
+ ## Tech Stack
6
+
7
+ - **Framework**: Next.js 16 (App Router, React 19, server components by default)
8
+ - **Language**: TypeScript 5.9 (strict mode, `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`)
9
+ - **Database**: Drizzle ORM 0.44 + PostgreSQL via `__DATABASE_DRIVER__`
10
+ - **Auth**: Better Auth 1.3 (email/password, session cookies, edge middleware)
11
+ - **Billing**: provider abstraction (`__BILLING_PROVIDER__`) — see `src/lib/billing/`
12
+ - **Email**: `__EMAIL_PROVIDER__` — see `src/lib/email/`
13
+ - **Testing**: Vitest 3.2 (unit, jsdom) + Playwright 1.55 (e2e)
14
+ - **Linting**: ESLint 9 flat config + Prettier (double quotes, no trailing commas, 88 chars)
15
+ - **CI**: GitHub Actions — lint, typecheck, test, build
16
+
17
+ ## Next.js Docs
18
+
19
+ Before writing Next.js code, read the bundled docs at `node_modules/next/dist/docs/`. Your training data may be outdated — these docs match the installed version.
20
+
21
+ ## Commands
22
+
23
+ ```bash
24
+ pnpm dev # start dev server
25
+ pnpm build # production build
26
+ pnpm lint # eslint (zero warnings allowed)
27
+ pnpm lint:fix # eslint --fix
28
+ pnpm typecheck # tsc --noEmit
29
+ pnpm test # vitest run
30
+ pnpm test:watch # vitest watch mode
31
+ pnpm test:e2e # playwright (starts dev server)
32
+ pnpm format # prettier --write
33
+ pnpm check # lint + typecheck + test in sequence
34
+ pnpm db:generate # drizzle-kit generate migration
35
+ pnpm db:migrate # drizzle-kit migrate
36
+ pnpm db:push # drizzle-kit push (skip migrations)
37
+ pnpm db:studio # drizzle-kit studio (GUI)
38
+ pnpm db:seed # run seed script
39
+ pnpm auth:generate # regenerate Better Auth schema
40
+ pnpm auth:secret # generate a new auth secret
41
+ ```
42
+
43
+ ## Verification
44
+
45
+ Always run after making changes:
46
+
47
+ ```bash
48
+ pnpm typecheck && pnpm lint:fix && pnpm format
49
+ ```
50
+
51
+ ## MUST DO
52
+
53
+ - Use `env` from `@/lib/env` for all environment variables — never `process.env`
54
+ - Use server components by default; add `"use client"` only when hooks or browser APIs are needed
55
+ - Keep route files thin — fetch data in server component, delegate logic to `src/lib/`
56
+ - Use `getSession()` from `@/lib/auth-session` for server-side session access
57
+ - Use `authClient` from `@/lib/auth-client` for client-side auth operations
58
+ - Use `type` imports: `import { type Foo }` (enforced by ESLint)
59
+ - Run `pnpm typecheck` after making changes
60
+ - Add new env vars to three places: Zod schema + parse block in `src/lib/env.ts`, and `.env.example`
61
+
62
+ ## MUST NOT DO
63
+
64
+ - Never access `process.env` outside `src/lib/env.ts` and `drizzle.config.ts` (ESLint enforces this)
65
+ - Never import from `@/db` or `server-only` modules in client components
66
+ - Never use the `any` type
67
+ - Never commit `.env` files or secrets
68
+ - Never skip the pre-commit hook (`pnpm lint-staged && pnpm typecheck`)
69
+
70
+ ## Where to Find Things
71
+
72
+ | Area | Location |
73
+ |------|----------|
74
+ | App routes | `src/app/` |
75
+ | Auth config | `src/lib/auth.ts` |
76
+ | Auth client | `src/lib/auth-client.ts` |
77
+ | Session helper | `src/lib/auth-session.ts` |
78
+ | Database connection | `src/db/index.ts` |
79
+ | DB schema | `src/db/schema/` |
80
+ | DB migrations | `src/db/migrations/` |
81
+ | DB seeds | `src/db/seeds/index.ts` |
82
+ | Billing providers | `src/lib/billing/` |
83
+ | Email service | `src/lib/email/` |
84
+ | Email templates | `src/lib/email/templates.ts` |
85
+ | Env validation | `src/lib/env.ts` |
86
+ | Route protection | `proxy.ts` (middleware) |
87
+ | ESLint config | `eslint.config.mjs` |
88
+ | Prettier config | `prettier.config.mjs` |
89
+ | CI workflow | `.github/workflows/ci.yml` |
90
+ | Unit tests | `src/**/*.test.{ts,tsx}` |
91
+ | E2E tests | `tests/e2e/` |
92
+
93
+ ## Code Patterns
94
+
95
+ ### Adding a new page
96
+
97
+ ```
98
+ src/app/my-feature/page.tsx ← server component, thin
99
+ src/lib/my-feature.ts ← business logic
100
+ ```
101
+
102
+ ### Adding a new DB table
103
+
104
+ 1. Create schema file: `src/db/schema/my-table.ts`
105
+ 2. Re-export from `src/db/schema/index.ts`
106
+ 3. Run `pnpm db:generate` then `pnpm db:migrate`
107
+
108
+ ### Adding a protected route
109
+
110
+ Edit `proxy.ts` — add the path to both `__PROTECTED_ROUTE_CONDITION__` and `config.matcher`.
111
+
112
+ ### Adding a new env var
113
+
114
+ 1. Add Zod field in `src/lib/env.ts` schema
115
+ 2. Add `process.env.MY_VAR` in `parseEnv()` block
116
+ 3. Add default value in `.env.example`
@@ -0,0 +1,103 @@
1
+ # Architecture
2
+
3
+ > Generated by Skit. Customize freely.
4
+
5
+ ## Directory Tree
6
+
7
+ ```
8
+ ├── proxy.ts # Edge middleware — route protection
9
+ ├── drizzle.config.ts # Drizzle Kit config
10
+ ├── eslint.config.mjs # ESLint 9 flat config
11
+ ├── prettier.config.mjs # Prettier config
12
+ ├── vitest.config.ts # Vitest config
13
+ ├── playwright.config.ts # Playwright E2E config
14
+ ├── .env.example # Environment variable template
15
+ ├── .github/workflows/ci.yml # CI pipeline
16
+
17
+ ├── src/
18
+ │ ├── app/ # Next.js App Router pages
19
+ │ │ ├── layout.tsx # Root layout
20
+ │ │ ├── page.tsx # Landing / home page
21
+ │ │ ├── loading.tsx # Global loading skeleton
22
+ │ │ ├── error.tsx # Global error boundary
23
+ │ │ ├── not-found.tsx # 404 page
24
+ │ │ ├── sign-in/ # Sign-in page
25
+ │ │ ├── sign-up/ # Sign-up page
26
+ │ │ ├── dashboard/ # Dashboard page
27
+ │ │ ├── billing/ # Billing page
28
+ │ │ ├── email/ # Email test page
29
+ │ │ └── api/
30
+ │ │ ├── auth/[...all]/ # Better Auth catch-all handler
31
+ │ │ ├── billing/ # Checkout + portal API routes
32
+ │ │ ├── email/ # Email test route
33
+ │ │ └── webhooks/ # Stripe / Polar webhook handlers
34
+ │ │
35
+ │ ├── components/ # Shared React components
36
+ │ │ └── auth/ # Auth form, sign-out button
37
+ │ │
38
+ │ ├── db/
39
+ │ │ ├── index.ts # Database connection (Drizzle + driver)
40
+ │ │ ├── schema/ # Drizzle table definitions
41
+ │ │ │ ├── index.ts # Re-exports all schemas
42
+ │ │ │ ├── auth.ts # Better Auth tables
43
+ │ │ │ └── projects.ts # Starter project table
44
+ │ │ ├── migrations/ # Generated SQL migrations
45
+ │ │ └── seeds/ # Seed scripts
46
+ │ │
47
+ │ └── lib/
48
+ │ ├── env.ts # Zod-validated env (single source of truth)
49
+ │ ├── auth.ts # Better Auth server config
50
+ │ ├── auth-client.ts # Client-side auth (useSession, signIn, etc.)
51
+ │ ├── auth-session.ts # Server-side getSession() helper
52
+ │ ├── billing/
53
+ │ │ ├── index.ts # Provider factory
54
+ │ │ ├── types.ts # BillingProvider interface
55
+ │ │ └── providers/ # Stripe, Polar implementations
56
+ │ └── email/
57
+ │ ├── index.ts # sendEmail function
58
+ │ └── templates.ts # Email HTML templates
59
+
60
+ └── tests/
61
+ └── e2e/ # Playwright E2E tests
62
+ ```
63
+
64
+ ## Request Flow
65
+
66
+ ```
67
+ Browser
68
+
69
+
70
+ proxy.ts (Edge Middleware)
71
+ │ checks session cookie
72
+ │ redirects unauthenticated users to /sign-in
73
+
74
+ src/app/**/page.tsx (Server Component)
75
+ │ calls getSession() for auth context
76
+ │ delegates to src/lib/* for business logic
77
+
78
+ src/lib/* (Business Logic)
79
+ │ uses env from src/lib/env.ts
80
+ │ uses db from src/db/index.ts
81
+
82
+ src/db/ (Drizzle ORM → PostgreSQL)
83
+ ```
84
+
85
+ ## Key Boundaries
86
+
87
+ - **`src/lib/env.ts`** is the only file that may read `process.env`. All other files import `env` from it.
88
+ - **`src/db/`** is server-only. Never import it in client components.
89
+ - **`src/lib/auth.ts`** is server-only. Use `src/lib/auth-client.ts` on the client.
90
+ - **`proxy.ts`** runs at the edge. It checks session cookies — no DB access.
91
+ - **`src/lib/billing/types.ts`** defines the provider interface. Implementations live in `providers/`.
92
+
93
+ ## Extension Points
94
+
95
+ | Want to... | Do this |
96
+ |------------|---------|
97
+ | Add a page | Create `src/app/my-page/page.tsx` (server component) |
98
+ | Add a DB table | New file in `src/db/schema/`, re-export from `index.ts`, run `pnpm db:generate` |
99
+ | Add an API route | Create `src/app/api/my-route/route.ts` |
100
+ | Add a billing provider | Implement `BillingProvider` in `src/lib/billing/providers/` |
101
+ | Add an email template | Add factory in `src/lib/email/templates.ts` |
102
+ | Protect a route | Update condition + matcher in `proxy.ts` |
103
+ | Add an env var | Schema + parse in `src/lib/env.ts`, default in `.env.example` |