create-stitchkit 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 (174) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/LICENSE +21 -0
  3. package/README.md +35 -0
  4. package/dist/cli.js +230 -0
  5. package/package.json +50 -0
  6. package/template/README.md +69 -0
  7. package/template/_env +11 -0
  8. package/template/_env.example +12 -0
  9. package/template/_gitignore +10 -0
  10. package/template/biome.json +43 -0
  11. package/template/bun.lock +1372 -0
  12. package/template/e2e/starter.spec.ts +237 -0
  13. package/template/ecosystem.config.cjs +28 -0
  14. package/template/ecosystem.dev.config.cjs +29 -0
  15. package/template/package.json +55 -0
  16. package/template/packages/backend/package.json +31 -0
  17. package/template/packages/backend/src/cli.ts +12 -0
  18. package/template/packages/backend/src/domain/errors.ts +9 -0
  19. package/template/packages/backend/src/domain/repository/github-cache.test.ts +72 -0
  20. package/template/packages/backend/src/domain/repository/github-cache.ts +205 -0
  21. package/template/packages/backend/src/index.ts +56 -0
  22. package/template/packages/backend/src/lib/db.ts +7 -0
  23. package/template/packages/backend/src/surface.ts +13 -0
  24. package/template/packages/backend/src/tools.ts +21 -0
  25. package/template/packages/backend/src/transport/errors.ts +28 -0
  26. package/template/packages/backend/src/transport/repository-service.ts +18 -0
  27. package/template/packages/backend/tsconfig.json +8 -0
  28. package/template/packages/config/package.json +22 -0
  29. package/template/packages/config/src/server.ts +24 -0
  30. package/template/packages/config/tsconfig.json +5 -0
  31. package/template/packages/db/migrations/20260808000000_init/migration.sql +16 -0
  32. package/template/packages/db/migrations/20260808170000_repository_visibility/migration.sql +4 -0
  33. package/template/packages/db/migrations/migration_lock.toml +1 -0
  34. package/template/packages/db/package.json +29 -0
  35. package/template/packages/db/prisma.config.ts +8 -0
  36. package/template/packages/db/schema.prisma +31 -0
  37. package/template/packages/db/tsconfig.json +5 -0
  38. package/template/packages/frontend/messages/en.json +15 -0
  39. package/template/packages/frontend/messages/ru.json +15 -0
  40. package/template/packages/frontend/next.config.ts +18 -0
  41. package/template/packages/frontend/package.json +62 -0
  42. package/template/packages/frontend/postcss.config.mjs +1 -0
  43. package/template/packages/frontend/public/ASSETS.md +6 -0
  44. package/template/packages/frontend/public/favicon/mascot-stitch.png +0 -0
  45. package/template/packages/frontend/public/icons/biome.svg +1 -0
  46. package/template/packages/frontend/public/icons/bun.svg +19 -0
  47. package/template/packages/frontend/public/icons/next.svg +1 -0
  48. package/template/packages/frontend/public/icons/postgresql.svg +1 -0
  49. package/template/packages/frontend/public/icons/prisma.svg +6 -0
  50. package/template/packages/frontend/public/icons/react-query.svg +9 -0
  51. package/template/packages/frontend/public/icons/react.svg +1 -0
  52. package/template/packages/frontend/public/icons/tailwind.svg +12 -0
  53. package/template/packages/frontend/public/icons/tsconfig.svg +1 -0
  54. package/template/packages/frontend/public/icons/typescript.svg +7 -0
  55. package/template/packages/frontend/public/icons/zod.svg +1 -0
  56. package/template/packages/frontend/public/mascot-stitch.png +0 -0
  57. package/template/packages/frontend/public/theme-dark.svg +8 -0
  58. package/template/packages/frontend/public/theme-light.svg +8 -0
  59. package/template/packages/frontend/src/app/[locale]/error.tsx +19 -0
  60. package/template/packages/frontend/src/app/[locale]/layout.tsx +59 -0
  61. package/template/packages/frontend/src/app/[locale]/loading.tsx +14 -0
  62. package/template/packages/frontend/src/app/[locale]/page.tsx +44 -0
  63. package/template/packages/frontend/src/app/[locale]/starter-page.tsx +151 -0
  64. package/template/packages/frontend/src/app/[locale]/ui/[story]/page.tsx +40 -0
  65. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/account-showcase.tsx +112 -0
  66. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/admin-showcase.tsx +101 -0
  67. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/catalogue-navigation.tsx +56 -0
  68. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/catalogue-shell.tsx +51 -0
  69. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/code-foundation.tsx +35 -0
  70. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/components-story.tsx +54 -0
  71. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/data-story.tsx +126 -0
  72. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/image-preloader.tsx +8 -0
  73. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/index.ts +3 -0
  74. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/landing-sections.tsx +269 -0
  75. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/landing-showcase.tsx +159 -0
  76. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/motion-story.tsx +78 -0
  77. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/overlays-story.tsx +134 -0
  78. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/payment-showcase.tsx +82 -0
  79. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/presentation-story.tsx +68 -0
  80. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/primitives-story.tsx +267 -0
  81. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/story-page.tsx +14 -0
  82. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/theme-story-client.tsx +208 -0
  83. package/template/packages/frontend/src/app/[locale]/ui/_catalogue/theme-story.tsx +13 -0
  84. package/template/packages/frontend/src/app/[locale]/ui/layout.tsx +6 -0
  85. package/template/packages/frontend/src/app/[locale]/ui/page.tsx +10 -0
  86. package/template/packages/frontend/src/app/api/og/[locale]/[page]/route.tsx +53 -0
  87. package/template/packages/frontend/src/app/globals.css +210 -0
  88. package/template/packages/frontend/src/app/robots.ts +9 -0
  89. package/template/packages/frontend/src/app/sitemap.ts +22 -0
  90. package/template/packages/frontend/src/components/brand-mark.tsx +22 -0
  91. package/template/packages/frontend/src/components/repository-summary.tsx +107 -0
  92. package/template/packages/frontend/src/components/system-controls.tsx +92 -0
  93. package/template/packages/frontend/src/components/ui/adaptive-modal.tsx +382 -0
  94. package/template/packages/frontend/src/components/ui/animated-counter.tsx +80 -0
  95. package/template/packages/frontend/src/components/ui/avatar.tsx +46 -0
  96. package/template/packages/frontend/src/components/ui/badge.tsx +94 -0
  97. package/template/packages/frontend/src/components/ui/button-variants.ts +24 -0
  98. package/template/packages/frontend/src/components/ui/button.tsx +81 -0
  99. package/template/packages/frontend/src/components/ui/card-deck.tsx +73 -0
  100. package/template/packages/frontend/src/components/ui/card.tsx +81 -0
  101. package/template/packages/frontend/src/components/ui/charts/area-chart.tsx +167 -0
  102. package/template/packages/frontend/src/components/ui/charts/index.ts +7 -0
  103. package/template/packages/frontend/src/components/ui/checkbox.tsx +133 -0
  104. package/template/packages/frontend/src/components/ui/confirmation-modal.tsx +129 -0
  105. package/template/packages/frontend/src/components/ui/copyable-text.tsx +85 -0
  106. package/template/packages/frontend/src/components/ui/drawer.tsx +136 -0
  107. package/template/packages/frontend/src/components/ui/dropdown-menu.tsx +185 -0
  108. package/template/packages/frontend/src/components/ui/field.tsx +28 -0
  109. package/template/packages/frontend/src/components/ui/hand-drawn-icons.tsx +41 -0
  110. package/template/packages/frontend/src/components/ui/index.ts +36 -0
  111. package/template/packages/frontend/src/components/ui/input.tsx +24 -0
  112. package/template/packages/frontend/src/components/ui/label.tsx +18 -0
  113. package/template/packages/frontend/src/components/ui/otp-input.tsx +140 -0
  114. package/template/packages/frontend/src/components/ui/select.tsx +240 -0
  115. package/template/packages/frontend/src/components/ui/sidebar.tsx +343 -0
  116. package/template/packages/frontend/src/components/ui/skeleton.tsx +8 -0
  117. package/template/packages/frontend/src/components/ui/slider.tsx +31 -0
  118. package/template/packages/frontend/src/components/ui/slot-drum.tsx +50 -0
  119. package/template/packages/frontend/src/components/ui/spinner.tsx +30 -0
  120. package/template/packages/frontend/src/components/ui/switch.tsx +31 -0
  121. package/template/packages/frontend/src/components/ui/table.tsx +106 -0
  122. package/template/packages/frontend/src/components/ui/tabs.tsx +196 -0
  123. package/template/packages/frontend/src/components/ui/textarea.tsx +21 -0
  124. package/template/packages/frontend/src/components/ui/toaster.tsx +72 -0
  125. package/template/packages/frontend/src/components/ui/tooltip.tsx +31 -0
  126. package/template/packages/frontend/src/env.ts +21 -0
  127. package/template/packages/frontend/src/features/data-table/components/column-helpers.tsx +136 -0
  128. package/template/packages/frontend/src/features/data-table/components/data-table.tsx +303 -0
  129. package/template/packages/frontend/src/features/data-table/components/inline-stat.tsx +113 -0
  130. package/template/packages/frontend/src/features/data-table/components/sortable-header.tsx +33 -0
  131. package/template/packages/frontend/src/features/data-table/components/table-error.tsx +24 -0
  132. package/template/packages/frontend/src/features/data-table/components/table-filters.tsx +82 -0
  133. package/template/packages/frontend/src/features/data-table/hooks/useTableState.ts +111 -0
  134. package/template/packages/frontend/src/features/data-table/index.ts +26 -0
  135. package/template/packages/frontend/src/features/data-table/table-features.ts +26 -0
  136. package/template/packages/frontend/src/hooks/index.ts +3 -0
  137. package/template/packages/frontend/src/hooks/useInfiniteScroll.ts +67 -0
  138. package/template/packages/frontend/src/hooks/useIsTouchDevice.ts +25 -0
  139. package/template/packages/frontend/src/hooks/useMediaQuery.ts +30 -0
  140. package/template/packages/frontend/src/i18n/locales.ts +6 -0
  141. package/template/packages/frontend/src/i18n/navigation.ts +4 -0
  142. package/template/packages/frontend/src/i18n/request.ts +9 -0
  143. package/template/packages/frontend/src/i18n/routing.ts +8 -0
  144. package/template/packages/frontend/src/lib/api/client.ts +20 -0
  145. package/template/packages/frontend/src/lib/api/queries.ts +11 -0
  146. package/template/packages/frontend/src/lib/query-client.ts +20 -0
  147. package/template/packages/frontend/src/lib/realtime/repository.ts +21 -0
  148. package/template/packages/frontend/src/lib/seo/metadata.ts +50 -0
  149. package/template/packages/frontend/src/lib/seo/pages.test.ts +25 -0
  150. package/template/packages/frontend/src/lib/seo/pages.ts +107 -0
  151. package/template/packages/frontend/src/lib/utils/cn.ts +6 -0
  152. package/template/packages/frontend/src/lib/utils/format.ts +25 -0
  153. package/template/packages/frontend/src/lib/utils/index.ts +2 -0
  154. package/template/packages/frontend/src/providers/index.tsx +18 -0
  155. package/template/packages/frontend/src/providers/react-query.tsx +9 -0
  156. package/template/packages/frontend/src/providers/realtime.tsx +16 -0
  157. package/template/packages/frontend/src/proxy.ts +6 -0
  158. package/template/packages/frontend/src/theme/config.ts +20 -0
  159. package/template/packages/frontend/src/theme/transition.ts +54 -0
  160. package/template/packages/frontend/tsconfig.json +13 -0
  161. package/template/packages/shared/package.json +23 -0
  162. package/template/packages/shared/src/contracts/repository.ts +28 -0
  163. package/template/packages/shared/src/events/repository.ts +9 -0
  164. package/template/packages/shared/src/index.ts +3 -0
  165. package/template/packages/shared/src/schemas/repository.test.ts +36 -0
  166. package/template/packages/shared/src/schemas/repository.ts +28 -0
  167. package/template/packages/shared/tsconfig.json +5 -0
  168. package/template/playwright.config.ts +15 -0
  169. package/template/scripts/check-authored.ts +81 -0
  170. package/template/scripts/dev.ts +23 -0
  171. package/template/scripts/local-env.ts +12 -0
  172. package/template/scripts/runtime-smoke.ts +67 -0
  173. package/template/scripts/tooling-env.ts +15 -0
  174. package/template/tsconfig.json +13 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,46 @@
