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.
Files changed (98) hide show
  1. package/.changeset/2026-02-11-test-patch-bump.md +5 -0
  2. package/.changeset/README.md +10 -0
  3. package/.changeset/config.json +16 -0
  4. package/.cursor/rules/README.md +184 -0
  5. package/.cursor/rules/architecture.mdc +437 -0
  6. package/.cursor/rules/components.mdc +436 -0
  7. package/.cursor/rules/integrations.mdc +447 -0
  8. package/.cursor/rules/main.mdc +278 -0
  9. package/.cursor/rules/styling.mdc +433 -0
  10. package/.github/PULL_REQUEST_TEMPLATE.md +14 -0
  11. package/.github/workflows/.gitkeep +0 -0
  12. package/.github/workflows/ci.yml +37 -0
  13. package/.github/workflows/release.yml +54 -0
  14. package/.tldr/cache/call_graph.json +7 -0
  15. package/.tldr/languages.json +6 -0
  16. package/.tldr/status +1 -0
  17. package/.tldrignore +84 -0
  18. package/.vscode/extensions.json +20 -0
  19. package/.vscode/settings.json +98 -0
  20. package/CHANGELOG.md +13 -0
  21. package/CLAUDE.md +138 -0
  22. package/README.md +176 -0
  23. package/bin/index.js +262 -0
  24. package/biome.json +44 -0
  25. package/bun.lock +496 -0
  26. package/changelog/04-02-26.md +86 -0
  27. package/changelog/05-02-26.md +101 -0
  28. package/changelog/09-02-26.md +83 -0
  29. package/docs/fix-studio-hydration.md +46 -0
  30. package/docs/plans/2026-01-29-sanity-smart-merge-design.md +196 -0
  31. package/docs/plans/2026-01-29-sanity-smart-merge-implementation.md +695 -0
  32. package/docs/sanity-setup-steps.md +199 -0
  33. package/integrations/basehub/README.md +3 -0
  34. package/integrations/sanity/app/api/draft-mode/disable/route.ts +7 -0
  35. package/integrations/sanity/app/api/draft-mode/enable/route.ts +21 -0
  36. package/integrations/sanity/app/api/revalidate/route.ts +37 -0
  37. package/integrations/sanity/app/layout.tsx +111 -0
  38. package/integrations/sanity/app/sitemap.ts +80 -0
  39. package/integrations/sanity/app/studio/[[...tool]]/page.tsx +8 -0
  40. package/integrations/sanity/app/studio/layout.tsx +7 -0
  41. package/integrations/sanity/components/ui/sanity-image/index.tsx +37 -0
  42. package/integrations/sanity/lib/integrations/README.md +58 -0
  43. package/integrations/sanity/lib/integrations/check-integration.ts +62 -0
  44. package/integrations/sanity/lib/integrations/sanity/README.md +144 -0
  45. package/integrations/sanity/lib/integrations/sanity/client.ts +30 -0
  46. package/integrations/sanity/lib/integrations/sanity/components/disable-draft-mode.tsx +29 -0
  47. package/integrations/sanity/lib/integrations/sanity/components/rich-text.tsx +73 -0
  48. package/integrations/sanity/lib/integrations/sanity/env.ts +38 -0
  49. package/integrations/sanity/lib/integrations/sanity/live/index.tsx +34 -0
  50. package/integrations/sanity/lib/integrations/sanity/queries.ts +99 -0
  51. package/integrations/sanity/lib/integrations/sanity/sanity.cli.ts +20 -0
  52. package/integrations/sanity/lib/integrations/sanity/sanity.config.ts +94 -0
  53. package/integrations/sanity/lib/integrations/sanity/sanity.types.ts +337 -0
  54. package/integrations/sanity/lib/integrations/sanity/schema.json +1850 -0
  55. package/integrations/sanity/lib/integrations/sanity/schemas/article.ts +132 -0
  56. package/integrations/sanity/lib/integrations/sanity/schemas/example.ts +203 -0
  57. package/integrations/sanity/lib/integrations/sanity/schemas/index.ts +37 -0
  58. package/integrations/sanity/lib/integrations/sanity/schemas/link.ts +127 -0
  59. package/integrations/sanity/lib/integrations/sanity/schemas/metadata.ts +68 -0
  60. package/integrations/sanity/lib/integrations/sanity/schemas/navigation.ts +39 -0
  61. package/integrations/sanity/lib/integrations/sanity/schemas/page.ts +77 -0
  62. package/integrations/sanity/lib/integrations/sanity/schemas/richText.ts +59 -0
  63. package/integrations/sanity/lib/integrations/sanity/structure.ts +5 -0
  64. package/integrations/sanity/lib/integrations/sanity/utils/image.ts +11 -0
  65. package/integrations/sanity/lib/integrations/sanity/utils/link.ts +61 -0
  66. package/integrations/sanity/lib/scripts/copy-sanity-mcp.ts +23 -0
  67. package/integrations/sanity/lib/scripts/generate-page.ts +310 -0
  68. package/integrations/sanity/lib/utils/metadata.ts +190 -0
  69. package/layers/experiment/components/layout/header/index.tsx +58 -0
  70. package/layers/experiment/components/layout/navigation-menu.tsx +127 -0
  71. package/layers/experiment/lib/constants.ts +12 -0
  72. package/layers/webgl/app/page.tsx +10 -0
  73. package/layers/webgl/components/webgl/canvas/dynamic.tsx +34 -0
  74. package/layers/webgl/components/webgl/canvas/index.tsx +43 -0
  75. package/layers/webgl/components/webgl/components/scene/index.tsx +21 -0
  76. package/layers/webgpu/.gitkeep +0 -0
  77. package/package.json +44 -0
  78. package/plugins/README.md +21 -0
  79. package/plugins/no-anchor-element.grit +11 -0
  80. package/plugins/no-relative-parent-imports.grit +6 -0
  81. package/plugins/no-unnecessary-forwardref.grit +5 -0
  82. package/src/commands/add-integration.js +325 -0
  83. package/src/commands/create.js +415 -0
  84. package/src/commands/setup-sanity.js +426 -0
  85. package/src/commands/worktree.js +805 -0
  86. package/src/mergers/check-integration-merger.js +105 -0
  87. package/src/mergers/config.js +137 -0
  88. package/src/mergers/index.js +355 -0
  89. package/src/mergers/layout-merger.js +223 -0
  90. package/src/mergers/next-config-merger.js +63 -0
  91. package/src/mergers/sitemap-merger.js +121 -0
  92. package/tasks/prd-next-starter-dynamic-layers.md +184 -0
  93. package/tasks/prd.json +153 -0
  94. package/tasks/progress.txt +115 -0
  95. package/template-hooks/use-battery.ts +126 -0
  96. package/template-hooks/use-device-perf.ts +184 -0
  97. package/template-hooks/use-intersection-observer.ts +32 -0
  98. 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
+ }