create-patties 0.0.6
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/bin/create-patties.ts +5 -0
- package/package.json +18 -0
- package/src/index.ts +212 -0
- package/templates/_claude/.claude/settings.json +5 -0
- package/templates/_claude/CLAUDE.md +56 -0
- package/templates/_codex/AGENTS.md +48 -0
- package/templates/default/README-template.md +16 -0
- package/templates/default/app/islands/Counter.tsx +12 -0
- package/templates/default/app/routes/index.tsx +10 -0
- package/templates/default/gitignore +7 -0
- package/templates/default/patties.config.ts +5 -0
- package/templates/default/tsconfig.json +12 -0
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-patties",
|
|
3
|
+
"version": "0.0.6",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Scaffolder for new Patties projects.",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-patties": "./bin/create-patties.ts"
|
|
8
|
+
},
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./src/index.ts"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin",
|
|
14
|
+
"src",
|
|
15
|
+
"templates",
|
|
16
|
+
"README.md"
|
|
17
|
+
]
|
|
18
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
2
|
+
import { dirname, isAbsolute, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
type AgentTemplate = "claude" | "codex" | "none";
|
|
5
|
+
|
|
6
|
+
interface Args {
|
|
7
|
+
name?: string;
|
|
8
|
+
template: AgentTemplate;
|
|
9
|
+
target: "bun" | "edge";
|
|
10
|
+
deploy: "cloudflare" | "vercel" | "deno" | "netlify" | "bun" | "none";
|
|
11
|
+
install: boolean;
|
|
12
|
+
git: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const TEMPLATES_ROOT = resolve(dirname(import.meta.dir), "templates");
|
|
16
|
+
const BASE_TEMPLATE = "default";
|
|
17
|
+
const VALID_TEMPLATES: AgentTemplate[] = ["claude", "codex", "none"];
|
|
18
|
+
|
|
19
|
+
export async function run(argv: string[]): Promise<number> {
|
|
20
|
+
const args = parseArgs(argv);
|
|
21
|
+
|
|
22
|
+
if (!args.name) {
|
|
23
|
+
printUsage();
|
|
24
|
+
return 2;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!isValidName(args.name)) {
|
|
28
|
+
stderr(`✗ invalid project name: "${args.name}"`);
|
|
29
|
+
return 2;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!VALID_TEMPLATES.includes(args.template)) {
|
|
33
|
+
stderr(
|
|
34
|
+
`✗ unknown --template "${args.template}" (expected: ${VALID_TEMPLATES.join(", ")})`,
|
|
35
|
+
);
|
|
36
|
+
return 2;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const targetDir = isAbsolute(args.name)
|
|
40
|
+
? args.name
|
|
41
|
+
: resolve(process.cwd(), args.name);
|
|
42
|
+
|
|
43
|
+
if (existsSync(targetDir) && readdirSync(targetDir).length > 0) {
|
|
44
|
+
stderr(`✗ directory not empty: ${targetDir}`);
|
|
45
|
+
return 2;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const baseDir = resolve(TEMPLATES_ROOT, BASE_TEMPLATE);
|
|
49
|
+
if (!existsSync(baseDir)) {
|
|
50
|
+
stderr(`✗ base template missing: ${baseDir}`);
|
|
51
|
+
return 1;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
await Bun.$`mkdir -p ${targetDir}`.quiet();
|
|
55
|
+
await Bun.$`cp -R ${baseDir}/. ${targetDir}`.quiet();
|
|
56
|
+
|
|
57
|
+
await renameTemplateFiles(targetDir);
|
|
58
|
+
await writePackageJson(targetDir, args.name);
|
|
59
|
+
await patchPattiesConfig(targetDir, args);
|
|
60
|
+
|
|
61
|
+
if (args.template !== "none") {
|
|
62
|
+
const overlay = resolve(TEMPLATES_ROOT, `_${args.template}`);
|
|
63
|
+
if (existsSync(overlay)) {
|
|
64
|
+
await Bun.$`cp -R ${overlay}/. ${targetDir}`.quiet();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (args.install) {
|
|
69
|
+
await Bun.$`bun install`.cwd(targetDir).quiet().nothrow();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (args.git) {
|
|
73
|
+
await Bun.$`git init`.cwd(targetDir).quiet().nothrow();
|
|
74
|
+
await Bun.$`git add -A`.cwd(targetDir).quiet().nothrow();
|
|
75
|
+
await Bun.$`git commit -m ${"chore: initial commit from create-patties"}`
|
|
76
|
+
.cwd(targetDir)
|
|
77
|
+
.quiet()
|
|
78
|
+
.nothrow();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
process.stdout.write(
|
|
82
|
+
`\n✓ created ${args.name}\n\n cd ${args.name}\n bun dev\n`,
|
|
83
|
+
);
|
|
84
|
+
if (args.template === "claude") {
|
|
85
|
+
process.stdout.write(
|
|
86
|
+
"\nClaude Code is configured (CLAUDE.md). Run `claude` in the project to start a session.\n",
|
|
87
|
+
);
|
|
88
|
+
} else if (args.template === "codex") {
|
|
89
|
+
process.stdout.write(
|
|
90
|
+
"\nCodex is configured (AGENTS.md). Run `codex` in the project to start a session.\n",
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function parseArgs(argv: string[]): Args {
|
|
97
|
+
const out: Args = {
|
|
98
|
+
template: "claude",
|
|
99
|
+
target: "bun",
|
|
100
|
+
deploy: "none",
|
|
101
|
+
install: true,
|
|
102
|
+
git: true,
|
|
103
|
+
};
|
|
104
|
+
for (let i = 0; i < argv.length; i++) {
|
|
105
|
+
const a = argv[i];
|
|
106
|
+
if (a === undefined) continue;
|
|
107
|
+
if (a === "--template") out.template = next(argv, ++i) as AgentTemplate;
|
|
108
|
+
else if (a.startsWith("--template="))
|
|
109
|
+
out.template = a.slice(11) as AgentTemplate;
|
|
110
|
+
else if (a === "--target") out.target = next(argv, ++i) as Args["target"];
|
|
111
|
+
else if (a.startsWith("--target="))
|
|
112
|
+
out.target = a.slice(9) as Args["target"];
|
|
113
|
+
else if (a === "--deploy") out.deploy = next(argv, ++i) as Args["deploy"];
|
|
114
|
+
else if (a.startsWith("--deploy="))
|
|
115
|
+
out.deploy = a.slice(9) as Args["deploy"];
|
|
116
|
+
else if (a === "--no-install") out.install = false;
|
|
117
|
+
else if (a === "--no-git") out.git = false;
|
|
118
|
+
else if (!out.name && !a.startsWith("-")) out.name = a;
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function next(argv: string[], i: number): string {
|
|
124
|
+
return argv[i] ?? "";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function isValidName(name: string): boolean {
|
|
128
|
+
if (!name) return false;
|
|
129
|
+
if (name.includes("/") || name.includes("\\")) return false;
|
|
130
|
+
if (name.startsWith(".")) return false;
|
|
131
|
+
return /^[a-z0-9][a-z0-9_-]*$/.test(name);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function renameTemplateFiles(dir: string): Promise<void> {
|
|
135
|
+
const renames: Array<[string, string]> = [
|
|
136
|
+
["gitignore", ".gitignore"],
|
|
137
|
+
["README-template.md", "README.md"],
|
|
138
|
+
];
|
|
139
|
+
for (const [from, to] of renames) {
|
|
140
|
+
const src = `${dir}/${from}`;
|
|
141
|
+
if (await Bun.file(src).exists()) {
|
|
142
|
+
await Bun.$`mv ${src} ${dir}/${to}`.quiet();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function writePackageJson(dir: string, name: string): Promise<void> {
|
|
148
|
+
const pkg = {
|
|
149
|
+
name,
|
|
150
|
+
version: "0.1.0",
|
|
151
|
+
private: true,
|
|
152
|
+
type: "module",
|
|
153
|
+
scripts: {
|
|
154
|
+
dev: "patties dev",
|
|
155
|
+
build: "patties build",
|
|
156
|
+
start: "patties start",
|
|
157
|
+
},
|
|
158
|
+
dependencies: sorted({
|
|
159
|
+
patties: "latest",
|
|
160
|
+
react: "^19.0.0",
|
|
161
|
+
"react-dom": "^19.0.0",
|
|
162
|
+
}),
|
|
163
|
+
devDependencies: sorted({
|
|
164
|
+
"@types/react": "^19.0.0",
|
|
165
|
+
"@types/react-dom": "^19.0.0",
|
|
166
|
+
"bun-types": "latest",
|
|
167
|
+
typescript: "^5.5.0",
|
|
168
|
+
}),
|
|
169
|
+
};
|
|
170
|
+
await Bun.write(`${dir}/package.json`, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function sorted(deps: Record<string, string>): Record<string, string> {
|
|
174
|
+
const out: Record<string, string> = {};
|
|
175
|
+
for (const k of Object.keys(deps).sort()) out[k] = deps[k] as string;
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function patchPattiesConfig(dir: string, args: Args): Promise<void> {
|
|
180
|
+
const path = `${dir}/patties.config.ts`;
|
|
181
|
+
if (!(await Bun.file(path).exists())) return;
|
|
182
|
+
const current = await Bun.file(path).text();
|
|
183
|
+
const next = current.replace(
|
|
184
|
+
/target:\s*"(bun|edge)"/,
|
|
185
|
+
`target: "${args.target}"`,
|
|
186
|
+
);
|
|
187
|
+
if (next !== current) await Bun.write(path, next);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function stderr(msg: string): void {
|
|
191
|
+
process.stderr.write(`${msg}\n`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function printUsage(): void {
|
|
195
|
+
process.stdout.write(`create-patties — scaffold a new Patties project
|
|
196
|
+
|
|
197
|
+
Usage:
|
|
198
|
+
bunx create-patties@latest <name> [options]
|
|
199
|
+
|
|
200
|
+
Options:
|
|
201
|
+
--template <claude|codex|none> Agent platform (default: claude)
|
|
202
|
+
--target <bun|edge> Runtime target (default: bun)
|
|
203
|
+
--deploy <cloudflare|vercel|deno|netlify|bun|none>
|
|
204
|
+
--no-install Skip 'bun install'
|
|
205
|
+
--no-git Skip 'git init'
|
|
206
|
+
|
|
207
|
+
Examples:
|
|
208
|
+
bunx create-patties@latest my-app
|
|
209
|
+
bunx create-patties@latest my-app --template codex
|
|
210
|
+
bunx create-patties@latest my-app --template none --no-git
|
|
211
|
+
`);
|
|
212
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
Built with Patties — Bun-native full-stack meta-framework.
|
|
4
|
+
|
|
5
|
+
## Stack
|
|
6
|
+
|
|
7
|
+
Bun runtime · `Bun.serve({ routes })` dispatcher · React renderer (`react-dom/server` + `react-dom/client`) · `{{DEPLOY_TARGET}}` deploy.
|
|
8
|
+
|
|
9
|
+
## File conventions
|
|
10
|
+
|
|
11
|
+
| Path | Becomes |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `app/routes/index.tsx` | `/` |
|
|
14
|
+
| `app/routes/about.tsx` | `/about` |
|
|
15
|
+
| `app/routes/hotels/[city].tsx` | `/hotels/:city` |
|
|
16
|
+
| `app/routes/api/revenue.ts` | `/api/revenue` |
|
|
17
|
+
| `app/islands/counter.tsx` | interactive island `Counter` |
|
|
18
|
+
| `app/agents/booking.ts` | agent `booking` |
|
|
19
|
+
| `app/tools/search.ts` | MCP tool `search` |
|
|
20
|
+
| `app/middleware.ts` | global middleware |
|
|
21
|
+
| `patties.config.ts` | framework config |
|
|
22
|
+
|
|
23
|
+
Files starting with `_` are private and never routed. The URL prefix `/__patties_*` and output directory `/_patties/*` are reserved.
|
|
24
|
+
|
|
25
|
+
## Critical rules
|
|
26
|
+
|
|
27
|
+
1. Never import from `"next"`.
|
|
28
|
+
2. Never use Node.js `http`, `fs.watch`, or `chokidar`. Use `Bun.serve` and `bun --watch`.
|
|
29
|
+
3. Never use webpack or vite. Use `Bun.build`.
|
|
30
|
+
4. Use React for rendering. Server: `react-dom/server.renderToReadableStream`. Client: `react-dom/client.hydrateRoot`. Never `renderToPipeableStream` or `renderToString`. Never `hono/jsx` or `hono/jsx/dom`.
|
|
31
|
+
5. `tsconfig.json` sets `"jsx": "react-jsx"` and `"jsxImportSource": "react"` — no manual `import React` in user code.
|
|
32
|
+
6. All routes are plain `(req: Request, ctx: PattiesContext) => Response | Promise<Response>`. Never import from `"hono"`.
|
|
33
|
+
7. Islands live in `app/islands/` — no exceptions.
|
|
34
|
+
8. API routes export named `GET`, `POST`, `PUT`, `DELETE` functions. Default exports are reserved for page components.
|
|
35
|
+
9. Middleware default-exports a `Middleware = (req, ctx, next) => Promise<Response>`. Never `MiddlewareHandler` from Hono.
|
|
36
|
+
10. Use Bun primitives for I/O: `Bun.file`, `Bun.write`, `Bun.spawn`, `Bun.CryptoHasher`, `Bun.env`, `Bun.password`, `bun:sqlite`, `Bun.sql`, `Bun.RedisClient`, `Bun.S3Client`. `node:fs` only when no Bun built-in exists.
|
|
37
|
+
|
|
38
|
+
## How to run
|
|
39
|
+
|
|
40
|
+
- `bun install`
|
|
41
|
+
- `bun dev` — start the dev server
|
|
42
|
+
- `bun build` — build for production
|
|
43
|
+
- `bun test`
|
|
44
|
+
|
|
45
|
+
## How to add a feature
|
|
46
|
+
|
|
47
|
+
Use the slash commands under `.claude/commands/`:
|
|
48
|
+
|
|
49
|
+
- `/new-route <path>` — page or API route
|
|
50
|
+
- `/new-island <Name>` — interactive island
|
|
51
|
+
- `/new-agent <name>` — agent
|
|
52
|
+
- `/new-tool <name>` — MCP tool
|
|
53
|
+
|
|
54
|
+
## Where to find things
|
|
55
|
+
|
|
56
|
+
`AGENTS.md` (auto-regenerated by `patties build`) is the live inventory of routes, islands, agents, and tools. Trust `AGENTS.md` over this file for inventory questions; trust this file for rules and conventions.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
Built with Patties — Bun-native full-stack meta-framework.
|
|
4
|
+
|
|
5
|
+
> This file is read by Codex CLI and other AGENTS.md-aware tools. Patties also
|
|
6
|
+
> auto-regenerates a live inventory `AGENTS.md` on `patties build` — trust the
|
|
7
|
+
> generated inventory for routes/islands/agents/tools, and this file for rules
|
|
8
|
+
> and conventions.
|
|
9
|
+
|
|
10
|
+
## Stack
|
|
11
|
+
|
|
12
|
+
Bun runtime · `Bun.serve({ routes })` dispatcher · React renderer (`react-dom/server` + `react-dom/client`) · `{{DEPLOY_TARGET}}` deploy.
|
|
13
|
+
|
|
14
|
+
## File conventions
|
|
15
|
+
|
|
16
|
+
| Path | Becomes |
|
|
17
|
+
|---|---|
|
|
18
|
+
| `app/routes/index.tsx` | `/` |
|
|
19
|
+
| `app/routes/about.tsx` | `/about` |
|
|
20
|
+
| `app/routes/hotels/[city].tsx` | `/hotels/:city` |
|
|
21
|
+
| `app/routes/api/revenue.ts` | `/api/revenue` |
|
|
22
|
+
| `app/islands/counter.tsx` | interactive island `Counter` |
|
|
23
|
+
| `app/agents/booking.ts` | agent `booking` |
|
|
24
|
+
| `app/tools/search.ts` | MCP tool `search` |
|
|
25
|
+
| `app/middleware.ts` | global middleware |
|
|
26
|
+
| `patties.config.ts` | framework config |
|
|
27
|
+
|
|
28
|
+
Files starting with `_` are private and never routed. The URL prefix `/__patties_*` and output directory `/_patties/*` are reserved.
|
|
29
|
+
|
|
30
|
+
## Critical rules
|
|
31
|
+
|
|
32
|
+
1. Never import from `"next"`.
|
|
33
|
+
2. Never use Node.js `http`, `fs.watch`, or `chokidar`. Use `Bun.serve` and `bun --watch`.
|
|
34
|
+
3. Never use webpack or vite. Use `Bun.build`.
|
|
35
|
+
4. Use React for rendering. Server: `react-dom/server.renderToReadableStream`. Client: `react-dom/client.hydrateRoot`. Never `renderToPipeableStream` or `renderToString`. Never `hono/jsx` or `hono/jsx/dom`.
|
|
36
|
+
5. `tsconfig.json` sets `"jsx": "react-jsx"` and `"jsxImportSource": "react"` — no manual `import React` in user code.
|
|
37
|
+
6. All routes are plain `(req: Request, ctx: PattiesContext) => Response | Promise<Response>`. Never import from `"hono"`.
|
|
38
|
+
7. Islands live in `app/islands/` — no exceptions.
|
|
39
|
+
8. API routes export named `GET`, `POST`, `PUT`, `DELETE` functions. Default exports are reserved for page components.
|
|
40
|
+
9. Middleware default-exports a `Middleware = (req, ctx, next) => Promise<Response>`. Never `MiddlewareHandler` from Hono.
|
|
41
|
+
10. Use Bun primitives for I/O: `Bun.file`, `Bun.write`, `Bun.spawn`, `Bun.CryptoHasher`, `Bun.env`, `Bun.password`, `bun:sqlite`, `Bun.sql`, `Bun.RedisClient`, `Bun.S3Client`. `node:fs` only when no Bun built-in exists.
|
|
42
|
+
|
|
43
|
+
## How to run
|
|
44
|
+
|
|
45
|
+
- `bun install`
|
|
46
|
+
- `bun dev` — start the dev server
|
|
47
|
+
- `bun build` — build for production
|
|
48
|
+
- `bun test`
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"types": ["bun-types", "react", "react-dom"]
|
|
10
|
+
},
|
|
11
|
+
"include": ["app", "patties.config.ts"]
|
|
12
|
+
}
|