1
+ # Changelog
2
+
3
+ All notable changes to **create-stitchkit** are documented here. The scaffolder
4
+ has its own version and release line; the Stitchkit range tested by its template
5
+ is declared in the template root catalog.
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] — 2026-08-08
10
+
11
+ ### Added
12
+
13
+ - **Production-shaped official starter.** `bun create stitchkit my-app`
14
+ generates a Bun workspace with a separate Next.js frontend and Stitchkit API,
15
+ Prisma/PostgreSQL, shared contracts, typed HTTP and React Query clients,
16
+ Socket.IO cache updates, OpenAPI, MCP, CLI, a production-shaped reference app and a
17
+ complete `/ui` component catalogue.
18
+ - **Complete starter SEO surface.** A typed locale-aware page registry now drives
19
+ unique page metadata, canonical and language alternatives, Open Graph and
20
+ Twitter cards, sitemap entries and visible catalogue headings.
21
+ - **Direct PM2 entrypoints.** Development and production configs supervise Bun
22
+ and Next entrypoints without package-script wrappers.
23
+ - **External PostgreSQL boundary.** Generated applications own Prisma schemas
24
+ and migrations but connect to environment-provided PostgreSQL exclusively
25
+ through `DATABASE_URL`; they do not package or start a database runtime.
26
+ - **One runnable canonical template.** Starter development now runs directly
27
+ from `packages/create-stitchkit/template` with Bun/Next HMR; generated
28
+ consumers exist only as ephemeral release-lane fixtures.
29
+ - **Fleet-shaped workspace layout.** Backend, frontend, config, database and
30
+ shared code live together under one `packages/*` namespace.
31
+ - **Self-contained consumer gates.** A fresh scaffold generates its Prisma
32
+ client before typechecking, remains lint-clean after a Next build and excludes
33
+ runtime test artifacts from the published tarball.
34
+ - **Canonical status palette.** Success and destructive states use the source
35
+ UI system hues in both themes with accessible foreground contrast.
36
+ - **Compact reference surface.** The starter home now fits one desktop viewport
37
+ and demonstrates one PostgreSQL-backed repository query, one refresh mutation,
38
+ cache invalidation and realtime propagation without shipping a demo CRUD domain.
39
+ - **Removable component catalogue.** Every catalogue-only screen and composition
40
+ lives under the `/ui` route directory, while reusable primitives stay in
41
+ `components/ui`; its desktop navigation is a compact floating palette.
42
+ - **Generated Next.js declarations stay generated.** `next-env.d.ts` is ignored
43
+ and `next typegen` runs before standalone TypeScript checks, preventing dev/build churn.
44
+ - **Independent compatibility target.** The committed template catalog and
45
+ lockfile keep generated projects on the last explicitly validated Stitchkit
46
+ range until a separate starter release advances it.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Max Listov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # create-stitchkit
2
+
3
+ Create the canonical Stitchkit application:
4
+
5
+ ```bash
6
+ bun create stitchkit my-app
7
+ cd my-app
8
+ # Point DATABASE_URL in .env at your PostgreSQL database.
9
+ bun run dev
10
+ ```
11
+
12
+ The generated Bun workspace contains a Next.js frontend, a separate Stitchkit
13
+ API, Prisma/PostgreSQL, typed shared contracts, Socket.IO, MCP, CLI tools and a
14
+ complete production UI system.
15
+
16
+ It uses one conventional `packages/*` namespace: `backend`, `frontend`,
17
+ `config`, `db` and `shared`. The copied application keeps the neutral
18
+ `stitchkit-starter` identity until its owner explicitly renames it.
19
+
20
+ Its runnable example is the Stitchkit Starter: one configurable GitHub repository
21
+ is persisted through a server-side PostgreSQL cache, including a Prisma-backed
22
+ visibility enum that flows through the shared schema and typed client. `/en/ui`
23
+ presents every reusable primitive and composition. Every public page ships with
24
+ localized metadata, canonical and language alternatives, sitemap coverage and a
25
+ generated Open Graph card.
26
+
27
+ The included PM2 configs launch Bun and Next entrypoints directly, so package
28
+ script wrappers do not hide the supervised processes or distort their metrics.
29
+ PostgreSQL is supplied by the application environment through `DATABASE_URL`;
30
+ the generated project does not package or start a database runtime.
31
+
32
+ The generated workspace uses the Stitchkit range declared once in the template
33
+ catalog and ships with the exact lockfile validated by this package. Framework
34
+ and scaffolder releases are independent: a new Stitchkit version does not move
35
+ the starter until that target is deliberately updated and revalidated.
package/dist/cli.js ADDED
@@ -0,0 +1,230 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+
4
+ // src/cli.ts
5
+ import { basename as basename2, resolve as resolve2 } from "path";
6
+ var {spawn } = globalThis.Bun;
7
+
8
+ // src/options.ts
9
+ var HELP = `Create a production-shaped Stitchkit application.
10
+
11
+ Usage:
12
+ bun create stitchkit <directory> [--no-install]
13
+
14
+ Options:
15
+ --no-install Generate files without installing dependencies
16
+ --help Show this help
17
+ `;
18
+ function helpText() {
19
+ return HELP;
20
+ }
21
+ function parseOptions(args) {
22
+ if (args.includes("--help") || args.includes("-h"))
23
+ return "help";
24
+ const unknown = args.filter((arg) => arg.startsWith("-") && arg !== "--no-install");
25
+ if (unknown.length > 0) {
26
+ throw new Error(`Unknown option: ${unknown[0]}`);
27
+ }
28
+ const positionals = args.filter((arg) => !arg.startsWith("-"));
29
+ if (positionals.length !== 1) {
30
+ throw new Error("Exactly one destination directory is required");
31
+ }
32
+ const destination = positionals[0];
33
+ if (destination === undefined)
34
+ throw new Error("Destination directory is required");
35
+ return {
36
+ destination,
37
+ install: !args.includes("--no-install")
38
+ };
39
+ }
40
+
41
+ // src/scaffold.ts
42
+ import { lstat, mkdir, readdir, readFile, rm, writeFile } from "fs/promises";
43
+ import { homedir } from "os";
44
+ import { basename, dirname, join, parse, relative, resolve, sep } from "path";
45
+ var TEXT_EXTENSIONS = new Set([
46
+ ".cjs",
47
+ ".css",
48
+ ".example",
49
+ ".html",
50
+ ".js",
51
+ ".json",
52
+ ".lock",
53
+ ".md",
54
+ ".mjs",
55
+ ".prisma",
56
+ ".sql",
57
+ ".ts",
58
+ ".tsx",
59
+ ".txt",
60
+ ".yaml",
61
+ ".yml"
62
+ ]);
63
+ var TEMPLATE_RENAMES = new Map([
64
+ ["_env", ".env"],
65
+ ["_env.example", ".env.example"],
66
+ ["_gitignore", ".gitignore"]
67
+ ]);
68
+ var IGNORED_DIRECTORIES = new Set([
69
+ ".next",
70
+ "coverage",
71
+ "dist",
72
+ "node_modules",
73
+ "playwright-report",
74
+ "test-results"
75
+ ]);
76
+ function isTemplateSourcePathIncluded(sourcePath) {
77
+ const normalized = sourcePath.replaceAll("\\", "/").replace(/^\.\//, "");
78
+ if (!normalized)
79
+ return true;
80
+ const segments = normalized.split("/");
81
+ if (segments.some((segment) => IGNORED_DIRECTORIES.has(segment)))
82
+ return false;
83
+ if (normalized === "packages/db/src/generated" || normalized.startsWith("packages/db/src/generated/"))
84
+ return false;
85
+ const name = basename(normalized);
86
+ return name !== ".env" && name !== "next-env.d.ts" && !name.endsWith(".log") && !name.endsWith(".tsbuildinfo");
87
+ }
88
+ function shouldIncludeTemplatePath(templateDirectory, sourcePath) {
89
+ return isTemplateSourcePathIncluded(relative(templateDirectory, sourcePath));
90
+ }
91
+ function portablePath(path) {
92
+ return path.split(sep).join("/");
93
+ }
94
+ function isUnsafeDestination(destination) {
95
+ const root = parse(destination).root;
96
+ const home = resolve(homedir());
97
+ return destination === root || destination === home;
98
+ }
99
+ async function assertDestinationAvailable(destination) {
100
+ if (isUnsafeDestination(destination)) {
101
+ throw new Error("Refusing to scaffold into a filesystem root or home directory");
102
+ }
103
+ try {
104
+ const info = await lstat(destination);
105
+ if (info.isSymbolicLink()) {
106
+ throw new Error(`Destination cannot be a symbolic link: ${destination}`);
107
+ }
108
+ if (!info.isDirectory()) {
109
+ throw new Error(`Destination exists and is not a directory: ${destination}`);
110
+ }
111
+ const entries = await readdir(destination);
112
+ if (entries.length > 0) {
113
+ throw new Error(`Destination is not empty: ${destination}`);
114
+ }
115
+ return true;
116
+ } catch (error) {
117
+ if (error instanceof Error && "code" in error && error.code === "ENOENT")
118
+ return false;
119
+ throw error;
120
+ }
121
+ }
122
+ async function collectMaterialisedFiles(templateDirectory, directory, files) {
123
+ const entries = await readdir(directory, { withFileTypes: true });
124
+ entries.sort((left, right) => left.name.localeCompare(right.name));
125
+ for (const entry of entries) {
126
+ const sourcePath = join(directory, entry.name);
127
+ if (!shouldIncludeTemplatePath(templateDirectory, sourcePath))
128
+ continue;
129
+ if (entry.isDirectory()) {
130
+ await collectMaterialisedFiles(templateDirectory, sourcePath, files);
131
+ continue;
132
+ }
133
+ if (!entry.isFile()) {
134
+ throw new Error(`Template entries must be files or directories: ${sourcePath}`);
135
+ }
136
+ const targetName = TEMPLATE_RENAMES.get(entry.name);
137
+ const sourceRelativePath = relative(templateDirectory, sourcePath);
138
+ const outputRelativePath = targetName ? join(dirname(sourceRelativePath), targetName) : sourceRelativePath;
139
+ const extension = targetName?.includes(".") ? targetName.slice(targetName.indexOf(".")) : "";
140
+ const fileExtension = extension || sourcePath.slice(sourcePath.lastIndexOf("."));
141
+ const content = TEXT_EXTENSIONS.has(fileExtension) || targetName ? await readFile(sourcePath, "utf8") : await readFile(sourcePath);
142
+ files.push({
143
+ sourcePath: portablePath(sourceRelativePath),
144
+ outputPath: portablePath(outputRelativePath),
145
+ content
146
+ });
147
+ }
148
+ }
149
+ async function materialiseTemplateFiles(templateDirectory) {
150
+ const files = [];
151
+ await collectMaterialisedFiles(templateDirectory, templateDirectory, files);
152
+ return files;
153
+ }
154
+ async function writeMaterialisedFiles(destination, files) {
155
+ for (const file of files) {
156
+ const targetPath = join(destination, file.outputPath);
157
+ await mkdir(dirname(targetPath), { recursive: true });
158
+ await writeFile(targetPath, file.content);
159
+ }
160
+ }
161
+ async function scaffoldProject(templateDirectory, destination) {
162
+ const resolvedDestination = resolve(destination);
163
+ const destinationExisted = await assertDestinationAvailable(resolvedDestination);
164
+ await mkdir(resolvedDestination, { recursive: true });
165
+ try {
166
+ await writeMaterialisedFiles(resolvedDestination, await materialiseTemplateFiles(templateDirectory));
167
+ } catch (error) {
168
+ if (!destinationExisted) {
169
+ await rm(resolvedDestination, { recursive: true, force: true });
170
+ } else {
171
+ const entries = await readdir(resolvedDestination);
172
+ await Promise.all(entries.map((entry) => rm(join(resolvedDestination, entry), { recursive: true, force: true })));
173
+ }
174
+ throw error;
175
+ }
176
+ }
177
+
178
+ // src/cli.ts
179
+ async function run(args) {
180
+ try {
181
+ const options = parseOptions(args);
182
+ if (options === "help") {
183
+ process.stdout.write(helpText());
184
+ return 0;
185
+ }
186
+ const destination = resolve2(options.destination);
187
+ const templateDirectory = resolve2(import.meta.dir, "../template");
188
+ await scaffoldProject(templateDirectory, destination);
189
+ if (options.install) {
190
+ const install = spawn(["bun", "install"], {
191
+ cwd: destination,
192
+ stdin: "inherit",
193
+ stdout: "inherit",
194
+ stderr: "inherit"
195
+ });
196
+ const exitCode = await install.exited;
197
+ if (exitCode !== 0)
198
+ throw new Error(`bun install failed with exit code ${exitCode}`);
199
+ }
200
+ process.stdout.write(`
201
+ Created Stitchkit Starter in ${basename2(destination)}
202
+
203
+ `);
204
+ process.stdout.write(` cd ${options.destination}
205
+ `);
206
+ process.stdout.write(` bun run dev
207
+
208
+ `);
209
+ process.stdout.write(`Web: http://localhost:3210
210
+ `);
211
+ process.stdout.write(`API: http://localhost:3211
212
+ `);
213
+ process.stdout.write(`MCP: http://localhost:3211/mcp
214
+ `);
215
+ process.stdout.write(`OpenAPI: http://localhost:3211/openapi.json
216
+ `);
217
+ return 0;
218
+ } catch (error) {
219
+ const message = error instanceof Error ? error.message : String(error);
220
+ process.stderr.write(`create-stitchkit: ${message}
221
+ `);
222
+ return 1;
223
+ }
224
+ }
225
+ if (import.meta.main) {
226
+ process.exitCode = await run(Bun.argv.slice(2));
227
+ }
228
+ export {
229
+ run
230
+ };
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "create-stitchkit",
3
+ "version": "0.1.0",
4
+ "description": "Create a production-shaped Stitchkit application",
5
+ "license": "MIT",
6
+ "author": "Max Listov <maxlistov@gmail.com>",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/max-listov/stitchkit.git",
10
+ "directory": "packages/create-stitchkit"
11
+ },
12
+ "type": "module",
13
+ "bin": {
14
+ "create-stitchkit": "./dist/cli.js"
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "template/**/*",
19
+ "!template/**/.env",
20
+ "!template/**/node_modules/**",
21
+ "!template/**/.next/**",
22
+ "!template/**/dist/**",
23
+ "!template/**/playwright-report/**",
24
+ "!template/**/test-results/**",
25
+ "!template/**/next-env.d.ts",
26
+ "!template/**/src/generated/**",
27
+ "!template/**/*.log",
28
+ "!template/**/*.tsbuildinfo",
29
+ "README.md",
30
+ "CHANGELOG.md",
31
+ "LICENSE"
32
+ ],
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "scripts": {
37
+ "check": "bun x tsc --noEmit",
38
+ "test": "bun test tests",
39
+ "build": "rm -rf dist && bun build src/cli.ts --outdir dist --target bun --packages external && chmod +x dist/cli.js",
40
+ "prepublishOnly": "bun run check && bun run test && bun run build"
41
+ },
42
+ "devDependencies": {
43
+ "@types/bun": "^1.3.14",
44
+ "typescript": "^7.0.2"
45
+ },
46
+ "engines": {
47
+ "bun": ">=1.2.0"
48
+ },
49
+ "packageManager": "bun@1.3.14"
50
+ }
@@ -0,0 +1,69 @@
1
+ # Stitchkit Starter
2
+
3
+ Production-shaped application generated by `create-stitchkit`.
4
+
5
+ ## Start
6
+
7
+ Point `DATABASE_URL` in `.env` at an existing PostgreSQL database, then run:
8
+
9
+ ```bash
10
+ bun run dev
11
+ ```
12
+
13
+ The command validates the environment, generates the Prisma client, applies the
14
+ checked-in migrations and launches:
15
+
16
+ - Web: <http://localhost:3210>
17
+ - UI catalogue: <http://localhost:3210/en/ui>
18
+ - API: <http://localhost:3211>
19
+ - OpenAPI: <http://localhost:3211/openapi.json>
20
+ - MCP: <http://localhost:3211/mcp>
21
+
22
+ The home page is a compact Stitchkit Starter reference surface. One configured
23
+ GitHub repository exercises the PostgreSQL cache → contract → typed client →
24
+ mutation invalidation → realtime path without adding a demo product domain.
25
+ Configure it with `GITHUB_REPOSITORY`; `GITHUB_TOKEN` is optional.
26
+ Repository visibility demonstrates the canonical Prisma enum → shared Zod schema
27
+ → HTTP/UI path without duplicating its allowed values.
28
+
29
+ The `/en/ui` catalogue is isolated under
30
+ `packages/frontend/src/app/[locale]/ui` and can be removed as one directory;
31
+ reusable primitives remain in `components/ui`. It renders the complete
32
+ foundation, overlay, data, motion, theme and composition system with generic
33
+ fixtures. The root theme uses
34
+ `@wrksz/themes/next` with hybrid cookie/localStorage persistence: the server can
35
+ read the selected theme, while browser tabs remain synchronized. Client code
36
+ imports typed hooks from `@wrksz/themes/client/*`; global theme ownership stays
37
+ in the server layout. Interactive changes use a presentation-only runner around
38
+ the native View Transition API. Its default is a 250 ms crossfade; the theme
39
+ catalogue demonstrates configurable crossfade/radial effects and speeds.
40
+ Unsupported browsers and users requesting reduced motion switch immediately.
41
+
42
+ ## Gates
43
+
44
+ ```bash
45
+ bun run check
46
+ bun run test
47
+ bun run build
48
+ ```
49
+
50
+ ## Production
51
+
52
+ Provide a production `.env`, then:
53
+
54
+ ```bash
55
+ bun run db:deploy
56
+ bun run build
57
+ bun run pm2:prod
58
+ ```
59
+
60
+ The Next.js frontend and Stitchkit API are separate processes and can also be
61
+ deployed independently.
62
+
63
+ PostgreSQL is external infrastructure in development and production. The
64
+ application owns its schema and migrations; the environment owns the database
65
+ process and supplies its connection through `DATABASE_URL`.
66
+
67
+ `bun run dev` and `bun run pm2:dev` use the same direct PM2 development path.
68
+ Rename the neutral `stitchkit-starter` package and process names when adopting
69
+ the template for a product.
package/template/_env ADDED
@@ -0,0 +1,11 @@
1
+ NODE_ENV=development
2
+ DATABASE_URL=postgresql://USER:PASSWORD@127.0.0.1:5432/stitchkit_starter
3
+ API_PORT=3211
4
+ WEB_PORT=3210
5
+ NEXT_PUBLIC_API_URL=http://127.0.0.1:3211
6
+ INTERNAL_API_URL=http://127.0.0.1:3211
7
+ NEXT_PUBLIC_WEB_URL=http://127.0.0.1:3210
8
+ LOG_FORMAT=pretty
9
+ GITHUB_REPOSITORY=max-listov/stitchkit
10
+ GITHUB_CACHE_TTL_SECONDS=900
11
+ # GITHUB_TOKEN=github_pat_...
@@ -0,0 +1,12 @@
1
+ NODE_ENV=development
2
+ DATABASE_URL=postgresql://USER:PASSWORD@127.0.0.1:5432/stitchkit_starter
3
+ API_PORT=3211
4
+ WEB_PORT=3210
5
+ NEXT_PUBLIC_API_URL=http://127.0.0.1:3211
6
+ INTERNAL_API_URL=http://127.0.0.1:3211
7
+ NEXT_PUBLIC_WEB_URL=http://127.0.0.1:3210
8
+ LOG_FORMAT=pretty
9
+ GITHUB_REPOSITORY=max-listov/stitchkit
10
+ GITHUB_CACHE_TTL_SECONDS=900
11
+ # Optional. Authenticated conditional requests have a higher GitHub rate limit.
12
+ # GITHUB_TOKEN=github_pat_...
@@ -0,0 +1,10 @@
1
+ node_modules/
2
+ dist/
3
+ .next/
4
+ next-env.d.ts
5
+ .env
6
+ coverage/
7
+ *.log
8
+ packages/db/src/generated/
9
+ playwright-report/
10
+ test-results/
@@ -0,0 +1,43 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.5.7/schema.json",
3
+ "files": {
4
+ "includes": [
5
+ "**",
6
+ "!!**/src/generated",
7
+ "!!**/.next",
8
+ "!!**/dist",
9
+ "!!playwright-report",
10
+ "!!test-results",
11
+ "!!packages/frontend/next-env.d.ts",
12
+ "!!packages/frontend/public"
13
+ ]
14
+ },
15
+ "formatter": {
16
+ "enabled": true,
17
+ "indentStyle": "space",
18
+ "indentWidth": 2,
19
+ "lineWidth": 96
20
+ },
21
+ "javascript": {
22
+ "formatter": {
23
+ "quoteStyle": "single",
24
+ "jsxQuoteStyle": "single",
25
+ "trailingCommas": "all",
26
+ "semicolons": "always"
27
+ }
28
+ },
29
+ "css": {
30
+ "parser": {
31
+ "tailwindDirectives": true
32
+ }
33
+ },
34
+ "linter": {
35
+ "enabled": true,
36
+ "rules": {
37
+ "preset": "recommended",
38
+ "suspicious": {
39
+ "noExplicitAny": "error"
40
+ }
41
+ }
42
+ }
43
+ }