ai-forge-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +148 -0
- package/dist/add-feature-YXWSRIVE.js +141 -0
- package/dist/check-RCJRXIU5.js +377 -0
- package/dist/chunk-J4V5PGVT.js +55 -0
- package/dist/chunk-PIFX2L5H.js +46 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +17 -0
- package/dist/init-GQA3WDXQ.js +114 -0
- package/dist/templates/feature/convex/index.ts.hbs +3 -0
- package/dist/templates/feature/convex/mutations.ts.hbs +37 -0
- package/dist/templates/feature/convex/queries.ts.hbs +16 -0
- package/dist/templates/feature/convex/schema.ts.hbs +10 -0
- package/dist/templates/feature/routes/$id.tsx.hbs +27 -0
- package/dist/templates/feature/routes/index.tsx.hbs +21 -0
- package/dist/templates/feature/src/components/index.ts.hbs +4 -0
- package/dist/templates/feature/src/hooks.ts.hbs +29 -0
- package/dist/templates/feature/src/index.ts.hbs +2 -0
- package/dist/templates/init/app/client.tsx.hbs +7 -0
- package/dist/templates/init/app/router.tsx.hbs +17 -0
- package/dist/templates/init/app/routes/__root.tsx.hbs +38 -0
- package/dist/templates/init/app/routes/index.tsx.hbs +18 -0
- package/dist/templates/init/app/ssr.tsx.hbs +11 -0
- package/dist/templates/init/app.config.ts.hbs +8 -0
- package/dist/templates/init/biome.json.hbs +32 -0
- package/dist/templates/init/claude.md.hbs +93 -0
- package/dist/templates/init/convex/schema.ts.hbs +7 -0
- package/dist/templates/init/package.json.hbs +34 -0
- package/dist/templates/init/postcss.config.js.hbs +6 -0
- package/dist/templates/init/src/lib/cn.ts.hbs +6 -0
- package/dist/templates/init/src/providers/index.tsx.hbs +10 -0
- package/dist/templates/init/tailwind.config.ts.hbs +12 -0
- package/dist/templates/init/tsconfig.json.hbs +24 -0
- package/package.json +59 -0
- package/templates/feature/convex/index.ts.hbs +3 -0
- package/templates/feature/convex/mutations.ts.hbs +37 -0
- package/templates/feature/convex/queries.ts.hbs +16 -0
- package/templates/feature/convex/schema.ts.hbs +10 -0
- package/templates/feature/routes/$id.tsx.hbs +27 -0
- package/templates/feature/routes/index.tsx.hbs +21 -0
- package/templates/feature/src/components/index.ts.hbs +4 -0
- package/templates/feature/src/hooks.ts.hbs +29 -0
- package/templates/feature/src/index.ts.hbs +2 -0
- package/templates/init/app/client.tsx.hbs +7 -0
- package/templates/init/app/router.tsx.hbs +17 -0
- package/templates/init/app/routes/__root.tsx.hbs +38 -0
- package/templates/init/app/routes/index.tsx.hbs +18 -0
- package/templates/init/app/ssr.tsx.hbs +11 -0
- package/templates/init/app.config.ts.hbs +8 -0
- package/templates/init/biome.json.hbs +32 -0
- package/templates/init/claude.md.hbs +93 -0
- package/templates/init/convex/schema.ts.hbs +7 -0
- package/templates/init/package.json.hbs +34 -0
- package/templates/init/postcss.config.js.hbs +6 -0
- package/templates/init/src/lib/cn.ts.hbs +6 -0
- package/templates/init/src/providers/index.tsx.hbs +10 -0
- package/templates/init/tailwind.config.ts.hbs +12 -0
- package/templates/init/tsconfig.json.hbs +24 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{name}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vinxi dev",
|
|
8
|
+
"build": "vinxi build",
|
|
9
|
+
"start": "vinxi start",
|
|
10
|
+
"lint": "biome check .",
|
|
11
|
+
"lint:fix": "biome check . --write",
|
|
12
|
+
"format": "biome format . --write"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@tanstack/react-router": "^1.95.1",
|
|
16
|
+
"@tanstack/start": "^1.95.1",
|
|
17
|
+
"convex": "^1.17.4",
|
|
18
|
+
"react": "^19.0.0",
|
|
19
|
+
"react-dom": "^19.0.0",
|
|
20
|
+
"vinxi": "^0.5.1",
|
|
21
|
+
"clsx": "^2.1.1",
|
|
22
|
+
"tailwind-merge": "^2.6.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@biomejs/biome": "^1.9.4",
|
|
26
|
+
"@types/react": "^19.0.0",
|
|
27
|
+
"@types/react-dom": "^19.0.0",
|
|
28
|
+
"autoprefixer": "^10.4.20",
|
|
29
|
+
"postcss": "^8.4.49",
|
|
30
|
+
"tailwindcss": "^3.4.17",
|
|
31
|
+
"typescript": "^5.7.2",
|
|
32
|
+
"vite-tsconfig-paths": "^5.1.4"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ConvexProvider, ConvexReactClient } from "convex/react";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
|
|
4
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
5
|
+
const [convex] = useState(
|
|
6
|
+
() => new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string)
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
return <ConvexProvider client={convex}>{children}</ConvexProvider>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"moduleResolution": "bundler",
|
|
8
|
+
"allowImportingTsExtensions": true,
|
|
9
|
+
"resolveJsonModule": true,
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"noEmit": true,
|
|
12
|
+
"jsx": "react-jsx",
|
|
13
|
+
"strict": true,
|
|
14
|
+
"noUnusedLocals": true,
|
|
15
|
+
"noUnusedParameters": true,
|
|
16
|
+
"noFallthroughCasesInSwitch": true,
|
|
17
|
+
"noUncheckedIndexedAccess": true,
|
|
18
|
+
"paths": {
|
|
19
|
+
"~/*": ["./src/*"],
|
|
20
|
+
"@convex/*": ["./convex/*"]
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"include": ["src", "app", "convex"]
|
|
24
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ai-forge-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript stack scaffolding & enforcement CLI for TanStack Start + Convex",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Pablo",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Thaloz/forge-cli.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/Thaloz/forge-cli#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/Thaloz/forge-cli/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"cli",
|
|
18
|
+
"scaffold",
|
|
19
|
+
"typescript",
|
|
20
|
+
"tanstack",
|
|
21
|
+
"convex",
|
|
22
|
+
"tailwind",
|
|
23
|
+
"ai",
|
|
24
|
+
"claude"
|
|
25
|
+
],
|
|
26
|
+
"bin": {
|
|
27
|
+
"forge": "./dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"templates"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"dev": "tsx src/index.ts",
|
|
35
|
+
"build": "tsup",
|
|
36
|
+
"typecheck": "tsc --noEmit",
|
|
37
|
+
"test": "vitest run",
|
|
38
|
+
"test:watch": "vitest",
|
|
39
|
+
"prepublishOnly": "pnpm run build"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"citty": "^0.1.6",
|
|
43
|
+
"consola": "^3.2.3",
|
|
44
|
+
"fast-glob": "^3.3.2",
|
|
45
|
+
"handlebars": "^4.7.8",
|
|
46
|
+
"oxc-parser": "^0.56.0",
|
|
47
|
+
"picocolors": "^1.1.1"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^22.10.5",
|
|
51
|
+
"tsx": "^4.19.2",
|
|
52
|
+
"tsup": "^8.3.5",
|
|
53
|
+
"typescript": "^5.7.2",
|
|
54
|
+
"vitest": "^2.1.8"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=18"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { mutation } from "convex/_generated/server";
|
|
2
|
+
import { v } from "convex/values";
|
|
3
|
+
|
|
4
|
+
export const create = mutation({
|
|
5
|
+
args: {
|
|
6
|
+
// Define your args here
|
|
7
|
+
},
|
|
8
|
+
handler: async (ctx, args) => {
|
|
9
|
+
const now = Date.now();
|
|
10
|
+
return await ctx.db.insert("{{camelCase name}}", {
|
|
11
|
+
...args,
|
|
12
|
+
createdAt: now,
|
|
13
|
+
updatedAt: now,
|
|
14
|
+
});
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const update = mutation({
|
|
19
|
+
args: {
|
|
20
|
+
id: v.id("{{camelCase name}}"),
|
|
21
|
+
// Define your update args here
|
|
22
|
+
},
|
|
23
|
+
handler: async (ctx, args) => {
|
|
24
|
+
const { id, ...fields } = args;
|
|
25
|
+
await ctx.db.patch(id, {
|
|
26
|
+
...fields,
|
|
27
|
+
updatedAt: Date.now(),
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export const remove = mutation({
|
|
33
|
+
args: { id: v.id("{{camelCase name}}") },
|
|
34
|
+
handler: async (ctx, args) => {
|
|
35
|
+
await ctx.db.delete(args.id);
|
|
36
|
+
},
|
|
37
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { query } from "convex/_generated/server";
|
|
2
|
+
import { v } from "convex/values";
|
|
3
|
+
|
|
4
|
+
export const list = query({
|
|
5
|
+
args: {},
|
|
6
|
+
handler: async (ctx) => {
|
|
7
|
+
return await ctx.db.query("{{camelCase name}}").collect();
|
|
8
|
+
},
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export const getById = query({
|
|
12
|
+
args: { id: v.id("{{camelCase name}}") },
|
|
13
|
+
handler: async (ctx, args) => {
|
|
14
|
+
return await ctx.db.get(args.id);
|
|
15
|
+
},
|
|
16
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { defineTable } from "convex/server";
|
|
2
|
+
import { v } from "convex/values";
|
|
3
|
+
|
|
4
|
+
export const {{camelCase name}}Tables = {
|
|
5
|
+
{{camelCase name}}: defineTable({
|
|
6
|
+
// Define your fields here
|
|
7
|
+
createdAt: v.number(),
|
|
8
|
+
updatedAt: v.number(),
|
|
9
|
+
}).index("by_created", ["createdAt"]),
|
|
10
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
import { use{{pascalCase name}} } from "~/features/{{camelCase name}}";
|
|
3
|
+
import type { Id } from "@convex/_generated/dataModel";
|
|
4
|
+
|
|
5
|
+
export const Route = createFileRoute("/{{kebabCase name}}/$id")({
|
|
6
|
+
component: {{pascalCase name}}DetailRoute,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
function {{pascalCase name}}DetailRoute() {
|
|
10
|
+
const { id } = Route.useParams();
|
|
11
|
+
const { item, isLoading } = use{{pascalCase name}}(id as Id<"{{camelCase name}}">);
|
|
12
|
+
|
|
13
|
+
if (isLoading) {
|
|
14
|
+
return <div>Loading...</div>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!item) {
|
|
18
|
+
return <div>Not found</div>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div>
|
|
23
|
+
<h1>{{pascalCase name}} Detail</h1>
|
|
24
|
+
{/* Build your UI here */}
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
import { use{{pascalCase name}}List } from "~/features/{{camelCase name}}";
|
|
3
|
+
|
|
4
|
+
export const Route = createFileRoute("/{{kebabCase name}}/")({
|
|
5
|
+
component: {{pascalCase name}}IndexRoute,
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
function {{pascalCase name}}IndexRoute() {
|
|
9
|
+
const { items, isLoading } = use{{pascalCase name}}List();
|
|
10
|
+
|
|
11
|
+
if (isLoading) {
|
|
12
|
+
return <div>Loading...</div>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div>
|
|
17
|
+
<h1>{{pascalCase name}}</h1>
|
|
18
|
+
{/* Build your UI here */}
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useQuery, useMutation } from "convex/react";
|
|
2
|
+
import { api } from "@convex/_generated/api";
|
|
3
|
+
import type { Id } from "@convex/_generated/dataModel";
|
|
4
|
+
|
|
5
|
+
export function use{{pascalCase name}}List() {
|
|
6
|
+
const items = useQuery(api.features.{{camelCase name}}.queries.list);
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
items: items ?? [],
|
|
10
|
+
isLoading: items === undefined,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function use{{pascalCase name}}(id: Id<"{{camelCase name}}">) {
|
|
15
|
+
const item = useQuery(api.features.{{camelCase name}}.queries.getById, { id });
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
item,
|
|
19
|
+
isLoading: item === undefined,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function use{{pascalCase name}}Mutations() {
|
|
24
|
+
const create = useMutation(api.features.{{camelCase name}}.mutations.create);
|
|
25
|
+
const update = useMutation(api.features.{{camelCase name}}.mutations.update);
|
|
26
|
+
const remove = useMutation(api.features.{{camelCase name}}.mutations.remove);
|
|
27
|
+
|
|
28
|
+
return { create, update, remove };
|
|
29
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createRouter as createTanStackRouter } from "@tanstack/react-router";
|
|
2
|
+
import { routeTree } from "./routeTree.gen";
|
|
3
|
+
|
|
4
|
+
export function createRouter() {
|
|
5
|
+
const router = createTanStackRouter({
|
|
6
|
+
routeTree,
|
|
7
|
+
defaultPreload: "intent",
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
return router;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
declare module "@tanstack/react-router" {
|
|
14
|
+
interface Register {
|
|
15
|
+
router: ReturnType<typeof createRouter>;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Outlet, ScrollRestoration, createRootRoute } from "@tanstack/react-router";
|
|
2
|
+
import { Meta, Scripts } from "@tanstack/start";
|
|
3
|
+
import { Providers } from "~/providers";
|
|
4
|
+
|
|
5
|
+
export const Route = createRootRoute({
|
|
6
|
+
head: () => ({
|
|
7
|
+
meta: [
|
|
8
|
+
{ charSet: "utf-8" },
|
|
9
|
+
{ name: "viewport", content: "width=device-width, initial-scale=1" },
|
|
10
|
+
{ title: "{{pascalCase name}}" },
|
|
11
|
+
],
|
|
12
|
+
links: [{ rel: "stylesheet", href: "/styles.css" }],
|
|
13
|
+
}),
|
|
14
|
+
component: RootComponent,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
function RootComponent() {
|
|
18
|
+
return (
|
|
19
|
+
<RootDocument>
|
|
20
|
+
<Outlet />
|
|
21
|
+
</RootDocument>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function RootDocument({ children }: { children: React.ReactNode }) {
|
|
26
|
+
return (
|
|
27
|
+
<html lang="en">
|
|
28
|
+
<head>
|
|
29
|
+
<Meta />
|
|
30
|
+
</head>
|
|
31
|
+
<body>
|
|
32
|
+
<Providers>{children}</Providers>
|
|
33
|
+
<ScrollRestoration />
|
|
34
|
+
<Scripts />
|
|
35
|
+
</body>
|
|
36
|
+
</html>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
|
|
3
|
+
export const Route = createFileRoute("/")({
|
|
4
|
+
component: Home,
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
function Home() {
|
|
8
|
+
return (
|
|
9
|
+
<div className="min-h-screen flex items-center justify-center">
|
|
10
|
+
<div className="text-center">
|
|
11
|
+
<h1 className="text-4xl font-bold mb-4">{{pascalCase name}}</h1>
|
|
12
|
+
<p className="text-gray-600">
|
|
13
|
+
Your TanStack Start + Convex app is ready.
|
|
14
|
+
</p>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createStartHandler,
|
|
3
|
+
defaultStreamHandler,
|
|
4
|
+
} from "@tanstack/start/server";
|
|
5
|
+
import { getRouterManifest } from "@tanstack/start/router-manifest";
|
|
6
|
+
import { createRouter } from "./router";
|
|
7
|
+
|
|
8
|
+
export default createStartHandler({
|
|
9
|
+
createRouter,
|
|
10
|
+
getRouterManifest,
|
|
11
|
+
})(defaultStreamHandler);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
|
3
|
+
"vcs": {
|
|
4
|
+
"enabled": true,
|
|
5
|
+
"clientKind": "git",
|
|
6
|
+
"useIgnoreFile": true
|
|
7
|
+
},
|
|
8
|
+
"files": {
|
|
9
|
+
"ignoreUnknown": false,
|
|
10
|
+
"ignore": ["node_modules", "dist", ".vinxi", "convex/_generated"]
|
|
11
|
+
},
|
|
12
|
+
"formatter": {
|
|
13
|
+
"enabled": true,
|
|
14
|
+
"indentStyle": "space",
|
|
15
|
+
"indentWidth": 2
|
|
16
|
+
},
|
|
17
|
+
"organizeImports": {
|
|
18
|
+
"enabled": true
|
|
19
|
+
},
|
|
20
|
+
"linter": {
|
|
21
|
+
"enabled": true,
|
|
22
|
+
"rules": {
|
|
23
|
+
"recommended": true
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"javascript": {
|
|
27
|
+
"formatter": {
|
|
28
|
+
"quoteStyle": "double",
|
|
29
|
+
"semicolons": "always"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
## YOU MUST USE FORGE CLI
|
|
4
|
+
|
|
5
|
+
When the user asks you to build ANY feature, you MUST:
|
|
6
|
+
|
|
7
|
+
1. FIRST run `forge add:feature <name>`
|
|
8
|
+
2. THEN fill in the generated files
|
|
9
|
+
3. NEVER create feature files manually
|
|
10
|
+
|
|
11
|
+
## Trigger Words → Action
|
|
12
|
+
|
|
13
|
+
| User says | You run |
|
|
14
|
+
|-----------|---------|
|
|
15
|
+
| "add feature", "create feature", "build feature" | `forge add:feature <name>` |
|
|
16
|
+
| "add X with Y" (any domain entity) | `forge add:feature <name>` |
|
|
17
|
+
| "new page for X" | `forge add:feature <name>` |
|
|
18
|
+
| "build X functionality" | `forge add:feature <name>` |
|
|
19
|
+
|
|
20
|
+
## File Creation Rules
|
|
21
|
+
|
|
22
|
+
DO NOT create these files manually:
|
|
23
|
+
- `src/features/**/*` (forge creates the structure)
|
|
24
|
+
- `convex/features/**/*` (forge creates the structure)
|
|
25
|
+
- `app/routes/<feature>/**/*` (forge creates the structure)
|
|
26
|
+
|
|
27
|
+
ONLY the forge CLI creates these. You fill them in after.
|
|
28
|
+
|
|
29
|
+
## What You CAN Create Manually
|
|
30
|
+
|
|
31
|
+
- Files inside existing feature component folders (after forge created them)
|
|
32
|
+
- `src/components/*` (shared UI)
|
|
33
|
+
- `src/lib/*` (utilities)
|
|
34
|
+
- `src/hooks/*` (global hooks)
|
|
35
|
+
|
|
36
|
+
## Workflow Example
|
|
37
|
+
|
|
38
|
+
User: "Add a comments feature with replies"
|
|
39
|
+
|
|
40
|
+
You MUST:
|
|
41
|
+
```
|
|
42
|
+
Run: forge add:feature comments
|
|
43
|
+
Edit: convex/features/comments/schema.ts (add fields for comments + replies)
|
|
44
|
+
Edit: convex/features/comments/queries.ts (add queries)
|
|
45
|
+
Edit: convex/features/comments/mutations.ts (add mutations)
|
|
46
|
+
Edit: src/features/comments/hooks.ts (add/modify hooks)
|
|
47
|
+
Create: src/features/comments/components/CommentCard.tsx
|
|
48
|
+
Create: src/features/comments/components/CommentList.tsx
|
|
49
|
+
Create: src/features/comments/components/ReplyForm.tsx
|
|
50
|
+
Update: src/features/comments/components/index.ts (add exports)
|
|
51
|
+
Edit: app/routes/comments/index.tsx (wire up components)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Validation
|
|
55
|
+
|
|
56
|
+
Before finishing ANY task, run:
|
|
57
|
+
```bash
|
|
58
|
+
forge check
|
|
59
|
+
```
|
|
60
|
+
If it fails, fix ALL issues before responding to the user.
|
|
61
|
+
|
|
62
|
+
## Project Structure
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
app/routes/ → Thin route files only (import from features, no logic)
|
|
66
|
+
src/features/ → All feature code (components, hooks, types)
|
|
67
|
+
src/components/ → Shared UI only (used across features)
|
|
68
|
+
src/lib/ → Pure utilities
|
|
69
|
+
convex/features/ → Backend mirrors frontend features
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Rules
|
|
73
|
+
|
|
74
|
+
1. **Routes are thin**: Only import and render. No business logic. No hooks defined here.
|
|
75
|
+
2. **No cross-feature imports**: `src/features/X` cannot import from `src/features/Y`
|
|
76
|
+
3. **Hooks in hooks.ts**: All feature hooks in `src/features/<name>/hooks.ts`
|
|
77
|
+
4. **Components in components/**: All feature components in `src/features/<name>/components/`
|
|
78
|
+
5. **Mirror structure**: Every `src/features/X` has `convex/features/X`
|
|
79
|
+
|
|
80
|
+
## Stack Quick Reference
|
|
81
|
+
|
|
82
|
+
- **Data fetching**: Use hooks from `src/features/<name>/hooks.ts` (they wrap Convex)
|
|
83
|
+
- **Mutations**: Use mutation hooks from `src/features/<name>/hooks.ts`
|
|
84
|
+
- **UI primitives**: Import from `~/components/ui/*` (shadcn)
|
|
85
|
+
- **Styling**: Tailwind classes only, no CSS files
|
|
86
|
+
- **Forms**: react-hook-form + zod for validation
|
|
87
|
+
|
|
88
|
+
## Commands Reference
|
|
89
|
+
|
|
90
|
+
| Command | When to use |
|
|
91
|
+
|---------|-------------|
|
|
92
|
+
| `forge add:feature <name>` | BEFORE building any new feature |
|
|
93
|
+
| `forge check` | BEFORE completing any task |
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{name}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vinxi dev",
|
|
8
|
+
"build": "vinxi build",
|
|
9
|
+
"start": "vinxi start",
|
|
10
|
+
"lint": "biome check .",
|
|
11
|
+
"lint:fix": "biome check . --write",
|
|
12
|
+
"format": "biome format . --write"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@tanstack/react-router": "^1.95.1",
|
|
16
|
+
"@tanstack/start": "^1.95.1",
|
|
17
|
+
"convex": "^1.17.4",
|
|
18
|
+
"react": "^19.0.0",
|
|
19
|
+
"react-dom": "^19.0.0",
|
|
20
|
+
"vinxi": "^0.5.1",
|
|
21
|
+
"clsx": "^2.1.1",
|
|
22
|
+
"tailwind-merge": "^2.6.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@biomejs/biome": "^1.9.4",
|
|
26
|
+
"@types/react": "^19.0.0",
|
|
27
|
+
"@types/react-dom": "^19.0.0",
|
|
28
|
+
"autoprefixer": "^10.4.20",
|
|
29
|
+
"postcss": "^8.4.49",
|
|
30
|
+
"tailwindcss": "^3.4.17",
|
|
31
|
+
"typescript": "^5.7.2",
|
|
32
|
+
"vite-tsconfig-paths": "^5.1.4"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ConvexProvider, ConvexReactClient } from "convex/react";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
|
|
4
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
5
|
+
const [convex] = useState(
|
|
6
|
+
() => new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string)
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
return <ConvexProvider client={convex}>{children}</ConvexProvider>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"moduleResolution": "bundler",
|
|
8
|
+
"allowImportingTsExtensions": true,
|
|
9
|
+
"resolveJsonModule": true,
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"noEmit": true,
|
|
12
|
+
"jsx": "react-jsx",
|
|
13
|
+
"strict": true,
|
|
14
|
+
"noUnusedLocals": true,
|
|
15
|
+
"noUnusedParameters": true,
|
|
16
|
+
"noFallthroughCasesInSwitch": true,
|
|
17
|
+
"noUncheckedIndexedAccess": true,
|
|
18
|
+
"paths": {
|
|
19
|
+
"~/*": ["./src/*"],
|
|
20
|
+
"@convex/*": ["./convex/*"]
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"include": ["src", "app", "convex"]
|
|
24
|
+
}
|