bsmnt 0.0.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/.changeset/2026-02-11-test-patch-bump.md +5 -0
- package/.changeset/README.md +10 -0
- package/.changeset/config.json +16 -0
- package/.cursor/rules/README.md +184 -0
- package/.cursor/rules/architecture.mdc +437 -0
- package/.cursor/rules/components.mdc +436 -0
- package/.cursor/rules/integrations.mdc +447 -0
- package/.cursor/rules/main.mdc +278 -0
- package/.cursor/rules/styling.mdc +433 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +14 -0
- package/.github/workflows/.gitkeep +0 -0
- package/.github/workflows/ci.yml +37 -0
- package/.github/workflows/release.yml +54 -0
- package/.tldr/cache/call_graph.json +7 -0
- package/.tldr/languages.json +6 -0
- package/.tldr/status +1 -0
- package/.tldrignore +84 -0
- package/.vscode/extensions.json +20 -0
- package/.vscode/settings.json +98 -0
- package/CHANGELOG.md +13 -0
- package/CLAUDE.md +138 -0
- package/README.md +176 -0
- package/bin/index.js +262 -0
- package/biome.json +44 -0
- package/bun.lock +496 -0
- package/changelog/04-02-26.md +86 -0
- package/changelog/05-02-26.md +101 -0
- package/changelog/09-02-26.md +83 -0
- package/docs/fix-studio-hydration.md +46 -0
- package/docs/plans/2026-01-29-sanity-smart-merge-design.md +196 -0
- package/docs/plans/2026-01-29-sanity-smart-merge-implementation.md +695 -0
- package/docs/sanity-setup-steps.md +199 -0
- package/integrations/basehub/README.md +3 -0
- package/integrations/sanity/app/api/draft-mode/disable/route.ts +7 -0
- package/integrations/sanity/app/api/draft-mode/enable/route.ts +21 -0
- package/integrations/sanity/app/api/revalidate/route.ts +37 -0
- package/integrations/sanity/app/layout.tsx +111 -0
- package/integrations/sanity/app/sitemap.ts +80 -0
- package/integrations/sanity/app/studio/[[...tool]]/page.tsx +8 -0
- package/integrations/sanity/app/studio/layout.tsx +7 -0
- package/integrations/sanity/components/ui/sanity-image/index.tsx +37 -0
- package/integrations/sanity/lib/integrations/README.md +58 -0
- package/integrations/sanity/lib/integrations/check-integration.ts +62 -0
- package/integrations/sanity/lib/integrations/sanity/README.md +144 -0
- package/integrations/sanity/lib/integrations/sanity/client.ts +30 -0
- package/integrations/sanity/lib/integrations/sanity/components/disable-draft-mode.tsx +29 -0
- package/integrations/sanity/lib/integrations/sanity/components/rich-text.tsx +73 -0
- package/integrations/sanity/lib/integrations/sanity/env.ts +38 -0
- package/integrations/sanity/lib/integrations/sanity/live/index.tsx +34 -0
- package/integrations/sanity/lib/integrations/sanity/queries.ts +99 -0
- package/integrations/sanity/lib/integrations/sanity/sanity.cli.ts +20 -0
- package/integrations/sanity/lib/integrations/sanity/sanity.config.ts +94 -0
- package/integrations/sanity/lib/integrations/sanity/sanity.types.ts +337 -0
- package/integrations/sanity/lib/integrations/sanity/schema.json +1850 -0
- package/integrations/sanity/lib/integrations/sanity/schemas/article.ts +132 -0
- package/integrations/sanity/lib/integrations/sanity/schemas/example.ts +203 -0
- package/integrations/sanity/lib/integrations/sanity/schemas/index.ts +37 -0
- package/integrations/sanity/lib/integrations/sanity/schemas/link.ts +127 -0
- package/integrations/sanity/lib/integrations/sanity/schemas/metadata.ts +68 -0
- package/integrations/sanity/lib/integrations/sanity/schemas/navigation.ts +39 -0
- package/integrations/sanity/lib/integrations/sanity/schemas/page.ts +77 -0
- package/integrations/sanity/lib/integrations/sanity/schemas/richText.ts +59 -0
- package/integrations/sanity/lib/integrations/sanity/structure.ts +5 -0
- package/integrations/sanity/lib/integrations/sanity/utils/image.ts +11 -0
- package/integrations/sanity/lib/integrations/sanity/utils/link.ts +61 -0
- package/integrations/sanity/lib/scripts/copy-sanity-mcp.ts +23 -0
- package/integrations/sanity/lib/scripts/generate-page.ts +310 -0
- package/integrations/sanity/lib/utils/metadata.ts +190 -0
- package/layers/experiment/components/layout/header/index.tsx +58 -0
- package/layers/experiment/components/layout/navigation-menu.tsx +127 -0
- package/layers/experiment/lib/constants.ts +12 -0
- package/layers/webgl/app/page.tsx +10 -0
- package/layers/webgl/components/webgl/canvas/dynamic.tsx +34 -0
- package/layers/webgl/components/webgl/canvas/index.tsx +43 -0
- package/layers/webgl/components/webgl/components/scene/index.tsx +21 -0
- package/layers/webgpu/.gitkeep +0 -0
- package/package.json +44 -0
- package/plugins/README.md +21 -0
- package/plugins/no-anchor-element.grit +11 -0
- package/plugins/no-relative-parent-imports.grit +6 -0
- package/plugins/no-unnecessary-forwardref.grit +5 -0
- package/src/commands/add-integration.js +325 -0
- package/src/commands/create.js +415 -0
- package/src/commands/setup-sanity.js +426 -0
- package/src/commands/worktree.js +805 -0
- package/src/mergers/check-integration-merger.js +105 -0
- package/src/mergers/config.js +137 -0
- package/src/mergers/index.js +355 -0
- package/src/mergers/layout-merger.js +223 -0
- package/src/mergers/next-config-merger.js +63 -0
- package/src/mergers/sitemap-merger.js +121 -0
- package/tasks/prd-next-starter-dynamic-layers.md +184 -0
- package/tasks/prd.json +153 -0
- package/tasks/progress.txt +115 -0
- package/template-hooks/use-battery.ts +126 -0
- package/template-hooks/use-device-perf.ts +184 -0
- package/template-hooks/use-intersection-observer.ts +32 -0
- package/template-hooks/use-media.ts +33 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import dotenv from "dotenv";
|
|
5
|
+
import fs from "fs-extra";
|
|
6
|
+
import ora from "ora";
|
|
7
|
+
import pc from "picocolors";
|
|
8
|
+
import prompts from "prompts";
|
|
9
|
+
import tiged from "tiged";
|
|
10
|
+
import { getLayerConfig } from "../mergers/config.js";
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = path.dirname(__filename);
|
|
14
|
+
|
|
15
|
+
dotenv.config({ path: path.resolve(__dirname, "../../.env") });
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates a new project by downloading a template from GitHub.
|
|
19
|
+
*/
|
|
20
|
+
export async function createProject(projectName, options = {}) {
|
|
21
|
+
// 1. Interactive Prompts (skip if flag provided)
|
|
22
|
+
const answers = await prompts([
|
|
23
|
+
{
|
|
24
|
+
type: projectName ? null : "text",
|
|
25
|
+
name: "projectName",
|
|
26
|
+
message: "What is the name of your project?",
|
|
27
|
+
initial: "my-basement-app",
|
|
28
|
+
validate: (value) => {
|
|
29
|
+
if (/^([A-Za-z\-_\d])+$/.test(value)) return true;
|
|
30
|
+
return "Project name may only include letters, numbers, underscores and hashes.";
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
type: options.template ? null : "select",
|
|
35
|
+
name: "projectType",
|
|
36
|
+
message: "What type of project are you building?",
|
|
37
|
+
choices: [
|
|
38
|
+
{ title: "Default (Next.js Starter)", value: "default" },
|
|
39
|
+
{ title: "WebGL (3D Graphics)", value: "webgl" },
|
|
40
|
+
{ title: "WebGPU (Next-gen Graphics)", value: "webgpu" },
|
|
41
|
+
{ title: "Experiment (Creative Coding)", value: "experiment" },
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
type: options.cms ? null : "select",
|
|
46
|
+
name: "cms",
|
|
47
|
+
message: "Which CMS will you use?",
|
|
48
|
+
choices: [
|
|
49
|
+
{ title: "Sanity", value: "sanity" },
|
|
50
|
+
{ title: "BaseHub", value: "basehub" },
|
|
51
|
+
{ title: "None", value: "none" },
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
type: options.animation ? null : "select",
|
|
56
|
+
name: "animation",
|
|
57
|
+
message: "Which animation library do you prefer?",
|
|
58
|
+
choices: [
|
|
59
|
+
{ title: "GSAP", value: "gsap" },
|
|
60
|
+
{ title: "Framer Motion", value: "motion" },
|
|
61
|
+
{ title: "None", value: "none" },
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
type: options.agent ? null : "select",
|
|
66
|
+
name: "agent",
|
|
67
|
+
message: "Which agent are you using?",
|
|
68
|
+
choices: [
|
|
69
|
+
{ title: "OpenCode", value: "opencode" },
|
|
70
|
+
{ title: "Claude Code", value: "claude" },
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
type: options.hooks !== undefined ? null : "multiselect",
|
|
75
|
+
name: "hooks",
|
|
76
|
+
message: "Select hooks you want to add:",
|
|
77
|
+
choices: async () => {
|
|
78
|
+
const hooksPath = path.join(__dirname, "..", "..", "template-hooks");
|
|
79
|
+
const files = await fs.readdir(hooksPath);
|
|
80
|
+
return files
|
|
81
|
+
.filter((f) => f.endsWith(".ts"))
|
|
82
|
+
.map((f) => ({ title: f.replace(".ts", ""), value: f }));
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
const name = projectName || answers.projectName;
|
|
88
|
+
const type = options.template || answers.projectType;
|
|
89
|
+
const cms = options.cms || answers.cms;
|
|
90
|
+
const animation = options.animation || answers.animation;
|
|
91
|
+
const agent = options.agent || answers.agent;
|
|
92
|
+
const hooks = options.hooks !== undefined ? options.hooks : answers.hooks;
|
|
93
|
+
|
|
94
|
+
if (!name || !type) {
|
|
95
|
+
console.log(pc.yellow("\n⚠ Operation cancelled.\n"));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const targetDir = path.join(process.cwd(), name);
|
|
100
|
+
|
|
101
|
+
// 2. Directory Check
|
|
102
|
+
if (fs.existsSync(targetDir)) {
|
|
103
|
+
const { overwrite } = await prompts({
|
|
104
|
+
type: "confirm",
|
|
105
|
+
name: "overwrite",
|
|
106
|
+
message: `Directory ${pc.cyan(name)} already exists. Overwrite?`,
|
|
107
|
+
initial: false,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (!overwrite) {
|
|
111
|
+
console.log(pc.yellow("⚠ Action cancelled."));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
await fs.remove(targetDir);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 3. Download from GitHub
|
|
118
|
+
const downloadSpinner = ora(
|
|
119
|
+
`Downloading next-starter from GitHub...`,
|
|
120
|
+
).start();
|
|
121
|
+
|
|
122
|
+
const repoPath = `github:basementstudio/next-starter#main`;
|
|
123
|
+
const emitter = tiged(repoPath, {
|
|
124
|
+
cache: false,
|
|
125
|
+
force: true,
|
|
126
|
+
verbose: true,
|
|
127
|
+
mode: "git",
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Diagnostic logging
|
|
131
|
+
emitter.on("info", (info) => {
|
|
132
|
+
downloadSpinner.text = pc.dim(`[tiged] ${info.message}`);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
emitter.on("warn", (warning) => {
|
|
136
|
+
console.warn(pc.yellow(`\n⚠ [tiged] ${warning.message}`));
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
await emitter.clone(targetDir);
|
|
141
|
+
|
|
142
|
+
// Delete bun.lock since dependencies will be modified by layers/CMS/animation
|
|
143
|
+
const bunLockPath = path.join(targetDir, "bun.lock");
|
|
144
|
+
if (fs.existsSync(bunLockPath)) {
|
|
145
|
+
await fs.remove(bunLockPath);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
downloadSpinner.succeed(pc.green("Template downloaded."));
|
|
149
|
+
|
|
150
|
+
// 3.2. Inject Technology Layer
|
|
151
|
+
if (type && type !== "default") {
|
|
152
|
+
const layerSpinner = ora(`Applying ${pc.cyan(type)} layer...`).start();
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
const { injectLayer, formatMergeResults } = await import(
|
|
156
|
+
"../mergers/index.js"
|
|
157
|
+
);
|
|
158
|
+
const layerResults = await injectLayer(targetDir, type, layerSpinner);
|
|
159
|
+
|
|
160
|
+
const hasErrors = layerResults.failed.length > 0;
|
|
161
|
+
const resultLines = formatMergeResults(layerResults);
|
|
162
|
+
|
|
163
|
+
if (hasErrors) {
|
|
164
|
+
layerSpinner.warn(pc.yellow(`${type} layer applied with warnings:`));
|
|
165
|
+
} else {
|
|
166
|
+
layerSpinner.succeed(pc.green(`${type} layer applied:`));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
for (const line of resultLines) {
|
|
170
|
+
console.log(pc.dim(line));
|
|
171
|
+
}
|
|
172
|
+
} catch (e) {
|
|
173
|
+
console.warn(
|
|
174
|
+
pc.yellow(`\n⚠ Failed to apply ${type} layer: ${e.message}`),
|
|
175
|
+
);
|
|
176
|
+
console.log(pc.dim(" Continuing with base template..."));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 3.5. Inject Integration Files (Smart Merge)
|
|
181
|
+
if (cms && cms !== "none") {
|
|
182
|
+
const integrationSpinner = ora(`Integrating ${pc.cyan(cms)}...`).start();
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const { injectIntegration, formatMergeResults } = await import(
|
|
186
|
+
"../mergers/index.js"
|
|
187
|
+
);
|
|
188
|
+
const results = await injectIntegration(
|
|
189
|
+
targetDir,
|
|
190
|
+
cms,
|
|
191
|
+
integrationSpinner,
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
const hasErrors = results.failed.length > 0;
|
|
195
|
+
const resultLines = formatMergeResults(results);
|
|
196
|
+
|
|
197
|
+
if (hasErrors) {
|
|
198
|
+
integrationSpinner.warn(
|
|
199
|
+
pc.yellow(`${cms} integration completed with warnings:`),
|
|
200
|
+
);
|
|
201
|
+
} else {
|
|
202
|
+
integrationSpinner.succeed(pc.green(`${cms} integration added:`));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Display detailed results
|
|
206
|
+
for (const line of resultLines) {
|
|
207
|
+
console.log(pc.dim(line));
|
|
208
|
+
}
|
|
209
|
+
} catch (e) {
|
|
210
|
+
integrationSpinner.warn(
|
|
211
|
+
pc.yellow(`Failed to inject ${cms} integration: ${e.message}`),
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// 4. Hydration: Update package.json
|
|
217
|
+
const configSpinner = ora("Configuring project...").start();
|
|
218
|
+
const finalPkgPath = path.join(targetDir, "package.json");
|
|
219
|
+
|
|
220
|
+
if (fs.existsSync(finalPkgPath)) {
|
|
221
|
+
const pkg = await fs.readJson(finalPkgPath);
|
|
222
|
+
pkg.name = name;
|
|
223
|
+
pkg.version = "0.1.0";
|
|
224
|
+
|
|
225
|
+
// Inject Layer Dependencies from LAYER_CONFIG
|
|
226
|
+
const layerConfig = getLayerConfig(type);
|
|
227
|
+
if (layerConfig) {
|
|
228
|
+
pkg.dependencies = {
|
|
229
|
+
...pkg.dependencies,
|
|
230
|
+
...layerConfig.dependencies,
|
|
231
|
+
};
|
|
232
|
+
pkg.devDependencies = {
|
|
233
|
+
...pkg.devDependencies,
|
|
234
|
+
...layerConfig.devDependencies,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Inject CMS Dependencies
|
|
239
|
+
if (cms === "sanity") {
|
|
240
|
+
pkg.dependencies = {
|
|
241
|
+
...pkg.dependencies,
|
|
242
|
+
"@sanity/asset-utils": "^2.3.0",
|
|
243
|
+
"@sanity/image-url": "^2.0.3",
|
|
244
|
+
"@sanity/visual-editing": "^5.1.1",
|
|
245
|
+
"@sanity/vision": "^5.7.0",
|
|
246
|
+
"next-sanity": "^12.0.14",
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
pkg.devDependencies = {
|
|
250
|
+
...pkg.devDependencies,
|
|
251
|
+
sanity: "^5.6.0",
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
pkg.scripts = {
|
|
255
|
+
...pkg.scripts,
|
|
256
|
+
"sanity:extract":
|
|
257
|
+
"cd lib/integrations/sanity && bun --env-file ../../../.env.local sanity schema extract",
|
|
258
|
+
"sanity:typegen":
|
|
259
|
+
"cd lib/integrations/sanity && bun --env-file ../../../.env.local sanity typegen generate && bun biome check --write --unsafe",
|
|
260
|
+
};
|
|
261
|
+
} else if (cms === "basehub") {
|
|
262
|
+
pkg.dependencies = {
|
|
263
|
+
...pkg.dependencies,
|
|
264
|
+
basehub: "^3.0.0",
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Inject Animation Dependencies
|
|
269
|
+
if (animation === "gsap") {
|
|
270
|
+
pkg.dependencies = {
|
|
271
|
+
...pkg.dependencies,
|
|
272
|
+
gsap: "^3.12.0",
|
|
273
|
+
"@gsap/react": "^2.0.0",
|
|
274
|
+
};
|
|
275
|
+
} else if (animation === "motion") {
|
|
276
|
+
pkg.dependencies = {
|
|
277
|
+
...pkg.dependencies,
|
|
278
|
+
motion: "^12.0.0",
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
await fs.writeJson(finalPkgPath, pkg, { spaces: 2 });
|
|
283
|
+
}
|
|
284
|
+
configSpinner.succeed(pc.green("Project configured."));
|
|
285
|
+
|
|
286
|
+
// 5. Agent Empowerment
|
|
287
|
+
const agentArg = agent === "opencode" ? "opencode" : "claude-code";
|
|
288
|
+
const agentFolder = agent === "opencode" ? ".opencode" : ".claude";
|
|
289
|
+
|
|
290
|
+
const skillsSpinner = ora(
|
|
291
|
+
`Adding skills to your super basement agents...`,
|
|
292
|
+
).start();
|
|
293
|
+
|
|
294
|
+
// Create skills folder
|
|
295
|
+
const skillsPath = path.join(targetDir, agentFolder, "skills");
|
|
296
|
+
await fs.ensureDir(skillsPath);
|
|
297
|
+
await fs.writeFile(path.join(skillsPath, ".gitkeep"), "");
|
|
298
|
+
|
|
299
|
+
const skillsCommands = [
|
|
300
|
+
`bunx skills add vercel-labs/agent-skills --skill vercel-react-best-practices --skill web-design-guidelines -a ${agentArg} -y`,
|
|
301
|
+
`bunx skills add anthropics/skills --skill frontend-design -a ${agentArg} -y`,
|
|
302
|
+
`bunx skills add vercel-labs/next-skills --skill next-best-practices -a ${agentArg} -y`,
|
|
303
|
+
];
|
|
304
|
+
|
|
305
|
+
if (type === "webgpu" || type === "experiment") {
|
|
306
|
+
skillsCommands.push(
|
|
307
|
+
`bunx skills add https://github.com/dgreenheck/webgpu-claude-skill --skill webgpu-threejs-tsl -a ${agentArg} -y`,
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (cms === "sanity") {
|
|
312
|
+
skillsCommands.push(
|
|
313
|
+
`bunx skills add sanity-io/agent-toolkit -a ${agentArg} -y`,
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
try {
|
|
318
|
+
execSync(skillsCommands.join(" && "), {
|
|
319
|
+
cwd: targetDir,
|
|
320
|
+
stdio: "ignore",
|
|
321
|
+
});
|
|
322
|
+
skillsSpinner.succeed(pc.green("Agent skills installed."));
|
|
323
|
+
} catch (_e) {
|
|
324
|
+
skillsSpinner.warn(
|
|
325
|
+
pc.yellow("Some skills failed to install. Check your connection."),
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// 5.5. Automated Sanity Project Setup
|
|
330
|
+
let sanitySetupDone = false;
|
|
331
|
+
if (cms === "sanity") {
|
|
332
|
+
try {
|
|
333
|
+
const { setupSanityProject } = await import("./setup-sanity.js");
|
|
334
|
+
sanitySetupDone = await setupSanityProject(targetDir, {
|
|
335
|
+
projectName: name,
|
|
336
|
+
});
|
|
337
|
+
} catch (e) {
|
|
338
|
+
console.log(pc.yellow(`\n Sanity auto-setup skipped: ${e.message}`));
|
|
339
|
+
console.log(pc.dim(" You can set up Sanity manually later.\n"));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// 5.6. Register Sanity MCP Server (Claude Code only)
|
|
344
|
+
if (cms === "sanity" && agent === "claude") {
|
|
345
|
+
const mcpSpinner = ora("Registering Sanity MCP server...").start();
|
|
346
|
+
try {
|
|
347
|
+
execSync(
|
|
348
|
+
"claude mcp add Sanity -t http https://mcp.sanity.io --scope user",
|
|
349
|
+
{
|
|
350
|
+
stdio: "pipe",
|
|
351
|
+
},
|
|
352
|
+
);
|
|
353
|
+
mcpSpinner.succeed(pc.green("Sanity MCP server registered."));
|
|
354
|
+
} catch (e) {
|
|
355
|
+
const stderr = e.stderr?.toString() || "";
|
|
356
|
+
if (stderr.includes("already exists")) {
|
|
357
|
+
mcpSpinner.succeed(pc.green("Sanity MCP server already registered."));
|
|
358
|
+
} else {
|
|
359
|
+
mcpSpinner.warn(
|
|
360
|
+
pc.yellow(
|
|
361
|
+
"Could not register Sanity MCP server. Run manually: claude mcp add Sanity -t http https://mcp.sanity.io --scope user",
|
|
362
|
+
),
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// 6. Add Hooks
|
|
369
|
+
if (hooks && hooks.length > 0) {
|
|
370
|
+
const hooksSpinner = ora("Adding selected hooks...").start();
|
|
371
|
+
const projectHooksPath = path.join(targetDir, "hooks");
|
|
372
|
+
await fs.ensureDir(projectHooksPath);
|
|
373
|
+
|
|
374
|
+
for (const hookFile of hooks) {
|
|
375
|
+
const sourcePath = path.join(
|
|
376
|
+
__dirname,
|
|
377
|
+
"..",
|
|
378
|
+
"..",
|
|
379
|
+
"template-hooks",
|
|
380
|
+
hookFile,
|
|
381
|
+
);
|
|
382
|
+
await fs.copy(sourcePath, path.join(projectHooksPath, hookFile));
|
|
383
|
+
}
|
|
384
|
+
hooksSpinner.succeed(pc.green(`${hooks.length} hooks added.`));
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// 7. Final Success Message
|
|
388
|
+
console.log(
|
|
389
|
+
`\n${pc.bold(pc.green("✅ Success!"))} Project ${pc.cyan(name)} created with agent skills.`,
|
|
390
|
+
);
|
|
391
|
+
console.log(`\nNext steps:`);
|
|
392
|
+
console.log(pc.cyan(` cd ${name}`));
|
|
393
|
+
console.log(pc.cyan(` bun install`));
|
|
394
|
+
|
|
395
|
+
if (cms === "sanity" && !sanitySetupDone) {
|
|
396
|
+
console.log(pc.cyan(` # Set up Sanity credentials:`));
|
|
397
|
+
console.log(pc.dim(` # Add to .env.local:`));
|
|
398
|
+
console.log(
|
|
399
|
+
pc.dim(` # NEXT_PUBLIC_SANITY_PROJECT_ID=your-project-id`),
|
|
400
|
+
);
|
|
401
|
+
console.log(pc.dim(` # NEXT_PUBLIC_SANITY_DATASET=production`));
|
|
402
|
+
console.log(pc.dim(` # SANITY_API_READ_TOKEN=your-read-token`));
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
console.log(pc.cyan(` bun dev\n`));
|
|
406
|
+
} catch (error) {
|
|
407
|
+
downloadSpinner.fail(pc.red("Failed to create project."));
|
|
408
|
+
console.error(pc.dim(`\nError details: ${error.message}`));
|
|
409
|
+
console.log(pc.yellow("\n💡 Troubleshooting:"));
|
|
410
|
+
console.log(`1. Ensure you have a stable internet connection.`);
|
|
411
|
+
console.log(`2. Verify the repo exists: basementstudio/next-starter`);
|
|
412
|
+
console.log(`3. Branch "main" must exist in the repository.`);
|
|
413
|
+
process.exit(1);
|
|
414
|
+
}
|
|
415
|
+
}
|