create-reactor 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 +123 -0
- package/create-app.mjs +712 -0
- package/lib/build.mjs +434 -0
- package/lib/pm.mjs +85 -0
- package/lib/presets.mjs +122 -0
- package/lib/templates/ai-docs.mjs +80 -0
- package/lib/templates/app.mjs +961 -0
- package/lib/templates/backend.mjs +715 -0
- package/lib/templates/base.mjs +671 -0
- package/lib/templates/biome.mjs +107 -0
- package/lib/templates/extras.mjs +360 -0
- package/lib/templates/features.mjs +463 -0
- package/lib/templates/quality.mjs +159 -0
- package/lib/templates/readme.mjs +351 -0
- package/lib/templates/security.mjs +70 -0
- package/lib/templates/server.mjs +141 -0
- package/lib/templates/state.mjs +192 -0
- package/package.json +52 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// State management: Zustand, Jotai, or Redux Toolkit — each with a counter
|
|
2
|
+
// store and a matching demo card.
|
|
3
|
+
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Zustand
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
export function zustandStore() {
|
|
9
|
+
return `import { create } from "zustand";
|
|
10
|
+
|
|
11
|
+
interface CounterState {
|
|
12
|
+
count: number;
|
|
13
|
+
increment: () => void;
|
|
14
|
+
decrement: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const useCounterStore = create<CounterState>((set) => ({
|
|
18
|
+
count: 0,
|
|
19
|
+
increment: () => set((s) => ({ count: s.count + 1 })),
|
|
20
|
+
decrement: () => set((s) => ({ count: s.count - 1 })),
|
|
21
|
+
}));
|
|
22
|
+
`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Jotai
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
export function jotaiStore() {
|
|
30
|
+
return `import { atom } from "jotai";
|
|
31
|
+
|
|
32
|
+
export const countAtom = atom(0);
|
|
33
|
+
|
|
34
|
+
// Derived atom example: reads other atoms, recomputes automatically
|
|
35
|
+
export const doubleCountAtom = atom((get) => get(countAtom) * 2);
|
|
36
|
+
`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Redux Toolkit
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
export function reduxStore() {
|
|
44
|
+
return `import { configureStore } from "@reduxjs/toolkit";
|
|
45
|
+
import counterReducer from "./counter-slice";
|
|
46
|
+
|
|
47
|
+
export const store = configureStore({
|
|
48
|
+
reducer: {
|
|
49
|
+
counter: counterReducer,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export type RootState = ReturnType<typeof store.getState>;
|
|
54
|
+
export type AppDispatch = typeof store.dispatch;
|
|
55
|
+
`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function reduxSlice() {
|
|
59
|
+
return `import { createSlice } from "@reduxjs/toolkit";
|
|
60
|
+
|
|
61
|
+
const counterSlice = createSlice({
|
|
62
|
+
name: "counter",
|
|
63
|
+
initialState: { value: 0 },
|
|
64
|
+
reducers: {
|
|
65
|
+
increment: (state) => {
|
|
66
|
+
state.value += 1;
|
|
67
|
+
},
|
|
68
|
+
decrement: (state) => {
|
|
69
|
+
state.value -= 1;
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
export const { increment, decrement } = counterSlice.actions;
|
|
75
|
+
export default counterSlice.reducer;
|
|
76
|
+
`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function reduxHooks() {
|
|
80
|
+
return `import { useDispatch, useSelector } from "react-redux";
|
|
81
|
+
import type { AppDispatch, RootState } from "./store";
|
|
82
|
+
|
|
83
|
+
// Pre-typed hooks — use these instead of plain useDispatch/useSelector
|
|
84
|
+
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
|
|
85
|
+
export const useAppSelector = useSelector.withTypes<RootState>();
|
|
86
|
+
`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Counter demo (varies by state library)
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
export function counterDemo(state) {
|
|
94
|
+
const titles = {
|
|
95
|
+
zustand: ["Zustand store", "src/stores/counter.ts"],
|
|
96
|
+
jotai: ["Jotai atoms", "src/stores/counter.ts"],
|
|
97
|
+
redux: ["Redux Toolkit", "src/stores/counter-slice.ts"],
|
|
98
|
+
};
|
|
99
|
+
const [title, file] = titles[state];
|
|
100
|
+
|
|
101
|
+
let hookImport;
|
|
102
|
+
let hookUsage;
|
|
103
|
+
if (state === "zustand") {
|
|
104
|
+
hookImport = `import { useCounterStore } from "@/stores/counter";`;
|
|
105
|
+
hookUsage = ` const { count, increment, decrement } = useCounterStore();`;
|
|
106
|
+
} else if (state === "jotai") {
|
|
107
|
+
hookImport = `import { useAtom } from "jotai";
|
|
108
|
+
import { countAtom } from "@/stores/counter";`;
|
|
109
|
+
hookUsage = ` const [count, setCount] = useAtom(countAtom);
|
|
110
|
+
const increment = () => setCount((c) => c + 1);
|
|
111
|
+
const decrement = () => setCount((c) => c - 1);`;
|
|
112
|
+
} else {
|
|
113
|
+
hookImport = `import { useAppDispatch, useAppSelector } from "@/stores/hooks";
|
|
114
|
+
import { decrement as decrementAction, increment as incrementAction } from "@/stores/counter-slice";`;
|
|
115
|
+
hookUsage = ` const count = useAppSelector((s) => s.counter.value);
|
|
116
|
+
const dispatch = useAppDispatch();
|
|
117
|
+
const increment = () => dispatch(incrementAction());
|
|
118
|
+
const decrement = () => dispatch(decrementAction());`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return `import { Minus, Plus } from "lucide-react";
|
|
122
|
+
${hookImport}
|
|
123
|
+
import { Button } from "@/components/ui/button";
|
|
124
|
+
import {
|
|
125
|
+
Card,
|
|
126
|
+
CardContent,
|
|
127
|
+
CardDescription,
|
|
128
|
+
CardHeader,
|
|
129
|
+
CardTitle,
|
|
130
|
+
} from "@/components/ui/card";
|
|
131
|
+
|
|
132
|
+
export function CounterDemo() {
|
|
133
|
+
${hookUsage}
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<Card>
|
|
137
|
+
<CardHeader>
|
|
138
|
+
<CardTitle>${title}</CardTitle>
|
|
139
|
+
<CardDescription>
|
|
140
|
+
Global state from <code>${file}</code>.
|
|
141
|
+
</CardDescription>
|
|
142
|
+
</CardHeader>
|
|
143
|
+
<CardContent className="flex items-center gap-4">
|
|
144
|
+
<Button variant="outline" size="icon" onClick={decrement}>
|
|
145
|
+
<Minus className="size-4" />
|
|
146
|
+
</Button>
|
|
147
|
+
<span className="min-w-10 text-center text-2xl font-bold tabular-nums">
|
|
148
|
+
{count}
|
|
149
|
+
</span>
|
|
150
|
+
<Button variant="outline" size="icon" onClick={increment}>
|
|
151
|
+
<Plus className="size-4" />
|
|
152
|
+
</Button>
|
|
153
|
+
</CardContent>
|
|
154
|
+
</Card>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Files + deps for the chosen state library. */
|
|
161
|
+
export function statePlan(state) {
|
|
162
|
+
if (state === "zustand") {
|
|
163
|
+
return {
|
|
164
|
+
deps: ["zustand"],
|
|
165
|
+
files: {
|
|
166
|
+
"src/stores/counter.ts": zustandStore(),
|
|
167
|
+
"src/components/counter-demo.tsx": counterDemo("zustand"),
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
if (state === "jotai") {
|
|
172
|
+
return {
|
|
173
|
+
deps: ["jotai"],
|
|
174
|
+
files: {
|
|
175
|
+
"src/stores/counter.ts": jotaiStore(),
|
|
176
|
+
"src/components/counter-demo.tsx": counterDemo("jotai"),
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
if (state === "redux") {
|
|
181
|
+
return {
|
|
182
|
+
deps: ["@reduxjs/toolkit", "react-redux"],
|
|
183
|
+
files: {
|
|
184
|
+
"src/stores/store.ts": reduxStore(),
|
|
185
|
+
"src/stores/counter-slice.ts": reduxSlice(),
|
|
186
|
+
"src/stores/hooks.ts": reduxHooks(),
|
|
187
|
+
"src/components/counter-demo.tsx": counterDemo("redux"),
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return { deps: [], files: {} };
|
|
192
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-reactor",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Interactive generator for modern React starters: Vite + TypeScript + Tailwind v4 + shadcn/ui + TanStack Router + Convex/Supabase + Clerk/Convex Auth + AI SDK",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-reactor": "./create-app.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"create-app.mjs",
|
|
11
|
+
"lib",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"create": "node create-app.mjs"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"react",
|
|
20
|
+
"vite",
|
|
21
|
+
"typescript",
|
|
22
|
+
"tailwind",
|
|
23
|
+
"shadcn",
|
|
24
|
+
"tanstack",
|
|
25
|
+
"convex",
|
|
26
|
+
"supabase",
|
|
27
|
+
"clerk",
|
|
28
|
+
"scaffold",
|
|
29
|
+
"generator",
|
|
30
|
+
"starter",
|
|
31
|
+
"boilerplate",
|
|
32
|
+
"create-react-app",
|
|
33
|
+
"ai-sdk"
|
|
34
|
+
],
|
|
35
|
+
"author": "Ansh Roshan <ianshroshan@gmail.com> (https://github.com/anshroshan)",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"homepage": "https://github.com/anshroshan/create-reactor#readme",
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/anshroshan/create-reactor.git"
|
|
41
|
+
},
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/anshroshan/create-reactor/issues"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@clack/prompts": "^0.11.0",
|
|
47
|
+
"picocolors": "^1.1.1"
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=20.19"
|
|
51
|
+
}
|
|
52
|
+
}
|