blocks-dusted 0.1.1 → 0.1.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/README.md +967 -3
- package/package.json +2 -2
- package/src/commands/add.js +143 -45
- package/src/commands/list.js +2 -2
- package/src/installers/checkRequirements.js +1 -0
- package/src/installers/copyTemplateFiles.js +23 -2
- package/src/installers/patchPayloadConfigCollections.js +92 -0
- package/src/installers/writeInstalledBlockReadme.js +17 -4
- package/src/registry/listTemplates.js +16 -7
- package/src/registry/loadTemplateManifest.js +20 -11
- package/src/registry/validateTemplateManifest.js +6 -5
- package/src/utils/paths.js +7 -0
- package/templates/blocks/DD-BookCall/Component.tsx +535 -573
- package/templates/blocks/DD-CardTemplate01/Component.tsx +45 -49
- package/templates/blocks/DD-Carousel-A/Component.tsx +249 -266
- package/templates/blocks/DD-Carousel-B/Component.tsx +403 -432
- package/templates/blocks/DD-ComparisonTable/Component.tsx +256 -272
- package/templates/blocks/DD-Contact/Component.tsx +434 -439
- package/templates/blocks/DD-Contact/config.ts +8 -3
- package/templates/blocks/DD-FeatCat/Component.tsx +302 -361
- package/templates/blocks/DD-FeatCat/config.ts +201 -204
- package/templates/blocks/DD-FeatStrip/Component.tsx +171 -201
- package/templates/blocks/DD-Hero/Component.tsx +297 -317
- package/templates/blocks/DD-HorizontalScroll/Component.tsx +244 -303
- package/templates/blocks/DD-MasonaryMedia/Component.tsx +202 -228
- package/templates/blocks/DD-MasonaryMedia/config.ts +13 -14
- package/templates/blocks/DD-Pricing/Component.tsx +372 -380
- package/templates/blocks/DD-Process/Component.tsx +149 -155
- package/templates/blocks/DD-Section/Component.tsx +266 -327
- package/templates/blocks/DD-Services/Component.tsx +176 -188
- package/templates/blocks/DD-ShowcaseGrid/Component.tsx +307 -317
- package/templates/blocks/DD-Slider-A/Component.tsx +237 -252
- package/templates/blocks/DD-StackCards/Component.tsx +137 -160
- package/templates/blocks/DD-Team/Component.tsx +247 -265
- package/templates/blocks/DD-TechStack/Component.tsx +57 -72
- package/templates/blocks/DD-Testimonials/Component.tsx +147 -147
- package/templates/blocks/DD-Testimonials/config.ts +66 -66
- package/templates/blocks/DD-Work/Component.tsx +315 -328
- package/templates/blocks/DD-Work-B/Component.tsx +294 -320
- package/templates/collections/Testimonials/README.md +11 -0
- package/templates/collections/Testimonials/Testimonials.ts +161 -0
- package/templates/collections/Testimonials/manifest.json +55 -0
- package/templates/components/HeaderDD02/MobileBottomNav/index.tsx +545 -0
- package/templates/components/HeaderDD02/README.md +45 -0
- package/templates/components/HeaderDD02/RowLabel.tsx +33 -0
- package/templates/components/HeaderDD02/config.ts +302 -0
- package/templates/components/HeaderDD02/index.tsx +517 -0
- package/templates/components/HeaderDD02/manifest.json +123 -0
- package/templates/components/RichText/README.md +13 -0
- package/templates/components/RichText/converter/componentConverter/blocks.tsx +43 -0
- package/templates/components/RichText/converter/componentConverter/types.ts +11 -0
- package/templates/components/RichText/converter/index.tsx +18 -0
- package/templates/components/RichText/converter/internalLinks.tsx +45 -0
- package/templates/components/RichText/converter/textConverter.tsx +27 -0
- package/templates/components/RichText/index.tsx +35 -0
- package/templates/components/RichText/manifest.json +80 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blocks-dusted",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Manifest-driven CLI for installing reusable Payload CMS blocks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -37,4 +37,4 @@
|
|
|
37
37
|
},
|
|
38
38
|
"homepage": "https://github.com/Hadizainal/blocks-dusted#readme",
|
|
39
39
|
"license": "MIT"
|
|
40
|
-
}
|
|
40
|
+
}
|
package/src/commands/add.js
CHANGED
|
@@ -2,12 +2,15 @@ import { access } from 'node:fs/promises'
|
|
|
2
2
|
import { join } from 'node:path'
|
|
3
3
|
import { checkRequirements } from '../installers/checkRequirements.js'
|
|
4
4
|
import {
|
|
5
|
+
backupExistingDestination,
|
|
5
6
|
copySharedFiles,
|
|
6
7
|
copyTemplateFiles,
|
|
8
|
+
plannedBackupPath,
|
|
7
9
|
} from '../installers/copyTemplateFiles.js'
|
|
8
10
|
import { checkDependencies } from '../installers/installDependencies.js'
|
|
9
11
|
import { writeInstalledBlockReadme } from '../installers/writeInstalledBlockReadme.js'
|
|
10
12
|
import { patchPayloadBlocksArray } from '../installers/patchPayloadBlocksArray.js'
|
|
13
|
+
import { patchPayloadConfigCollections } from '../installers/patchPayloadConfigCollections.js'
|
|
11
14
|
import { patchRenderBlocks } from '../installers/patchRenderBlocks.js'
|
|
12
15
|
import { loadTemplateManifest } from '../registry/loadTemplateManifest.js'
|
|
13
16
|
import { validateTemplateManifest } from '../registry/validateTemplateManifest.js'
|
|
@@ -23,7 +26,7 @@ export async function addTemplate({
|
|
|
23
26
|
}) {
|
|
24
27
|
if (!templateName) {
|
|
25
28
|
throw new Error(
|
|
26
|
-
'A
|
|
29
|
+
'A template name is required. Example: blocks-dusted add DD-Hero --dry-run',
|
|
27
30
|
)
|
|
28
31
|
}
|
|
29
32
|
|
|
@@ -32,6 +35,9 @@ export async function addTemplate({
|
|
|
32
35
|
await validateManifestSourceFiles({ manifest, templateDirectory, packageRoot })
|
|
33
36
|
const checks = await checkRequirements({ manifest, targetDirectory })
|
|
34
37
|
const dependencyChecks = await checkDependencies({ manifest, targetDirectory })
|
|
38
|
+
const isBlockTemplate = manifest.type === 'block'
|
|
39
|
+
const isCollectionTemplate = manifest.type === 'collection'
|
|
40
|
+
const canBackupDestination = manifest.backupExistingDestination === true
|
|
35
41
|
|
|
36
42
|
if (!dryRun) {
|
|
37
43
|
await validateTargetProject(targetDirectory)
|
|
@@ -43,7 +49,7 @@ export async function addTemplate({
|
|
|
43
49
|
}
|
|
44
50
|
}
|
|
45
51
|
|
|
46
|
-
heading('
|
|
52
|
+
heading('Template')
|
|
47
53
|
info(`Name: ${manifest.name}`)
|
|
48
54
|
if (manifest.sourceName) info(`Source: ${manifest.sourceName}`)
|
|
49
55
|
if (manifest.portableName) info(`Portable name: ${manifest.portableName}`)
|
|
@@ -51,7 +57,7 @@ export async function addTemplate({
|
|
|
51
57
|
info(`Type: ${manifest.type}`)
|
|
52
58
|
info(`Version: ${manifest.version}`)
|
|
53
59
|
info(`Target directory: ${targetDirectory}`)
|
|
54
|
-
info(`Register: ${noRegister ? '
|
|
60
|
+
info(`Register: ${isBlockTemplate && !noRegister ? 'yes' : 'no'}`)
|
|
55
61
|
info(`Source template path: ${templateDirectory}`)
|
|
56
62
|
info(`Target install path: ${checks.collisions.destinationPath}`)
|
|
57
63
|
|
|
@@ -98,28 +104,50 @@ export async function addTemplate({
|
|
|
98
104
|
}
|
|
99
105
|
|
|
100
106
|
heading('Collision checks')
|
|
107
|
+
const destinationWillBeBackedUp =
|
|
108
|
+
checks.collisions.destinationExists && canBackupDestination
|
|
101
109
|
collisionStatus(
|
|
102
|
-
!checks.collisions.destinationExists,
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
!checks.collisions.slugFound,
|
|
107
|
-
`Payload slug: ${manifest.slug}`,
|
|
108
|
-
)
|
|
109
|
-
collisionStatus(
|
|
110
|
-
!checks.collisions.configExportFound,
|
|
111
|
-
`Config export: ${manifest.exportName ?? manifest.name}`,
|
|
112
|
-
)
|
|
113
|
-
collisionStatus(
|
|
114
|
-
!checks.collisions.interfaceNameFound,
|
|
115
|
-
`interfaceName: ${manifest.interfaceName ?? manifest.exportName ?? manifest.name}`,
|
|
116
|
-
)
|
|
117
|
-
collisionStatus(
|
|
118
|
-
!checks.collisions.renderBlocksMappingFound,
|
|
119
|
-
`RenderBlocks mapping: ${manifest.slug}`,
|
|
110
|
+
!checks.collisions.destinationExists || destinationWillBeBackedUp,
|
|
111
|
+
destinationWillBeBackedUp
|
|
112
|
+
? `Destination path: ${checks.collisions.destinationPath} (will be backed up)`
|
|
113
|
+
: `Destination path: ${checks.collisions.destinationPath}`,
|
|
120
114
|
)
|
|
115
|
+
if (isBlockTemplate) {
|
|
116
|
+
collisionStatus(
|
|
117
|
+
!checks.collisions.slugFound,
|
|
118
|
+
`Payload slug: ${manifest.slug}`,
|
|
119
|
+
)
|
|
120
|
+
collisionStatus(
|
|
121
|
+
!checks.collisions.configExportFound,
|
|
122
|
+
`Config export: ${manifest.exportName ?? manifest.name}`,
|
|
123
|
+
)
|
|
124
|
+
collisionStatus(
|
|
125
|
+
!checks.collisions.interfaceNameFound,
|
|
126
|
+
`interfaceName: ${manifest.interfaceName ?? manifest.exportName ?? manifest.name}`,
|
|
127
|
+
)
|
|
128
|
+
collisionStatus(
|
|
129
|
+
!checks.collisions.renderBlocksMappingFound,
|
|
130
|
+
`RenderBlocks mapping: ${manifest.slug}`,
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const destinationBackupPath = destinationWillBeBackedUp
|
|
135
|
+
? await plannedBackupPath({
|
|
136
|
+
destinationPath: checks.collisions.destinationPath,
|
|
137
|
+
targetDirectory,
|
|
138
|
+
})
|
|
139
|
+
: null
|
|
140
|
+
|
|
141
|
+
if (destinationBackupPath) {
|
|
142
|
+
heading('Existing destination backup')
|
|
143
|
+
item(
|
|
144
|
+
dryRun
|
|
145
|
+
? `Would back up existing ${checks.collisions.destinationPath} to ${destinationBackupPath}`
|
|
146
|
+
: `Existing ${checks.collisions.destinationPath} will be backed up to ${destinationBackupPath}`,
|
|
147
|
+
)
|
|
148
|
+
}
|
|
121
149
|
|
|
122
|
-
if (checks.collisions.destinationExists) {
|
|
150
|
+
if (checks.collisions.destinationExists && !canBackupDestination) {
|
|
123
151
|
throw new Error(
|
|
124
152
|
`Refusing to install because ${checks.collisions.destinationPath} already exists.`,
|
|
125
153
|
)
|
|
@@ -164,20 +192,37 @@ export async function addTemplate({
|
|
|
164
192
|
targetDirectory,
|
|
165
193
|
dryRun: true,
|
|
166
194
|
})
|
|
167
|
-
const dryRunCollectionResults =
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
195
|
+
const dryRunCollectionResults = registrationResults({
|
|
196
|
+
isBlockTemplate,
|
|
197
|
+
manifest,
|
|
198
|
+
noRegister,
|
|
199
|
+
registration: 'collection block registration',
|
|
200
|
+
action: () => patchPayloadBlocksArray({
|
|
201
|
+
manifest,
|
|
202
|
+
targetDirectory,
|
|
203
|
+
dryRun: true,
|
|
204
|
+
}),
|
|
205
|
+
})
|
|
206
|
+
const dryRunPayloadConfigResults = collectionRegistrationResults({
|
|
207
|
+
isCollectionTemplate,
|
|
208
|
+
manifest,
|
|
209
|
+
action: () => patchPayloadConfigCollections({
|
|
210
|
+
manifest,
|
|
211
|
+
targetDirectory,
|
|
212
|
+
dryRun: true,
|
|
213
|
+
}),
|
|
214
|
+
})
|
|
215
|
+
const dryRunRenderBlocksResults = registrationResults({
|
|
216
|
+
isBlockTemplate,
|
|
217
|
+
manifest,
|
|
218
|
+
noRegister,
|
|
219
|
+
registration: 'RenderBlocks registration',
|
|
220
|
+
action: () => patchRenderBlocks({
|
|
221
|
+
manifest,
|
|
222
|
+
targetDirectory,
|
|
223
|
+
dryRun: true,
|
|
224
|
+
}),
|
|
225
|
+
})
|
|
181
226
|
|
|
182
227
|
heading('Dry-run shared file results')
|
|
183
228
|
if (dryRunSharedResults.length === 0) item('None')
|
|
@@ -186,12 +231,16 @@ export async function addTemplate({
|
|
|
186
231
|
`${result.status}: ${result.to}${result.reason ? ` - ${result.reason}` : ''}`,
|
|
187
232
|
),
|
|
188
233
|
)
|
|
234
|
+
heading('Dry-run Payload config registration')
|
|
235
|
+
;(await dryRunPayloadConfigResults).forEach((result) =>
|
|
236
|
+
item(`${result.status}: ${result.file ?? result.reason}${result.backupPath ? ` (backup: ${result.backupPath})` : ''}`),
|
|
237
|
+
)
|
|
189
238
|
heading('Dry-run collection registration')
|
|
190
|
-
dryRunCollectionResults.forEach((result) =>
|
|
239
|
+
;(await dryRunCollectionResults).forEach((result) =>
|
|
191
240
|
item(`${result.status}: ${result.file ?? result.reason}`),
|
|
192
241
|
)
|
|
193
242
|
heading('Dry-run RenderBlocks registration')
|
|
194
|
-
dryRunRenderBlocksResults.forEach((result) =>
|
|
243
|
+
;(await dryRunRenderBlocksResults).forEach((result) =>
|
|
195
244
|
item(`${result.status}: ${result.file ?? result.reason}`),
|
|
196
245
|
)
|
|
197
246
|
info('Dry run complete: no files were changed.')
|
|
@@ -204,24 +253,45 @@ export async function addTemplate({
|
|
|
204
253
|
targetDirectory,
|
|
205
254
|
})
|
|
206
255
|
|
|
256
|
+
const actualDestinationBackupPath = checks.collisions.destinationExists
|
|
257
|
+
? await backupExistingDestination({
|
|
258
|
+
destinationPath: checks.collisions.destinationPath,
|
|
259
|
+
targetDirectory,
|
|
260
|
+
})
|
|
261
|
+
: null
|
|
262
|
+
|
|
207
263
|
const copiedFiles = await copyTemplateFiles({
|
|
208
264
|
manifest,
|
|
209
265
|
templateDirectory,
|
|
210
266
|
targetDirectory,
|
|
211
267
|
})
|
|
212
268
|
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
269
|
+
const payloadConfigResults = await collectionRegistrationResults({
|
|
270
|
+
isCollectionTemplate,
|
|
271
|
+
manifest,
|
|
272
|
+
action: () => patchPayloadConfigCollections({ manifest, targetDirectory }),
|
|
273
|
+
})
|
|
274
|
+
const collectionResults = await registrationResults({
|
|
275
|
+
isBlockTemplate,
|
|
276
|
+
manifest,
|
|
277
|
+
noRegister,
|
|
278
|
+
registration: 'collection block registration',
|
|
279
|
+
action: () => patchPayloadBlocksArray({ manifest, targetDirectory }),
|
|
280
|
+
})
|
|
281
|
+
const renderBlocksResults = await registrationResults({
|
|
282
|
+
isBlockTemplate,
|
|
283
|
+
manifest,
|
|
284
|
+
noRegister,
|
|
285
|
+
registration: 'RenderBlocks registration',
|
|
286
|
+
action: () => patchRenderBlocks({ manifest, targetDirectory }),
|
|
287
|
+
})
|
|
219
288
|
const readmePath = await writeInstalledBlockReadme({
|
|
220
289
|
manifest,
|
|
221
290
|
targetDirectory,
|
|
222
291
|
copiedFiles,
|
|
223
292
|
sharedResults,
|
|
224
293
|
dependencyChecks,
|
|
294
|
+
payloadConfigResults,
|
|
225
295
|
collectionResults,
|
|
226
296
|
renderBlocksResults,
|
|
227
297
|
packageManager: dependencyChecks.packageManager,
|
|
@@ -236,6 +306,13 @@ export async function addTemplate({
|
|
|
236
306
|
|
|
237
307
|
heading('Copied files')
|
|
238
308
|
copiedFiles.forEach((file) => item(file))
|
|
309
|
+
if (actualDestinationBackupPath) {
|
|
310
|
+
heading('Existing destination backup')
|
|
311
|
+
item(actualDestinationBackupPath)
|
|
312
|
+
item(
|
|
313
|
+
`Review ${actualDestinationBackupPath} and copy any project-specific RichText behaviour missing from the newly installed ${checks.collisions.destinationPath}.`,
|
|
314
|
+
)
|
|
315
|
+
}
|
|
239
316
|
heading('Shared file results')
|
|
240
317
|
if (sharedResults.length === 0) item('None')
|
|
241
318
|
sharedResults.forEach((result) =>
|
|
@@ -249,6 +326,12 @@ export async function addTemplate({
|
|
|
249
326
|
item(
|
|
250
327
|
`Run manually: ${installCommandText(dependencyChecks.packageManager, dependencyChecks.missing)}`,
|
|
251
328
|
)
|
|
329
|
+
heading('Payload config registration')
|
|
330
|
+
payloadConfigResults.forEach((result) =>
|
|
331
|
+
item(
|
|
332
|
+
`${result.status}: ${result.file ?? result.reason}${result.backupPath ? ` (backup: ${result.backupPath})` : ''}`,
|
|
333
|
+
),
|
|
334
|
+
)
|
|
252
335
|
heading('Collection registration')
|
|
253
336
|
collectionResults.forEach((result) =>
|
|
254
337
|
item(
|
|
@@ -270,6 +353,21 @@ export async function addTemplate({
|
|
|
270
353
|
)
|
|
271
354
|
}
|
|
272
355
|
|
|
356
|
+
function collectionRegistrationResults({ isCollectionTemplate, manifest, action }) {
|
|
357
|
+
if (!isCollectionTemplate) {
|
|
358
|
+
return [{ status: 'skipped', reason: `${manifest.type} templates do not use Payload config collection registration` }]
|
|
359
|
+
}
|
|
360
|
+
return action()
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function registrationResults({ isBlockTemplate, manifest, noRegister, registration, action }) {
|
|
364
|
+
if (!isBlockTemplate) {
|
|
365
|
+
return [{ status: 'skipped', reason: `${manifest.type} templates do not use ${registration}` }]
|
|
366
|
+
}
|
|
367
|
+
if (noRegister) return [{ status: 'skipped', reason: '--no-register was passed' }]
|
|
368
|
+
return action()
|
|
369
|
+
}
|
|
370
|
+
|
|
273
371
|
function formatDependency(dependency) {
|
|
274
372
|
return typeof dependency === 'string'
|
|
275
373
|
? dependency
|
|
@@ -324,4 +422,4 @@ async function validateManifestSourceFiles({ manifest, templateDirectory, packag
|
|
|
324
422
|
if (missing.length > 0) {
|
|
325
423
|
throw new Error(`Manifest source files are missing:\n${missing.map((file) => `- ${file}`).join('\n')}`)
|
|
326
424
|
}
|
|
327
|
-
}
|
|
425
|
+
}
|
package/src/commands/list.js
CHANGED
|
@@ -3,7 +3,7 @@ import { heading, info, item } from '../utils/logger.js'
|
|
|
3
3
|
|
|
4
4
|
export async function list() {
|
|
5
5
|
const templates = await listTemplates()
|
|
6
|
-
heading('Available
|
|
6
|
+
heading('Available templates')
|
|
7
7
|
|
|
8
8
|
if (templates.length === 0) {
|
|
9
9
|
item('None')
|
|
@@ -15,4 +15,4 @@ export async function list() {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
info(`Total: ${templates.length}`)
|
|
18
|
-
}
|
|
18
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { constants } from 'node:fs'
|
|
2
|
-
import { access, copyFile, cp, mkdir, readFile, stat } from 'node:fs/promises'
|
|
2
|
+
import { access, copyFile, cp, mkdir, readFile, rename, stat } from 'node:fs/promises'
|
|
3
3
|
import { dirname, join } from 'node:path'
|
|
4
4
|
|
|
5
5
|
export async function copyTemplateFiles({ manifest, templateDirectory, targetDirectory }) {
|
|
@@ -17,6 +17,17 @@ export async function copyTemplateFiles({ manifest, templateDirectory, targetDir
|
|
|
17
17
|
return copiedFiles
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
export async function backupExistingDestination({ destinationPath, targetDirectory }) {
|
|
21
|
+
const targetPath = join(targetDirectory, destinationPath)
|
|
22
|
+
const backupPath = await nextBackupPath(targetPath)
|
|
23
|
+
await rename(targetPath, backupPath)
|
|
24
|
+
return backupPath
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function plannedBackupPath({ destinationPath, targetDirectory }) {
|
|
28
|
+
return nextBackupPath(join(targetDirectory, destinationPath))
|
|
29
|
+
}
|
|
30
|
+
|
|
20
31
|
export async function copySharedFiles({ manifest, packageRoot, targetDirectory, dryRun = false }) {
|
|
21
32
|
const results = []
|
|
22
33
|
const sharedFiles = manifest.sharedFiles ?? []
|
|
@@ -54,6 +65,16 @@ export async function copySharedFiles({ manifest, packageRoot, targetDirectory,
|
|
|
54
65
|
return results
|
|
55
66
|
}
|
|
56
67
|
|
|
68
|
+
async function nextBackupPath(path) {
|
|
69
|
+
let candidate = `${path}.bak`
|
|
70
|
+
let index = 1
|
|
71
|
+
while (await pathExists(candidate)) {
|
|
72
|
+
candidate = `${path}.bak.${index}`
|
|
73
|
+
index += 1
|
|
74
|
+
}
|
|
75
|
+
return candidate
|
|
76
|
+
}
|
|
77
|
+
|
|
57
78
|
async function collisionReason(sourcePath, targetPath) {
|
|
58
79
|
try {
|
|
59
80
|
const [sourceStats, targetStats] = await Promise.all([stat(sourcePath), stat(targetPath)])
|
|
@@ -76,4 +97,4 @@ async function pathExists(path) {
|
|
|
76
97
|
} catch {
|
|
77
98
|
return false
|
|
78
99
|
}
|
|
79
|
-
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { access, copyFile, readFile, writeFile } from 'node:fs/promises'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
export async function patchPayloadConfigCollections({ manifest, targetDirectory, dryRun = false }) {
|
|
5
|
+
const registration = manifest.payloadConfigRegistration ?? {}
|
|
6
|
+
if (registration.enabled === false) {
|
|
7
|
+
return [{ status: 'skipped', reason: 'Payload config registration disabled by manifest' }]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const file = await findPayloadConfig(targetDirectory)
|
|
11
|
+
if (!file) return [{ status: 'skipped', reason: 'No payload.config.ts or payload.config.js found' }]
|
|
12
|
+
|
|
13
|
+
const exportName = registration.exportName ?? manifest.exportName ?? manifest.name
|
|
14
|
+
const importPath = registration.importPath ?? `./collections/${manifest.name}`
|
|
15
|
+
const content = await readFile(file, 'utf8')
|
|
16
|
+
const patched = patchPayloadConfigContent(content, { exportName, importPath })
|
|
17
|
+
|
|
18
|
+
if (!patched.changed) return [{ file, status: 'skipped', reason: patched.reason }]
|
|
19
|
+
|
|
20
|
+
const backupPath = await nextBackupPath(file)
|
|
21
|
+
if (!dryRun) {
|
|
22
|
+
await copyFile(file, backupPath)
|
|
23
|
+
await writeFile(file, patched.content)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return [{ file, status: dryRun ? 'would-patch' : 'patched', backupPath }]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function findPayloadConfig(targetDirectory) {
|
|
30
|
+
for (const fileName of ['payload.config.ts', 'payload.config.js', 'src/payload.config.ts', 'src/payload.config.js']) {
|
|
31
|
+
const file = join(targetDirectory, fileName)
|
|
32
|
+
if (await exists(file)) return file
|
|
33
|
+
}
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function patchPayloadConfigContent(content, { exportName, importPath }) {
|
|
38
|
+
if (content.includes(importPath) && content.includes(exportName)) {
|
|
39
|
+
return { changed: false, reason: 'Collection already appears to be registered' }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const match = selectCollectionsArray(content)
|
|
43
|
+
if (!match) return { changed: false, reason: 'No clear Payload collections array found' }
|
|
44
|
+
if (match.items.includes(exportName)) return { changed: false, reason: 'Collection already exists in collections array' }
|
|
45
|
+
|
|
46
|
+
const importLine = `import { ${exportName} } from '${importPath}'\n`
|
|
47
|
+
const nextWithImport = content.includes(importPath) ? content : insertImport(content, importLine)
|
|
48
|
+
const adjustedMatch = selectCollectionsArray(nextWithImport)
|
|
49
|
+
if (!adjustedMatch) return { changed: false, reason: 'No clear Payload collections array found after import insertion' }
|
|
50
|
+
|
|
51
|
+
const existingItems = adjustedMatch.items.replace(/,\s*$/, '').trim()
|
|
52
|
+
const insertion = existingItems
|
|
53
|
+
? `collections: [${existingItems}, ${exportName}]`
|
|
54
|
+
: `collections: [${exportName}]`
|
|
55
|
+
const next = `${nextWithImport.slice(0, adjustedMatch.start)}${insertion}${nextWithImport.slice(adjustedMatch.end)}`
|
|
56
|
+
return { changed: next !== content, content: next }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function insertImport(content, importLine) {
|
|
60
|
+
const importMatches = [...content.matchAll(/^import .+$/gm)]
|
|
61
|
+
if (importMatches.length === 0) return `${importLine}${content}`
|
|
62
|
+
const last = importMatches[importMatches.length - 1]
|
|
63
|
+
const insertAt = last.index + last[0].length
|
|
64
|
+
return `${content.slice(0, insertAt)}\n${importLine}${content.slice(insertAt)}`
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function selectCollectionsArray(content) {
|
|
68
|
+
const matches = [...content.matchAll(/collections:\s*\[([\s\S]*?)\]/gm)].map((match) => ({
|
|
69
|
+
start: match.index,
|
|
70
|
+
end: match.index + match[0].length,
|
|
71
|
+
items: match[1],
|
|
72
|
+
}))
|
|
73
|
+
if (matches.length !== 1) return null
|
|
74
|
+
return matches[0]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function nextBackupPath(file) {
|
|
78
|
+
const base = `${file}.bak`
|
|
79
|
+
if (!(await exists(base))) return base
|
|
80
|
+
let index = 1
|
|
81
|
+
while (await exists(`${base}.${index}`)) index += 1
|
|
82
|
+
return `${base}.${index}`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function exists(path) {
|
|
86
|
+
try {
|
|
87
|
+
await access(path)
|
|
88
|
+
return true
|
|
89
|
+
} catch {
|
|
90
|
+
return false
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -7,18 +7,20 @@ export async function writeInstalledBlockReadme({
|
|
|
7
7
|
copiedFiles,
|
|
8
8
|
sharedResults,
|
|
9
9
|
dependencyChecks,
|
|
10
|
+
payloadConfigResults = [],
|
|
10
11
|
collectionResults,
|
|
11
12
|
renderBlocksResults,
|
|
12
13
|
packageManager,
|
|
13
14
|
installCommand,
|
|
14
15
|
}) {
|
|
15
|
-
const readmePath = join(targetDirectory,
|
|
16
|
+
const readmePath = join(targetDirectory, destinationDirectory(manifest.files), 'README.md')
|
|
16
17
|
await mkdir(dirname(readmePath), { recursive: true })
|
|
17
18
|
await writeFile(readmePath, renderReadme({
|
|
18
19
|
manifest,
|
|
19
20
|
copiedFiles,
|
|
20
21
|
sharedResults,
|
|
21
22
|
dependencyChecks,
|
|
23
|
+
payloadConfigResults,
|
|
22
24
|
collectionResults,
|
|
23
25
|
renderBlocksResults,
|
|
24
26
|
packageManager,
|
|
@@ -32,6 +34,7 @@ function renderReadme({
|
|
|
32
34
|
copiedFiles,
|
|
33
35
|
sharedResults,
|
|
34
36
|
dependencyChecks,
|
|
37
|
+
payloadConfigResults = [],
|
|
35
38
|
collectionResults,
|
|
36
39
|
renderBlocksResults,
|
|
37
40
|
packageManager,
|
|
@@ -42,6 +45,7 @@ function renderReadme({
|
|
|
42
45
|
|
|
43
46
|
return `# ${manifest.name}
|
|
44
47
|
|
|
48
|
+
Template type: \`${manifest.type}\`
|
|
45
49
|
Payload slug: \`${manifest.slug}\`
|
|
46
50
|
|
|
47
51
|
## Files installed
|
|
@@ -66,6 +70,10 @@ Install missing dependencies manually:
|
|
|
66
70
|
${dependencyCommand}
|
|
67
71
|
\`\`\`
|
|
68
72
|
|
|
73
|
+
## Payload config registration status
|
|
74
|
+
|
|
75
|
+
${list(payloadConfigResults.map(formatPatchResult))}
|
|
76
|
+
|
|
69
77
|
## Collection registration status
|
|
70
78
|
|
|
71
79
|
${list(collectionResults.map(formatPatchResult))}
|
|
@@ -88,17 +96,22 @@ ${generatorPrefix} generate:importmap
|
|
|
88
96
|
## Copy-paste Codex prompt for finishing integration
|
|
89
97
|
|
|
90
98
|
\`\`\`text
|
|
91
|
-
Review the installed ${manifest.name}
|
|
99
|
+
Review the installed ${manifest.name} ${manifest.type} integration.
|
|
92
100
|
Only fix issues directly related to ${manifest.name}.
|
|
93
101
|
Check imports, Pages collection, Posts collection, RenderBlocks, shared utilities, shared components, and missing dependencies.
|
|
94
102
|
Run Payload generators manually if schema or admin imports changed.
|
|
95
103
|
Do not rewrite unrelated application logic.
|
|
96
104
|
Do not change environment files.
|
|
97
|
-
Do not rewrite routes unless explicitly required by the
|
|
105
|
+
Do not rewrite routes unless explicitly required by the installed README.
|
|
98
106
|
\`\`\`
|
|
99
107
|
`
|
|
100
108
|
}
|
|
101
109
|
|
|
110
|
+
function destinationDirectory(files) {
|
|
111
|
+
if (!files || files.length === 0) return '.'
|
|
112
|
+
return dirname(files[0].to)
|
|
113
|
+
}
|
|
114
|
+
|
|
102
115
|
function formatDependency(dependency) {
|
|
103
116
|
return `${dependency.name}${dependency.version ? `@${dependency.version}` : ''}`
|
|
104
117
|
}
|
|
@@ -114,4 +127,4 @@ function manualSteps(manifest) {
|
|
|
114
127
|
function list(values) {
|
|
115
128
|
if (!values || values.length === 0) return '- None'
|
|
116
129
|
return values.map((value) => `- ${value}`).join('\n')
|
|
117
|
-
}
|
|
130
|
+
}
|
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
import { readdir } from 'node:fs/promises'
|
|
2
|
+
import { join } from 'node:path'
|
|
2
3
|
import { loadTemplateManifest } from './loadTemplateManifest.js'
|
|
3
4
|
import { validateTemplateManifest } from './validateTemplateManifest.js'
|
|
4
|
-
import {
|
|
5
|
+
import { templateDirectories } from '../utils/paths.js'
|
|
5
6
|
|
|
6
7
|
export async function listTemplates() {
|
|
7
|
-
const entries = await readdir(blocksDirectory, { withFileTypes: true })
|
|
8
8
|
const templates = []
|
|
9
9
|
|
|
10
|
-
for (const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
for (const directory of templateDirectories) {
|
|
11
|
+
let entries = []
|
|
12
|
+
try {
|
|
13
|
+
entries = await readdir(directory.path, { withFileTypes: true })
|
|
14
|
+
} catch {
|
|
15
|
+
continue
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
for (const entry of entries.filter((item) => item.isDirectory())) {
|
|
19
|
+
const { manifest } = await loadTemplateManifest(entry.name)
|
|
20
|
+
validateTemplateManifest(manifest)
|
|
21
|
+
templates.push(manifest)
|
|
22
|
+
}
|
|
14
23
|
}
|
|
15
24
|
|
|
16
25
|
return templates.sort((a, b) => a.name.localeCompare(b.name))
|
|
17
|
-
}
|
|
26
|
+
}
|
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
|
-
import {
|
|
3
|
+
import { templateDirectories } from '../utils/paths.js'
|
|
4
4
|
import { isSafeTemplateName } from '../utils/strings.js'
|
|
5
5
|
|
|
6
6
|
export async function loadTemplateManifest(templateName) {
|
|
7
7
|
if (!isSafeTemplateName(templateName)) throw new Error(`Invalid template name: ${templateName}`)
|
|
8
8
|
|
|
9
|
-
const
|
|
10
|
-
const manifestPath = join(templateDirectory, 'manifest.json')
|
|
9
|
+
const misses = []
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
for (const directory of templateDirectories) {
|
|
12
|
+
const templateDirectory = join(directory.path, templateName)
|
|
13
|
+
const manifestPath = join(templateDirectory, 'manifest.json')
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const manifest = JSON.parse(await readFile(manifestPath, 'utf8'))
|
|
17
|
+
return { manifest, manifestPath, templateDirectory }
|
|
18
|
+
} catch (error) {
|
|
19
|
+
if (error.code === 'ENOENT') {
|
|
20
|
+
misses.push(manifestPath)
|
|
21
|
+
continue
|
|
22
|
+
}
|
|
23
|
+
if (error instanceof SyntaxError) throw new Error(`Invalid JSON in ${manifestPath}: ${error.message}`)
|
|
24
|
+
throw error
|
|
25
|
+
}
|
|
19
26
|
}
|
|
20
|
-
|
|
27
|
+
|
|
28
|
+
throw new Error(`Template not found: ${templateName}`)
|
|
29
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const REQUIRED_STRINGS = ['name', 'type', 'slug', 'version']
|
|
2
|
+
const TEMPLATE_TYPES = new Set(['block', 'component', 'collection'])
|
|
2
3
|
const OPTIONAL_ARRAYS = [
|
|
3
4
|
'files',
|
|
4
5
|
'requiredProjectFiles',
|
|
@@ -14,13 +15,13 @@ const OPTIONAL_ARRAYS = [
|
|
|
14
15
|
export function validateTemplateManifest(manifest) {
|
|
15
16
|
const errors = []
|
|
16
17
|
if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
|
|
17
|
-
throw new Error('
|
|
18
|
+
throw new Error('Template manifest must be a JSON object.')
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
for (const key of REQUIRED_STRINGS) {
|
|
21
22
|
if (typeof manifest[key] !== 'string' || manifest[key].trim() === '') errors.push(`${key} must be a non-empty string`)
|
|
22
23
|
}
|
|
23
|
-
if (manifest.type
|
|
24
|
+
if (!TEMPLATE_TYPES.has(manifest.type)) errors.push('type must be one of: block, component, collection')
|
|
24
25
|
if (manifest.label !== undefined && typeof manifest.label !== 'string') errors.push('label must be a string when present')
|
|
25
26
|
if (manifest.sourceName !== undefined && typeof manifest.sourceName !== 'string') errors.push('sourceName must be a string when present')
|
|
26
27
|
if (manifest.portableName !== undefined && typeof manifest.portableName !== 'string') errors.push('portableName must be a string when present')
|
|
@@ -33,7 +34,7 @@ export function validateTemplateManifest(manifest) {
|
|
|
33
34
|
if (!Array.isArray(manifest.files)) errors.push('files must be an array')
|
|
34
35
|
if (manifest.source !== undefined && (!manifest.source || typeof manifest.source !== 'object' || Array.isArray(manifest.source))) errors.push('source must be an object when present')
|
|
35
36
|
if (manifest.patchTargets !== undefined && (!manifest.patchTargets || typeof manifest.patchTargets !== 'object' || Array.isArray(manifest.patchTargets))) errors.push('patchTargets must be an object when present')
|
|
36
|
-
for (const key of ['collectionRegistration', 'renderBlocksRegistration', 'renderBlocksBehavior', 'pageRouteIntegration']) {
|
|
37
|
+
for (const key of ['collectionRegistration', 'renderBlocksRegistration', 'renderBlocksBehavior', 'pageRouteIntegration', 'payloadConfigRegistration']) {
|
|
37
38
|
if (manifest[key] !== undefined && (!manifest[key] || typeof manifest[key] !== 'object' || Array.isArray(manifest[key]))) errors.push(`${key} must be an object when present`)
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -64,6 +65,6 @@ export function validateTemplateManifest(manifest) {
|
|
|
64
65
|
})
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
if (errors.length > 0) throw new Error(`Invalid
|
|
68
|
+
if (errors.length > 0) throw new Error(`Invalid template manifest:\n- ${errors.join('\n- ')}`)
|
|
68
69
|
return manifest
|
|
69
|
-
}
|
|
70
|
+
}
|