@majordigital/create-acorn 1.5.7 → 1.5.9
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/bin/create-acorn.mjs
CHANGED
|
@@ -70,7 +70,17 @@ function parseFlag(name) {
|
|
|
70
70
|
|
|
71
71
|
function runCommand(cmd, args, options = {}) {
|
|
72
72
|
return new Promise((resolve, reject) => {
|
|
73
|
-
const
|
|
73
|
+
const isWindows = process.platform === 'win32';
|
|
74
|
+
// On Windows, npm/npx are .cmd shims that require a shell to resolve.
|
|
75
|
+
// Quote args containing spaces/special chars so shell parsing preserves them.
|
|
76
|
+
const quotedArgs = isWindows
|
|
77
|
+
? args.map((a) => (/[\s"'&|<>^()]/.test(a) ? `"${a.replace(/"/g, '\\"')}"` : a))
|
|
78
|
+
: args;
|
|
79
|
+
const child = spawn(cmd, quotedArgs, {
|
|
80
|
+
stdio: 'inherit',
|
|
81
|
+
shell: isWindows,
|
|
82
|
+
...options,
|
|
83
|
+
});
|
|
74
84
|
child.on('close', (code) => {
|
|
75
85
|
if (code === 0) resolve();
|
|
76
86
|
else reject(new Error(`${cmd} ${args.join(' ')} exited with code ${code}`));
|
|
@@ -408,14 +418,13 @@ NEXT_DATOCMS_ENVIRONMENT=draft
|
|
|
408
418
|
console.log('Warning: Could not create DatoCMS models automatically.');
|
|
409
419
|
console.log(` Error: ${err.message}`);
|
|
410
420
|
console.log('');
|
|
411
|
-
console.log('You can create models
|
|
412
|
-
console.log('
|
|
413
|
-
console.log('Layout (singleton), MenuItem (block), CaseStudy, LegalPage, Post,');
|
|
414
|
-
console.log('CaseStudiesListing (singleton), PostListing (singleton).');
|
|
421
|
+
console.log('You can create models later by running:');
|
|
422
|
+
console.log(' npm run setup-dato-models');
|
|
415
423
|
}
|
|
416
424
|
} else {
|
|
417
425
|
console.log('');
|
|
418
|
-
console.log('Skipping model creation — you can
|
|
426
|
+
console.log('Skipping model creation — you can run this later inside your project:');
|
|
427
|
+
console.log(' npm run setup-dato-models');
|
|
419
428
|
}
|
|
420
429
|
|
|
421
430
|
// Update next.config.ts — replace Prismic image patterns with DatoCMS
|
|
@@ -474,6 +483,7 @@ NEXT_DATOCMS_ENVIRONMENT=draft
|
|
|
474
483
|
const targetScripts = join(process.cwd(), 'scripts');
|
|
475
484
|
mkdirSync(targetScripts, { recursive: true });
|
|
476
485
|
cpSync(join(datoSrcDir, 'scripts', 'buildLinkIndex.ts'), join(targetScripts, 'buildLinkIndex.ts'));
|
|
486
|
+
cpSync(join(datoSrcDir, 'scripts', 'setupDatoModels.mjs'), join(targetScripts, 'setupDatoModels.mjs'));
|
|
477
487
|
console.log('Copied build scripts.');
|
|
478
488
|
|
|
479
489
|
// Update package.json scripts for DatoCMS
|
|
@@ -494,6 +504,7 @@ NEXT_DATOCMS_ENVIRONMENT=draft
|
|
|
494
504
|
pkg.scripts['fix:prod'] = 'biome check . --write';
|
|
495
505
|
pkg.scripts['generate-types'] = 'graphql-codegen --config graphql.config.yml';
|
|
496
506
|
pkg.scripts['check-types'] = 'tsc --noEmit';
|
|
507
|
+
pkg.scripts['setup-dato-models'] = 'node scripts/setupDatoModels.mjs';
|
|
497
508
|
writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
498
509
|
console.log('Updated package.json scripts for DatoCMS.');
|
|
499
510
|
} catch {
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Standalone script to create DatoCMS models via the Management API.
|
|
4
|
+
* Run with: npm run setup-dato-models
|
|
5
|
+
*
|
|
6
|
+
* This is useful if you:
|
|
7
|
+
* - Skipped the token during initial CLI setup
|
|
8
|
+
* - Created your DatoCMS project after running the CLI
|
|
9
|
+
* - Need to re-run model creation on a fresh DatoCMS project
|
|
10
|
+
*
|
|
11
|
+
* Requires: DATO_API_TOKEN in .env.local (Full-access token with Admin role + CMA enabled)
|
|
12
|
+
* Or pass it when prompted.
|
|
13
|
+
*/
|
|
14
|
+
import readline from 'node:readline';
|
|
15
|
+
import { readFileSync } from 'node:fs';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
import { buildClient } from '@datocms/cma-client-node';
|
|
18
|
+
|
|
19
|
+
function ask(question, defaultValue) {
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
22
|
+
const prompt = defaultValue ? `${question} (${defaultValue}): ` : `${question}: `;
|
|
23
|
+
rl.question(prompt, (answer) => {
|
|
24
|
+
rl.close();
|
|
25
|
+
resolve(answer && answer.trim() ? answer.trim() : defaultValue);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Try to read token from .env.local
|
|
31
|
+
function readTokenFromEnv() {
|
|
32
|
+
try {
|
|
33
|
+
const envPath = join(process.cwd(), '.env.local');
|
|
34
|
+
const content = readFileSync(envPath, 'utf-8');
|
|
35
|
+
const match = content.match(/^DATO_API_TOKEN=(.+)$/m);
|
|
36
|
+
return match?.[1]?.trim() || '';
|
|
37
|
+
} catch {
|
|
38
|
+
return '';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function findOrCreateItemType(client, existingTypes, config) {
|
|
43
|
+
const existing = existingTypes.find(t => t.api_key === config.api_key);
|
|
44
|
+
if (existing) {
|
|
45
|
+
console.log(` Skipping ${config.name} (already exists)...`);
|
|
46
|
+
return existing;
|
|
47
|
+
}
|
|
48
|
+
return client.itemTypes.create(config);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function findOrCreateField(client, modelId, config) {
|
|
52
|
+
const existingFields = await client.fields.list(modelId);
|
|
53
|
+
const existing = existingFields.find(f => f.api_key === config.api_key);
|
|
54
|
+
if (existing) return existing;
|
|
55
|
+
return client.fields.create(modelId, config);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function main() {
|
|
59
|
+
console.log('');
|
|
60
|
+
console.log('=== DatoCMS Model Setup ===');
|
|
61
|
+
console.log('');
|
|
62
|
+
console.log('This will create all required models, blocks, and fields');
|
|
63
|
+
console.log('in your DatoCMS project via the Management API.');
|
|
64
|
+
console.log('');
|
|
65
|
+
console.log('You need a Full-access API token with Admin role');
|
|
66
|
+
console.log('and "Access the Content Management API" enabled.');
|
|
67
|
+
console.log('');
|
|
68
|
+
|
|
69
|
+
const envToken = readTokenFromEnv();
|
|
70
|
+
const apiToken = await ask('Paste your Full-access API token', envToken || undefined);
|
|
71
|
+
|
|
72
|
+
if (!apiToken) {
|
|
73
|
+
console.log('No token provided. Exiting.');
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log('');
|
|
78
|
+
console.log('Creating DatoCMS models...');
|
|
79
|
+
console.log('');
|
|
80
|
+
|
|
81
|
+
const client = buildClient({ apiToken });
|
|
82
|
+
|
|
83
|
+
// Fetch existing models so we can skip duplicates
|
|
84
|
+
const existingTypes = await client.itemTypes.list();
|
|
85
|
+
|
|
86
|
+
// --- Block models ---
|
|
87
|
+
|
|
88
|
+
console.log(' Creating Button block...');
|
|
89
|
+
const buttonBlock = await findOrCreateItemType(client, existingTypes, {
|
|
90
|
+
name: 'Button',
|
|
91
|
+
api_key: 'button',
|
|
92
|
+
modular_block: true,
|
|
93
|
+
});
|
|
94
|
+
await findOrCreateField(client, buttonBlock.id, {
|
|
95
|
+
label: 'Text',
|
|
96
|
+
field_type: 'string',
|
|
97
|
+
api_key: 'text',
|
|
98
|
+
validators: { required: {} },
|
|
99
|
+
});
|
|
100
|
+
await findOrCreateField(client, buttonBlock.id, {
|
|
101
|
+
label: 'Variant',
|
|
102
|
+
field_type: 'string',
|
|
103
|
+
api_key: 'variant',
|
|
104
|
+
validators: { enum: { values: ['primary', 'secondary', 'outline'] } },
|
|
105
|
+
appearance: {
|
|
106
|
+
addons: [],
|
|
107
|
+
editor: 'string_select',
|
|
108
|
+
parameters: { options: [
|
|
109
|
+
{ hint: '', label: 'Primary', value: 'primary' },
|
|
110
|
+
{ hint: '', label: 'Secondary', value: 'secondary' },
|
|
111
|
+
{ hint: '', label: 'Outline', value: 'outline' },
|
|
112
|
+
]},
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
await findOrCreateField(client, buttonBlock.id, {
|
|
116
|
+
label: 'Link',
|
|
117
|
+
field_type: 'json',
|
|
118
|
+
api_key: 'link',
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
console.log(' Creating Hero block...');
|
|
122
|
+
const heroBlock = await findOrCreateItemType(client, existingTypes, {
|
|
123
|
+
name: 'Hero',
|
|
124
|
+
api_key: 'hero',
|
|
125
|
+
modular_block: true,
|
|
126
|
+
});
|
|
127
|
+
await findOrCreateField(client, heroBlock.id, {
|
|
128
|
+
label: 'Heading',
|
|
129
|
+
field_type: 'string',
|
|
130
|
+
api_key: 'heading',
|
|
131
|
+
validators: { required: {} },
|
|
132
|
+
});
|
|
133
|
+
await findOrCreateField(client, heroBlock.id, {
|
|
134
|
+
label: 'Eyebrow',
|
|
135
|
+
field_type: 'string',
|
|
136
|
+
api_key: 'eyebrow',
|
|
137
|
+
});
|
|
138
|
+
await findOrCreateField(client, heroBlock.id, {
|
|
139
|
+
label: 'Content',
|
|
140
|
+
field_type: 'text',
|
|
141
|
+
api_key: 'content',
|
|
142
|
+
appearance: {
|
|
143
|
+
addons: [],
|
|
144
|
+
editor: 'textarea',
|
|
145
|
+
parameters: {},
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
await findOrCreateField(client, heroBlock.id, {
|
|
149
|
+
label: 'Layout',
|
|
150
|
+
field_type: 'string',
|
|
151
|
+
api_key: 'layout',
|
|
152
|
+
validators: { enum: { values: ['default', 'centered', 'split'] } },
|
|
153
|
+
appearance: {
|
|
154
|
+
addons: [],
|
|
155
|
+
editor: 'string_select',
|
|
156
|
+
parameters: { options: [
|
|
157
|
+
{ hint: '', label: 'Default', value: 'default' },
|
|
158
|
+
{ hint: '', label: 'Centered', value: 'centered' },
|
|
159
|
+
{ hint: '', label: 'Split', value: 'split' },
|
|
160
|
+
]},
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
await findOrCreateField(client, heroBlock.id, {
|
|
164
|
+
label: 'Theme',
|
|
165
|
+
field_type: 'string',
|
|
166
|
+
api_key: 'theme',
|
|
167
|
+
validators: { enum: { values: ['light', 'dark'] } },
|
|
168
|
+
appearance: {
|
|
169
|
+
addons: [],
|
|
170
|
+
editor: 'string_select',
|
|
171
|
+
parameters: { options: [
|
|
172
|
+
{ hint: '', label: 'Light', value: 'light' },
|
|
173
|
+
{ hint: '', label: 'Dark', value: 'dark' },
|
|
174
|
+
]},
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
await findOrCreateField(client, heroBlock.id, {
|
|
178
|
+
label: 'Image',
|
|
179
|
+
field_type: 'file',
|
|
180
|
+
api_key: 'image',
|
|
181
|
+
});
|
|
182
|
+
await findOrCreateField(client, heroBlock.id, {
|
|
183
|
+
label: 'Actions',
|
|
184
|
+
field_type: 'rich_text',
|
|
185
|
+
api_key: 'actions',
|
|
186
|
+
validators: {
|
|
187
|
+
rich_text_blocks: { item_types: [buttonBlock.id] },
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
console.log(' Creating MenuItem block...');
|
|
192
|
+
const menuItemBlock = await findOrCreateItemType(client, existingTypes, {
|
|
193
|
+
name: 'Menu Item',
|
|
194
|
+
api_key: 'menu_item',
|
|
195
|
+
modular_block: true,
|
|
196
|
+
});
|
|
197
|
+
await findOrCreateField(client, menuItemBlock.id, {
|
|
198
|
+
label: 'Heading',
|
|
199
|
+
field_type: 'string',
|
|
200
|
+
api_key: 'heading',
|
|
201
|
+
validators: { required: {} },
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// --- Regular models ---
|
|
205
|
+
|
|
206
|
+
console.log(' Creating Page model...');
|
|
207
|
+
const pageModel = await findOrCreateItemType(client, existingTypes, {
|
|
208
|
+
name: 'Page',
|
|
209
|
+
api_key: 'page',
|
|
210
|
+
draft_mode_active: true,
|
|
211
|
+
});
|
|
212
|
+
const pageHeadingField = await findOrCreateField(client, pageModel.id, {
|
|
213
|
+
label: 'Heading',
|
|
214
|
+
field_type: 'string',
|
|
215
|
+
api_key: 'heading',
|
|
216
|
+
validators: { required: {} },
|
|
217
|
+
});
|
|
218
|
+
await findOrCreateField(client, pageModel.id, {
|
|
219
|
+
label: 'Description',
|
|
220
|
+
field_type: 'text',
|
|
221
|
+
api_key: 'description',
|
|
222
|
+
});
|
|
223
|
+
await findOrCreateField(client, pageModel.id, {
|
|
224
|
+
label: 'Slug',
|
|
225
|
+
field_type: 'slug',
|
|
226
|
+
api_key: 'slug',
|
|
227
|
+
validators: {
|
|
228
|
+
required: {},
|
|
229
|
+
slug_title_field: { title_field_id: pageHeadingField.id },
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
await findOrCreateField(client, pageModel.id, {
|
|
233
|
+
label: 'Parent Page',
|
|
234
|
+
field_type: 'link',
|
|
235
|
+
api_key: 'parent_page',
|
|
236
|
+
validators: {
|
|
237
|
+
item_item_type: { item_types: [pageModel.id] },
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
await findOrCreateField(client, pageModel.id, {
|
|
241
|
+
label: 'Body',
|
|
242
|
+
field_type: 'rich_text',
|
|
243
|
+
api_key: 'body',
|
|
244
|
+
validators: {
|
|
245
|
+
rich_text_blocks: { item_types: [heroBlock.id] },
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Add target link to MenuItem
|
|
250
|
+
await findOrCreateField(client, menuItemBlock.id, {
|
|
251
|
+
label: 'Target',
|
|
252
|
+
field_type: 'link',
|
|
253
|
+
api_key: 'target',
|
|
254
|
+
validators: {
|
|
255
|
+
item_item_type: { item_types: [pageModel.id] },
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
console.log(' Creating Case Study model...');
|
|
260
|
+
const caseStudyModel = await findOrCreateItemType(client, existingTypes, {
|
|
261
|
+
name: 'Case Study',
|
|
262
|
+
api_key: 'case_study',
|
|
263
|
+
draft_mode_active: true,
|
|
264
|
+
});
|
|
265
|
+
const caseStudyTitleField = await findOrCreateField(client, caseStudyModel.id, {
|
|
266
|
+
label: 'Title',
|
|
267
|
+
field_type: 'string',
|
|
268
|
+
api_key: 'title',
|
|
269
|
+
validators: { required: {} },
|
|
270
|
+
});
|
|
271
|
+
await findOrCreateField(client, caseStudyModel.id, {
|
|
272
|
+
label: 'Slug',
|
|
273
|
+
field_type: 'slug',
|
|
274
|
+
api_key: 'slug',
|
|
275
|
+
validators: {
|
|
276
|
+
required: {},
|
|
277
|
+
slug_title_field: { title_field_id: caseStudyTitleField.id },
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
console.log(' Creating Legal Page model...');
|
|
282
|
+
const legalPageModel = await findOrCreateItemType(client, existingTypes, {
|
|
283
|
+
name: 'Legal Page',
|
|
284
|
+
api_key: 'legal_page',
|
|
285
|
+
draft_mode_active: true,
|
|
286
|
+
});
|
|
287
|
+
const legalTitleField = await findOrCreateField(client, legalPageModel.id, {
|
|
288
|
+
label: 'Title',
|
|
289
|
+
field_type: 'string',
|
|
290
|
+
api_key: 'title',
|
|
291
|
+
validators: { required: {} },
|
|
292
|
+
});
|
|
293
|
+
await findOrCreateField(client, legalPageModel.id, {
|
|
294
|
+
label: 'Slug',
|
|
295
|
+
field_type: 'slug',
|
|
296
|
+
api_key: 'slug',
|
|
297
|
+
validators: {
|
|
298
|
+
required: {},
|
|
299
|
+
slug_title_field: { title_field_id: legalTitleField.id },
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
await findOrCreateField(client, legalPageModel.id, {
|
|
303
|
+
label: 'Download',
|
|
304
|
+
field_type: 'file',
|
|
305
|
+
api_key: 'download',
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
console.log(' Creating Post model...');
|
|
309
|
+
const postModel = await findOrCreateItemType(client, existingTypes, {
|
|
310
|
+
name: 'Post',
|
|
311
|
+
api_key: 'post',
|
|
312
|
+
draft_mode_active: true,
|
|
313
|
+
});
|
|
314
|
+
const postTitleField = await findOrCreateField(client, postModel.id, {
|
|
315
|
+
label: 'Title',
|
|
316
|
+
field_type: 'string',
|
|
317
|
+
api_key: 'title',
|
|
318
|
+
validators: { required: {} },
|
|
319
|
+
});
|
|
320
|
+
await findOrCreateField(client, postModel.id, {
|
|
321
|
+
label: 'Slug',
|
|
322
|
+
field_type: 'slug',
|
|
323
|
+
api_key: 'slug',
|
|
324
|
+
validators: {
|
|
325
|
+
required: {},
|
|
326
|
+
slug_title_field: { title_field_id: postTitleField.id },
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
console.log(' Creating Case Studies Listing singleton...');
|
|
331
|
+
const caseStudiesListing = await findOrCreateItemType(client, existingTypes, {
|
|
332
|
+
name: 'Case Studies Listing',
|
|
333
|
+
api_key: 'case_studies_listing',
|
|
334
|
+
singleton: true,
|
|
335
|
+
});
|
|
336
|
+
await findOrCreateField(client, caseStudiesListing.id, {
|
|
337
|
+
label: 'Slug',
|
|
338
|
+
field_type: 'string',
|
|
339
|
+
api_key: 'slug',
|
|
340
|
+
validators: { required: {} },
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
console.log(' Creating Post Listing singleton...');
|
|
344
|
+
const postListing = await findOrCreateItemType(client, existingTypes, {
|
|
345
|
+
name: 'Post Listing',
|
|
346
|
+
api_key: 'post_listing',
|
|
347
|
+
singleton: true,
|
|
348
|
+
});
|
|
349
|
+
await findOrCreateField(client, postListing.id, {
|
|
350
|
+
label: 'Slug',
|
|
351
|
+
field_type: 'string',
|
|
352
|
+
api_key: 'slug',
|
|
353
|
+
validators: { required: {} },
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
console.log(' Creating Layout singleton...');
|
|
357
|
+
const layoutModel = await findOrCreateItemType(client, existingTypes, {
|
|
358
|
+
name: 'Layout',
|
|
359
|
+
api_key: 'layout',
|
|
360
|
+
singleton: true,
|
|
361
|
+
});
|
|
362
|
+
await findOrCreateField(client, layoutModel.id, {
|
|
363
|
+
label: 'Navigation',
|
|
364
|
+
field_type: 'rich_text',
|
|
365
|
+
api_key: 'navigation',
|
|
366
|
+
validators: {
|
|
367
|
+
rich_text_blocks: { item_types: [menuItemBlock.id] },
|
|
368
|
+
},
|
|
369
|
+
});
|
|
370
|
+
await findOrCreateField(client, layoutModel.id, {
|
|
371
|
+
label: 'Footer Navigation',
|
|
372
|
+
field_type: 'rich_text',
|
|
373
|
+
api_key: 'footer_navigation',
|
|
374
|
+
validators: {
|
|
375
|
+
rich_text_blocks: { item_types: [menuItemBlock.id] },
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
await findOrCreateField(client, layoutModel.id, {
|
|
379
|
+
label: 'Copyright',
|
|
380
|
+
field_type: 'string',
|
|
381
|
+
api_key: 'copyright',
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
console.log('');
|
|
385
|
+
console.log(' Models created: Page, Case Study, Legal Page, Post');
|
|
386
|
+
console.log(' Singletons created: Layout, Case Studies Listing, Post Listing');
|
|
387
|
+
console.log(' Blocks created: Hero, Button, Menu Item');
|
|
388
|
+
console.log('');
|
|
389
|
+
console.log('Done! You can now run:');
|
|
390
|
+
console.log(' npm run generate-types');
|
|
391
|
+
console.log(' npm run dev');
|
|
392
|
+
console.log('');
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
main().catch((err) => {
|
|
396
|
+
console.error('');
|
|
397
|
+
console.error('Failed to create DatoCMS models.');
|
|
398
|
+
console.error(` Error: ${err.message}`);
|
|
399
|
+
console.error('');
|
|
400
|
+
process.exit(1);
|
|
401
|
+
});
|
package/package.json
CHANGED