create-fate 0.0.0 → 1.0.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/LICENSE +21 -0
- package/README.md +24 -0
- package/bin/create-fate.mjs +412 -0
- package/package.json +26 -3
- package/templates/fate/README.md +8 -0
- package/templates/fate/drizzle/.github/workflows/push.yml +27 -0
- package/templates/fate/drizzle/.oxfmtrc.jsonc +23 -0
- package/templates/fate/drizzle/.vite-hooks/pre-commit +1 -0
- package/templates/fate/drizzle/.vscode/extensions.json +12 -0
- package/templates/fate/drizzle/.vscode/settings.json +25 -0
- package/templates/fate/drizzle/AGENTS.md +55 -0
- package/templates/fate/drizzle/README.md +112 -0
- package/templates/fate/drizzle/_gitignore +20 -0
- package/templates/fate/drizzle/client/package.json +58 -0
- package/templates/fate/drizzle/client/pages/index.tsx +96 -0
- package/templates/fate/drizzle/client/pages/layout.tsx +104 -0
- package/templates/fate/drizzle/client/pages/login.tsx +107 -0
- package/templates/fate/drizzle/client/pages/post/[id].tsx +22 -0
- package/templates/fate/drizzle/client/pages/search.tsx +95 -0
- package/templates/fate/drizzle/client/public/icon.svg +1 -0
- package/templates/fate/drizzle/client/src/App.css +153 -0
- package/templates/fate/drizzle/client/src/lib/AvailableLanguages.tsx +5 -0
- package/templates/fate/drizzle/client/src/lib/cx.tsx +6 -0
- package/templates/fate/drizzle/client/src/lib/env.tsx +5 -0
- package/templates/fate/drizzle/client/src/ui/Badge.tsx +31 -0
- package/templates/fate/drizzle/client/src/ui/Button.tsx +108 -0
- package/templates/fate/drizzle/client/src/ui/Card.tsx +32 -0
- package/templates/fate/drizzle/client/src/ui/CommentCard.tsx +75 -0
- package/templates/fate/drizzle/client/src/ui/Error.tsx +19 -0
- package/templates/fate/drizzle/client/src/ui/H2.tsx +15 -0
- package/templates/fate/drizzle/client/src/ui/H3.tsx +20 -0
- package/templates/fate/drizzle/client/src/ui/Header.tsx +96 -0
- package/templates/fate/drizzle/client/src/ui/Input.tsx +17 -0
- package/templates/fate/drizzle/client/src/ui/Link.tsx +11 -0
- package/templates/fate/drizzle/client/src/ui/LocaleSwitcher.tsx +52 -0
- package/templates/fate/drizzle/client/src/ui/PostCard.tsx +284 -0
- package/templates/fate/drizzle/client/src/ui/Section.tsx +23 -0
- package/templates/fate/drizzle/client/src/ui/UserCard.tsx +131 -0
- package/templates/fate/drizzle/client/src/user/AuthClient.tsx +6 -0
- package/templates/fate/drizzle/client/translations/de_DE.json +689 -0
- package/templates/fate/drizzle/client/translations/ja_JP.json +689 -0
- package/templates/fate/drizzle/client/vite.config.ts +39 -0
- package/templates/fate/drizzle/client/void.json +29 -0
- package/templates/fate/drizzle/docker-compose.yml +11 -0
- package/templates/fate/drizzle/package.json +46 -0
- package/templates/fate/drizzle/pnpm-workspace.yaml +36 -0
- package/templates/fate/drizzle/public/fate-logo-dark.svg +4 -0
- package/templates/fate/drizzle/public/fate-logo.svg +4 -0
- package/templates/fate/drizzle/server/.env.example +5 -0
- package/templates/fate/drizzle/server/drizzle.config.ts +11 -0
- package/templates/fate/drizzle/server/package.json +48 -0
- package/templates/fate/drizzle/server/src/__tests__/index.test.tsx +5 -0
- package/templates/fate/drizzle/server/src/app.tsx +44 -0
- package/templates/fate/drizzle/server/src/drizzle/db.ts +25 -0
- package/templates/fate/drizzle/server/src/drizzle/queries.ts +89 -0
- package/templates/fate/drizzle/server/src/drizzle/schema.ts +174 -0
- package/templates/fate/drizzle/server/src/drizzle/seed.tsx +77 -0
- package/templates/fate/drizzle/server/src/drizzle/seedData.ts +205 -0
- package/templates/fate/drizzle/server/src/index.tsx +24 -0
- package/templates/fate/drizzle/server/src/lib/auth.ts +33 -0
- package/templates/fate/drizzle/server/src/lib/env.ts +3 -0
- package/templates/fate/drizzle/server/src/lib/origins.ts +18 -0
- package/templates/fate/drizzle/server/src/router.ts +15 -0
- package/templates/fate/drizzle/server/src/trpc/connection.ts +4 -0
- package/templates/fate/drizzle/server/src/trpc/context.ts +22 -0
- package/templates/fate/drizzle/server/src/trpc/init.ts +51 -0
- package/templates/fate/drizzle/server/src/trpc/routers/comment.ts +146 -0
- package/templates/fate/drizzle/server/src/trpc/routers/post.ts +99 -0
- package/templates/fate/drizzle/server/src/trpc/routers/user.ts +68 -0
- package/templates/fate/drizzle/server/src/trpc/views.ts +75 -0
- package/templates/fate/drizzle/server/src/user/SessionUser.tsx +17 -0
- package/templates/fate/drizzle/server/vite.config.ts +8 -0
- package/templates/fate/drizzle/tsconfig.json +33 -0
- package/templates/fate/drizzle/vite.config.ts +71 -0
- package/templates/fate/http/.github/workflows/push.yml +27 -0
- package/templates/fate/http/.oxfmtrc.jsonc +23 -0
- package/templates/fate/http/.vite-hooks/pre-commit +1 -0
- package/templates/fate/http/.vscode/extensions.json +12 -0
- package/templates/fate/http/.vscode/settings.json +25 -0
- package/templates/fate/http/AGENTS.md +55 -0
- package/templates/fate/http/README.md +112 -0
- package/templates/fate/http/_gitignore +20 -0
- package/templates/fate/http/client/package.json +56 -0
- package/templates/fate/http/client/pages/index.tsx +96 -0
- package/templates/fate/http/client/pages/layout.tsx +89 -0
- package/templates/fate/http/client/pages/login.tsx +107 -0
- package/templates/fate/http/client/pages/post/[id].tsx +22 -0
- package/templates/fate/http/client/pages/search.tsx +95 -0
- package/templates/fate/http/client/public/icon.svg +1 -0
- package/templates/fate/http/client/src/App.css +153 -0
- package/templates/fate/http/client/src/lib/AvailableLanguages.tsx +5 -0
- package/templates/fate/http/client/src/lib/cx.tsx +6 -0
- package/templates/fate/http/client/src/lib/env.tsx +5 -0
- package/templates/fate/http/client/src/ui/Badge.tsx +31 -0
- package/templates/fate/http/client/src/ui/Button.tsx +108 -0
- package/templates/fate/http/client/src/ui/Card.tsx +32 -0
- package/templates/fate/http/client/src/ui/CommentCard.tsx +75 -0
- package/templates/fate/http/client/src/ui/Error.tsx +19 -0
- package/templates/fate/http/client/src/ui/H2.tsx +15 -0
- package/templates/fate/http/client/src/ui/H3.tsx +20 -0
- package/templates/fate/http/client/src/ui/Header.tsx +96 -0
- package/templates/fate/http/client/src/ui/Input.tsx +17 -0
- package/templates/fate/http/client/src/ui/Link.tsx +11 -0
- package/templates/fate/http/client/src/ui/LocaleSwitcher.tsx +52 -0
- package/templates/fate/http/client/src/ui/PostCard.tsx +281 -0
- package/templates/fate/http/client/src/ui/Section.tsx +23 -0
- package/templates/fate/http/client/src/ui/UserCard.tsx +131 -0
- package/templates/fate/http/client/src/user/AuthClient.tsx +6 -0
- package/templates/fate/http/client/translations/de_DE.json +689 -0
- package/templates/fate/http/client/translations/ja_JP.json +689 -0
- package/templates/fate/http/client/vite.config.ts +40 -0
- package/templates/fate/http/client/void.json +29 -0
- package/templates/fate/http/docker-compose.yml +11 -0
- package/templates/fate/http/package.json +46 -0
- package/templates/fate/http/pnpm-workspace.yaml +36 -0
- package/templates/fate/http/public/fate-logo-dark.svg +4 -0
- package/templates/fate/http/public/fate-logo.svg +4 -0
- package/templates/fate/http/server/.env.example +5 -0
- package/templates/fate/http/server/drizzle.config.ts +11 -0
- package/templates/fate/http/server/package.json +46 -0
- package/templates/fate/http/server/src/__tests__/index.test.tsx +5 -0
- package/templates/fate/http/server/src/app.tsx +36 -0
- package/templates/fate/http/server/src/drizzle/db.ts +25 -0
- package/templates/fate/http/server/src/drizzle/queries.ts +89 -0
- package/templates/fate/http/server/src/drizzle/schema.ts +174 -0
- package/templates/fate/http/server/src/drizzle/seed.tsx +77 -0
- package/templates/fate/http/server/src/drizzle/seedData.ts +205 -0
- package/templates/fate/http/server/src/fate/context.ts +20 -0
- package/templates/fate/http/server/src/fate/server.ts +285 -0
- package/templates/fate/http/server/src/fate/views.ts +75 -0
- package/templates/fate/http/server/src/index.tsx +24 -0
- package/templates/fate/http/server/src/lib/auth.ts +33 -0
- package/templates/fate/http/server/src/lib/env.ts +3 -0
- package/templates/fate/http/server/src/lib/origins.ts +18 -0
- package/templates/fate/http/server/src/router.ts +2 -0
- package/templates/fate/http/server/src/user/SessionUser.tsx +17 -0
- package/templates/fate/http/server/vite.config.ts +8 -0
- package/templates/fate/http/tsconfig.json +33 -0
- package/templates/fate/http/vite.config.ts +71 -0
- package/templates/fate/prisma/.github/workflows/push.yml +27 -0
- package/templates/fate/prisma/.oxfmtrc.jsonc +23 -0
- package/templates/fate/prisma/.vite-hooks/pre-commit +1 -0
- package/templates/fate/prisma/.vscode/extensions.json +12 -0
- package/templates/fate/prisma/.vscode/settings.json +25 -0
- package/templates/fate/prisma/AGENTS.md +55 -0
- package/templates/fate/prisma/README.md +126 -0
- package/templates/fate/prisma/_gitignore +20 -0
- package/templates/fate/prisma/client/package.json +58 -0
- package/templates/fate/prisma/client/pages/index.tsx +96 -0
- package/templates/fate/prisma/client/pages/layout.tsx +94 -0
- package/templates/fate/prisma/client/pages/login.tsx +107 -0
- package/templates/fate/prisma/client/pages/post/[id].tsx +22 -0
- package/templates/fate/prisma/client/pages/search.tsx +95 -0
- package/templates/fate/prisma/client/public/icon.svg +1 -0
- package/templates/fate/prisma/client/src/App.css +153 -0
- package/templates/fate/prisma/client/src/lib/AvailableLanguages.tsx +5 -0
- package/templates/fate/prisma/client/src/lib/cx.tsx +6 -0
- package/templates/fate/prisma/client/src/lib/env.tsx +5 -0
- package/templates/fate/prisma/client/src/ui/Badge.tsx +31 -0
- package/templates/fate/prisma/client/src/ui/Button.tsx +108 -0
- package/templates/fate/prisma/client/src/ui/Card.tsx +32 -0
- package/templates/fate/prisma/client/src/ui/CommentCard.tsx +75 -0
- package/templates/fate/prisma/client/src/ui/Error.tsx +19 -0
- package/templates/fate/prisma/client/src/ui/H2.tsx +15 -0
- package/templates/fate/prisma/client/src/ui/H3.tsx +20 -0
- package/templates/fate/prisma/client/src/ui/Header.tsx +96 -0
- package/templates/fate/prisma/client/src/ui/Input.tsx +17 -0
- package/templates/fate/prisma/client/src/ui/Link.tsx +11 -0
- package/templates/fate/prisma/client/src/ui/LocaleSwitcher.tsx +52 -0
- package/templates/fate/prisma/client/src/ui/PostCard.tsx +281 -0
- package/templates/fate/prisma/client/src/ui/Section.tsx +23 -0
- package/templates/fate/prisma/client/src/ui/UserCard.tsx +131 -0
- package/templates/fate/prisma/client/src/user/AuthClient.tsx +6 -0
- package/templates/fate/prisma/client/translations/de_DE.json +689 -0
- package/templates/fate/prisma/client/translations/ja_JP.json +689 -0
- package/templates/fate/prisma/client/vite.config.ts +39 -0
- package/templates/fate/prisma/client/void.json +29 -0
- package/templates/fate/prisma/docker-compose.yml +11 -0
- package/templates/fate/prisma/package.json +47 -0
- package/templates/fate/prisma/pnpm-workspace.yaml +39 -0
- package/templates/fate/prisma/public/fate-logo-dark.svg +4 -0
- package/templates/fate/prisma/public/fate-logo.svg +4 -0
- package/templates/fate/prisma/server/.env.example +5 -0
- package/templates/fate/prisma/server/package.json +42 -0
- package/templates/fate/prisma/server/prisma.config.ts +21 -0
- package/templates/fate/prisma/server/src/__tests__/index.test.tsx +5 -0
- package/templates/fate/prisma/server/src/app.tsx +40 -0
- package/templates/fate/prisma/server/src/index.tsx +24 -0
- package/templates/fate/prisma/server/src/lib/auth.ts +31 -0
- package/templates/fate/prisma/server/src/lib/env.ts +3 -0
- package/templates/fate/prisma/server/src/lib/origins.ts +18 -0
- package/templates/fate/prisma/server/src/prisma/prisma.ts +9 -0
- package/templates/fate/prisma/server/src/prisma/schema.prisma +98 -0
- package/templates/fate/prisma/server/src/prisma/seed.tsx +71 -0
- package/templates/fate/prisma/server/src/prisma/seedData.ts +205 -0
- package/templates/fate/prisma/server/src/router.ts +14 -0
- package/templates/fate/prisma/server/src/trpc/connection.ts +4 -0
- package/templates/fate/prisma/server/src/trpc/context.ts +22 -0
- package/templates/fate/prisma/server/src/trpc/init.ts +8 -0
- package/templates/fate/prisma/server/src/trpc/routers/comment.ts +176 -0
- package/templates/fate/prisma/server/src/trpc/routers/post.ts +154 -0
- package/templates/fate/prisma/server/src/trpc/routers/user.ts +71 -0
- package/templates/fate/prisma/server/src/trpc/views.ts +77 -0
- package/templates/fate/prisma/server/src/user/SessionUser.tsx +17 -0
- package/templates/fate/prisma/server/vite.config.ts +8 -0
- package/templates/fate/prisma/tsconfig.json +33 -0
- package/templates/fate/prisma/vite.config.ts +72 -0
- package/templates/fate/void/.oxfmtrc.jsonc +14 -0
- package/templates/fate/void/README.md +55 -0
- package/templates/fate/void/auth.ts +22 -0
- package/templates/fate/void/db/migrations/20260508073357_initial_schema.sql +133 -0
- package/templates/fate/void/db/migrations/20260508120500_seed_void_demo.sql +170 -0
- package/templates/fate/void/db/migrations/meta/20260508073357_snapshot.json +868 -0
- package/templates/fate/void/db/migrations/meta/_journal.json +20 -0
- package/templates/fate/void/db/queries.ts +211 -0
- package/templates/fate/void/db/schema.ts +325 -0
- package/templates/fate/void/db/seed-auth.ts +31 -0
- package/templates/fate/void/db/seed.ts +163 -0
- package/templates/fate/void/middleware/01.shared.ts +29 -0
- package/templates/fate/void/package.json +65 -0
- package/templates/fate/void/pages/category/[id].tsx +23 -0
- package/templates/fate/void/pages/index.tsx +153 -0
- package/templates/fate/void/pages/layout.tsx +46 -0
- package/templates/fate/void/pages/login.tsx +103 -0
- package/templates/fate/void/pages/post/[id].tsx +20 -0
- package/templates/fate/void/pages/search.tsx +81 -0
- package/templates/fate/void/pnpm-workspace.yaml +31 -0
- package/templates/fate/void/public/.assetsignore +1 -0
- package/templates/fate/void/public/icon.svg +1 -0
- package/templates/fate/void/routes/fate-live.ts +8 -0
- package/templates/fate/void/routes/fate.ts +8 -0
- package/templates/fate/void/seedData.ts +535 -0
- package/templates/fate/void/src/App.css +130 -0
- package/templates/fate/void/src/fate/__tests__/commentPermissions.test.ts +31 -0
- package/templates/fate/void/src/fate/commentPermissions.ts +17 -0
- package/templates/fate/void/src/fate/context.ts +15 -0
- package/templates/fate/void/src/fate/live.ts +6 -0
- package/templates/fate/void/src/fate/server.ts +401 -0
- package/templates/fate/void/src/fate/views.ts +164 -0
- package/templates/fate/void/src/lib/cx.tsx +6 -0
- package/templates/fate/void/src/lib/formatLabel.tsx +6 -0
- package/templates/fate/void/src/lib/shared.ts +10 -0
- package/templates/fate/void/src/ui/Badge.tsx +31 -0
- package/templates/fate/void/src/ui/Button.tsx +92 -0
- package/templates/fate/void/src/ui/Card.tsx +29 -0
- package/templates/fate/void/src/ui/CategoryCard.tsx +105 -0
- package/templates/fate/void/src/ui/CommentCard.tsx +80 -0
- package/templates/fate/void/src/ui/CreatePost.tsx +111 -0
- package/templates/fate/void/src/ui/Error.tsx +19 -0
- package/templates/fate/void/src/ui/EventCard.tsx +152 -0
- package/templates/fate/void/src/ui/H2.tsx +15 -0
- package/templates/fate/void/src/ui/H3.tsx +20 -0
- package/templates/fate/void/src/ui/Header.tsx +96 -0
- package/templates/fate/void/src/ui/Input.tsx +33 -0
- package/templates/fate/void/src/ui/Link.tsx +11 -0
- package/templates/fate/void/src/ui/Number.tsx +5 -0
- package/templates/fate/void/src/ui/PostCard.tsx +294 -0
- package/templates/fate/void/src/ui/Section.tsx +23 -0
- package/templates/fate/void/src/ui/TagBadge.tsx +23 -0
- package/templates/fate/void/src/ui/UserCard.tsx +129 -0
- package/templates/fate/void/src/user/AuthClient.tsx +1 -0
- package/templates/fate/void/src/user/SessionUser.tsx +17 -0
- package/templates/fate/void/tsconfig.json +34 -0
- package/templates/fate/void/vite.config.ts +83 -0
- package/templates/fate/void/void.json +32 -0
- package/templates/fate/void/wrangler.jsonc +16 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Nakazawa Tech
|
|
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,24 @@
|
|
|
1
|
+
# create-fate
|
|
2
|
+
|
|
3
|
+
Create a new fate app:
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
vp create fate my-app
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
The generated app is installed and the fate client is generated during creation.
|
|
10
|
+
|
|
11
|
+
Choose between these templates:
|
|
12
|
+
|
|
13
|
+
- `void`: Void pages router with Drizzle.
|
|
14
|
+
- `drizzle`: tRPC with Drizzle.
|
|
15
|
+
- `http`: Native HTTP with Drizzle.
|
|
16
|
+
- `prisma`: tRPC with Prisma.
|
|
17
|
+
|
|
18
|
+
For a non-interactive install:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
vp create fate my-app --template void
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The template sources live in `packages/create-fate/templates/fate`.
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import process from 'node:process';
|
|
6
|
+
import { fileURLToPath, URL } from 'node:url';
|
|
7
|
+
import { cancel, intro, isCancel, outro, select, text } from '@clack/prompts';
|
|
8
|
+
|
|
9
|
+
const variants = {
|
|
10
|
+
drizzle: {
|
|
11
|
+
description: 'tRPC with Drizzle',
|
|
12
|
+
label: 'Drizzle',
|
|
13
|
+
template: 'drizzle',
|
|
14
|
+
},
|
|
15
|
+
http: {
|
|
16
|
+
description: 'Native HTTP with Drizzle',
|
|
17
|
+
label: 'Native HTTP',
|
|
18
|
+
template: 'http',
|
|
19
|
+
},
|
|
20
|
+
prisma: {
|
|
21
|
+
description: 'tRPC with Prisma',
|
|
22
|
+
label: 'Prisma',
|
|
23
|
+
template: 'prisma',
|
|
24
|
+
},
|
|
25
|
+
void: {
|
|
26
|
+
description: 'Void with void-fate and Drizzle',
|
|
27
|
+
label: 'Void',
|
|
28
|
+
template: 'void',
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const fateDependencyNames = ['@nkzw/fate', 'react-fate', 'void-fate'];
|
|
33
|
+
|
|
34
|
+
const usage = () => {
|
|
35
|
+
process.stdout.write(`Usage: create-fate [directory] [--template void|drizzle|http|prisma]
|
|
36
|
+
|
|
37
|
+
Create a new fate app.
|
|
38
|
+
|
|
39
|
+
Options:
|
|
40
|
+
--template, -t Template variant to create
|
|
41
|
+
--no-setup Skip dependency installation and fate client generation
|
|
42
|
+
--help, -h Show this help message
|
|
43
|
+
`);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const normalizePackageName = (name) =>
|
|
47
|
+
name
|
|
48
|
+
.trim()
|
|
49
|
+
.toLowerCase()
|
|
50
|
+
.replaceAll(/[^a-z0-9._-]+/g, '-')
|
|
51
|
+
.replaceAll(/^-+|-+$/g, '') || 'my-app';
|
|
52
|
+
|
|
53
|
+
const parseArgs = (args) => {
|
|
54
|
+
let setup = true;
|
|
55
|
+
let targetDir;
|
|
56
|
+
let variant;
|
|
57
|
+
|
|
58
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
59
|
+
const arg = args[index];
|
|
60
|
+
if (arg === '--help' || arg === '-h') {
|
|
61
|
+
return { help: true };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (arg === '--no-setup') {
|
|
65
|
+
setup = false;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (arg === '--template' || arg === '-t' || arg === '--variant') {
|
|
70
|
+
variant = args[++index];
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (arg?.startsWith('--template=')) {
|
|
75
|
+
variant = arg.slice('--template='.length);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (arg?.startsWith('--variant=')) {
|
|
80
|
+
variant = arg.slice('--variant='.length);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!targetDir) {
|
|
85
|
+
targetDir = arg;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
throw new Error(`Unexpected argument: ${arg}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return { setup, targetDir, variant };
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const validateTargetDir = (value) => {
|
|
96
|
+
const targetDir = value?.trim();
|
|
97
|
+
if (!targetDir) {
|
|
98
|
+
throw new Error('Target directory is required.');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const normalized = path.normalize(targetDir);
|
|
102
|
+
if (
|
|
103
|
+
!path.isAbsolute(normalized) &&
|
|
104
|
+
(normalized === '.' || normalized.startsWith('..') || normalized.includes(`${path.sep}..`))
|
|
105
|
+
) {
|
|
106
|
+
throw new Error('Target directory cannot escape the current directory.');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return normalized;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const validateTargetDirInput = (value) => {
|
|
113
|
+
try {
|
|
114
|
+
validateTargetDir(value || 'my-app');
|
|
115
|
+
return;
|
|
116
|
+
} catch (error) {
|
|
117
|
+
return error instanceof Error ? error.message : String(error);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const abortPrompt = () => {
|
|
122
|
+
cancel('Create fate app cancelled.');
|
|
123
|
+
process.exit(0);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const promptForTargetDir = async () => {
|
|
127
|
+
const result = await text({
|
|
128
|
+
defaultValue: 'my-app',
|
|
129
|
+
message: 'Project directory',
|
|
130
|
+
placeholder: 'my-app',
|
|
131
|
+
validate: validateTargetDirInput,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (isCancel(result)) {
|
|
135
|
+
abortPrompt();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return result;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const promptForVariant = async () => {
|
|
142
|
+
const result = await select({
|
|
143
|
+
message: 'Select a fate template',
|
|
144
|
+
options: Object.entries(variants).map(([value, variant]) => ({
|
|
145
|
+
hint: variant.description,
|
|
146
|
+
label: variant.label,
|
|
147
|
+
value,
|
|
148
|
+
})),
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
if (isCancel(result)) {
|
|
152
|
+
abortPrompt();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return result;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const getRegistryURL = () => {
|
|
159
|
+
const registry = process.env.npm_config_registry || 'https://registry.npmjs.org/';
|
|
160
|
+
return registry.endsWith('/') ? registry : `${registry}/`;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const fetchLatestPackageVersion = async (packageName) => {
|
|
164
|
+
const response = await globalThis.fetch(
|
|
165
|
+
new URL(encodeURIComponent(packageName), getRegistryURL()),
|
|
166
|
+
);
|
|
167
|
+
if (!response.ok) {
|
|
168
|
+
throw new Error(`Could not fetch ${packageName} from npm registry.`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const metadata = await response.json();
|
|
172
|
+
const version = metadata?.['dist-tags']?.latest;
|
|
173
|
+
if (typeof version !== 'string' || !version) {
|
|
174
|
+
throw new Error(`Could not resolve latest version for ${packageName}.`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return version;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const collectFateDependencies = (dir, dependencies = new Set()) => {
|
|
181
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
182
|
+
if (entry.name === 'node_modules' || entry.name === '.git') {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const entryPath = path.join(dir, entry.name);
|
|
187
|
+
if (entry.isDirectory()) {
|
|
188
|
+
collectFateDependencies(entryPath, dependencies);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (entry.name !== 'package.json') {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const json = JSON.parse(fs.readFileSync(entryPath, 'utf8'));
|
|
197
|
+
for (const dependencyType of ['dependencies', 'devDependencies', 'peerDependencies']) {
|
|
198
|
+
const packageDependencies = json[dependencyType];
|
|
199
|
+
if (!packageDependencies) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
for (const dependencyName of fateDependencyNames) {
|
|
204
|
+
if (packageDependencies[dependencyName]) {
|
|
205
|
+
dependencies.add(dependencyName);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return dependencies;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const resolveFateDependencyVersions = async (rootDir) => {
|
|
215
|
+
const dependencies = [...collectFateDependencies(rootDir)];
|
|
216
|
+
return Object.fromEntries(
|
|
217
|
+
await Promise.all(
|
|
218
|
+
dependencies.map(async (dependencyName) => [
|
|
219
|
+
dependencyName,
|
|
220
|
+
`^${await fetchLatestPackageVersion(dependencyName)}`,
|
|
221
|
+
]),
|
|
222
|
+
),
|
|
223
|
+
);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const updatePackageJsonFiles = (rootDir, dir, packageName, fateDependencyVersions) => {
|
|
227
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
228
|
+
if (entry.name === 'node_modules' || entry.name === '.git') {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const entryPath = path.join(dir, entry.name);
|
|
233
|
+
if (entry.isDirectory()) {
|
|
234
|
+
updatePackageJsonFiles(rootDir, entryPath, packageName, fateDependencyVersions);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (entry.name !== 'package.json') {
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const json = JSON.parse(fs.readFileSync(entryPath, 'utf8'));
|
|
243
|
+
if (entryPath === path.join(rootDir, 'package.json')) {
|
|
244
|
+
json.name = packageName;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
for (const dependencyType of ['dependencies', 'devDependencies', 'peerDependencies']) {
|
|
248
|
+
const dependencies = json[dependencyType];
|
|
249
|
+
if (!dependencies) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
for (const [dependencyName, version] of Object.entries(fateDependencyVersions)) {
|
|
254
|
+
if (dependencies[dependencyName]) {
|
|
255
|
+
dependencies[dependencyName] = version;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
fs.writeFileSync(entryPath, `${JSON.stringify(json, null, 2)}\n`);
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const restoreTemplateFileNames = (dir) => {
|
|
265
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
266
|
+
const entryPath = path.join(dir, entry.name);
|
|
267
|
+
if (entry.isDirectory()) {
|
|
268
|
+
restoreTemplateFileNames(entryPath);
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (entry.name === '_gitignore') {
|
|
273
|
+
fs.renameSync(entryPath, path.join(dir, '.gitignore'));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const copyExampleEnvFiles = (dir) => {
|
|
279
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
280
|
+
const entryPath = path.join(dir, entry.name);
|
|
281
|
+
if (entry.isDirectory()) {
|
|
282
|
+
copyExampleEnvFiles(entryPath);
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (!entry.name.endsWith('.env.example')) {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const envPath = path.join(dir, entry.name.slice(0, -'.example'.length));
|
|
291
|
+
if (!fs.existsSync(envPath)) {
|
|
292
|
+
fs.copyFileSync(entryPath, envPath);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const runCommand = (command, args, cwd) => {
|
|
298
|
+
const result = spawnSync(command, args, {
|
|
299
|
+
cwd,
|
|
300
|
+
shell: process.platform === 'win32',
|
|
301
|
+
stdio: 'inherit',
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
if (result.error) {
|
|
305
|
+
throw result.error;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (result.status !== 0) {
|
|
309
|
+
throw new Error(`Command failed: ${[command, ...args].join(' ')}`);
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const setupProject = (targetPath, selectedVariant) => {
|
|
314
|
+
runCommand('vp', ['install'], targetPath);
|
|
315
|
+
|
|
316
|
+
switch (selectedVariant) {
|
|
317
|
+
case 'prisma':
|
|
318
|
+
runCommand('vp', ['run', '--filter', '@app/server', 'dev:setup'], targetPath);
|
|
319
|
+
runCommand('vp', ['run', 'fate:generate'], targetPath);
|
|
320
|
+
break;
|
|
321
|
+
case 'void':
|
|
322
|
+
runCommand('vp', ['run', 'prepare:void'], targetPath);
|
|
323
|
+
runCommand('vp', ['run', 'fate:generate'], targetPath);
|
|
324
|
+
break;
|
|
325
|
+
default:
|
|
326
|
+
runCommand('vp', ['run', 'fate:generate'], targetPath);
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const printReadme = (targetPath) => {
|
|
332
|
+
const readmePath = path.join(targetPath, 'README.md');
|
|
333
|
+
if (!fs.existsSync(readmePath)) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
process.stdout.write(`\nNext steps are in README.md. Follow these instructions:\n\n`);
|
|
338
|
+
process.stdout.write(fs.readFileSync(readmePath, 'utf8'));
|
|
339
|
+
process.stdout.write('\n');
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
343
|
+
|
|
344
|
+
const resolveTemplateRoot = (template) => {
|
|
345
|
+
const templateRoot = path.resolve(packageRoot, 'templates', 'fate', template);
|
|
346
|
+
if (!fs.existsSync(templateRoot)) {
|
|
347
|
+
throw new Error(`Template '${template}' could not be found.`);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return templateRoot;
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
const main = async () => {
|
|
354
|
+
const options = parseArgs(process.argv.slice(2));
|
|
355
|
+
if (options.help) {
|
|
356
|
+
usage();
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const interactive = process.stdin.isTTY && process.stdout.isTTY;
|
|
361
|
+
if (interactive) {
|
|
362
|
+
intro('create-fate');
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const selectedVariant = options.variant ?? (interactive ? await promptForVariant() : 'void');
|
|
366
|
+
if (!selectedVariant || !variants[selectedVariant]) {
|
|
367
|
+
throw new Error(
|
|
368
|
+
`Unknown template '${selectedVariant}'. Expected one of: ${Object.keys(variants).join(', ')}.`,
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const targetDir = validateTargetDir(
|
|
373
|
+
options.targetDir ?? (interactive ? await promptForTargetDir() : 'my-app'),
|
|
374
|
+
);
|
|
375
|
+
const targetPath = path.resolve(process.cwd(), targetDir);
|
|
376
|
+
if (fs.existsSync(targetPath) && fs.readdirSync(targetPath).length > 0) {
|
|
377
|
+
throw new Error(`Target directory '${targetDir}' is not empty.`);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const templateRoot = resolveTemplateRoot(variants[selectedVariant].template);
|
|
381
|
+
const fateDependencyVersions = await resolveFateDependencyVersions(templateRoot);
|
|
382
|
+
fs.mkdirSync(targetPath, { recursive: true });
|
|
383
|
+
fs.cpSync(templateRoot, targetPath, { recursive: true });
|
|
384
|
+
restoreTemplateFileNames(targetPath);
|
|
385
|
+
copyExampleEnvFiles(targetPath);
|
|
386
|
+
updatePackageJsonFiles(
|
|
387
|
+
targetPath,
|
|
388
|
+
targetPath,
|
|
389
|
+
normalizePackageName(path.basename(targetDir)),
|
|
390
|
+
fateDependencyVersions,
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
if (options.setup) {
|
|
394
|
+
setupProject(targetPath, selectedVariant);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const message = `Created ${variants[selectedVariant].label} fate app in ${targetDir}`;
|
|
398
|
+
if (interactive) {
|
|
399
|
+
outro(message);
|
|
400
|
+
} else {
|
|
401
|
+
process.stdout.write(`${message}\n`);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
printReadme(targetPath);
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
try {
|
|
408
|
+
await main();
|
|
409
|
+
} catch (error) {
|
|
410
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
411
|
+
process.exit(1);
|
|
412
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-fate",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Create a new fate app.",
|
|
5
|
+
"homepage": "https://github.com/nkzw-tech/fate",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "Christoph Nakazawa",
|
|
9
|
+
"email": "christoph.pojer@gmail.com"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/nkzw-tech/fate",
|
|
14
|
+
"directory": "packages/create-fate"
|
|
15
|
+
},
|
|
16
|
+
"bin": {
|
|
17
|
+
"create-fate": "./bin/create-fate.mjs"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"bin",
|
|
21
|
+
"templates"
|
|
22
|
+
],
|
|
5
23
|
"type": "module",
|
|
6
|
-
"
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@clack/prompts": "^1.4.0"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=24.0.0"
|
|
29
|
+
}
|
|
7
30
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# fate Templates
|
|
2
|
+
|
|
3
|
+
This directory is the canonical source for fate project scaffolds.
|
|
4
|
+
|
|
5
|
+
- `void/` contains the Void pages router + Drizzle template.
|
|
6
|
+
- `drizzle/` contains the tRPC + Drizzle template.
|
|
7
|
+
- `http/` contains the native HTTP + Drizzle template.
|
|
8
|
+
- `prisma/` contains the tRPC + Prisma template.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
branches:
|
|
9
|
+
- main
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
test:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
strategy:
|
|
15
|
+
matrix:
|
|
16
|
+
node-version: [24]
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: voidzero-dev/setup-vp@v1
|
|
20
|
+
with:
|
|
21
|
+
node-version: ${{ matrix.node-version }}
|
|
22
|
+
cache: true
|
|
23
|
+
run-install: true
|
|
24
|
+
- run: |
|
|
25
|
+
vp run dev:setup
|
|
26
|
+
vp run test:all
|
|
27
|
+
vp run build
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/oxfmt/configuration_schema.json",
|
|
3
|
+
"singleQuote": true,
|
|
4
|
+
"experimentalSortImports": {
|
|
5
|
+
"newlinesBetween": false,
|
|
6
|
+
},
|
|
7
|
+
"experimentalSortPackageJson": {
|
|
8
|
+
"sortScripts": true,
|
|
9
|
+
},
|
|
10
|
+
"experimentalTailwindcss": {
|
|
11
|
+
"stylesheet": "client/src/App.css",
|
|
12
|
+
},
|
|
13
|
+
"ignorePatterns": [
|
|
14
|
+
"client/dist/",
|
|
15
|
+
"client/index.html",
|
|
16
|
+
"client/src/fate.ts",
|
|
17
|
+
"client/src/translations/",
|
|
18
|
+
"coverage/",
|
|
19
|
+
"dist/",
|
|
20
|
+
"pnpm-lock.yaml",
|
|
21
|
+
"server/dist",
|
|
22
|
+
],
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
vp staged
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"recommendations": [
|
|
3
|
+
"dbaeumer.vscode-eslint",
|
|
4
|
+
"oxc.oxc-vscode",
|
|
5
|
+
"styled-components.vscode-styled-components",
|
|
6
|
+
"sysoev.vscode-open-in-github",
|
|
7
|
+
"tailwindcss.intellisense",
|
|
8
|
+
"TypeScriptTeam.native-preview",
|
|
9
|
+
"usernamehw.errorlens",
|
|
10
|
+
"wix.vscode-import-cost"
|
|
11
|
+
]
|
|
12
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"editor.formatOnSave": true,
|
|
3
|
+
"files.insertFinalNewline": true,
|
|
4
|
+
"files.trimFinalNewlines": true,
|
|
5
|
+
"editor.codeActionsOnSave": {
|
|
6
|
+
"source.fixAll.eslint": "explicit"
|
|
7
|
+
},
|
|
8
|
+
"[javascript]": {
|
|
9
|
+
"editor.defaultFormatter": "oxc.oxc-vscode"
|
|
10
|
+
},
|
|
11
|
+
"[typescriptreact]": {
|
|
12
|
+
"editor.defaultFormatter": "oxc.oxc-vscode"
|
|
13
|
+
},
|
|
14
|
+
"[typescript]": {
|
|
15
|
+
"editor.defaultFormatter": "oxc.oxc-vscode"
|
|
16
|
+
},
|
|
17
|
+
"[json]": {
|
|
18
|
+
"editor.defaultFormatter": "oxc.oxc-vscode"
|
|
19
|
+
},
|
|
20
|
+
"typescript.preferences.importModuleSpecifierEnding": "js",
|
|
21
|
+
"typescript.reportStyleChecksAsWarnings": false,
|
|
22
|
+
"typescript.updateImportsOnFileMove.enabled": "always",
|
|
23
|
+
"typescript.preferences.autoImportFileExcludePatterns": ["**/node_modules/vitest"],
|
|
24
|
+
"typescript.experimental.useTsgo": true
|
|
25
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Using _fate_ for React applications
|
|
2
|
+
|
|
3
|
+
This guidance is for agents working in projects bootstrapped from the fate template. It focuses on the client-side patterns for fate's view composition and action execution.
|
|
4
|
+
|
|
5
|
+
## Core Workflow
|
|
6
|
+
|
|
7
|
+
- **Define views with explicit selections:** Define reusable view objects with `view<T>()({...})`, compose them via spreads, and resolve them with `useView`.
|
|
8
|
+
- **Resolve data via references:** Avoid passing raw fetched objects; instead pass `ViewRef`s to downstream components to keep selections scoped and cache-aware. Then call `const result = useView(viewDefinition, ref)` to read only the selected fields while subscribing to cache updates. Name the prop after the result data, and rename it using destructuring in the function arguments: `const PostComponent = ({ post: postRef }: { post: ViewRef<'Post'>}) => { const post = useView(PostView, postRef); ... }`.
|
|
9
|
+
- **Request data at screen roots:** Use `useRequest` to compose all views required for a route or screen into a single typed request; wrap roots in `Suspense` and an error boundary since requests may suspend or throw.
|
|
10
|
+
- **Compose reusable views:** Extract nested selections into their own views (e.g., `UserView`) and compose them inside parent views (`author: UserView`) to avoid duplication and overfetching. This keeps selections DRY and ensures view updates propagate consistently when the cache changes.
|
|
11
|
+
|
|
12
|
+
## Actions & Mutations
|
|
13
|
+
|
|
14
|
+
- **Prefer `fate.actions` with `useActionState`:** Trigger mutations through React Actions to integrate with Suspense/`useTransition`, receiving `[result, action, isPending]`. Call actions via the relevant data inputs, for example `action({input: { id: post.id }})`.
|
|
15
|
+
- **Optimistic updates by default:** Pass `optimistic` payloads to immediately update the normalized cache; only views selecting the changed fields re-render and failures roll back automatically. Optimistic payloads should match the expected server data changes of the associated object.
|
|
16
|
+
- **Select extra fields when side effects matter:** When a mutation affects related entities (e.g., comment count on a post), pass a `view` to the action to fetch the needed fields in the same round-trip and keep the cache coherent for all dependent views.
|
|
17
|
+
- **Imperative escape hatch:** Use `fate.mutations` when you must fire mutations outside React components or without waiting on prior actions; handle loading/error states yourself.
|
|
18
|
+
- **Handle deletions and resets explicitly:** Use the `delete: true` flag to evict records from the cache, and send the `'reset'` token to clear stale action errors without re-running the mutation.
|
|
19
|
+
- **Error handling model:** Treat expected errors (e.g., 404) at the call site via the action result; unexpected errors bubble to the nearest React error boundary.
|
|
20
|
+
|
|
21
|
+
## Common Pitfalls
|
|
22
|
+
|
|
23
|
+
- **Use ViewRefs:** Pass references instead of raw data. Keep components focused on views and `ViewRef`s to let fate manage data masking and updates.
|
|
24
|
+
- **Co-locate data needs:** Keep view definitions near their components and compose them upward to the request root for predictable fetching.
|
|
25
|
+
- **Use Suspense:** Wrap pages in `Suspense`/`ErrorBoundary` instead of ad-hoc loading/error flags; fate aligns with Async React by design.
|
|
26
|
+
- **Do not duplicate types:** Keep generated tRPC types and views imported from the server package. Avoid redefining entity shapes on the client. This ensures view selections stay type-safe end-to-end.
|
|
27
|
+
- **`useRequest` only at the root:** When adding routes or layouts, create a dedicated `useRequest` call per screen root that pulls together all child views; do not scatter `useRequest` across leaf components unless it's necessary to issue requests due to user input.
|
|
28
|
+
- **React Actions:** Prefer React Action-friendly UI primitives (buttons/forms that accept an `action` prop). If unavailable, wrap action calls with `startTransition` in `onClick` handlers.
|
|
29
|
+
- **Generated Client** The fate client is generated via `pnpm fate:generate`. It should not be manually edited. Instead make the proper schema changes on the server and run `pnpm fate:generate`.
|
|
30
|
+
- **Library Versions:** This repository uses the most recent releases of React, fate, Vite, tRPC, and more. You might not know about the new releases yet, please don't get confused. The versions you see are real, and there are no feature or version mismatches. You can search the internet for more information about them.
|
|
31
|
+
|
|
32
|
+
Full documentation can be found on the filesystem at `./client/node_modules/react-fate/README.md` or online at [fate.technology](https://fate.technology/).
|
|
33
|
+
|
|
34
|
+
## Review Checklist for Agents
|
|
35
|
+
|
|
36
|
+
- [ ] Every component that reads server data does so through `useView` and receives a `ViewRef` prop.
|
|
37
|
+
- [ ] Root components gather data with a single `useRequest` call.
|
|
38
|
+
- [ ] Mutations are wired through `useActionState` with optimistic updates and necessary `view` selections; imperative `fate.mutations` are justified when used.
|
|
39
|
+
- [ ] Shared selections live in reusable views using regular JavaScript object spreads rather than duplicated field lists.
|
|
40
|
+
|
|
41
|
+
<!--VITE PLUS START-->
|
|
42
|
+
|
|
43
|
+
# Using Vite+, the Unified Toolchain for the Web
|
|
44
|
+
|
|
45
|
+
This project is using Vite+, a unified toolchain built on top of Vite, Rolldown, Vitest, tsdown, Oxlint, Oxfmt, and Vite Task. Vite+ wraps runtime management, package management, and frontend tooling in a single global CLI called `vp`. Vite+ is distinct from Vite, and it invokes Vite through `vp dev` and `vp build`. Run `vp help` to print a list of commands and `vp <command> --help` for information about a specific command.
|
|
46
|
+
|
|
47
|
+
Docs are local at `node_modules/vite-plus/docs` or online at https://viteplus.dev/guide/.
|
|
48
|
+
|
|
49
|
+
## Review Checklist
|
|
50
|
+
|
|
51
|
+
- [ ] Run `vp install` after pulling remote changes and before getting started.
|
|
52
|
+
- [ ] Run `vp check` and `vp test` to format, lint, type check and test changes.
|
|
53
|
+
- [ ] Check if there are `vite.config.ts` tasks or `package.json` scripts necessary for validation, run via `vp run <script>`.
|
|
54
|
+
|
|
55
|
+
<!--VITE PLUS END-->
|