create-sprinkles 0.2.3
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/bin.mjs +295 -0
- package/dist/index.d.mts +46 -0
- package/dist/index.mjs +180 -0
- package/package.json +45 -0
- package/templates/react-router-convex/.env.local +2 -0
- package/templates/react-router-convex/convex/schema.ts +9 -0
- package/templates/react-router-rsc/app/home.tsx.hbs +31 -0
- package/templates/react-router-rsc/app/root.tsx.hbs +55 -0
- package/templates/react-router-rsc/tsconfig.json.hbs +31 -0
- package/templates/react-router-rsc/workers/entry.rsc.tsx +44 -0
- package/templates/react-router-rsc/workers/entry.ssr.tsx +41 -0
- package/templates/react-router-rsc/wrangler.rsc.jsonc.hbs +25 -0
- package/templates/react-router-rsc/wrangler.ssr.jsonc.hbs +14 -0
- package/templates/react-router-rsc-content-layer/app/content.config.ts.hbs +26 -0
- package/templates/react-router-rsc-content-layer/content-layer/api.ts +350 -0
- package/templates/react-router-rsc-content-layer/content-layer/codegen.ts +89 -0
- package/templates/react-router-rsc-content-layer/content-layer/config.ts +20 -0
- package/templates/react-router-rsc-content-layer/content-layer/digest.ts +6 -0
- package/templates/react-router-rsc-content-layer/content-layer/frontmatter.ts +19 -0
- package/templates/react-router-rsc-content-layer/content-layer/loaders/file.ts +55 -0
- package/templates/react-router-rsc-content-layer/content-layer/loaders/glob.ts +82 -0
- package/templates/react-router-rsc-content-layer/content-layer/loaders/index.ts +2 -0
- package/templates/react-router-rsc-content-layer/content-layer/plugin.ts +419 -0
- package/templates/react-router-rsc-content-layer/content-layer/resolve-hook.js +12 -0
- package/templates/react-router-rsc-content-layer/content-layer/runtime.ts +73 -0
- package/templates/react-router-rsc-content-layer/content-layer/store.ts +59 -0
- package/templates/react-router-spa/app/home.tsx.hbs +7 -0
- package/templates/react-router-spa/app/root.tsx.hbs +60 -0
- package/templates/react-router-spa/tsconfig.json.hbs +26 -0
- package/templates/react-router-spa/wrangler.jsonc.hbs +9 -0
- package/templates/react-router-ssr/app/home.tsx.hbs +21 -0
- package/templates/react-router-ssr/app/root.tsx.hbs +105 -0
- package/templates/react-router-ssr/convex/schema.ts +7 -0
- package/templates/react-router-ssr/tsconfig.json.hbs +28 -0
- package/templates/react-router-ssr/wrangler.jsonc.hbs +13 -0
- package/templates/react-router-ssr-convex/app/lib/client.ts +19 -0
- package/templates/react-router-ssr-convex/app/tanstack-query-integration/middleware.ts +18 -0
- package/templates/react-router-ssr-convex/app/tanstack-query-integration/query-preloader.ts +125 -0
- package/templates/react-shared/app/routes.ts.hbs +3 -0
- package/templates/react-shared/app/styles/tailwind.css +1 -0
- package/templates/react-shared/react-compiler.plugin.ts.hbs +10 -0
- package/templates/react-shared/react-router.config.ts.hbs +9 -0
- package/templates/shared/.gitignore.hbs +23 -0
- package/templates/shared/.node-version +1 -0
- package/templates/shared/.vscode/extensions.json.hbs +8 -0
- package/templates/shared/.vscode/settings.json.hbs +72 -0
- package/templates/shared/AGENTS.md.hbs +599 -0
- package/templates/shared/README.md.hbs +24 -0
- package/templates/shared/package.json.hbs +41 -0
- package/templates/shared/vite.config.ts.hbs +384 -0
- package/templates/ts-package/src/index.ts +3 -0
- package/templates/ts-package/tests/index.test.ts +9 -0
- package/templates/ts-package/tsconfig.json +18 -0
- package/templates/ts-package-cli/bin/index.ts.hbs +1 -0
- package/templates/ts-package-cli/src/cli.ts.hbs +37 -0
- package/templates/ts-package-generator/bin/create.ts.hbs +2 -0
- package/templates/ts-package-generator/src/template.ts.hbs +22 -0
- package/templates/ts-package-sea/sea-config.json.hbs +2 -0
- package/templates/ts-package-sea/src/sea-entry.ts.hbs +4 -0
package/dist/bin.mjs
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as prompts from "@clack/prompts";
|
|
3
|
+
import { createTemplate, runTemplate } from "bingo";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { handlebars } from "bingo-handlebars";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
//#region src/context.ts
|
|
8
|
+
function buildContext(opts) {
|
|
9
|
+
const isSPA = opts.kind === "react-router-spa";
|
|
10
|
+
const isSSR = opts.kind === "react-router-ssr";
|
|
11
|
+
const isRSC = opts.kind === "react-router-rsc";
|
|
12
|
+
return {
|
|
13
|
+
...opts,
|
|
14
|
+
hasContentLayer: isRSC && Boolean(opts.contentLayer),
|
|
15
|
+
hasConvex: (isSPA || isSSR) && Boolean(opts.convex),
|
|
16
|
+
isPackage: opts.kind === "ts-package",
|
|
17
|
+
isRSC,
|
|
18
|
+
isReactRouter: opts.kind !== "ts-package",
|
|
19
|
+
isSPA,
|
|
20
|
+
isSSR,
|
|
21
|
+
ssr: isSSR || isRSC
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/merge.ts
|
|
26
|
+
function isDirectory(value) {
|
|
27
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
28
|
+
}
|
|
29
|
+
function mergeFiles(...layers) {
|
|
30
|
+
const result = {};
|
|
31
|
+
for (const layer of layers) if (layer) for (const [key, value] of Object.entries(layer)) {
|
|
32
|
+
const existing = result[key];
|
|
33
|
+
if (isDirectory(existing) && isDirectory(value)) result[key] = mergeFiles(existing, value);
|
|
34
|
+
else result[key] = value;
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/options.ts
|
|
40
|
+
const kind$1 = z.enum([
|
|
41
|
+
"react-router-spa",
|
|
42
|
+
"react-router-ssr",
|
|
43
|
+
"react-router-rsc",
|
|
44
|
+
"ts-package"
|
|
45
|
+
]).describe("project kind");
|
|
46
|
+
const options = {
|
|
47
|
+
cli: z.boolean().optional(),
|
|
48
|
+
contentLayer: z.boolean().optional(),
|
|
49
|
+
convex: z.boolean().optional(),
|
|
50
|
+
generator: z.boolean().optional(),
|
|
51
|
+
kind: kind$1,
|
|
52
|
+
owner: z.string().describe("GitHub owner or organization"),
|
|
53
|
+
repository: z.string().describe("repository name"),
|
|
54
|
+
sea: z.boolean().optional()
|
|
55
|
+
};
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/scripts.ts
|
|
58
|
+
function buildDependencyCommands(context) {
|
|
59
|
+
const commands = [];
|
|
60
|
+
commands.push("vp add -D @types/node @typescript/native-preview");
|
|
61
|
+
if (context.isReactRouter) {
|
|
62
|
+
commands.push("vp add react react-dom react-router");
|
|
63
|
+
commands.push("vp add -D @types/react @types/react-dom @react-router/dev @tailwindcss/vite tailwindcss vite-plugin-devtools-json");
|
|
64
|
+
commands.push("vp add -D @rolldown/plugin-babel @vitejs/plugin-react babel-plugin-react-compiler");
|
|
65
|
+
commands.push("vp add -D @cloudflare/vite-plugin wrangler");
|
|
66
|
+
commands.push("vp add -D eslint-plugin-perfectionist eslint-plugin-react-hooks");
|
|
67
|
+
}
|
|
68
|
+
if (context.isReactRouter) commands.push("vp add @react-router/node isbot");
|
|
69
|
+
if (context.hasConvex) commands.push("vp add convex @convex-dev/react-query @tanstack/react-query");
|
|
70
|
+
if (context.isRSC) commands.push("vp add -D @vitejs/plugin-rsc");
|
|
71
|
+
if (context.hasContentLayer) {
|
|
72
|
+
commands.push("vp add jsr:@std/jsonc jsr:@std/yaml gray-matter github-slugger @remix-run/data-schema");
|
|
73
|
+
commands.push("vp add -D @mdx-js/rollup");
|
|
74
|
+
}
|
|
75
|
+
if (context.isPackage) {
|
|
76
|
+
if (context.cli) commands.push("vp add @bomb.sh/args");
|
|
77
|
+
if (context.generator) commands.push("vp add bingo bingo-handlebars zod");
|
|
78
|
+
}
|
|
79
|
+
commands.push("vp add -D vite-plus vite@npm:@voidzero-dev/vite-plus-core@latest vitest@npm:@voidzero-dev/vite-plus-test@latest");
|
|
80
|
+
return commands;
|
|
81
|
+
}
|
|
82
|
+
function buildScripts(context) {
|
|
83
|
+
const scripts = [];
|
|
84
|
+
const phase0Commands = [...buildDependencyCommands(context), "vp install"];
|
|
85
|
+
scripts.push({
|
|
86
|
+
commands: phase0Commands,
|
|
87
|
+
phase: 0
|
|
88
|
+
});
|
|
89
|
+
if (context.isRSC) scripts.push({
|
|
90
|
+
commands: ["vpx wrangler types -c wrangler.rsc.jsonc"],
|
|
91
|
+
phase: 1
|
|
92
|
+
});
|
|
93
|
+
else if (context.isSSR) scripts.push({
|
|
94
|
+
commands: ["vpx wrangler types"],
|
|
95
|
+
phase: 1
|
|
96
|
+
});
|
|
97
|
+
scripts.push({
|
|
98
|
+
commands: ["vp fmt"],
|
|
99
|
+
phase: 2
|
|
100
|
+
});
|
|
101
|
+
if (context.hasConvex) scripts.push({
|
|
102
|
+
commands: ["vpx convex dev --once"],
|
|
103
|
+
phase: 3,
|
|
104
|
+
silent: true
|
|
105
|
+
});
|
|
106
|
+
scripts.push({
|
|
107
|
+
commands: [
|
|
108
|
+
"ln -sf AGENTS.md CLAUDE.md",
|
|
109
|
+
"git init",
|
|
110
|
+
"git add -A",
|
|
111
|
+
"git commit -m initial"
|
|
112
|
+
],
|
|
113
|
+
phase: 4
|
|
114
|
+
});
|
|
115
|
+
return scripts;
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/suggestions.ts
|
|
119
|
+
function buildSuggestions(context) {
|
|
120
|
+
const suggestions = [];
|
|
121
|
+
if (context.hasConvex) suggestions.push("Open the Convex dashboard: https://dashboard.convex.dev");
|
|
122
|
+
if (context.isSSR || context.isRSC) suggestions.push("Log in to Cloudflare: vpx wrangler login");
|
|
123
|
+
if (context.isReactRouter) suggestions.push("Start the dev server: vp dev");
|
|
124
|
+
else suggestions.push("Start development: vp run dev");
|
|
125
|
+
return suggestions;
|
|
126
|
+
}
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/template.ts
|
|
129
|
+
const templatesDir = path.join(import.meta.dirname, "../templates");
|
|
130
|
+
const kindDirectories = {
|
|
131
|
+
"react-router-rsc": "react-router-rsc",
|
|
132
|
+
"react-router-spa": "react-router-spa",
|
|
133
|
+
"react-router-ssr": "react-router-ssr",
|
|
134
|
+
"ts-package": "ts-package"
|
|
135
|
+
};
|
|
136
|
+
async function tryHandlebars(dir, context) {
|
|
137
|
+
try {
|
|
138
|
+
return await handlebars(path.join(templatesDir, dir), context) || null;
|
|
139
|
+
} catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async function collectAddonLayers(context) {
|
|
144
|
+
const addons = [];
|
|
145
|
+
if (context.isPackage && context.cli) addons.push(await tryHandlebars("ts-package-cli", context));
|
|
146
|
+
if (context.isPackage && context.generator) addons.push(await tryHandlebars("ts-package-generator", context));
|
|
147
|
+
if (context.isPackage && context.sea) addons.push(await tryHandlebars("ts-package-sea", context));
|
|
148
|
+
if (context.hasConvex) addons.push(await tryHandlebars("react-router-convex", context));
|
|
149
|
+
if (context.isSSR && context.hasConvex) addons.push(await tryHandlebars("react-router-ssr-convex", context));
|
|
150
|
+
if (context.hasContentLayer) addons.push(await tryHandlebars("react-router-rsc-content-layer", context));
|
|
151
|
+
return addons;
|
|
152
|
+
}
|
|
153
|
+
async function buildLayers(context) {
|
|
154
|
+
const shared = await tryHandlebars("shared", context);
|
|
155
|
+
let reactShared = null;
|
|
156
|
+
if (context.isReactRouter) reactShared = await tryHandlebars("react-shared", context);
|
|
157
|
+
const kindDir = kindDirectories[context.kind];
|
|
158
|
+
let kindSpecific = null;
|
|
159
|
+
if (kindDir) kindSpecific = await tryHandlebars(kindDir, context);
|
|
160
|
+
const addons = await collectAddonLayers(context);
|
|
161
|
+
return mergeFiles(shared, reactShared, kindSpecific, ...addons);
|
|
162
|
+
}
|
|
163
|
+
var template_default = createTemplate({
|
|
164
|
+
about: {
|
|
165
|
+
description: "Get started with development by creating projects from templates quickly.",
|
|
166
|
+
name: NAME
|
|
167
|
+
},
|
|
168
|
+
options,
|
|
169
|
+
async produce({ options: opts }) {
|
|
170
|
+
const context = buildContext(opts);
|
|
171
|
+
return {
|
|
172
|
+
files: await buildLayers(context),
|
|
173
|
+
scripts: buildScripts(context),
|
|
174
|
+
suggestions: buildSuggestions(context)
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
//#endregion
|
|
179
|
+
//#region src/index.ts
|
|
180
|
+
const NAME = "create-sprinkles";
|
|
181
|
+
//#endregion
|
|
182
|
+
//#region bin/index.ts
|
|
183
|
+
prompts.intro(NAME);
|
|
184
|
+
const directory = await prompts.text({
|
|
185
|
+
message: "Where should we create the project?",
|
|
186
|
+
placeholder: "./my-project",
|
|
187
|
+
validate: (value) => {
|
|
188
|
+
if (!value) return "Directory is required";
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
if (prompts.isCancel(directory)) process.exit(0);
|
|
192
|
+
const kind = await prompts.select({
|
|
193
|
+
message: "What kind of project?",
|
|
194
|
+
options: [
|
|
195
|
+
{
|
|
196
|
+
label: "React Router — SPA",
|
|
197
|
+
value: "react-router-spa"
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
label: "React Router — SSR",
|
|
201
|
+
value: "react-router-ssr"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
label: "React Router — RSC",
|
|
205
|
+
value: "react-router-rsc"
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
label: "TypeScript Package",
|
|
209
|
+
value: "ts-package"
|
|
210
|
+
}
|
|
211
|
+
]
|
|
212
|
+
});
|
|
213
|
+
if (prompts.isCancel(kind)) process.exit(0);
|
|
214
|
+
let convex = false;
|
|
215
|
+
let contentLayer = false;
|
|
216
|
+
let cli = false;
|
|
217
|
+
let generator = false;
|
|
218
|
+
let sea = false;
|
|
219
|
+
if (kind === "react-router-spa" || kind === "react-router-ssr") {
|
|
220
|
+
const answer = await prompts.confirm({
|
|
221
|
+
initialValue: false,
|
|
222
|
+
message: "Include Convex backend?"
|
|
223
|
+
});
|
|
224
|
+
if (prompts.isCancel(answer)) process.exit(0);
|
|
225
|
+
convex = answer;
|
|
226
|
+
}
|
|
227
|
+
if (kind === "react-router-rsc") {
|
|
228
|
+
const answer = await prompts.confirm({
|
|
229
|
+
initialValue: false,
|
|
230
|
+
message: "Include content-layer plugin?"
|
|
231
|
+
});
|
|
232
|
+
if (prompts.isCancel(answer)) process.exit(0);
|
|
233
|
+
contentLayer = answer;
|
|
234
|
+
}
|
|
235
|
+
if (kind === "ts-package") {
|
|
236
|
+
const features = await prompts.multiselect({
|
|
237
|
+
message: "Include optional features?",
|
|
238
|
+
options: [
|
|
239
|
+
{
|
|
240
|
+
label: "CLI scaffold",
|
|
241
|
+
value: "cli"
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
label: "Bingo generator scaffold",
|
|
245
|
+
value: "generator"
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
label: "Single Executable Application (SEA)",
|
|
249
|
+
value: "sea"
|
|
250
|
+
}
|
|
251
|
+
],
|
|
252
|
+
required: false
|
|
253
|
+
});
|
|
254
|
+
if (prompts.isCancel(features)) process.exit(0);
|
|
255
|
+
cli = features.includes("cli");
|
|
256
|
+
generator = features.includes("generator");
|
|
257
|
+
sea = features.includes("sea");
|
|
258
|
+
}
|
|
259
|
+
const resolvedDir = path.resolve(directory);
|
|
260
|
+
const repository = path.basename(resolvedDir);
|
|
261
|
+
const owner = await prompts.text({
|
|
262
|
+
message: "GitHub owner or organization?",
|
|
263
|
+
placeholder: "my-org",
|
|
264
|
+
validate: (value) => {
|
|
265
|
+
if (!value) return "Owner is required";
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
if (prompts.isCancel(owner)) process.exit(0);
|
|
269
|
+
const spinner = prompts.spinner();
|
|
270
|
+
spinner.start("Scaffolding project...");
|
|
271
|
+
try {
|
|
272
|
+
const creation = await runTemplate(template_default, {
|
|
273
|
+
directory: resolvedDir,
|
|
274
|
+
mode: "setup",
|
|
275
|
+
options: {
|
|
276
|
+
cli,
|
|
277
|
+
contentLayer,
|
|
278
|
+
convex,
|
|
279
|
+
generator,
|
|
280
|
+
kind,
|
|
281
|
+
owner,
|
|
282
|
+
repository,
|
|
283
|
+
sea
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
spinner.stop("Project scaffolded!");
|
|
287
|
+
if (creation.suggestions?.length) prompts.note(creation.suggestions.join("\n"), "Next steps");
|
|
288
|
+
prompts.outro(`Created ${repository} at ${resolvedDir}`);
|
|
289
|
+
} catch (error) {
|
|
290
|
+
spinner.stop("Failed to scaffold project");
|
|
291
|
+
prompts.log.error(String(error));
|
|
292
|
+
process.exit(1);
|
|
293
|
+
}
|
|
294
|
+
//#endregion
|
|
295
|
+
export {};
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as bingo from "bingo";
|
|
2
|
+
import * as zod from "zod";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
//#region src/template.d.ts
|
|
6
|
+
declare const _default: bingo.Template<{
|
|
7
|
+
cli: zod.ZodOptional<zod.ZodBoolean>;
|
|
8
|
+
contentLayer: zod.ZodOptional<zod.ZodBoolean>;
|
|
9
|
+
convex: zod.ZodOptional<zod.ZodBoolean>;
|
|
10
|
+
generator: zod.ZodOptional<zod.ZodBoolean>;
|
|
11
|
+
kind: zod.ZodEnum<["react-router-spa", "react-router-ssr", "react-router-rsc", "ts-package"]>;
|
|
12
|
+
owner: zod.ZodString;
|
|
13
|
+
repository: zod.ZodString;
|
|
14
|
+
sea: zod.ZodOptional<zod.ZodBoolean>;
|
|
15
|
+
}, unknown>;
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/options.d.ts
|
|
18
|
+
declare const options: {
|
|
19
|
+
cli: z.ZodOptional<z.ZodBoolean>;
|
|
20
|
+
contentLayer: z.ZodOptional<z.ZodBoolean>;
|
|
21
|
+
convex: z.ZodOptional<z.ZodBoolean>;
|
|
22
|
+
generator: z.ZodOptional<z.ZodBoolean>;
|
|
23
|
+
kind: z.ZodEnum<["react-router-spa", "react-router-ssr", "react-router-rsc", "ts-package"]>;
|
|
24
|
+
owner: z.ZodString;
|
|
25
|
+
repository: z.ZodString;
|
|
26
|
+
sea: z.ZodOptional<z.ZodBoolean>;
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/context.d.ts
|
|
30
|
+
type Options = { [Key in keyof typeof options]: z.infer<(typeof options)[Key]> };
|
|
31
|
+
interface TemplateContext extends Options {
|
|
32
|
+
isSPA: boolean;
|
|
33
|
+
isSSR: boolean;
|
|
34
|
+
isRSC: boolean;
|
|
35
|
+
isPackage: boolean;
|
|
36
|
+
isReactRouter: boolean;
|
|
37
|
+
hasConvex: boolean;
|
|
38
|
+
hasContentLayer: boolean;
|
|
39
|
+
ssr: boolean;
|
|
40
|
+
}
|
|
41
|
+
declare function buildContext(opts: Options): TemplateContext;
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/index.d.ts
|
|
44
|
+
declare const NAME = "create-sprinkles";
|
|
45
|
+
//#endregion
|
|
46
|
+
export { NAME, type TemplateContext, buildContext, options, _default as template };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { createTemplate } from "bingo";
|
|
2
|
+
import { handlebars } from "bingo-handlebars";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
//#region src/context.ts
|
|
6
|
+
function buildContext(opts) {
|
|
7
|
+
const isSPA = opts.kind === "react-router-spa";
|
|
8
|
+
const isSSR = opts.kind === "react-router-ssr";
|
|
9
|
+
const isRSC = opts.kind === "react-router-rsc";
|
|
10
|
+
return {
|
|
11
|
+
...opts,
|
|
12
|
+
hasContentLayer: isRSC && Boolean(opts.contentLayer),
|
|
13
|
+
hasConvex: (isSPA || isSSR) && Boolean(opts.convex),
|
|
14
|
+
isPackage: opts.kind === "ts-package",
|
|
15
|
+
isRSC,
|
|
16
|
+
isReactRouter: opts.kind !== "ts-package",
|
|
17
|
+
isSPA,
|
|
18
|
+
isSSR,
|
|
19
|
+
ssr: isSSR || isRSC
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/merge.ts
|
|
24
|
+
function isDirectory(value) {
|
|
25
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
function mergeFiles(...layers) {
|
|
28
|
+
const result = {};
|
|
29
|
+
for (const layer of layers) if (layer) for (const [key, value] of Object.entries(layer)) {
|
|
30
|
+
const existing = result[key];
|
|
31
|
+
if (isDirectory(existing) && isDirectory(value)) result[key] = mergeFiles(existing, value);
|
|
32
|
+
else result[key] = value;
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/options.ts
|
|
38
|
+
const kind = z.enum([
|
|
39
|
+
"react-router-spa",
|
|
40
|
+
"react-router-ssr",
|
|
41
|
+
"react-router-rsc",
|
|
42
|
+
"ts-package"
|
|
43
|
+
]).describe("project kind");
|
|
44
|
+
const options = {
|
|
45
|
+
cli: z.boolean().optional(),
|
|
46
|
+
contentLayer: z.boolean().optional(),
|
|
47
|
+
convex: z.boolean().optional(),
|
|
48
|
+
generator: z.boolean().optional(),
|
|
49
|
+
kind,
|
|
50
|
+
owner: z.string().describe("GitHub owner or organization"),
|
|
51
|
+
repository: z.string().describe("repository name"),
|
|
52
|
+
sea: z.boolean().optional()
|
|
53
|
+
};
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/scripts.ts
|
|
56
|
+
function buildDependencyCommands(context) {
|
|
57
|
+
const commands = [];
|
|
58
|
+
commands.push("vp add -D @types/node @typescript/native-preview");
|
|
59
|
+
if (context.isReactRouter) {
|
|
60
|
+
commands.push("vp add react react-dom react-router");
|
|
61
|
+
commands.push("vp add -D @types/react @types/react-dom @react-router/dev @tailwindcss/vite tailwindcss vite-plugin-devtools-json");
|
|
62
|
+
commands.push("vp add -D @rolldown/plugin-babel @vitejs/plugin-react babel-plugin-react-compiler");
|
|
63
|
+
commands.push("vp add -D @cloudflare/vite-plugin wrangler");
|
|
64
|
+
commands.push("vp add -D eslint-plugin-perfectionist eslint-plugin-react-hooks");
|
|
65
|
+
}
|
|
66
|
+
if (context.isReactRouter) commands.push("vp add @react-router/node isbot");
|
|
67
|
+
if (context.hasConvex) commands.push("vp add convex @convex-dev/react-query @tanstack/react-query");
|
|
68
|
+
if (context.isRSC) commands.push("vp add -D @vitejs/plugin-rsc");
|
|
69
|
+
if (context.hasContentLayer) {
|
|
70
|
+
commands.push("vp add jsr:@std/jsonc jsr:@std/yaml gray-matter github-slugger @remix-run/data-schema");
|
|
71
|
+
commands.push("vp add -D @mdx-js/rollup");
|
|
72
|
+
}
|
|
73
|
+
if (context.isPackage) {
|
|
74
|
+
if (context.cli) commands.push("vp add @bomb.sh/args");
|
|
75
|
+
if (context.generator) commands.push("vp add bingo bingo-handlebars zod");
|
|
76
|
+
}
|
|
77
|
+
commands.push("vp add -D vite-plus vite@npm:@voidzero-dev/vite-plus-core@latest vitest@npm:@voidzero-dev/vite-plus-test@latest");
|
|
78
|
+
return commands;
|
|
79
|
+
}
|
|
80
|
+
function buildScripts(context) {
|
|
81
|
+
const scripts = [];
|
|
82
|
+
const phase0Commands = [...buildDependencyCommands(context), "vp install"];
|
|
83
|
+
scripts.push({
|
|
84
|
+
commands: phase0Commands,
|
|
85
|
+
phase: 0
|
|
86
|
+
});
|
|
87
|
+
if (context.isRSC) scripts.push({
|
|
88
|
+
commands: ["vpx wrangler types -c wrangler.rsc.jsonc"],
|
|
89
|
+
phase: 1
|
|
90
|
+
});
|
|
91
|
+
else if (context.isSSR) scripts.push({
|
|
92
|
+
commands: ["vpx wrangler types"],
|
|
93
|
+
phase: 1
|
|
94
|
+
});
|
|
95
|
+
scripts.push({
|
|
96
|
+
commands: ["vp fmt"],
|
|
97
|
+
phase: 2
|
|
98
|
+
});
|
|
99
|
+
if (context.hasConvex) scripts.push({
|
|
100
|
+
commands: ["vpx convex dev --once"],
|
|
101
|
+
phase: 3,
|
|
102
|
+
silent: true
|
|
103
|
+
});
|
|
104
|
+
scripts.push({
|
|
105
|
+
commands: [
|
|
106
|
+
"ln -sf AGENTS.md CLAUDE.md",
|
|
107
|
+
"git init",
|
|
108
|
+
"git add -A",
|
|
109
|
+
"git commit -m initial"
|
|
110
|
+
],
|
|
111
|
+
phase: 4
|
|
112
|
+
});
|
|
113
|
+
return scripts;
|
|
114
|
+
}
|
|
115
|
+
//#endregion
|
|
116
|
+
//#region src/suggestions.ts
|
|
117
|
+
function buildSuggestions(context) {
|
|
118
|
+
const suggestions = [];
|
|
119
|
+
if (context.hasConvex) suggestions.push("Open the Convex dashboard: https://dashboard.convex.dev");
|
|
120
|
+
if (context.isSSR || context.isRSC) suggestions.push("Log in to Cloudflare: vpx wrangler login");
|
|
121
|
+
if (context.isReactRouter) suggestions.push("Start the dev server: vp dev");
|
|
122
|
+
else suggestions.push("Start development: vp run dev");
|
|
123
|
+
return suggestions;
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region src/template.ts
|
|
127
|
+
const templatesDir = path.join(import.meta.dirname, "../templates");
|
|
128
|
+
const kindDirectories = {
|
|
129
|
+
"react-router-rsc": "react-router-rsc",
|
|
130
|
+
"react-router-spa": "react-router-spa",
|
|
131
|
+
"react-router-ssr": "react-router-ssr",
|
|
132
|
+
"ts-package": "ts-package"
|
|
133
|
+
};
|
|
134
|
+
async function tryHandlebars(dir, context) {
|
|
135
|
+
try {
|
|
136
|
+
return await handlebars(path.join(templatesDir, dir), context) || null;
|
|
137
|
+
} catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async function collectAddonLayers(context) {
|
|
142
|
+
const addons = [];
|
|
143
|
+
if (context.isPackage && context.cli) addons.push(await tryHandlebars("ts-package-cli", context));
|
|
144
|
+
if (context.isPackage && context.generator) addons.push(await tryHandlebars("ts-package-generator", context));
|
|
145
|
+
if (context.isPackage && context.sea) addons.push(await tryHandlebars("ts-package-sea", context));
|
|
146
|
+
if (context.hasConvex) addons.push(await tryHandlebars("react-router-convex", context));
|
|
147
|
+
if (context.isSSR && context.hasConvex) addons.push(await tryHandlebars("react-router-ssr-convex", context));
|
|
148
|
+
if (context.hasContentLayer) addons.push(await tryHandlebars("react-router-rsc-content-layer", context));
|
|
149
|
+
return addons;
|
|
150
|
+
}
|
|
151
|
+
async function buildLayers(context) {
|
|
152
|
+
const shared = await tryHandlebars("shared", context);
|
|
153
|
+
let reactShared = null;
|
|
154
|
+
if (context.isReactRouter) reactShared = await tryHandlebars("react-shared", context);
|
|
155
|
+
const kindDir = kindDirectories[context.kind];
|
|
156
|
+
let kindSpecific = null;
|
|
157
|
+
if (kindDir) kindSpecific = await tryHandlebars(kindDir, context);
|
|
158
|
+
const addons = await collectAddonLayers(context);
|
|
159
|
+
return mergeFiles(shared, reactShared, kindSpecific, ...addons);
|
|
160
|
+
}
|
|
161
|
+
var template_default = createTemplate({
|
|
162
|
+
about: {
|
|
163
|
+
description: "Get started with development by creating projects from templates quickly.",
|
|
164
|
+
name: NAME
|
|
165
|
+
},
|
|
166
|
+
options,
|
|
167
|
+
async produce({ options: opts }) {
|
|
168
|
+
const context = buildContext(opts);
|
|
169
|
+
return {
|
|
170
|
+
files: await buildLayers(context),
|
|
171
|
+
scripts: buildScripts(context),
|
|
172
|
+
suggestions: buildSuggestions(context)
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
//#endregion
|
|
177
|
+
//#region src/index.ts
|
|
178
|
+
const NAME = "create-sprinkles";
|
|
179
|
+
//#endregion
|
|
180
|
+
export { NAME, buildContext, options, template_default as template };
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-sprinkles",
|
|
3
|
+
"version": "0.2.3",
|
|
4
|
+
"description": "Get started with development by creating projects from templates quickly.",
|
|
5
|
+
"homepage": "https://github.com/withsprinkles/workbench#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/withsprinkles/workbench/issues"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"author": "Mark Malstrom <mark@malstrom.me>",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/withsprinkles/workbench.git"
|
|
14
|
+
},
|
|
15
|
+
"bin": {
|
|
16
|
+
"create-sprinkles": "dist/bin.mjs"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"templates"
|
|
21
|
+
],
|
|
22
|
+
"type": "module",
|
|
23
|
+
"types": "./dist/index.d.mts",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": "./dist/index.mjs",
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@bomb.sh/args": "^0.3.1",
|
|
30
|
+
"@clack/prompts": "^1.1.0",
|
|
31
|
+
"bingo": "^0.9.2",
|
|
32
|
+
"bingo-fs": "^0.5.6",
|
|
33
|
+
"bingo-handlebars": "^0.1.0",
|
|
34
|
+
"zod": "^3.25.76"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^25.3.5",
|
|
38
|
+
"@typescript/native-preview": "latest",
|
|
39
|
+
"bingo-testers": "^0.5.8",
|
|
40
|
+
"bumpp": "^10.4.1",
|
|
41
|
+
"vite-plus": "latest",
|
|
42
|
+
"vitest": "npm:@voidzero-dev/vite-plus-test@latest"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {}
|
|
45
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { defineSchema } from "convex/server";
|
|
2
|
+
|
|
3
|
+
export default defineSchema({
|
|
4
|
+
// Define your tables here
|
|
5
|
+
// Example:
|
|
6
|
+
// import { defineTable } from "convex/server";
|
|
7
|
+
// import { v } from "convex/values";
|
|
8
|
+
// messages: defineTable({ text: v.string() }),
|
|
9
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{{#if hasContentLayer}}
|
|
2
|
+
import { getCollection } from "sprinkles:content";
|
|
3
|
+
|
|
4
|
+
export async function ServerComponent() {
|
|
5
|
+
let posts = await getCollection("posts");
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<main>
|
|
9
|
+
<h1>Welcome to {{repository}}</h1>
|
|
10
|
+
<section>
|
|
11
|
+
<h2>Posts</h2>
|
|
12
|
+
<ul>
|
|
13
|
+
{posts.map((post) => (
|
|
14
|
+
<li key={post.id}>
|
|
15
|
+
<h3>{post.data.title}</h3>
|
|
16
|
+
</li>
|
|
17
|
+
))}
|
|
18
|
+
</ul>
|
|
19
|
+
</section>
|
|
20
|
+
</main>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
{{else}}
|
|
24
|
+
export async function ServerComponent() {
|
|
25
|
+
return (
|
|
26
|
+
<main>
|
|
27
|
+
<h1>Welcome to {{repository}}</h1>
|
|
28
|
+
</main>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
{{/if}}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { PropsWithChildren } from "react";
|
|
2
|
+
import { isRouteErrorResponse, Outlet } from "react-router";
|
|
3
|
+
|
|
4
|
+
import type { Route } from "./+types/root";
|
|
5
|
+
import styles from "./styles/tailwind.css?url";
|
|
6
|
+
|
|
7
|
+
export function Layout({ children }: PropsWithChildren) {
|
|
8
|
+
return (
|
|
9
|
+
<html lang="en">
|
|
10
|
+
<head>
|
|
11
|
+
<meta charSet="utf-8" />
|
|
12
|
+
<meta content="width=device-width, initial-scale=1" name="viewport" />
|
|
13
|
+
<meta content="#000000" name="theme-color" />
|
|
14
|
+
<link href={styles} rel="stylesheet" />
|
|
15
|
+
<title>{{repository}}</title>
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
{children}
|
|
19
|
+
</body>
|
|
20
|
+
</html>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function ServerComponent() {
|
|
25
|
+
return <Outlet />;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
|
|
29
|
+
let message = "Oops!";
|
|
30
|
+
let details = "An unexpected error occurred.";
|
|
31
|
+
let stack: string | undefined;
|
|
32
|
+
|
|
33
|
+
if (isRouteErrorResponse(error)) {
|
|
34
|
+
message = error.status === 404 ? "404" : "Error";
|
|
35
|
+
details =
|
|
36
|
+
error.status === 404
|
|
37
|
+
? "The requested page could not be found."
|
|
38
|
+
: error.statusText || details;
|
|
39
|
+
} else if (import.meta.env.DEV && error && error instanceof Error) {
|
|
40
|
+
details = error.message;
|
|
41
|
+
stack = error.stack;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<main>
|
|
46
|
+
<h1>{message}</h1>
|
|
47
|
+
<p>{details}</p>
|
|
48
|
+
{stack && (
|
|
49
|
+
<pre>
|
|
50
|
+
<code>{stack}</code>
|
|
51
|
+
</pre>
|
|
52
|
+
)}
|
|
53
|
+
</main>
|
|
54
|
+
);
|
|
55
|
+
}
|