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,695 @@
|
|
|
1
|
+
# Sanity Smart Merge Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
+
|
|
5
|
+
**Goal:** Replace the blind tiged overlay with a smart merge system that preserves template-specific code while adding Sanity integration.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Clone integration to temp directory, then process files in two modes: (1) additive files are copied directly, (2) merge files are intelligently combined using string manipulation to inject Sanity-specific imports, variables, and components.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Node.js, fs-extra (already installed), no new dependencies needed (using string manipulation instead of ts-morph for simplicity).
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Task 1: Create Merger Config
|
|
14
|
+
|
|
15
|
+
**Files:**
|
|
16
|
+
- Create: `src/mergers/config.js`
|
|
17
|
+
|
|
18
|
+
**Step 1: Create the mergers directory**
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
mkdir -p src/mergers
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Step 2: Write the config file**
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
// src/mergers/config.js
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Files that need smart merging (exist in both template and integration)
|
|
31
|
+
*/
|
|
32
|
+
export const MERGE_FILES = [
|
|
33
|
+
'app/layout.tsx',
|
|
34
|
+
'app/sitemap.ts',
|
|
35
|
+
'lib/integrations/check-integration.ts',
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Paths that are purely additive (copy directly, no conflict)
|
|
40
|
+
*/
|
|
41
|
+
export const ADDITIVE_PATHS = [
|
|
42
|
+
'lib/integrations/sanity',
|
|
43
|
+
'components/ui/sanity-image',
|
|
44
|
+
'app/api/draft-mode',
|
|
45
|
+
'lib/utils/metadata.ts',
|
|
46
|
+
'lib/scripts/generate-page.ts',
|
|
47
|
+
'lib/integrations/README.md',
|
|
48
|
+
]
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Step 3: Commit**
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
git add src/mergers/config.js
|
|
55
|
+
git commit -m "feat(mergers): add file classification config"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Task 2: Create Layout Merger
|
|
61
|
+
|
|
62
|
+
**Files:**
|
|
63
|
+
- Create: `src/mergers/layout-merger.js`
|
|
64
|
+
|
|
65
|
+
**Step 1: Write the layout merger**
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
// src/mergers/layout-merger.js
|
|
69
|
+
import fs from 'fs-extra'
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Sanity-specific imports to inject into layout
|
|
73
|
+
*/
|
|
74
|
+
const SANITY_IMPORTS = `import { draftMode } from "next/headers"
|
|
75
|
+
import { VisualEditing } from "next-sanity/visual-editing"
|
|
76
|
+
import { isSanityConfigured } from "@/lib/integrations/check-integration"
|
|
77
|
+
import { SanityLive } from "@/lib/integrations/sanity/live"`
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Sanity-specific variables to inject before return statement
|
|
81
|
+
*/
|
|
82
|
+
const SANITY_VARIABLES = ` const { isEnabled: isDraftMode } = await draftMode()
|
|
83
|
+
const sanityConfigured = isSanityConfigured()
|
|
84
|
+
`
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Sanity components to inject after {children}
|
|
88
|
+
*/
|
|
89
|
+
const SANITY_COMPONENTS = `
|
|
90
|
+
{/* Sanity Visual Editing - only when draft mode is enabled */}
|
|
91
|
+
{sanityConfigured && isDraftMode && (
|
|
92
|
+
<Suspense fallback={null}>
|
|
93
|
+
<VisualEditing />
|
|
94
|
+
<SanityLive />
|
|
95
|
+
</Suspense>
|
|
96
|
+
)}`
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Merge Sanity integration into template layout
|
|
100
|
+
* @param {string} templatePath - Path to the template's layout.tsx
|
|
101
|
+
*/
|
|
102
|
+
export async function mergeLayout(templatePath) {
|
|
103
|
+
let content = await fs.readFile(templatePath, 'utf-8')
|
|
104
|
+
|
|
105
|
+
// Skip if already has Sanity integration
|
|
106
|
+
if (content.includes('isSanityConfigured')) {
|
|
107
|
+
return { skipped: true, reason: 'Already has Sanity integration' }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 1. Add Sanity imports after existing imports
|
|
111
|
+
// Find the last import statement
|
|
112
|
+
const importMatches = [...content.matchAll(/^import .+$/gm)]
|
|
113
|
+
if (importMatches.length > 0) {
|
|
114
|
+
const lastImport = importMatches[importMatches.length - 1]
|
|
115
|
+
const insertPos = lastImport.index + lastImport[0].length
|
|
116
|
+
content = content.slice(0, insertPos) + '\n' + SANITY_IMPORTS + content.slice(insertPos)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 2. Add Sanity variables before the return statement in the Layout function
|
|
120
|
+
// Find "export default async function Layout" or similar and its return statement
|
|
121
|
+
const returnMatch = content.match(/export default async function \w+[^{]*\{[\s\S]*?(return\s*\()/m)
|
|
122
|
+
if (returnMatch) {
|
|
123
|
+
const returnIndex = content.indexOf(returnMatch[1], returnMatch.index)
|
|
124
|
+
content = content.slice(0, returnIndex) + SANITY_VARIABLES + '\n ' + content.slice(returnIndex)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 3. Add Sanity components after {children}
|
|
128
|
+
// Find {children} inside <body> and add Sanity components after it
|
|
129
|
+
const childrenMatch = content.match(/(\{children\})([\s\S]*?)(<\/body>)/m)
|
|
130
|
+
if (childrenMatch) {
|
|
131
|
+
content = content.replace(
|
|
132
|
+
/(\{children\})([\s\S]*?)(<\/body>)/m,
|
|
133
|
+
`$1${SANITY_COMPONENTS}$2$3`
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
await fs.writeFile(templatePath, content)
|
|
138
|
+
return { success: true }
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Step 2: Commit**
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
git add src/mergers/layout-merger.js
|
|
146
|
+
git commit -m "feat(mergers): add layout merger for Sanity integration"
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Task 3: Create Sitemap Merger
|
|
152
|
+
|
|
153
|
+
**Files:**
|
|
154
|
+
- Create: `src/mergers/sitemap-merger.js`
|
|
155
|
+
|
|
156
|
+
**Step 1: Write the sitemap merger**
|
|
157
|
+
|
|
158
|
+
```js
|
|
159
|
+
// src/mergers/sitemap-merger.js
|
|
160
|
+
import fs from 'fs-extra'
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Sanity-specific import to add
|
|
164
|
+
*/
|
|
165
|
+
const SANITY_IMPORT = `import { isSanityConfigured } from "@/lib/integrations/check-integration"`
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Sanity fetch logic to inject before return
|
|
169
|
+
*/
|
|
170
|
+
const SANITY_FETCH_LOGIC = `
|
|
171
|
+
// Only fetch Sanity pages if Sanity is configured
|
|
172
|
+
if (isSanityConfigured()) {
|
|
173
|
+
try {
|
|
174
|
+
const sanityModule = await import("@/lib/integrations/sanity/client")
|
|
175
|
+
const sanityGroq = await import("next-sanity")
|
|
176
|
+
|
|
177
|
+
const client = sanityModule?.client
|
|
178
|
+
const groq = sanityGroq?.groq
|
|
179
|
+
|
|
180
|
+
// Skip if client is null (shouldn't happen since we check isSanityConfigured)
|
|
181
|
+
if (client && groq) {
|
|
182
|
+
type SanityDocument = {
|
|
183
|
+
slug: { current: string }
|
|
184
|
+
_updatedAt: string
|
|
185
|
+
metadata?: { noIndex?: boolean }
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Fetch all published pages and articles
|
|
189
|
+
const pages = (await client.fetch(
|
|
190
|
+
groq\`*[_type == "page" && defined(slug.current)] {
|
|
191
|
+
slug,
|
|
192
|
+
_updatedAt,
|
|
193
|
+
metadata
|
|
194
|
+
}\`
|
|
195
|
+
)) as SanityDocument[]
|
|
196
|
+
|
|
197
|
+
const articles = (await client.fetch(
|
|
198
|
+
groq\`*[_type == "article" && defined(slug.current)] {
|
|
199
|
+
slug,
|
|
200
|
+
_updatedAt,
|
|
201
|
+
metadata
|
|
202
|
+
}\`
|
|
203
|
+
)) as SanityDocument[]
|
|
204
|
+
|
|
205
|
+
// Add pages to sitemap (exclude noIndex pages)
|
|
206
|
+
const pageEntries: MetadataRoute.Sitemap = pages
|
|
207
|
+
.filter((page: SanityDocument) => !page.metadata?.noIndex)
|
|
208
|
+
.map((page: SanityDocument) => ({
|
|
209
|
+
url: \`\${APP_BASE_URL}/sanity/\${page.slug.current}\`,
|
|
210
|
+
lastModified: new Date(page._updatedAt),
|
|
211
|
+
changeFrequency: "weekly" as const,
|
|
212
|
+
priority: 0.8,
|
|
213
|
+
}))
|
|
214
|
+
|
|
215
|
+
// Add articles to sitemap (exclude noIndex articles)
|
|
216
|
+
const articleEntries: MetadataRoute.Sitemap = articles
|
|
217
|
+
.filter((article: SanityDocument) => !article.metadata?.noIndex)
|
|
218
|
+
.map((article: SanityDocument) => ({
|
|
219
|
+
url: \`\${APP_BASE_URL}/sanity/\${article.slug.current}\`,
|
|
220
|
+
lastModified: new Date(article._updatedAt),
|
|
221
|
+
changeFrequency: "weekly" as const,
|
|
222
|
+
priority: 0.7,
|
|
223
|
+
}))
|
|
224
|
+
|
|
225
|
+
baseRoutes.push(...pageEntries, ...articleEntries)
|
|
226
|
+
}
|
|
227
|
+
} catch (error) {
|
|
228
|
+
console.error("Error generating sitemap from Sanity:", error)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
`
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Merge Sanity integration into template sitemap
|
|
235
|
+
* @param {string} templatePath - Path to the template's sitemap.ts
|
|
236
|
+
*/
|
|
237
|
+
export async function mergeSitemap(templatePath) {
|
|
238
|
+
let content = await fs.readFile(templatePath, 'utf-8')
|
|
239
|
+
|
|
240
|
+
// Skip if already has Sanity integration
|
|
241
|
+
if (content.includes('isSanityConfigured')) {
|
|
242
|
+
return { skipped: true, reason: 'Already has Sanity integration' }
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// 1. Add Sanity import after existing imports
|
|
246
|
+
const importMatches = [...content.matchAll(/^import .+$/gm)]
|
|
247
|
+
if (importMatches.length > 0) {
|
|
248
|
+
const lastImport = importMatches[importMatches.length - 1]
|
|
249
|
+
const insertPos = lastImport.index + lastImport[0].length
|
|
250
|
+
content = content.slice(0, insertPos) + '\n' + SANITY_IMPORT + content.slice(insertPos)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// 2. Add Sanity fetch logic before the return statement
|
|
254
|
+
// Find "return baseRoutes" or similar return statement
|
|
255
|
+
const returnMatch = content.match(/(\n)([ \t]*)(return\s+\w+)/m)
|
|
256
|
+
if (returnMatch) {
|
|
257
|
+
const returnIndex = content.indexOf(returnMatch[0])
|
|
258
|
+
content = content.slice(0, returnIndex) + SANITY_FETCH_LOGIC + content.slice(returnIndex)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
await fs.writeFile(templatePath, content)
|
|
262
|
+
return { success: true }
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**Step 2: Commit**
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
git add src/mergers/sitemap-merger.js
|
|
270
|
+
git commit -m "feat(mergers): add sitemap merger for Sanity integration"
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Task 4: Create Check-Integration Merger
|
|
276
|
+
|
|
277
|
+
**Files:**
|
|
278
|
+
- Create: `src/mergers/check-integration-merger.js`
|
|
279
|
+
|
|
280
|
+
**Step 1: Write the check-integration merger**
|
|
281
|
+
|
|
282
|
+
```js
|
|
283
|
+
// src/mergers/check-integration-merger.js
|
|
284
|
+
import fs from 'fs-extra'
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Sanity configuration check function to add
|
|
288
|
+
*/
|
|
289
|
+
const SANITY_FUNCTION = `
|
|
290
|
+
/**
|
|
291
|
+
* Check if Sanity CMS is configured
|
|
292
|
+
* Requires: NEXT_PUBLIC_SANITY_PROJECT_ID and NEXT_PUBLIC_SANITY_DATASET
|
|
293
|
+
*/
|
|
294
|
+
export function isSanityConfigured(): boolean {
|
|
295
|
+
return Boolean(
|
|
296
|
+
process.env.NEXT_PUBLIC_SANITY_PROJECT_ID &&
|
|
297
|
+
process.env.NEXT_PUBLIC_SANITY_DATASET
|
|
298
|
+
)
|
|
299
|
+
}
|
|
300
|
+
`
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Update to getConfiguredIntegrations to include Sanity
|
|
304
|
+
*/
|
|
305
|
+
const SANITY_CONFIGURED_CHECK = ` if (isSanityConfigured()) integrations.push("Sanity")`
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Update to getUnconfiguredIntegrations to include Sanity
|
|
309
|
+
*/
|
|
310
|
+
const SANITY_UNCONFIGURED_CHECK = ` if (!isSanityConfigured()) integrations.push("Sanity")`
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Merge Sanity integration into template check-integration
|
|
314
|
+
* @param {string} templatePath - Path to the template's check-integration.ts
|
|
315
|
+
*/
|
|
316
|
+
export async function mergeCheckIntegration(templatePath) {
|
|
317
|
+
let content = await fs.readFile(templatePath, 'utf-8')
|
|
318
|
+
|
|
319
|
+
// Skip if already has isSanityConfigured
|
|
320
|
+
if (content.includes('isSanityConfigured')) {
|
|
321
|
+
return { skipped: true, reason: 'Already has isSanityConfigured' }
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// 1. Add isSanityConfigured function after isAnalyticsConfigured
|
|
325
|
+
const analyticsMatch = content.match(/(export function isAnalyticsConfigured\(\)[^}]+\})/s)
|
|
326
|
+
if (analyticsMatch) {
|
|
327
|
+
const insertPos = analyticsMatch.index + analyticsMatch[0].length
|
|
328
|
+
content = content.slice(0, insertPos) + '\n' + SANITY_FUNCTION + content.slice(insertPos)
|
|
329
|
+
} else {
|
|
330
|
+
// If no isAnalyticsConfigured, add before getConfiguredIntegrations
|
|
331
|
+
const getConfiguredMatch = content.match(/export function getConfiguredIntegrations/)
|
|
332
|
+
if (getConfiguredMatch) {
|
|
333
|
+
content = content.slice(0, getConfiguredMatch.index) + SANITY_FUNCTION + '\n' + content.slice(getConfiguredMatch.index)
|
|
334
|
+
} else {
|
|
335
|
+
// Fallback: append to end
|
|
336
|
+
content = content.trimEnd() + '\n' + SANITY_FUNCTION
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// 2. Update getConfiguredIntegrations to include Sanity check
|
|
341
|
+
const configuredFnMatch = content.match(/(export function getConfiguredIntegrations\(\)[^{]*\{[\s\S]*?)(if \(isAnalyticsConfigured\(\)\))/m)
|
|
342
|
+
if (configuredFnMatch) {
|
|
343
|
+
const insertPos = content.indexOf(configuredFnMatch[2], configuredFnMatch.index)
|
|
344
|
+
content = content.slice(0, insertPos) + SANITY_CONFIGURED_CHECK + '\n ' + content.slice(insertPos)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// 3. Update getUnconfiguredIntegrations to include Sanity check
|
|
348
|
+
const unconfiguredFnMatch = content.match(/(export function getUnconfiguredIntegrations\(\)[^{]*\{[\s\S]*?)(if \(!isAnalyticsConfigured\(\)\))/m)
|
|
349
|
+
if (unconfiguredFnMatch) {
|
|
350
|
+
const insertPos = content.indexOf(unconfiguredFnMatch[2], unconfiguredFnMatch.index)
|
|
351
|
+
content = content.slice(0, insertPos) + SANITY_UNCONFIGURED_CHECK + '\n ' + content.slice(insertPos)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
await fs.writeFile(templatePath, content)
|
|
355
|
+
return { success: true }
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
**Step 2: Commit**
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
git add src/mergers/check-integration-merger.js
|
|
363
|
+
git commit -m "feat(mergers): add check-integration merger for Sanity"
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## Task 5: Create Merger Orchestrator
|
|
369
|
+
|
|
370
|
+
**Files:**
|
|
371
|
+
- Create: `src/mergers/index.js`
|
|
372
|
+
|
|
373
|
+
**Step 1: Write the orchestrator**
|
|
374
|
+
|
|
375
|
+
```js
|
|
376
|
+
// src/mergers/index.js
|
|
377
|
+
import fs from 'fs-extra'
|
|
378
|
+
import path from 'path'
|
|
379
|
+
import os from 'os'
|
|
380
|
+
import tiged from 'tiged'
|
|
381
|
+
import { MERGE_FILES, ADDITIVE_PATHS } from './config.js'
|
|
382
|
+
import { mergeLayout } from './layout-merger.js'
|
|
383
|
+
import { mergeSitemap } from './sitemap-merger.js'
|
|
384
|
+
import { mergeCheckIntegration } from './check-integration-merger.js'
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Map of file paths to their merger functions
|
|
388
|
+
*/
|
|
389
|
+
const MERGERS = {
|
|
390
|
+
'app/layout.tsx': mergeLayout,
|
|
391
|
+
'app/sitemap.ts': mergeSitemap,
|
|
392
|
+
'lib/integrations/check-integration.ts': mergeCheckIntegration,
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Inject Sanity integration using smart merge
|
|
397
|
+
* @param {string} targetDir - The project directory
|
|
398
|
+
* @param {string} cms - The CMS name (e.g., 'sanity')
|
|
399
|
+
* @param {object} spinner - Ora spinner for progress
|
|
400
|
+
* @returns {object} Results of the merge operation
|
|
401
|
+
*/
|
|
402
|
+
export async function injectIntegration(targetDir, cms, spinner) {
|
|
403
|
+
const results = {
|
|
404
|
+
merged: [],
|
|
405
|
+
copied: [],
|
|
406
|
+
skipped: [],
|
|
407
|
+
failed: [],
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// 1. Clone integration to temp directory
|
|
411
|
+
const tempDir = path.join(os.tmpdir(), `integration-${Date.now()}`)
|
|
412
|
+
|
|
413
|
+
try {
|
|
414
|
+
spinner.text = `Downloading ${cms} integration...`
|
|
415
|
+
|
|
416
|
+
const integrationRepoPath = `github:basementstudio/basement-cli/integrations/${cms}#sanity-integration`
|
|
417
|
+
const emitter = tiged(integrationRepoPath, {
|
|
418
|
+
cache: false,
|
|
419
|
+
force: true,
|
|
420
|
+
mode: 'git',
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
await emitter.clone(tempDir)
|
|
424
|
+
|
|
425
|
+
// 2. Copy additive files/directories
|
|
426
|
+
spinner.text = `Adding ${cms} files...`
|
|
427
|
+
|
|
428
|
+
for (const additivePath of ADDITIVE_PATHS) {
|
|
429
|
+
const src = path.join(tempDir, additivePath)
|
|
430
|
+
const dest = path.join(targetDir, additivePath)
|
|
431
|
+
|
|
432
|
+
try {
|
|
433
|
+
if (await fs.pathExists(src)) {
|
|
434
|
+
await fs.copy(src, dest, { overwrite: false })
|
|
435
|
+
results.copied.push(additivePath)
|
|
436
|
+
}
|
|
437
|
+
} catch (error) {
|
|
438
|
+
results.failed.push({ path: additivePath, error: error.message })
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// 3. Smart merge files that exist in both
|
|
443
|
+
spinner.text = `Merging ${cms} integration...`
|
|
444
|
+
|
|
445
|
+
for (const mergeFile of MERGE_FILES) {
|
|
446
|
+
const templateFile = path.join(targetDir, mergeFile)
|
|
447
|
+
const integrationFile = path.join(tempDir, mergeFile)
|
|
448
|
+
const merger = MERGERS[mergeFile]
|
|
449
|
+
|
|
450
|
+
try {
|
|
451
|
+
// Check if template file exists
|
|
452
|
+
if (!(await fs.pathExists(templateFile))) {
|
|
453
|
+
// Template doesn't have this file - copy from integration
|
|
454
|
+
if (await fs.pathExists(integrationFile)) {
|
|
455
|
+
await fs.copy(integrationFile, templateFile)
|
|
456
|
+
results.copied.push(mergeFile)
|
|
457
|
+
} else {
|
|
458
|
+
results.skipped.push({ path: mergeFile, reason: 'Not in integration' })
|
|
459
|
+
}
|
|
460
|
+
continue
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Check if integration file exists
|
|
464
|
+
if (!(await fs.pathExists(integrationFile))) {
|
|
465
|
+
results.skipped.push({ path: mergeFile, reason: 'Not in integration' })
|
|
466
|
+
continue
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Run the merger
|
|
470
|
+
if (merger) {
|
|
471
|
+
const result = await merger(templateFile)
|
|
472
|
+
if (result.skipped) {
|
|
473
|
+
results.skipped.push({ path: mergeFile, reason: result.reason })
|
|
474
|
+
} else {
|
|
475
|
+
results.merged.push(mergeFile)
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
} catch (error) {
|
|
479
|
+
results.failed.push({ path: mergeFile, error: error.message })
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
} finally {
|
|
484
|
+
// 4. Cleanup temp directory
|
|
485
|
+
try {
|
|
486
|
+
await fs.remove(tempDir)
|
|
487
|
+
} catch {
|
|
488
|
+
// Ignore cleanup errors
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return results
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Format merge results for display
|
|
497
|
+
* @param {object} results - Results from injectIntegration
|
|
498
|
+
* @returns {string[]} Lines to display
|
|
499
|
+
*/
|
|
500
|
+
export function formatMergeResults(results) {
|
|
501
|
+
const lines = []
|
|
502
|
+
|
|
503
|
+
if (results.merged.length > 0) {
|
|
504
|
+
lines.push(...results.merged.map(f => ` ✓ Merged: ${f}`))
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (results.copied.length > 0) {
|
|
508
|
+
lines.push(...results.copied.map(f => ` ✓ Added: ${f}`))
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (results.skipped.length > 0) {
|
|
512
|
+
lines.push(...results.skipped.map(s => ` ○ Skipped: ${s.path} (${s.reason})`))
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (results.failed.length > 0) {
|
|
516
|
+
lines.push(...results.failed.map(f => ` ✗ Failed: ${f.path} (${f.error})`))
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return lines
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
**Step 2: Commit**
|
|
524
|
+
|
|
525
|
+
```bash
|
|
526
|
+
git add src/mergers/index.js
|
|
527
|
+
git commit -m "feat(mergers): add orchestrator with smart merge logic"
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
---
|
|
531
|
+
|
|
532
|
+
## Task 6: Update create.js to Use Smart Merge
|
|
533
|
+
|
|
534
|
+
**Files:**
|
|
535
|
+
- Modify: `src/commands/create.js:137-164`
|
|
536
|
+
|
|
537
|
+
**Step 1: Update the integration injection section**
|
|
538
|
+
|
|
539
|
+
Replace lines 137-164 in `src/commands/create.js`:
|
|
540
|
+
|
|
541
|
+
```js
|
|
542
|
+
// 3.5. Inject Integration Files (Smart Merge)
|
|
543
|
+
if (cms && cms !== "none") {
|
|
544
|
+
const integrationSpinner = ora(
|
|
545
|
+
`Integrating ${pc.cyan(cms)}...`
|
|
546
|
+
).start();
|
|
547
|
+
|
|
548
|
+
try {
|
|
549
|
+
const { injectIntegration, formatMergeResults } = await import('../mergers/index.js');
|
|
550
|
+
const results = await injectIntegration(targetDir, cms, integrationSpinner);
|
|
551
|
+
|
|
552
|
+
const hasErrors = results.failed.length > 0;
|
|
553
|
+
const resultLines = formatMergeResults(results);
|
|
554
|
+
|
|
555
|
+
if (hasErrors) {
|
|
556
|
+
integrationSpinner.warn(pc.yellow(`${cms} integration completed with warnings:`));
|
|
557
|
+
} else {
|
|
558
|
+
integrationSpinner.succeed(pc.green(`${cms} integration added:`));
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Display detailed results
|
|
562
|
+
resultLines.forEach(line => console.log(pc.dim(line)));
|
|
563
|
+
|
|
564
|
+
} catch (e) {
|
|
565
|
+
integrationSpinner.warn(
|
|
566
|
+
pc.yellow(`Failed to inject ${cms} integration: ${e.message}`)
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
**Step 2: Commit**
|
|
573
|
+
|
|
574
|
+
```bash
|
|
575
|
+
git add src/commands/create.js
|
|
576
|
+
git commit -m "feat(create): use smart merge for CMS integration"
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
---
|
|
580
|
+
|
|
581
|
+
## Task 7: Test with Default Template
|
|
582
|
+
|
|
583
|
+
**Step 1: Link the CLI locally**
|
|
584
|
+
|
|
585
|
+
```bash
|
|
586
|
+
npm link
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
**Step 2: Create a test project with default template + Sanity**
|
|
590
|
+
|
|
591
|
+
```bash
|
|
592
|
+
cd /tmp
|
|
593
|
+
basement
|
|
594
|
+
# Select: my-test-app, default, sanity, none, claude, no hooks
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
**Step 3: Verify the merged files**
|
|
598
|
+
|
|
599
|
+
Check `app/layout.tsx`:
|
|
600
|
+
- Should have original template structure
|
|
601
|
+
- Should have Sanity imports added
|
|
602
|
+
- Should have draftMode variables
|
|
603
|
+
- Should have Sanity components after {children}
|
|
604
|
+
|
|
605
|
+
Check `app/sitemap.ts`:
|
|
606
|
+
- Should have original baseRoutes
|
|
607
|
+
- Should have isSanityConfigured import
|
|
608
|
+
- Should have Sanity fetch logic
|
|
609
|
+
|
|
610
|
+
Check `lib/integrations/check-integration.ts`:
|
|
611
|
+
- Should have isAnalyticsConfigured (from template)
|
|
612
|
+
- Should have isSanityConfigured (added)
|
|
613
|
+
- Should have updated getConfiguredIntegrations
|
|
614
|
+
|
|
615
|
+
Check additive files exist:
|
|
616
|
+
- `lib/integrations/sanity/` directory
|
|
617
|
+
- `components/ui/sanity-image/`
|
|
618
|
+
- `app/api/draft-mode/`
|
|
619
|
+
|
|
620
|
+
**Step 4: Verify the project builds**
|
|
621
|
+
|
|
622
|
+
```bash
|
|
623
|
+
cd my-test-app
|
|
624
|
+
bun install
|
|
625
|
+
bun build
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
Expected: Build succeeds without errors
|
|
629
|
+
|
|
630
|
+
**Step 5: Commit test results (if any fixes needed)**
|
|
631
|
+
|
|
632
|
+
```bash
|
|
633
|
+
git add -A
|
|
634
|
+
git commit -m "fix(mergers): address issues found during testing"
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
---
|
|
638
|
+
|
|
639
|
+
## Task 8: Test with WebGPU Template
|
|
640
|
+
|
|
641
|
+
**Step 1: Create a test project with webgpu template + Sanity**
|
|
642
|
+
|
|
643
|
+
```bash
|
|
644
|
+
cd /tmp
|
|
645
|
+
rm -rf my-test-webgpu
|
|
646
|
+
basement
|
|
647
|
+
# Select: my-test-webgpu, webgpu, sanity, none, claude, no hooks
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
**Step 2: Verify the merged files preserve WebGPU-specific code**
|
|
651
|
+
|
|
652
|
+
Check `app/layout.tsx`:
|
|
653
|
+
- Should preserve WebGPU providers/imports
|
|
654
|
+
- Should have Sanity additions
|
|
655
|
+
|
|
656
|
+
**Step 3: Verify the project builds**
|
|
657
|
+
|
|
658
|
+
```bash
|
|
659
|
+
cd my-test-webgpu
|
|
660
|
+
bun install
|
|
661
|
+
bun build
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
Expected: Build succeeds without errors
|
|
665
|
+
|
|
666
|
+
---
|
|
667
|
+
|
|
668
|
+
## Task 9: Final Cleanup and Documentation
|
|
669
|
+
|
|
670
|
+
**Step 1: Update the CLI's README if needed**
|
|
671
|
+
|
|
672
|
+
Document the new smart merge behavior.
|
|
673
|
+
|
|
674
|
+
**Step 2: Final commit**
|
|
675
|
+
|
|
676
|
+
```bash
|
|
677
|
+
git add -A
|
|
678
|
+
git commit -m "docs: document smart merge integration system"
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
---
|
|
682
|
+
|
|
683
|
+
## Summary
|
|
684
|
+
|
|
685
|
+
| Task | Description | Files |
|
|
686
|
+
|------|-------------|-------|
|
|
687
|
+
| 1 | Create merger config | `src/mergers/config.js` |
|
|
688
|
+
| 2 | Create layout merger | `src/mergers/layout-merger.js` |
|
|
689
|
+
| 3 | Create sitemap merger | `src/mergers/sitemap-merger.js` |
|
|
690
|
+
| 4 | Create check-integration merger | `src/mergers/check-integration-merger.js` |
|
|
691
|
+
| 5 | Create orchestrator | `src/mergers/index.js` |
|
|
692
|
+
| 6 | Update create.js | `src/commands/create.js` |
|
|
693
|
+
| 7 | Test with default template | Manual testing |
|
|
694
|
+
| 8 | Test with webgpu template | Manual testing |
|
|
695
|
+
| 9 | Final cleanup | Documentation |
|