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 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/ui/joy"`);
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/ui/joy";`);
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.1",
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 { useLoaderData } from "react-router";
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 function Admin() {
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/ui/joy";
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
+ }
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "dependencies": {
3
3
  "@cfast/actions": "^0.1.0",
4
+ "@cfast/joy": "^0.1.0",
4
5
  "@cfast/ui": "^0.1.0",
5
6
  "@emotion/react": "^11.14.0",
6
7
  "@emotion/styled": "^11.14.0",
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
- });