create-mikstack 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 (99) hide show
  1. package/README.md +54 -0
  2. package/dist/index.js +410 -0
  3. package/package.json +43 -0
  4. package/templates/adapters/cloudflare/package.json.partial +5 -0
  5. package/templates/adapters/cloudflare/svelte.config.js +19 -0
  6. package/templates/adapters/node/Dockerfile +30 -0
  7. package/templates/adapters/node/docker-compose.prod.yml +27 -0
  8. package/templates/adapters/node/package.json.partial +5 -0
  9. package/templates/adapters/node/svelte.config.js +19 -0
  10. package/templates/adapters/vercel/package.json.partial +5 -0
  11. package/templates/adapters/vercel/svelte.config.js +19 -0
  12. package/templates/base/.env.example +23 -0
  13. package/templates/base/.gitignore.append +2 -0
  14. package/templates/base/.mcp.json +9 -0
  15. package/templates/base/.prettierignore +10 -0
  16. package/templates/base/.vscode/extensions.json +3 -0
  17. package/templates/base/AGENTS.md +123 -0
  18. package/templates/base/README.md +27 -0
  19. package/templates/base/agents.md +28 -0
  20. package/templates/base/docker-compose.yml +15 -0
  21. package/templates/base/drizzle-zero.config.ts +17 -0
  22. package/templates/base/drizzle.config.ts +17 -0
  23. package/templates/base/eslint.config.ts +65 -0
  24. package/templates/base/package.json.partial +43 -0
  25. package/templates/base/prettier.config.js +6 -0
  26. package/templates/base/src/app.d.ts +12 -0
  27. package/templates/base/src/app.html +11 -0
  28. package/templates/base/src/hooks.server.ts +15 -0
  29. package/templates/base/src/lib/auth-client.ts +6 -0
  30. package/templates/base/src/lib/server/auth.ts +52 -0
  31. package/templates/base/src/lib/server/db/index.ts +19 -0
  32. package/templates/base/src/lib/server/db/schema.ts +117 -0
  33. package/templates/base/src/lib/server/db/seed.ts +21 -0
  34. package/templates/base/src/lib/server/emails/magic-link.ts +77 -0
  35. package/templates/base/src/lib/server/emails/send.ts +55 -0
  36. package/templates/base/src/lib/server/notifications/definitions.ts +12 -0
  37. package/templates/base/src/lib/server/notifications.ts +38 -0
  38. package/templates/base/src/lib/z.svelte.ts +14 -0
  39. package/templates/base/src/lib/zero/context.ts +9 -0
  40. package/templates/base/src/lib/zero/db-provider.server.ts +11 -0
  41. package/templates/base/src/lib/zero/mutators.ts +35 -0
  42. package/templates/base/src/lib/zero/queries.ts +21 -0
  43. package/templates/base/src/lib/zero/schema.ts +1 -0
  44. package/templates/base/src/routes/+layout.server.ts +5 -0
  45. package/templates/base/src/routes/+layout.svelte +7 -0
  46. package/templates/base/src/routes/+page.server.ts +7 -0
  47. package/templates/base/src/routes/+page.svelte +319 -0
  48. package/templates/base/src/routes/api/dev/emails/+server.ts +89 -0
  49. package/templates/base/src/routes/api/dev/emails/[id]/+server.ts +24 -0
  50. package/templates/base/src/routes/api/notifications/[...path]/+server.ts +10 -0
  51. package/templates/base/src/routes/api/zero/get-queries/+server.ts +29 -0
  52. package/templates/base/src/routes/api/zero/mutate/+server.ts +31 -0
  53. package/templates/base/src/routes/sign-in/+page.svelte +97 -0
  54. package/templates/base/tsconfig.json +40 -0
  55. package/templates/github-actions-bun/.github/workflows/ci.yml +22 -0
  56. package/templates/github-actions-npm/.github/workflows/ci.yml +25 -0
  57. package/templates/github-actions-pnpm/.github/workflows/ci.yml +27 -0
  58. package/templates/i18n/lingui.config.ts +16 -0
  59. package/templates/i18n/package.json.partial +14 -0
  60. package/templates/i18n/src/lib/i18n.ts +10 -0
  61. package/templates/i18n/src/locales/en.po +6 -0
  62. package/templates/i18n/src/po.d.ts +3 -0
  63. package/templates/i18n/vite.config.ts +7 -0
  64. package/templates/supply-chain-bun/bunfig.toml +3 -0
  65. package/templates/testing/package.json.partial +11 -0
  66. package/templates/testing/src/example.test.ts +7 -0
  67. package/templates/testing/src/lib/server/db/test-utils.ts +25 -0
  68. package/templates/testing/vitest.config.ts +9 -0
  69. package/templates/ui/.vscode/extensions.json +8 -0
  70. package/templates/ui/package.json.partial +13 -0
  71. package/templates/ui/src/app.css +94 -0
  72. package/templates/ui/src/routes/+layout.svelte +12 -0
  73. package/templates/ui/stylelint.config.js +7 -0
  74. package/templates/ui/vite.config.ts +6 -0
  75. package/templates/ui-dependency/package.json.partial +5 -0
  76. package/templates/ui-vendor/src/lib/components/ui/Accordion/Accordion.svelte +71 -0
  77. package/templates/ui-vendor/src/lib/components/ui/Accordion/index.ts +1 -0
  78. package/templates/ui-vendor/src/lib/components/ui/Alert/Alert.svelte +60 -0
  79. package/templates/ui-vendor/src/lib/components/ui/Alert/index.ts +1 -0
  80. package/templates/ui-vendor/src/lib/components/ui/Badge/Badge.svelte +48 -0
  81. package/templates/ui-vendor/src/lib/components/ui/Badge/index.ts +1 -0
  82. package/templates/ui-vendor/src/lib/components/ui/Button/Button.svelte +77 -0
  83. package/templates/ui-vendor/src/lib/components/ui/Button/index.ts +1 -0
  84. package/templates/ui-vendor/src/lib/components/ui/Card/Card.svelte +49 -0
  85. package/templates/ui-vendor/src/lib/components/ui/Card/index.ts +1 -0
  86. package/templates/ui-vendor/src/lib/components/ui/Dialog/Dialog.svelte +70 -0
  87. package/templates/ui-vendor/src/lib/components/ui/Dialog/index.ts +1 -0
  88. package/templates/ui-vendor/src/lib/components/ui/FormField/FormField.svelte +53 -0
  89. package/templates/ui-vendor/src/lib/components/ui/FormField/index.ts +1 -0
  90. package/templates/ui-vendor/src/lib/components/ui/Input/Input.svelte +27 -0
  91. package/templates/ui-vendor/src/lib/components/ui/Input/index.ts +1 -0
  92. package/templates/ui-vendor/src/lib/components/ui/Separator/Separator.svelte +26 -0
  93. package/templates/ui-vendor/src/lib/components/ui/Separator/index.ts +1 -0
  94. package/templates/ui-vendor/src/lib/components/ui/Skeleton/Skeleton.svelte +40 -0
  95. package/templates/ui-vendor/src/lib/components/ui/Skeleton/index.ts +1 -0
  96. package/templates/ui-vendor/src/lib/components/ui/Switch/Switch.svelte +86 -0
  97. package/templates/ui-vendor/src/lib/components/ui/Switch/index.ts +1 -0
  98. package/templates/ui-vendor/src/lib/components/ui/Textarea/Textarea.svelte +29 -0
  99. package/templates/ui-vendor/src/lib/components/ui/Textarea/index.ts +1 -0
