create-cfast 0.0.1 → 0.2.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/dist/index.js +42 -2
- package/llms.txt +151 -0
- package/package.json +22 -10
- package/templates/admin/app/routes/admin.tsx +6 -7
- package/templates/auth/app/routes/login.tsx +1 -1
- package/templates/base/CLAUDE.md +143 -0
- package/templates/email/app/email/templates/magic-link.tsx +17 -0
- package/templates/ui/package.json +1 -0
- package/LICENSE +0 -21
- package/templates/auth/app/auth.setup.server.ts +0 -17
package/dist/index.js
CHANGED
|
@@ -62,6 +62,7 @@ function resolveFeatureDeps(features) {
|
|
|
62
62
|
}
|
|
63
63
|
if (resolved.auth) {
|
|
64
64
|
resolved.db = true;
|
|
65
|
+
resolved.ui = true;
|
|
65
66
|
}
|
|
66
67
|
return resolved;
|
|
67
68
|
}
|
|
@@ -413,7 +414,7 @@ ${appLine}
|
|
|
413
414
|
function generateViteConfig(config) {
|
|
414
415
|
const optimizeDeps = [];
|
|
415
416
|
if (config.features.ui && config.uiLibrary === "joy") {
|
|
416
|
-
optimizeDeps.push(`"@cfast/
|
|
417
|
+
optimizeDeps.push(`"@cfast/joy"`);
|
|
417
418
|
}
|
|
418
419
|
if (config.features.ui) {
|
|
419
420
|
optimizeDeps.push(`"@cfast/actions/client"`);
|
|
@@ -469,7 +470,7 @@ function generateRootTsx(config) {
|
|
|
469
470
|
imports.push(
|
|
470
471
|
`import { createUIPlugin, UIPluginProvider, ConfirmProvider } from "@cfast/ui";`
|
|
471
472
|
);
|
|
472
|
-
imports.push(`import { ConfirmDialog } from "@cfast/
|
|
473
|
+
imports.push(`import { ConfirmDialog } from "@cfast/joy";`);
|
|
473
474
|
pluginSetup = `
|
|
474
475
|
const plugin = createUIPlugin({
|
|
475
476
|
components: { confirmDialog: ConfirmDialog },
|
|
@@ -594,6 +595,42 @@ function generateDevVars(config) {
|
|
|
594
595
|
return lines.join("\n") + "\n";
|
|
595
596
|
}
|
|
596
597
|
|
|
598
|
+
// src/generators/auth-setup.ts
|
|
599
|
+
function generateAuthSetup(config) {
|
|
600
|
+
const imports = [
|
|
601
|
+
`import { createAuth } from "@cfast/auth";`,
|
|
602
|
+
`import * as schema from "./db/schema";`,
|
|
603
|
+
`import { permissions } from "./permissions";`,
|
|
604
|
+
`import { env } from "./env";`
|
|
605
|
+
];
|
|
606
|
+
let sendMagicLinkBody;
|
|
607
|
+
if (config.features.email) {
|
|
608
|
+
imports.push(`import { email as emailClient } from "~/email.server";`);
|
|
609
|
+
imports.push(`import { MagicLinkEmail } from "~/email/templates/magic-link";`);
|
|
610
|
+
sendMagicLinkBody = ` await emailClient.send({
|
|
611
|
+
to: email,
|
|
612
|
+
subject: "Sign in to ${config.projectName}",
|
|
613
|
+
react: MagicLinkEmail({ url }),
|
|
614
|
+
});`;
|
|
615
|
+
} else {
|
|
616
|
+
sendMagicLinkBody = ` console.log(\`Magic link for \${email}: \${url}\`);`;
|
|
617
|
+
}
|
|
618
|
+
return `${imports.join("\n")}
|
|
619
|
+
|
|
620
|
+
export const initAuth = createAuth({
|
|
621
|
+
permissions,
|
|
622
|
+
schema,
|
|
623
|
+
magicLink: {
|
|
624
|
+
sendMagicLink: async ({ email, url }) => {
|
|
625
|
+
${sendMagicLinkBody}
|
|
626
|
+
},
|
|
627
|
+
},
|
|
628
|
+
session: { expiresIn: "30d" },
|
|
629
|
+
defaultRoles: ["member"],
|
|
630
|
+
});
|
|
631
|
+
`;
|
|
632
|
+
}
|
|
633
|
+
|
|
597
634
|
// src/scaffold.ts
|
|
598
635
|
function scaffold(config) {
|
|
599
636
|
const templatesDir = getTemplatesDir();
|
|
@@ -623,6 +660,9 @@ function scaffold(config) {
|
|
|
623
660
|
writeFile(path2.join(targetDir, "vite.config.ts"), generateViteConfig(config));
|
|
624
661
|
writeFile(path2.join(targetDir, "app", "root.tsx"), generateRootTsx(config));
|
|
625
662
|
writeFile(path2.join(targetDir, "app", "routes.ts"), generateRoutesTs(config));
|
|
663
|
+
if (config.features.auth) {
|
|
664
|
+
writeFile(path2.join(targetDir, "app", "auth.setup.server.ts"), generateAuthSetup(config));
|
|
665
|
+
}
|
|
626
666
|
const devVars = generateDevVars(config);
|
|
627
667
|
if (devVars) {
|
|
628
668
|
writeFile(path2.join(targetDir, ".dev.vars"), devVars);
|
package/llms.txt
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# create-cfast
|
|
2
|
+
|
|
3
|
+
> Scaffold a fully wired Cloudflare Workers + React Router + cfast project in one command.
|
|
4
|
+
|
|
5
|
+
## When to use
|
|
6
|
+
|
|
7
|
+
Use `create-cfast` to start a new cfast project. It sets up the project structure, installs selected @cfast packages, generates configuration files (wrangler.toml, env schema, auth setup), and provides working example routes.
|
|
8
|
+
|
|
9
|
+
## Key concepts
|
|
10
|
+
|
|
11
|
+
- **Interactive prompts**: Asks for project name, which @cfast packages to include, and UI library choice. Can be fully driven by CLI flags for non-interactive use.
|
|
12
|
+
- **Feature dependency resolution**: Selecting `admin` auto-enables `db`, `ui`, and `auth`. Selecting `auth` auto-enables `db`.
|
|
13
|
+
- **Template overlays**: A base template is copied first, then feature-specific overlays are applied in order: `db`, `auth`, `storage`, `email`, `ui`, `admin`.
|
|
14
|
+
- **Environment-aware secrets**: Creates `.dev.vars` for local development. All secret files are `.gitignore`d.
|
|
15
|
+
|
|
16
|
+
## API Reference
|
|
17
|
+
|
|
18
|
+
### CLI usage
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm create cfast@latest [project-name] [options]
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### CLI flags
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
--auth Include @cfast/auth (magic email + passkeys)
|
|
28
|
+
--db Include @cfast/db (D1 + Drizzle ORM)
|
|
29
|
+
--storage Include @cfast/storage (R2 file uploads)
|
|
30
|
+
--email Include @cfast/email (email sending)
|
|
31
|
+
--ui Include @cfast/ui (components + actions)
|
|
32
|
+
--admin Include @cfast/admin (admin panel)
|
|
33
|
+
--all Include all packages
|
|
34
|
+
--help Show help
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
When any feature flag is provided, interactive feature selection is skipped.
|
|
38
|
+
|
|
39
|
+
### Programmatic types
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
type Features = {
|
|
43
|
+
auth: boolean; db: boolean; storage: boolean;
|
|
44
|
+
email: boolean; ui: boolean; admin: boolean;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
type UiLibrary = "joy" | "headless";
|
|
48
|
+
|
|
49
|
+
type Config = {
|
|
50
|
+
projectName: string;
|
|
51
|
+
targetDir: string;
|
|
52
|
+
features: Features;
|
|
53
|
+
uiLibrary: UiLibrary | null; // null when ui feature is disabled
|
|
54
|
+
};
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Key internal functions
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
scaffold(config: Config): void // main scaffolding entry point
|
|
61
|
+
resolveFeatureDeps(features: Features): Features // auto-enables dependencies
|
|
62
|
+
resolveConfig(raw: Config): Config // resolves deps + defaults uiLibrary to "joy"
|
|
63
|
+
parseArgs(argv: string[]): CliArgs // parses CLI arguments
|
|
64
|
+
promptForConfig(args: CliArgs): Promise<Config | null> // interactive prompts
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Generators (used internally by `scaffold()`)
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
generateWranglerToml(config): string
|
|
71
|
+
generateEnv(config): string
|
|
72
|
+
generateCfastServer(config): string
|
|
73
|
+
generateViteConfig(config): string
|
|
74
|
+
generateRootTsx(config): string
|
|
75
|
+
generateRoutesTs(config): string
|
|
76
|
+
generateDevVars(config): string | null
|
|
77
|
+
generateAuthSetup(config): string
|
|
78
|
+
mergePackageJsons(base, overlays): Record<string, unknown>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Usage Examples
|
|
82
|
+
|
|
83
|
+
### Interactive
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npm create cfast@latest my-app
|
|
87
|
+
# Prompts for features, UI library
|
|
88
|
+
# Scaffolds project, prints next steps
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Non-interactive (all features)
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npm create cfast@latest my-app --all
|
|
95
|
+
# Selects all features, defaults to Joy UI
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Specific features
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npm create cfast@latest my-app --auth --db --email
|
|
102
|
+
# auth auto-enables db (already selected), skips ui/admin/storage
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### After scaffolding
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
cd my-app
|
|
109
|
+
pnpm install
|
|
110
|
+
pnpm dev # local dev (wrangler + vite)
|
|
111
|
+
pnpm db:generate # generate D1 migrations
|
|
112
|
+
pnpm db:migrate:local # apply migrations locally
|
|
113
|
+
pnpm deploy:staging # deploy to staging
|
|
114
|
+
pnpm deploy:production # deploy to production
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Generated project structure
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
my-app/
|
|
121
|
+
CLAUDE.md # LLM-friendly project conventions
|
|
122
|
+
app/
|
|
123
|
+
routes/ # _index.tsx, login.tsx, dashboard.tsx
|
|
124
|
+
db/schema.ts # Drizzle schema with auth tables
|
|
125
|
+
auth.ts # @cfast/auth config
|
|
126
|
+
permissions.ts # @cfast/permissions definitions
|
|
127
|
+
cfast.server.ts # @cfast/core app setup
|
|
128
|
+
env.ts # @cfast/env schema
|
|
129
|
+
root.tsx # React root with providers
|
|
130
|
+
routes.ts # React Router route config
|
|
131
|
+
wrangler.toml # D1, KV bindings pre-configured
|
|
132
|
+
.dev.vars # local dev secrets
|
|
133
|
+
vite.config.ts
|
|
134
|
+
package.json
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Integration
|
|
138
|
+
|
|
139
|
+
- Scaffolds wiring for **@cfast/core** (`createApp` + plugins in `cfast.server.ts`).
|
|
140
|
+
- Sets up **@cfast/auth** with magic link and passkey config.
|
|
141
|
+
- Configures **@cfast/db** with Drizzle schema and D1 bindings.
|
|
142
|
+
- Generates **@cfast/env** schema from selected features.
|
|
143
|
+
- When `ui` is selected, sets up **@cfast/ui** with Joy UI or headless plugin.
|
|
144
|
+
- When `admin` is selected, adds admin route using **@cfast/admin**.
|
|
145
|
+
|
|
146
|
+
## Common Mistakes
|
|
147
|
+
|
|
148
|
+
- Running in a non-empty directory -- `scaffold()` throws if the target directory exists and is not empty.
|
|
149
|
+
- Expecting `--admin` alone to work without auth/db -- dependencies are auto-resolved, but be aware that admin implies auth + db + ui.
|
|
150
|
+
- Forgetting to set API keys in `.dev.vars` after scaffolding -- the generated file has placeholder comments.
|
|
151
|
+
- Using `npm` instead of `pnpm` for the generated project -- the scaffolded `package.json` expects pnpm.
|
package/package.json
CHANGED
|
@@ -1,21 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-cfast",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Scaffold a fully wired Cloudflare Workers + React Router project with cfast",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cfast",
|
|
7
|
+
"cloudflare-workers",
|
|
8
|
+
"create",
|
|
9
|
+
"scaffold",
|
|
10
|
+
"react-router"
|
|
11
|
+
],
|
|
5
12
|
"license": "MIT",
|
|
6
13
|
"repository": {
|
|
7
14
|
"type": "git",
|
|
8
15
|
"url": "https://github.com/DanielMSchmidt/cfast.git",
|
|
9
16
|
"directory": "packages/create-cfast"
|
|
10
17
|
},
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public",
|
|
20
|
+
"provenance": true
|
|
21
|
+
},
|
|
11
22
|
"type": "module",
|
|
12
23
|
"bin": {
|
|
13
24
|
"create-cfast": "./dist/index.js"
|
|
14
25
|
},
|
|
15
26
|
"files": [
|
|
16
27
|
"dist",
|
|
17
|
-
"templates"
|
|
28
|
+
"templates",
|
|
29
|
+
"llms.txt"
|
|
18
30
|
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsup src/index.ts --format esm",
|
|
33
|
+
"dev": "tsup src/index.ts --format esm --watch",
|
|
34
|
+
"typecheck": "tsc --noEmit",
|
|
35
|
+
"lint": "eslint src/",
|
|
36
|
+
"test": "vitest run"
|
|
37
|
+
},
|
|
19
38
|
"dependencies": {
|
|
20
39
|
"kolorist": "^1",
|
|
21
40
|
"prompts": "^2"
|
|
@@ -26,12 +45,5 @@
|
|
|
26
45
|
"tsup": "^8",
|
|
27
46
|
"typescript": "^5.7",
|
|
28
47
|
"vitest": "^3"
|
|
29
|
-
},
|
|
30
|
-
"scripts": {
|
|
31
|
-
"build": "tsup src/index.ts --format esm",
|
|
32
|
-
"dev": "tsup src/index.ts --format esm --watch",
|
|
33
|
-
"typecheck": "tsc --noEmit",
|
|
34
|
-
"lint": "eslint src/",
|
|
35
|
-
"test": "vitest run"
|
|
36
48
|
}
|
|
37
|
-
}
|
|
49
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { LoaderFunctionArgs, ActionFunctionArgs } from "react-router";
|
|
2
|
-
import {
|
|
3
|
-
import { AdminPanel } from "@cfast/admin/client";
|
|
4
|
-
import { joyAdminComponents } from "@cfast/ui/joy";
|
|
2
|
+
import { createAdminComponent, introspectSchema } from "@cfast/admin";
|
|
5
3
|
import { adminLoader, adminAction } from "~/admin.server";
|
|
4
|
+
import * as schema from "~/db/schema";
|
|
5
|
+
|
|
6
|
+
const tableMetas = introspectSchema(schema);
|
|
7
|
+
const AdminComponent = createAdminComponent(tableMetas);
|
|
6
8
|
|
|
7
9
|
export async function loader(args: LoaderFunctionArgs) {
|
|
8
10
|
return adminLoader(args.request);
|
|
@@ -12,7 +14,4 @@ export async function action(args: ActionFunctionArgs) {
|
|
|
12
14
|
return adminAction(args.request);
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
export default
|
|
16
|
-
const data = useLoaderData<typeof loader>();
|
|
17
|
-
return <AdminPanel data={data} components={joyAdminComponents} />;
|
|
18
|
-
}
|
|
17
|
+
export default AdminComponent;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { LoaderFunctionArgs } from "react-router";
|
|
2
2
|
import { redirect, useLoaderData } from "react-router";
|
|
3
3
|
import { LoginPage } from "@cfast/auth/client";
|
|
4
|
-
import { joyLoginComponents } from "@cfast/
|
|
4
|
+
import { joyLoginComponents } from "@cfast/joy";
|
|
5
5
|
import { getUser } from "~/auth.helpers.server";
|
|
6
6
|
import { authClient } from "~/auth.client";
|
|
7
7
|
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# {{projectName}}
|
|
2
|
+
|
|
3
|
+
Built with [cfast](https://github.com/DanielMSchmidt/cfast) — Cloudflare Workers + React Router 7 + Drizzle ORM + D1.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
- **Runtime:** Cloudflare Workers
|
|
8
|
+
- **Framework:** React Router v7 (file-based routing)
|
|
9
|
+
- **Database:** Cloudflare D1 via Drizzle ORM
|
|
10
|
+
- **Auth:** @cfast/auth (Better Auth — magic email + passkeys)
|
|
11
|
+
- **Permissions:** @cfast/permissions (isomorphic, DB-enforced)
|
|
12
|
+
- **UI:** @cfast/ui + @cfast/forms (schema-derived)
|
|
13
|
+
|
|
14
|
+
## Key Conventions
|
|
15
|
+
|
|
16
|
+
### Permissions are DB-enforced — never check manually in route handlers
|
|
17
|
+
|
|
18
|
+
Permissions are defined once in `app/permissions.ts` and enforced automatically by `@cfast/db`.
|
|
19
|
+
Do NOT add manual permission checks in loaders/actions. The DB layer rejects unauthorized queries.
|
|
20
|
+
|
|
21
|
+
### Use @cfast/actions for route actions
|
|
22
|
+
|
|
23
|
+
Actions are defined in `app/actions.server.ts` using `createAction` and composed with `composeActions`.
|
|
24
|
+
Do NOT use raw React Router `action` functions with manual request parsing.
|
|
25
|
+
|
|
26
|
+
### Forms are schema-derived
|
|
27
|
+
|
|
28
|
+
Use `@cfast/forms` to generate forms from Drizzle schema columns.
|
|
29
|
+
Customize with field overrides — do NOT rewrite forms from scratch.
|
|
30
|
+
|
|
31
|
+
### Use @cfast/db for all database operations
|
|
32
|
+
|
|
33
|
+
`@cfast/db` wraps Drizzle with permission-aware, lazy queries.
|
|
34
|
+
Do NOT use raw Drizzle `db.select()` / `db.insert()` calls directly.
|
|
35
|
+
|
|
36
|
+
### Auth is pre-configured
|
|
37
|
+
|
|
38
|
+
Use `requireUser(request)` or `requireAuthContext(request)` from `~/auth.helpers.server` for protected routes.
|
|
39
|
+
Do NOT build custom auth flows.
|
|
40
|
+
|
|
41
|
+
### File uploads use @cfast/storage
|
|
42
|
+
|
|
43
|
+
Schema-defined file types with R2 storage. Do NOT write custom upload handlers.
|
|
44
|
+
|
|
45
|
+
## Common Tasks
|
|
46
|
+
|
|
47
|
+
### Add a new entity
|
|
48
|
+
|
|
49
|
+
1. Define the table in `app/db/schema.ts` (Drizzle `sqliteTable`)
|
|
50
|
+
2. Run `pnpm db:generate` then `pnpm db:migrate:local`
|
|
51
|
+
3. Add permission grants in `app/permissions.ts`
|
|
52
|
+
4. Create actions in a `*.server.ts` file using `createAction` from `~/actions.server`
|
|
53
|
+
5. Create the route in `app/routes/` using `composeActions` for the action export
|
|
54
|
+
|
|
55
|
+
### Add a new page/route
|
|
56
|
+
|
|
57
|
+
1. Create a file in `app/routes/` (React Router file-based routing)
|
|
58
|
+
2. Export a `loader` for data fetching, `action` via `composeActions` for mutations
|
|
59
|
+
3. Export a default component for the UI
|
|
60
|
+
|
|
61
|
+
### Add a new action to an existing route
|
|
62
|
+
|
|
63
|
+
1. Define the action with `createAction` in the route's `*.server.ts` file
|
|
64
|
+
2. Add it to the existing `composeActions` call
|
|
65
|
+
3. Use `<ActionButton>` or a form with the action's `intent` hidden field in the UI
|
|
66
|
+
|
|
67
|
+
### Add a table to the admin panel
|
|
68
|
+
|
|
69
|
+
1. Import the table from `~/db/schema` in `app/admin.server.ts`
|
|
70
|
+
2. Add it to the `schema` object in the admin config
|
|
71
|
+
3. Optionally add dashboard widgets in the `dashboard.widgets` array
|
|
72
|
+
|
|
73
|
+
### Customize form fields
|
|
74
|
+
|
|
75
|
+
Use field overrides when calling the form generator:
|
|
76
|
+
```ts
|
|
77
|
+
// Override specific fields, keep the rest auto-generated
|
|
78
|
+
{ fields: { title: { label: "Item Name", placeholder: "Enter name..." } } }
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Anti-Patterns — Do NOT Do These
|
|
82
|
+
|
|
83
|
+
| Instead of... | Do this |
|
|
84
|
+
|---|---|
|
|
85
|
+
| Manual permission checks in loaders/actions | Define grants in `permissions.ts` — DB enforces them |
|
|
86
|
+
| `<PermissionGate>` with custom logic | Use `<PermissionGate>` with the grant system or action permission status |
|
|
87
|
+
| Raw Drizzle `db.select()`/`db.insert()` | Use `@cfast/db` operations (permission-aware) |
|
|
88
|
+
| Raw SQL queries | Use `@cfast/db` operations |
|
|
89
|
+
| Custom auth middleware or flows | Use `requireUser()` / `requireAuthContext()` from `~/auth.helpers.server` |
|
|
90
|
+
| Hand-writing full form markup | Use `@cfast/forms` with schema derivation + field overrides |
|
|
91
|
+
| Custom file upload endpoints | Use `@cfast/storage` with schema-defined file types |
|
|
92
|
+
| Manual `request.formData()` parsing in actions | Use `createAction` which handles parsing, validation, and permissions |
|
|
93
|
+
| `hasRole()` / `hasAnyRole()` checks | Use `can()` from `@cfast/permissions` to check permissions |
|
|
94
|
+
| Manually adding hidden `<input>` fields to forms | Use `<ActionForm>` which injects hidden fields (e.g. `intent`) automatically |
|
|
95
|
+
|
|
96
|
+
## Project Structure
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
app/
|
|
100
|
+
├── routes/ # React Router file-based routes
|
|
101
|
+
├── db/
|
|
102
|
+
│ ├── schema.ts # Drizzle schema (source of truth)
|
|
103
|
+
│ └── client.ts # DB client factory
|
|
104
|
+
├── permissions.ts # Permission definitions
|
|
105
|
+
├── auth.helpers.server.ts # Auth utilities (requireUser, etc.)
|
|
106
|
+
├── auth.setup.server.ts # Auth initialization
|
|
107
|
+
├── cfast.server.ts # App context setup
|
|
108
|
+
├── actions.server.ts # Shared action factory
|
|
109
|
+
├── env.ts # Type-safe env bindings
|
|
110
|
+
└── root.tsx # App root layout
|
|
111
|
+
workers/
|
|
112
|
+
└── app.ts # Cloudflare Worker entry point
|
|
113
|
+
wrangler.toml # Cloudflare config (D1, KV, R2 bindings)
|
|
114
|
+
drizzle.config.ts # Drizzle Kit config
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Commands
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
pnpm dev # Start local dev server
|
|
121
|
+
pnpm build # Production build
|
|
122
|
+
pnpm db:generate # Generate migrations from schema changes
|
|
123
|
+
pnpm db:migrate:local # Apply migrations locally
|
|
124
|
+
pnpm db:migrate:remote # Apply migrations to remote D1
|
|
125
|
+
pnpm deploy:staging # Deploy to staging
|
|
126
|
+
pnpm deploy:production # Deploy to production
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Keeping LLM Documentation Updated
|
|
130
|
+
|
|
131
|
+
When adding or changing public APIs in any `@cfast` package:
|
|
132
|
+
- Update the package's `llms.txt` with the new/changed API signatures, examples, and integration notes
|
|
133
|
+
- Update the root `llms.txt` (concise overview) and `llms-full.txt` (complete reference) if the change affects cross-package patterns or the mental model
|
|
134
|
+
- The scaffolded `CLAUDE.md` (this file's template in `create-cfast`) should be updated if conventions change
|
|
135
|
+
|
|
136
|
+
## Package Documentation
|
|
137
|
+
|
|
138
|
+
For detailed API reference on any cfast package, check:
|
|
139
|
+
`node_modules/@cfast/[package]/llms.txt`
|
|
140
|
+
|
|
141
|
+
Available packages: `@cfast/env`, `@cfast/permissions`, `@cfast/auth`, `@cfast/db`,
|
|
142
|
+
`@cfast/actions`, `@cfast/ui`, `@cfast/forms`, `@cfast/pagination`, `@cfast/storage`,
|
|
143
|
+
`@cfast/email`, `@cfast/admin`.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Html, Head, Body, Text, Link } from "@react-email/components";
|
|
2
|
+
|
|
3
|
+
type MagicLinkEmailProps = {
|
|
4
|
+
url: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export function MagicLinkEmail({ url }: MagicLinkEmailProps) {
|
|
8
|
+
return (
|
|
9
|
+
<Html>
|
|
10
|
+
<Head />
|
|
11
|
+
<Body>
|
|
12
|
+
<Text>Click the link below to sign in:</Text>
|
|
13
|
+
<Link href={url}>Sign in to {{projectName}}</Link>
|
|
14
|
+
</Body>
|
|
15
|
+
</Html>
|
|
16
|
+
);
|
|
17
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Daniel Schmidt
|
|
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.
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { createAuth } from "@cfast/auth";
|
|
2
|
-
import * as schema from "./db/schema";
|
|
3
|
-
import { permissions } from "./permissions";
|
|
4
|
-
import { env } from "./env";
|
|
5
|
-
|
|
6
|
-
export const initAuth = createAuth({
|
|
7
|
-
permissions,
|
|
8
|
-
schema,
|
|
9
|
-
magicLink: {
|
|
10
|
-
sendMagicLink: async ({ email, url }) => {
|
|
11
|
-
// TODO: integrate with @cfast/email when email feature is enabled
|
|
12
|
-
console.log(`Magic link for ${email}: ${url}`);
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
|
-
session: { expiresIn: "30d" },
|
|
16
|
-
defaultRoles: ["member"],
|
|
17
|
-
});
|