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.
- package/CHANGELOG.md +46 -0
- package/LICENSE +21 -0
- package/README.md +35 -0
- package/dist/cli.js +230 -0
- package/package.json +50 -0
- package/template/README.md +69 -0
- package/template/_env +11 -0
- package/template/_env.example +12 -0
- package/template/_gitignore +10 -0
- package/template/biome.json +43 -0
- package/template/bun.lock +1372 -0
- package/template/e2e/starter.spec.ts +237 -0
- package/template/ecosystem.config.cjs +28 -0
- package/template/ecosystem.dev.config.cjs +29 -0
- package/template/package.json +55 -0
- package/template/packages/backend/package.json +31 -0
- package/template/packages/backend/src/cli.ts +12 -0
- package/template/packages/backend/src/domain/errors.ts +9 -0
- package/template/packages/backend/src/domain/repository/github-cache.test.ts +72 -0
- package/template/packages/backend/src/domain/repository/github-cache.ts +205 -0
- package/template/packages/backend/src/index.ts +56 -0
- package/template/packages/backend/src/lib/db.ts +7 -0
- package/template/packages/backend/src/surface.ts +13 -0
- package/template/packages/backend/src/tools.ts +21 -0
- package/template/packages/backend/src/transport/errors.ts +28 -0
- package/template/packages/backend/src/transport/repository-service.ts +18 -0
- package/template/packages/backend/tsconfig.json +8 -0
- package/template/packages/config/package.json +22 -0
- package/template/packages/config/src/server.ts +24 -0
- package/template/packages/config/tsconfig.json +5 -0
- package/template/packages/db/migrations/20260808000000_init/migration.sql +16 -0
- package/template/packages/db/migrations/20260808170000_repository_visibility/migration.sql +4 -0
- package/template/packages/db/migrations/migration_lock.toml +1 -0
- package/template/packages/db/package.json +29 -0
- package/template/packages/db/prisma.config.ts +8 -0
- package/template/packages/db/schema.prisma +31 -0
- package/template/packages/db/tsconfig.json +5 -0
- package/template/packages/frontend/messages/en.json +15 -0
- package/template/packages/frontend/messages/ru.json +15 -0
- package/template/packages/frontend/next.config.ts +18 -0
- package/template/packages/frontend/package.json +62 -0
- package/template/packages/frontend/postcss.config.mjs +1 -0
- package/template/packages/frontend/public/ASSETS.md +6 -0
- package/template/packages/frontend/public/favicon/mascot-stitch.png +0 -0
- package/template/packages/frontend/public/icons/biome.svg +1 -0
- package/template/packages/frontend/public/icons/bun.svg +19 -0
- package/template/packages/frontend/public/icons/next.svg +1 -0
- package/template/packages/frontend/public/icons/postgresql.svg +1 -0
- package/template/packages/frontend/public/icons/prisma.svg +6 -0
- package/template/packages/frontend/public/icons/react-query.svg +9 -0
- package/template/packages/frontend/public/icons/react.svg +1 -0
- package/template/packages/frontend/public/icons/tailwind.svg +12 -0
- package/template/packages/frontend/public/icons/tsconfig.svg +1 -0
- package/template/packages/frontend/public/icons/typescript.svg +7 -0
- package/template/packages/frontend/public/icons/zod.svg +1 -0
- package/template/packages/frontend/public/mascot-stitch.png +0 -0
- package/template/packages/frontend/public/theme-dark.svg +8 -0
- package/template/packages/frontend/public/theme-light.svg +8 -0
- package/template/packages/frontend/src/app/[locale]/error.tsx +19 -0
- package/template/packages/frontend/src/app/[locale]/layout.tsx +59 -0
- package/template/packages/frontend/src/app/[locale]/loading.tsx +14 -0
- package/template/packages/frontend/src/app/[locale]/page.tsx +44 -0
- package/template/packages/frontend/src/app/[locale]/starter-page.tsx +151 -0
- package/template/packages/frontend/src/app/[locale]/ui/[story]/page.tsx +40 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/account-showcase.tsx +112 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/admin-showcase.tsx +101 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/catalogue-navigation.tsx +56 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/catalogue-shell.tsx +51 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/code-foundation.tsx +35 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/components-story.tsx +54 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/data-story.tsx +126 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/image-preloader.tsx +8 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/index.ts +3 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/landing-sections.tsx +269 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/landing-showcase.tsx +159 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/motion-story.tsx +78 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/overlays-story.tsx +134 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/payment-showcase.tsx +82 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/presentation-story.tsx +68 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/primitives-story.tsx +267 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/story-page.tsx +14 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/theme-story-client.tsx +208 -0
- package/template/packages/frontend/src/app/[locale]/ui/_catalogue/theme-story.tsx +13 -0
- package/template/packages/frontend/src/app/[locale]/ui/layout.tsx +6 -0
- package/template/packages/frontend/src/app/[locale]/ui/page.tsx +10 -0
- package/template/packages/frontend/src/app/api/og/[locale]/[page]/route.tsx +53 -0
- package/template/packages/frontend/src/app/globals.css +210 -0
- package/template/packages/frontend/src/app/robots.ts +9 -0
- package/template/packages/frontend/src/app/sitemap.ts +22 -0
- package/template/packages/frontend/src/components/brand-mark.tsx +22 -0
- package/template/packages/frontend/src/components/repository-summary.tsx +107 -0
- package/template/packages/frontend/src/components/system-controls.tsx +92 -0
- package/template/packages/frontend/src/components/ui/adaptive-modal.tsx +382 -0
- package/template/packages/frontend/src/components/ui/animated-counter.tsx +80 -0
- package/template/packages/frontend/src/components/ui/avatar.tsx +46 -0
- package/template/packages/frontend/src/components/ui/badge.tsx +94 -0
- package/template/packages/frontend/src/components/ui/button-variants.ts +24 -0
- package/template/packages/frontend/src/components/ui/button.tsx +81 -0
- package/template/packages/frontend/src/components/ui/card-deck.tsx +73 -0
- package/template/packages/frontend/src/components/ui/card.tsx +81 -0
- package/template/packages/frontend/src/components/ui/charts/area-chart.tsx +167 -0
- package/template/packages/frontend/src/components/ui/charts/index.ts +7 -0
- package/template/packages/frontend/src/components/ui/checkbox.tsx +133 -0
- package/template/packages/frontend/src/components/ui/confirmation-modal.tsx +129 -0
- package/template/packages/frontend/src/components/ui/copyable-text.tsx +85 -0
- package/template/packages/frontend/src/components/ui/drawer.tsx +136 -0
- package/template/packages/frontend/src/components/ui/dropdown-menu.tsx +185 -0
- package/template/packages/frontend/src/components/ui/field.tsx +28 -0
- package/template/packages/frontend/src/components/ui/hand-drawn-icons.tsx +41 -0
- package/template/packages/frontend/src/components/ui/index.ts +36 -0
- package/template/packages/frontend/src/components/ui/input.tsx +24 -0
- package/template/packages/frontend/src/components/ui/label.tsx +18 -0
- package/template/packages/frontend/src/components/ui/otp-input.tsx +140 -0
- package/template/packages/frontend/src/components/ui/select.tsx +240 -0
- package/template/packages/frontend/src/components/ui/sidebar.tsx +343 -0
- package/template/packages/frontend/src/components/ui/skeleton.tsx +8 -0
- package/template/packages/frontend/src/components/ui/slider.tsx +31 -0
- package/template/packages/frontend/src/components/ui/slot-drum.tsx +50 -0
- package/template/packages/frontend/src/components/ui/spinner.tsx +30 -0
- package/template/packages/frontend/src/components/ui/switch.tsx +31 -0
- package/template/packages/frontend/src/components/ui/table.tsx +106 -0
- package/template/packages/frontend/src/components/ui/tabs.tsx +196 -0
- package/template/packages/frontend/src/components/ui/textarea.tsx +21 -0
- package/template/packages/frontend/src/components/ui/toaster.tsx +72 -0
- package/template/packages/frontend/src/components/ui/tooltip.tsx +31 -0
- package/template/packages/frontend/src/env.ts +21 -0
- package/template/packages/frontend/src/features/data-table/components/column-helpers.tsx +136 -0
- package/template/packages/frontend/src/features/data-table/components/data-table.tsx +303 -0
- package/template/packages/frontend/src/features/data-table/components/inline-stat.tsx +113 -0
- package/template/packages/frontend/src/features/data-table/components/sortable-header.tsx +33 -0
- package/template/packages/frontend/src/features/data-table/components/table-error.tsx +24 -0
- package/template/packages/frontend/src/features/data-table/components/table-filters.tsx +82 -0
- package/template/packages/frontend/src/features/data-table/hooks/useTableState.ts +111 -0
- package/template/packages/frontend/src/features/data-table/index.ts +26 -0
- package/template/packages/frontend/src/features/data-table/table-features.ts +26 -0
- package/template/packages/frontend/src/hooks/index.ts +3 -0
- package/template/packages/frontend/src/hooks/useInfiniteScroll.ts +67 -0
- package/template/packages/frontend/src/hooks/useIsTouchDevice.ts +25 -0
- package/template/packages/frontend/src/hooks/useMediaQuery.ts +30 -0
- package/template/packages/frontend/src/i18n/locales.ts +6 -0
- package/template/packages/frontend/src/i18n/navigation.ts +4 -0
- package/template/packages/frontend/src/i18n/request.ts +9 -0
- package/template/packages/frontend/src/i18n/routing.ts +8 -0
- package/template/packages/frontend/src/lib/api/client.ts +20 -0
- package/template/packages/frontend/src/lib/api/queries.ts +11 -0
- package/template/packages/frontend/src/lib/query-client.ts +20 -0
- package/template/packages/frontend/src/lib/realtime/repository.ts +21 -0
- package/template/packages/frontend/src/lib/seo/metadata.ts +50 -0
- package/template/packages/frontend/src/lib/seo/pages.test.ts +25 -0
- package/template/packages/frontend/src/lib/seo/pages.ts +107 -0
- package/template/packages/frontend/src/lib/utils/cn.ts +6 -0
- package/template/packages/frontend/src/lib/utils/format.ts +25 -0
- package/template/packages/frontend/src/lib/utils/index.ts +2 -0
- package/template/packages/frontend/src/providers/index.tsx +18 -0
- package/template/packages/frontend/src/providers/react-query.tsx +9 -0
- package/template/packages/frontend/src/providers/realtime.tsx +16 -0
- package/template/packages/frontend/src/proxy.ts +6 -0
- package/template/packages/frontend/src/theme/config.ts +20 -0
- package/template/packages/frontend/src/theme/transition.ts +54 -0
- package/template/packages/frontend/tsconfig.json +13 -0
- package/template/packages/shared/package.json +23 -0
- package/template/packages/shared/src/contracts/repository.ts +28 -0
- package/template/packages/shared/src/events/repository.ts +9 -0
- package/template/packages/shared/src/index.ts +3 -0
- package/template/packages/shared/src/schemas/repository.test.ts +36 -0
- package/template/packages/shared/src/schemas/repository.ts +28 -0
- package/template/packages/shared/tsconfig.json +5 -0
- package/template/playwright.config.ts +15 -0
- package/template/scripts/check-authored.ts +81 -0
- package/template/scripts/dev.ts +23 -0
- package/template/scripts/local-env.ts +12 -0
- package/template/scripts/runtime-smoke.ts +67 -0
- package/template/scripts/tooling-env.ts +15 -0
- 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,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
|
+
}
|