package/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # create-mikstack
2
+
3
+ Scaffold an opinionated SvelteKit project.
4
+
5
+ ```bash
6
+ bun create mikstack@latest
7
+ pnpm create mikstack@latest
8
+ npm create mikstack@latest
9
+ ```
10
+
11
+ ## What you get
12
+
13
+ **Always included:**
14
+
15
+ - SvelteKit with TypeScript
16
+ - PostgreSQL + Drizzle ORM (with Docker Compose)
17
+ - better-auth with magic link (dev mode logs links to console)
18
+ - ESLint (type-aware, flat config) + Prettier
19
+ - `.env.example` with all keys stubbed
20
+
21
+ **Configurable:**
22
+
23
+ | Feature | Default | Options |
24
+ | ----------------- | ------------ | ------------------------------------ |
25
+ | UI | Tailwind CSS | Tailwind / vanilla CSS (+ Stylelint) |
26
+ | Testing | on | Vitest + testcontainers |
27
+ | GitHub Actions CI | on | lint, format, typecheck, build |
28
+ | SvelteKit adapter | node | node / vercel / cloudflare |
29
+
30
+ ## Usage
31
+
32
+ ### Interactive
33
+
34
+ ```bash
35
+ bun create mikstack@latest my-app
36
+ ```
37
+
38
+ Prompts you to choose between recommended defaults or customize each option.
39
+
40
+ ### Non-interactive
41
+
42
+ ```bash
43
+ bun create mikstack@latest my-app --yes
44
+ ```
45
+
46
+ Scaffolds with all recommended defaults. Also activates when `CI=true`.
47
+
48
+ ## Package manager detection
49
+
50
+ Automatically detected from `npm_config_user_agent`. Affects:
51
+
52
+ - `package.json` scripts
53
+ - `README.md` commands
54
+ - GitHub Actions CI workflow (separate template per PM)
package/dist/index.js ADDED
@@ -0,0 +1,410 @@
1
+ #!/usr/bin/env node
2
+ import * as p from "@clack/prompts";
3
+ import path from "node:path";
4
+ import { parseArgs } from "node:util";
5
+ import { execSync } from "node:child_process";
6
+ import fs from "node:fs";
7
+
8
+ //#region src/config.ts
9
+ const defaults = {
10
+ mode: "recommended",
11
+ uiMode: "dependency",
12
+ i18n: true,
13
+ testing: true,
14
+ githubActions: true,
15
+ adapter: "node"
16
+ };
17
+
18
+ //#endregion
19
+ //#region src/pm.ts
20
+ function detectPackageManager() {
21
+ const ua = process.env.npm_config_user_agent;
22
+ if (!ua) return "npm";
23
+ if (ua.startsWith("pnpm/")) return "pnpm";
24
+ if (ua.startsWith("bun/")) return "bun";
25
+ return "npm";
26
+ }
27
+ function pmInstall(pm) {
28
+ return pm === "npm" ? "npm install" : `${pm} install`;
29
+ }
30
+ function pmRun(pm) {
31
+ return pm === "npm" ? "npm run" : `${pm} run`;
32
+ }
33
+ function pmExec(pm) {
34
+ if (pm === "npm") return "npx";
35
+ if (pm === "pnpm") return "pnpm exec";
36
+ return "bunx";
37
+ }
38
+ function pmLockfile(pm) {
39
+ if (pm === "npm") return "package-lock.json";
40
+ if (pm === "pnpm") return "pnpm-lock.yaml";
41
+ return "bun.lock";
42
+ }
43
+
44
+ //#endregion
45
+ //#region src/post-scaffold.ts
46
+ function postScaffold(targetDir, packageManager) {
47
+ const cwd = path.resolve(process.cwd(), targetDir);
48
+ const run = (cmd) => execSync(cmd, {
49
+ cwd,
50
+ stdio: "pipe"
51
+ });
52
+ const s = p.spinner();
53
+ s.start("Installing dependencies...");
54
+ try {
55
+ run(`${packageManager} install`);
56
+ s.stop("Dependencies installed.");
57
+ } catch {
58
+ s.stop("Failed to install dependencies. You can run install manually.");
59
+ }
60
+ try {
61
+ run(`${packageManager} run format`);
62
+ } catch {}
63
+ s.start("Initializing git repository...");
64
+ try {
65
+ run("git init");
66
+ run("git add .");
67
+ run("git commit -m \"the future is now\"");
68
+ s.stop("Git repository initialized.");
69
+ } catch {
70
+ s.stop("Failed to initialize git. You can run git init manually.");
71
+ }
72
+ }
73
+
74
+ //#endregion
75
+ //#region src/prompts.ts
76
+ function isCancel(value) {
77
+ return p.isCancel(value);
78
+ }
79
+ function onCancel() {
80
+ p.cancel("Operation cancelled.");
81
+ process.exit(0);
82
+ }
83
+ async function runPrompts(projectName, packageManager) {
84
+ p.intro("create-mikstack");
85
+ if (!projectName) {
86
+ const name = await p.text({
87
+ message: "Project name:",
88
+ placeholder: "my-app",
89
+ validate(value) {
90
+ if (!value.trim()) return "Project name is required.";
91
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(value)) return "Invalid package name. Use lowercase, numbers, hyphens, dots, and underscores.";
92
+ }
93
+ });
94
+ if (isCancel(name)) onCancel();
95
+ projectName = name;
96
+ }
97
+ const targetDir = projectName;
98
+ const mode = await p.select({
99
+ message: "Setup mode:",
100
+ options: [{
101
+ value: "recommended",
102
+ label: "Recommended (full stack)"
103
+ }, {
104
+ value: "customize",
105
+ label: "Customize"
106
+ }]
107
+ });
108
+ if (isCancel(mode)) onCancel();
109
+ if (mode === "recommended") {
110
+ p.outro(`Scaffolding ${projectName} with recommended defaults...`);
111
+ return {
112
+ projectName,
113
+ targetDir,
114
+ packageManager,
115
+ ...defaults
116
+ };
117
+ }
118
+ const uiMode = await p.select({
119
+ message: "UI components (@mikstack/ui):",
120
+ options: [{
121
+ value: "dependency",
122
+ label: "Dependency (import from package)"
123
+ }, {
124
+ value: "vendor",
125
+ label: "Vendor (copy into project)"
126
+ }]
127
+ });
128
+ if (isCancel(uiMode)) onCancel();
129
+ const i18n = await p.confirm({
130
+ message: "Include i18n? (@mikstack/svelte-lingui)",
131
+ initialValue: true
132
+ });
133
+ if (isCancel(i18n)) onCancel();
134
+ const testing = await p.confirm({
135
+ message: "Include testing setup? (Vitest + testcontainers)",
136
+ initialValue: true
137
+ });
138
+ if (isCancel(testing)) onCancel();
139
+ const githubActions = await p.confirm({
140
+ message: "Include GitHub Actions CI?",
141
+ initialValue: true
142
+ });
143
+ if (isCancel(githubActions)) onCancel();
144
+ const adapter = await p.select({
145
+ message: "SvelteKit adapter:",
146
+ options: [
147
+ {
148
+ value: "node",
149
+ label: "Node"
150
+ },
151
+ {
152
+ value: "vercel",
153
+ label: "Vercel"
154
+ },
155
+ {
156
+ value: "cloudflare",
157
+ label: "Cloudflare"
158
+ }
159
+ ]
160
+ });
161
+ if (isCancel(adapter)) onCancel();
162
+ p.outro(`Scaffolding ${projectName}...`);
163
+ return {
164
+ projectName,
165
+ targetDir,
166
+ packageManager,
167
+ mode,
168
+ uiMode,
169
+ i18n,
170
+ testing,
171
+ githubActions,
172
+ adapter
173
+ };
174
+ }
175
+
176
+ //#endregion
177
+ //#region src/template-engine.ts
178
+ const CONDITIONAL_BLOCK_RE = /^[^\S\n]*(?:\/\/|#|<!--)[^\S\n]*\{\{#if:(\w+)\}\}[^\S\n]*(?:-->)?[^\S\n]*\n([\s\S]*?)^[^\S\n]*(?:\/\/|#|<!--)[^\S\n]*\{\{\/if:\1\}\}[^\S\n]*(?:-->)?[^\S\n]*\n?/gm;
179
+ const VARIABLE_RE = /\{\{(\w+)\}\}/g;
180
+ function renderTemplate(content, context) {
181
+ let result = content.replace(CONDITIONAL_BLOCK_RE, (_match, name, body) => {
182
+ if (context[name]) return body;
183
+ return "";
184
+ });
185
+ result = result.replace(VARIABLE_RE, (_match, name) => {
186
+ const value = context[name];
187
+ if (value === void 0) throw new Error(`Undefined template variable: {{${name}}}`);
188
+ return String(value);
189
+ });
190
+ return result;
191
+ }
192
+
193
+ //#endregion
194
+ //#region src/scaffold.ts
195
+ const TEMPLATES_DIR = path.resolve(import.meta.dirname, "..", "templates");
196
+ function scaffold(config) {
197
+ const target = path.resolve(process.cwd(), config.targetDir);
198
+ if (fs.existsSync(target)) {
199
+ if (fs.readdirSync(target).length > 0) throw new Error(`Target directory "${config.targetDir}" is not empty. Please choose a different name or remove the directory.`);
200
+ }
201
+ execSync(`${path.resolve(import.meta.dirname, "..", "node_modules", ".bin", "sv")} create ${target} --template minimal --types ts --add devtools-json --no-install`, { stdio: "pipe" });
202
+ const overlay = (dir) => {
203
+ copyDir(dir, target);
204
+ mergePartials(target);
205
+ };
206
+ overlay(path.join(TEMPLATES_DIR, "base"));
207
+ if (config.testing) overlay(path.join(TEMPLATES_DIR, "testing"));
208
+ overlay(path.join(TEMPLATES_DIR, "ui"));
209
+ if (config.i18n) overlay(path.join(TEMPLATES_DIR, "i18n"));
210
+ if (config.uiMode === "vendor") overlay(path.join(TEMPLATES_DIR, "ui-vendor"));
211
+ else overlay(path.join(TEMPLATES_DIR, "ui-dependency"));
212
+ if (config.githubActions) overlay(path.join(TEMPLATES_DIR, `github-actions-${config.packageManager}`));
213
+ overlay(path.join(TEMPLATES_DIR, `supply-chain-${config.packageManager}`));
214
+ overlay(path.join(TEMPLATES_DIR, "adapters", config.adapter));
215
+ renderDir(target, buildContext(config));
216
+ appendGitignoreFiles(target);
217
+ appendLockfileIgnores(target, config.packageManager);
218
+ fs.symlinkSync("AGENTS.md", path.join(target, "CLAUDE.md"));
219
+ const envExample = path.join(target, ".env.example");
220
+ const envFile = path.join(target, ".env");
221
+ if (fs.existsSync(envExample) && !fs.existsSync(envFile)) fs.copyFileSync(envExample, envFile);
222
+ }
223
+ function copyDir(src, dest) {
224
+ if (!fs.existsSync(src)) return;
225
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
226
+ const srcPath = path.join(src, entry.name);
227
+ const destPath = path.join(dest, entry.name);
228
+ if (entry.isDirectory()) {
229
+ fs.mkdirSync(destPath, { recursive: true });
230
+ copyDir(srcPath, destPath);
231
+ } else {
232
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
233
+ fs.copyFileSync(srcPath, destPath);
234
+ }
235
+ }
236
+ }
237
+ function mergePartials(dir) {
238
+ const pkgPath = path.join(dir, "package.json");
239
+ if (!fs.existsSync(pkgPath)) return;
240
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
241
+ const partials = findFiles(dir, "package.json.partial");
242
+ for (const partialPath of partials) {
243
+ deepMerge(pkg, JSON.parse(fs.readFileSync(partialPath, "utf-8")));
244
+ fs.unlinkSync(partialPath);
245
+ }
246
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
247
+ }
248
+ function deepMerge(target, source) {
249
+ for (const key of Object.keys(source)) {
250
+ const targetVal = target[key];
251
+ const sourceVal = source[key];
252
+ if (targetVal && sourceVal && typeof targetVal === "object" && typeof sourceVal === "object" && !Array.isArray(targetVal) && !Array.isArray(sourceVal)) deepMerge(targetVal, sourceVal);
253
+ else target[key] = sourceVal;
254
+ }
255
+ }
256
+ function findFiles(dir, filename) {
257
+ const results = [];
258
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
259
+ const fullPath = path.join(dir, entry.name);
260
+ if (entry.isDirectory()) results.push(...findFiles(fullPath, filename));
261
+ else if (entry.name === filename) results.push(fullPath);
262
+ }
263
+ return results;
264
+ }
265
+ function buildContext(config) {
266
+ return {
267
+ projectName: config.projectName,
268
+ pmInstall: pmInstall(config.packageManager),
269
+ pmRun: pmRun(config.packageManager),
270
+ pmExec: pmExec(config.packageManager),
271
+ pm: config.packageManager,
272
+ lockfile: pmLockfile(config.packageManager),
273
+ adapter: config.adapter,
274
+ seedRunner: config.packageManager === "bun" ? "bun" : "node --experimental-strip-types --env-file=.env",
275
+ i18n: config.i18n,
276
+ testing: config.testing,
277
+ githubActions: config.githubActions,
278
+ uiVendor: config.uiMode === "vendor",
279
+ uiPrefix: config.uiMode === "vendor" ? "$lib/components/ui" : "@mikstack/ui",
280
+ pmIsNpm: config.packageManager === "npm",
281
+ pmIsPnpm: config.packageManager === "pnpm",
282
+ pmIsBun: config.packageManager === "bun"
283
+ };
284
+ }
285
+ function appendGitignoreFiles(dir) {
286
+ const gitignorePath = path.join(dir, ".gitignore");
287
+ if (!fs.existsSync(gitignorePath)) return;
288
+ const appendFiles = findFiles(dir, ".gitignore.append");
289
+ if (appendFiles.length === 0) return;
290
+ let content = fs.readFileSync(gitignorePath, "utf-8");
291
+ for (const appendFile of appendFiles) {
292
+ const appendContent = fs.readFileSync(appendFile, "utf-8").trim();
293
+ if (appendContent) content = content.trimEnd() + "\n\n" + appendContent + "\n";
294
+ fs.unlinkSync(appendFile);
295
+ }
296
+ fs.writeFileSync(gitignorePath, content);
297
+ }
298
+ const ALL_LOCKFILES = {
299
+ "package-lock.json": "npm",
300
+ "pnpm-lock.yaml": "pnpm",
301
+ "bun.lock": "bun"
302
+ };
303
+ function appendLockfileIgnores(dir, pm) {
304
+ const gitignorePath = path.join(dir, ".gitignore");
305
+ const lines = Object.entries(ALL_LOCKFILES).filter(([, lockPm]) => lockPm !== pm).map(([file]) => file);
306
+ if (lines.length > 0) {
307
+ const content = fs.readFileSync(gitignorePath, "utf-8");
308
+ fs.writeFileSync(gitignorePath, content.trimEnd() + "\n\n# Other package manager lockfiles\n" + lines.join("\n") + "\n");
309
+ }
310
+ }
311
+ const BINARY_EXTENSIONS = new Set([
312
+ ".png",
313
+ ".jpg",
314
+ ".jpeg",
315
+ ".gif",
316
+ ".ico",
317
+ ".webp",
318
+ ".avif",
319
+ ".woff",
320
+ ".woff2",
321
+ ".ttf",
322
+ ".eot",
323
+ ".zip",
324
+ ".tar",
325
+ ".gz"
326
+ ]);
327
+ function renderDir(dir, context) {
328
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
329
+ const fullPath = path.join(dir, entry.name);
330
+ if (entry.isDirectory()) {
331
+ if (entry.name === ".git") continue;
332
+ renderDir(fullPath, context);
333
+ } else {
334
+ const ext = path.extname(entry.name).toLowerCase();
335
+ if (BINARY_EXTENSIONS.has(ext)) continue;
336
+ const content = fs.readFileSync(fullPath, "utf-8");
337
+ const rendered = renderTemplate(content, context);
338
+ if (rendered !== content) fs.writeFileSync(fullPath, rendered);
339
+ }
340
+ }
341
+ }
342
+
343
+ //#endregion
344
+ //#region src/cli.ts
345
+ async function cli() {
346
+ const { values, positionals } = parseArgs({
347
+ allowPositionals: true,
348
+ options: {
349
+ yes: {
350
+ type: "boolean",
351
+ short: "y",
352
+ default: false
353
+ },
354
+ help: {
355
+ type: "boolean",
356
+ short: "h",
357
+ default: false
358
+ }
359
+ }
360
+ });
361
+ if (values.help) {
362
+ printHelp();
363
+ return;
364
+ }
365
+ const packageManager = detectPackageManager();
366
+ const projectName = positionals[0];
367
+ const nonInteractive = values.yes || process.env.CI === "true";
368
+ let config;
369
+ if (nonInteractive) {
370
+ const targetDir = projectName || "my-mikstack-app";
371
+ config = {
372
+ projectName: path.basename(targetDir),
373
+ targetDir,
374
+ packageManager,
375
+ ...defaults
376
+ };
377
+ p.intro("create-mikstack");
378
+ p.log.info(`Scaffolding ${config.projectName} with recommended defaults (non-interactive)...`);
379
+ } else config = await runPrompts(projectName, packageManager);
380
+ try {
381
+ scaffold(config);
382
+ } catch (err) {
383
+ p.log.error(err instanceof Error ? err.message : String(err));
384
+ process.exit(1);
385
+ }
386
+ postScaffold(config.targetDir, config.packageManager);
387
+ p.note([`cd ${config.targetDir}`, `${config.packageManager} run dev`].join("\n"), "Next steps");
388
+ }
389
+ function printHelp() {
390
+ console.log(`
391
+ create-mikstack — scaffold an opinionated SvelteKit project
392
+
393
+ Usage:
394
+ create-mikstack [project-name] [options]
395
+
396
+ Options:
397
+ -y, --yes Use recommended defaults (non-interactive)
398
+ -h, --help Show this help message
399
+ `);
400
+ }
401
+
402
+ //#endregion
403
+ //#region src/index.ts
404
+ cli().catch((err) => {
405
+ console.error(err);
406
+ process.exit(1);
407
+ });
408
+
409
+ //#endregion
410
+ export { };
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "create-mikstack",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/MikaelSiidorow/mikstack.git",
8
+ "directory": "packages/create-mikstack"
9
+ },
10
+ "bin": {
11
+ "create-mikstack": "./dist/index.js"
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "templates"
16
+ ],
17
+ "type": "module",
18
+ "scripts": {
19
+ "sync:ui": "bash scripts/sync-ui-vendor.sh",
20
+ "build": "bun run sync:ui && tsdown",
21
+ "dev": "tsdown --watch",
22
+ "check": "tsc --noEmit",
23
+ "lint": "oxlint --type-aware --type-check --ignore-pattern templates && oxfmt --check src tsdown.config.ts",
24
+ "format": "oxfmt src tsdown.config.ts",
25
+ "test": "bun test"
26
+ },
27
+ "dependencies": {
28
+ "@clack/prompts": "^0.10.0",
29
+ "picocolors": "^1.1.1",
30
+ "sv": "0.12.1"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^22.15.0",
37
+ "oxfmt": "^0.28.0",
38
+ "oxlint": "^1.43.0",
39
+ "oxlint-tsgolint": "^0.11.4",
40
+ "tsdown": "^0.12.0",
41
+ "typescript": "^5.9.3"
42
+ }
43
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "devDependencies": {
3
+ "@sveltejs/adapter-cloudflare": "^7.2.6"
4
+ }
5
+ }
@@ -0,0 +1,19 @@
1
+ // {{#if:i18n}}
2
+ import { linguiPreprocess } from "@mikstack/svelte-lingui/preprocessor";
3
+ // {{/if:i18n}}
4
+ import adapter from "@sveltejs/adapter-cloudflare";
5
+
6
+ /** @type {import('@sveltejs/kit').Config} */
7
+ const config = {
8
+ // {{#if:i18n}}
9
+ preprocess: [linguiPreprocess()],
10
+ // {{/if:i18n}}
11
+ kit: {
12
+ adapter: adapter(),
13
+ experimental: {
14
+ remoteFunctions: true,
15
+ },
16
+ },
17
+ };
18
+
19
+ export default config;
@@ -0,0 +1,30 @@
1
+ FROM node:22-slim AS base
2
+ WORKDIR /app
3
+
4
+ FROM base AS deps
5
+ COPY package.json {{lockfile}} ./
6
+ # {{#if:pmIsNpm}}
7
+ RUN npm ci
8
+ # {{/if:pmIsNpm}}
9
+ # {{#if:pmIsPnpm}}
10
+ RUN corepack enable && pnpm install --frozen-lockfile
11
+ # {{/if:pmIsPnpm}}
12
+ # {{#if:pmIsBun}}
13
+ RUN npm i -g bun && bun install --frozen-lockfile
14
+ # {{/if:pmIsBun}}
15
+
16
+ FROM base AS build
17
+ WORKDIR /app
18
+ COPY --from=deps /app/node_modules ./node_modules
19
+ COPY . .
20
+ RUN npm run build
21
+ RUN npm prune --omit=dev
22
+
23
+ FROM base
24
+ WORKDIR /app
25
+ COPY --from=build /app/build ./build
26
+ COPY --from=build /app/node_modules ./node_modules
27
+ COPY package.json .
28
+ ENV NODE_ENV=production
29
+ EXPOSE 3000
30
+ CMD ["node", "build"]
@@ -0,0 +1,27 @@
1
+ services:
2
+ db:
3
+ image: postgres
4
+ restart: always
5
+ environment:
6
+ POSTGRES_USER: root
7
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
8
+ POSTGRES_DB: { { projectName } }
9
+ command: ["postgres", "-c", "wal_level=logical"]
10
+ volumes:
11
+ - pgdata:/var/lib/postgresql/data
12
+
13
+ app:
14
+ build: .
15
+ restart: always
16
+ ports:
17
+ - 3000:3000
18
+ environment:
19
+ DATABASE_URL: postgres://root:${POSTGRES_PASSWORD}@db:5432/{{projectName}}
20
+ BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
21
+ BETTER_AUTH_URL: ${BETTER_AUTH_URL}
22
+ ORIGIN: ${BETTER_AUTH_URL}
23
+ depends_on:
24
+ - db
25
+
26
+ volumes:
27
+ pgdata:
@@ -0,0 +1,5 @@
1
+ {
2
+ "devDependencies": {
3
+ "@sveltejs/adapter-node": "^5.5.2"
4
+ }
5
+ }
@@ -0,0 +1,19 @@
1
+ // {{#if:i18n}}
2
+ import { linguiPreprocess } from "@mikstack/svelte-lingui/preprocessor";
3
+ // {{/if:i18n}}
4
+ import adapter from "@sveltejs/adapter-node";
5
+
6
+ /** @type {import('@sveltejs/kit').Config} */
7
+ const config = {
8
+ // {{#if:i18n}}
9
+ preprocess: [linguiPreprocess()],
10
+ // {{/if:i18n}}
11
+ kit: {
12
+ adapter: adapter(),
13
+ experimental: {
14
+ remoteFunctions: true,
15
+ },
16
+ },
17
+ };
18
+
19
+ export default config;
@@ -0,0 +1,5 @@
1
+ {
2
+ "devDependencies": {
3
+ "@sveltejs/adapter-vercel": "^6.3.1"
4
+ }
5
+ }
@@ -0,0 +1,19 @@
1
+ // {{#if:i18n}}
2
+ import { linguiPreprocess } from "@mikstack/svelte-lingui/preprocessor";
3
+ // {{/if:i18n}}
4
+ import adapter from "@sveltejs/adapter-vercel";
5
+
6
+ /** @type {import('@sveltejs/kit').Config} */
7
+ const config = {
8
+ // {{#if:i18n}}
9
+ preprocess: [linguiPreprocess()],
10
+ // {{/if:i18n}}
11
+ kit: {
12
+ adapter: adapter(),
13
+ experimental: {
14
+ remoteFunctions: true,
15
+ },
16
+ },
17
+ };
18
+
19
+ export default config;
@@ -0,0 +1,23 @@
1
+ # Database (PostgreSQL with wal_level=logical required for Zero replication)
2
+ DATABASE_URL="postgres://root:mysecretpassword@localhost:5432/local"
3
+ ZERO_UPSTREAM_DB="postgres://root:mysecretpassword@localhost:5432/local"
4
+
5
+ # Zero Cache Configuration
6
+ ZERO_REPLICA_FILE="/tmp/{{projectName}}-sync-replica.db"
7
+ ZERO_MUTATE_URL="http://localhost:5173/api/zero/mutate"
8
+ ZERO_QUERY_URL="http://localhost:5173/api/zero/get-queries"
9
+ PUBLIC_SERVER="http://localhost:4848"
10
+ ZERO_QUERY_FORWARD_COOKIES=true
11
+ ZERO_MUTATE_FORWARD_COOKIES=true
12
+
13
+ # better-auth
14
+ # Generate with: openssl rand -base64 32
15
+ BETTER_AUTH_SECRET="change-me-generate-a-real-secret"
16
+ BETTER_AUTH_URL="http://localhost:5173"
17
+
18
+ # SMTP (for magic link emails in production)
19
+ # SMTP_HOST=smtp.example.com
20
+ # SMTP_PORT=587
21
+ # SMTP_USER=
22
+ # SMTP_PASS=
23
+ # SMTP_FROM=noreply@example.com
@@ -0,0 +1,2 @@
1
+ # Generated
2
+ *.gen.ts
@@ -0,0 +1,9 @@
1
+ {
2
+ "mcpServers": {
3
+ "svelte": {
4
+ "type": "stdio",
5
+ "command": "npx",
6
+ "args": ["-y", "@sveltejs/mcp"]
7
+ }
8
+ }
9
+ }
@@ -0,0 +1,10 @@
1
+ # Package Managers
2
+ package-lock.json
3
+ pnpm-lock.yaml
4
+ bun.lock
5
+
6
+ # Generated
7
+ .svelte-kit
8
+ build
9
+ dist
10
+ *.gen.ts