@remkoj/optimizely-cms-cli 6.0.0-pre8 → 6.0.0-pre9
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/dist/index.js +781 -594
- package/dist/index.js.map +1 -1
- package/package.json +12 -10
package/dist/index.js
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { globSync, glob, globIterate } from 'glob';
|
|
2
2
|
import { config } from 'dotenv';
|
|
3
3
|
import { expand } from 'dotenv-expand';
|
|
4
|
-
import { readPartialEnvConfig,
|
|
4
|
+
import { readPartialEnvConfig, createClient, OptiCmsVersion, ContentRoots } from '@remkoj/optimizely-cms-api';
|
|
5
5
|
import yargs from 'yargs';
|
|
6
6
|
import chalk from 'chalk';
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import fs from 'node:fs';
|
|
9
9
|
import figures from 'figures';
|
|
10
10
|
import Table from 'cli-table3';
|
|
11
|
+
import fsAsync from 'node:fs/promises';
|
|
11
12
|
import { input, select, confirm } from '@inquirer/prompts';
|
|
12
|
-
import fs$1 from 'node:fs/promises';
|
|
13
13
|
import createDeepMerge from '@fastify/deepmerge';
|
|
14
14
|
import { Ajv } from 'ajv';
|
|
15
15
|
import addFormats from 'ajv-formats';
|
|
16
|
+
import GraphQLGen from '@remkoj/optimizely-graph-functions/contenttype-loader';
|
|
16
17
|
import deepEqual from 'fast-deep-equal';
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -50,7 +51,7 @@ function createOptiCmsApp(scriptName, version, epilogue, envFiles) {
|
|
|
50
51
|
.usage('$0 <cmd> [args]')
|
|
51
52
|
.option("path", { alias: "p", description: "Application root folder", string: true, type: "string", demandOption: false, default: process.cwd() })
|
|
52
53
|
.option("components", { alias: "c", description: "Path to components folder", string: true, type: "string", demandOption: false, default: "./src/components/cms" })
|
|
53
|
-
.option("cms_url", { alias: "cu", description: "Optimizely CMS URL", string: true, type: "string", demandOption: isDemanded(config.base), default: config.base, coerce: (val) => new URL(val) })
|
|
54
|
+
.option("cms_url", { alias: "cu", description: "Optimizely CMS URL", string: true, type: "string", demandOption: isDemanded(config.base), default: config.base, coerce: (val) => val ? new URL(val) : undefined })
|
|
54
55
|
.option("client_id", { alias: "ci", description: "API Client ID", string: true, type: "string", demandOption: isDemanded(config.clientId), default: config.clientId })
|
|
55
56
|
.option('client_secret', { alias: "cs", description: "API Client Secrent", string: true, type: "string", demandOption: isDemanded(config.clientSecret), default: config.clientSecret })
|
|
56
57
|
.option('user_id', { alias: "u", description: "Impersonate user id", string: true, type: "string", demandOption: false, default: config.actAs })
|
|
@@ -115,13 +116,13 @@ function parseArgs({ client_id, client_secret, cms_url, user_id, verbose, path:
|
|
|
115
116
|
|
|
116
117
|
function createCmsClient(args) {
|
|
117
118
|
const cfg = getCmsIntegrationApiOptions(args);
|
|
118
|
-
const baseConfig =
|
|
119
|
+
const baseConfig = readPartialEnvConfig();
|
|
119
120
|
const client = createClient({
|
|
120
121
|
...baseConfig,
|
|
121
122
|
...cfg
|
|
122
123
|
});
|
|
123
124
|
if (cfg.debug)
|
|
124
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Connecting to ${
|
|
125
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Connecting to ${client.cmsUrl} as ${cfg.actAs ?? cfg.clientId}\n`));
|
|
125
126
|
return client;
|
|
126
127
|
}
|
|
127
128
|
function getCmsIntegrationApiOptions(args) {
|
|
@@ -165,7 +166,12 @@ const StylesPushCommand = {
|
|
|
165
166
|
return undefined; // Only include defined styles, if any
|
|
166
167
|
if (cfg.debug)
|
|
167
168
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Pushing: ${styleKey}\n`));
|
|
168
|
-
|
|
169
|
+
// Try to fetch the current template
|
|
170
|
+
const currentTemplate = await client.displayTemplatesGet({ path: { key: styleKey } }).catch(() => { return undefined; });
|
|
171
|
+
// Create / Replace the current template
|
|
172
|
+
const newTemplate = await (currentTemplate ?
|
|
173
|
+
client.displayTemplatesPatch({ path: { key: styleKey }, body: styleDefinition }) :
|
|
174
|
+
client.displayTemplatesCreate({ body: styleDefinition }));
|
|
169
175
|
return newTemplate;
|
|
170
176
|
}))).filter(isNotNullOrUndefined);
|
|
171
177
|
const styles = new Table({
|
|
@@ -205,64 +211,112 @@ function isNotNullOrUndefined(i) {
|
|
|
205
211
|
return i ? true : false;
|
|
206
212
|
}
|
|
207
213
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
+
/**
|
|
215
|
+
* This file contains tools that allow using the project that we're targeting
|
|
216
|
+
*/
|
|
217
|
+
/**
|
|
218
|
+
* Get all the file paths for the content type, taking the current project configuration into
|
|
219
|
+
* account.
|
|
220
|
+
*
|
|
221
|
+
* @param contentType
|
|
222
|
+
* @param basePath
|
|
223
|
+
* @returns
|
|
224
|
+
*/
|
|
225
|
+
function getContentTypePaths(contentType, basePath, createFolder = false, debug = false) {
|
|
226
|
+
const baseTypeSlug = typeToSlug(contentType.baseType);
|
|
227
|
+
const typePath = path.join(basePath, baseTypeSlug, contentType.key);
|
|
228
|
+
if (createFolder) {
|
|
229
|
+
if (!fs.existsSync(typePath)) {
|
|
230
|
+
fs.mkdirSync(typePath, { recursive: true });
|
|
231
|
+
if (debug)
|
|
232
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Created folder ${typePath} for Content-Type ${contentType.key}\n`));
|
|
233
|
+
}
|
|
234
|
+
// Check folders
|
|
235
|
+
if (!fs.statSync(typePath).isDirectory())
|
|
236
|
+
throw new Error(`The folder ${typePath} for Content-Type ${contentType.key} exists, but is not a directory!`);
|
|
237
|
+
}
|
|
238
|
+
const typeFile = path.join(typePath, `${contentType.key}.opti-type.json`);
|
|
239
|
+
const fragmentFile = path.join(typePath, `${contentType.key}.${baseTypeSlug}.graphql`);
|
|
240
|
+
const propertyFragmentFile = path.join(typePath, `${contentType.key}.property.graphql`);
|
|
241
|
+
const queryFile = path.join(typePath, `get${contentType.key}Data.query.graphql`);
|
|
242
|
+
const componentFile = path.join(typePath, `index.tsx`);
|
|
243
|
+
return {
|
|
244
|
+
type: contentType.key,
|
|
245
|
+
path: typePath,
|
|
246
|
+
typePath,
|
|
247
|
+
typeFile,
|
|
248
|
+
fragmentFile,
|
|
249
|
+
componentFile,
|
|
250
|
+
propertyFragmentFile,
|
|
251
|
+
queryFile
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
*
|
|
256
|
+
* @param typeKey
|
|
257
|
+
* @returns
|
|
258
|
+
*/
|
|
259
|
+
function typeToSlug(typeKey) {
|
|
260
|
+
let typeKeySlug = typeKey.toLowerCase();
|
|
261
|
+
if (typeKeySlug.startsWith('_'))
|
|
262
|
+
typeKeySlug = typeKeySlug.substring(1);
|
|
263
|
+
return typeKeySlug;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const ContentTypesArgsDefaults = {
|
|
267
|
+
excludeBaseTypes: ['folder', 'media', 'image', 'video'],
|
|
268
|
+
excludeTypes: [],
|
|
269
|
+
baseTypes: [],
|
|
270
|
+
types: [],
|
|
271
|
+
all: false
|
|
272
|
+
};
|
|
273
|
+
const contentTypesBuilder = (yargs, defaults = ContentTypesArgsDefaults) => {
|
|
274
|
+
yargs.option('excludeTypes', { alias: 'ect', description: "Exclude these content types", string: true, type: 'array', demandOption: false, default: defaults.excludeTypes });
|
|
275
|
+
yargs.option('excludeBaseTypes', { alias: 'ebt', description: "Exclude these base types", string: true, type: 'array', demandOption: false, default: defaults.excludeBaseTypes });
|
|
276
|
+
yargs.option("baseTypes", { alias: 'b', description: "Select only these base types", string: true, type: 'array', demandOption: false, default: defaults.baseTypes });
|
|
277
|
+
yargs.option("types", { alias: 't', description: "Select only these types", string: true, type: 'array', demandOption: false, default: defaults.types });
|
|
278
|
+
yargs.option('all', { alias: 'a', description: "Include non-supported base types", boolean: true, type: 'boolean', demandOption: false, default: defaults.all });
|
|
214
279
|
return yargs;
|
|
215
280
|
};
|
|
216
|
-
function
|
|
217
|
-
return Object.keys(enumObject).filter((item) => {
|
|
218
|
-
return isNaN(Number(item));
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
async function getContentTypes(client, args, pageSize = 5, allowSystem = false, customFilter) {
|
|
281
|
+
async function getContentTypes(client, args, pageSize = 25, allowSystem = false, customFilter) {
|
|
222
282
|
const { _config: cfg, excludeBaseTypes, excludeTypes, baseTypes, types, all } = parseArgs(args);
|
|
223
|
-
const validBaseTypes = getEnumOptions(IntegrationApi.ContentBaseType).map(x => x.toLowerCase());
|
|
224
283
|
const allContentTypes = [];
|
|
225
284
|
const filteredContentTypes = [];
|
|
226
285
|
for await (const contentType of getAllContentTypes(client, cfg.debug, pageSize)) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
else if (validBaseTypes.includes(normalizedContentType.baseType.toLowerCase()))
|
|
232
|
-
allContentTypes.push(normalizedContentType);
|
|
233
|
-
else if (cfg.debug) {
|
|
234
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${normalizedContentType.key} as it has an unsupported base type: ${normalizedContentType.baseType}\n`));
|
|
235
|
-
continue;
|
|
236
|
-
}
|
|
237
|
-
else {
|
|
286
|
+
// Skip content types mapped against Graph data, these should be used with their source type in Graph, not the reference in CMS
|
|
287
|
+
if (!all && contentType.key.toLowerCase().startsWith('graph:')) {
|
|
288
|
+
if (cfg.debug)
|
|
289
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.key} as it is a reference to external data in Optimizely Graph\n`));
|
|
238
290
|
continue;
|
|
239
291
|
}
|
|
292
|
+
// Build the unfiltered array
|
|
293
|
+
allContentTypes.push(contentType);
|
|
240
294
|
// Skip based upon base type filters
|
|
241
|
-
if (!shouldInclude(
|
|
295
|
+
if (!shouldInclude(typeToSlug(contentType.baseType), baseTypes.map(x => typeToSlug(x)), excludeBaseTypes.map(x => typeToSlug(x)))) {
|
|
242
296
|
if (cfg.debug)
|
|
243
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${
|
|
297
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.key} as it has a restricted base type: ${contentType.baseType}\n`));
|
|
244
298
|
continue;
|
|
245
299
|
}
|
|
246
300
|
// Skip based upon type filters
|
|
247
|
-
if (!shouldInclude(
|
|
301
|
+
if (!shouldInclude(contentType.key, types, excludeTypes)) {
|
|
248
302
|
if (cfg.debug)
|
|
249
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${
|
|
303
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.key} as it has a restricted type: ${contentType.key}\n`));
|
|
250
304
|
continue;
|
|
251
305
|
}
|
|
252
306
|
// Skip based upon system filter
|
|
253
|
-
if (
|
|
307
|
+
if (contentType.source == 'system' && !allowSystem) {
|
|
254
308
|
if (cfg.debug)
|
|
255
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Content-Type ${
|
|
309
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Content-Type ${contentType.key} due to it being a system type\n`));
|
|
256
310
|
continue;
|
|
257
311
|
}
|
|
258
312
|
// Skip based upon custom filter
|
|
259
|
-
if (customFilter && !await customFilter(
|
|
313
|
+
if (customFilter && !await customFilter(contentType)) {
|
|
260
314
|
if (cfg.debug)
|
|
261
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Content-Type ${
|
|
315
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Content-Type ${contentType.key} due to the custom filter\n`));
|
|
262
316
|
continue;
|
|
263
317
|
}
|
|
264
318
|
// Add to list
|
|
265
|
-
filteredContentTypes.push(
|
|
319
|
+
filteredContentTypes.push(contentType);
|
|
266
320
|
}
|
|
267
321
|
if (cfg.debug)
|
|
268
322
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Applied content type filters, reduced from ${allContentTypes.length} to ${filteredContentTypes.length} items\n`));
|
|
@@ -420,18 +474,9 @@ async function getStyleFilePath(definition, opts) {
|
|
|
420
474
|
return `${opts?.contentBaseType}/${definition.contentType}/${definition.key}.opti-style.json`;
|
|
421
475
|
if (!opts?.client)
|
|
422
476
|
throw new Error("Neither the contentBaseType, nor the ApiClient has been provided for a definition for a specific ContentType - unable to generate the path");
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
let pagesRemaining = Math.ceil(resultsPage.totalItemCount / resultsPage.pageSize) - (resultsPage.pageIndex + 1);
|
|
427
|
-
while (pagesRemaining > 0 && contentTypes.length < resultsPage.totalItemCount) {
|
|
428
|
-
resultsPage = await opts.client.contentTypesList({ query: { pageIndex: resultsPage.pageIndex + 1, pageSize: resultsPage.pageSize } });
|
|
429
|
-
contentTypes.push(...resultsPage.items);
|
|
430
|
-
pagesRemaining = Math.ceil(resultsPage.totalItemCount / resultsPage.pageSize) - (resultsPage.pageIndex + 1);
|
|
431
|
-
}
|
|
432
|
-
const fetchedBaseType = contentTypes.filter(x => x.key == definition.contentType).map(x => x.baseType).at(0);
|
|
433
|
-
if (fetchedBaseType)
|
|
434
|
-
return `${fetchedBaseType}/${definition.contentType}/${definition.key}.opti-style.json`;
|
|
477
|
+
const contentType = await opts.client.contentTypesGet({ path: { key: definition.contentType } }).catch(() => undefined);
|
|
478
|
+
if (contentType)
|
|
479
|
+
return `${contentType.baseType}/${definition.contentType}/${definition.key}.opti-style.json`;
|
|
435
480
|
}
|
|
436
481
|
throw new Error(`Unable to resolve the target for the DisplayTemplate: ${definition.key}`);
|
|
437
482
|
}
|
|
@@ -475,7 +520,7 @@ const StylesPullCommand = {
|
|
|
475
520
|
builder: (yargs) => {
|
|
476
521
|
const newYargs = stylesBuilder(yargs);
|
|
477
522
|
newYargs.option('force', { alias: 'f', description: "Overwrite existing files", boolean: true, type: 'boolean', demandOption: false, default: false });
|
|
478
|
-
newYargs.option("definitions", { alias: '
|
|
523
|
+
newYargs.option("definitions", { alias: 'u', description: "Create/overwrite typescript definitions", boolean: true, type: 'boolean', demandOption: false, default: true });
|
|
479
524
|
return newYargs;
|
|
480
525
|
},
|
|
481
526
|
handler: async (args) => {
|
|
@@ -490,29 +535,9 @@ const StylesPullCommand = {
|
|
|
490
535
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Start creating .opti-style.json files\n`));
|
|
491
536
|
const typeFiles = {};
|
|
492
537
|
const updatedTemplates = (await Promise.all(filteredResults.map(async (displayTemplate) => {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
if (displayTemplate.nodeType) {
|
|
497
|
-
itemPath = path.join(basePath, 'nodes', displayTemplate.nodeType, displayTemplate.key);
|
|
498
|
-
typesPath = path.join(basePath, 'nodes', displayTemplate.nodeType);
|
|
499
|
-
targetType = 'node/' + displayTemplate.nodeType;
|
|
500
|
-
}
|
|
501
|
-
else if (displayTemplate.baseType) {
|
|
502
|
-
itemPath = path.join(basePath, displayTemplate.baseType, 'styles', displayTemplate.key);
|
|
503
|
-
typesPath = path.join(basePath, displayTemplate.baseType, 'styles');
|
|
504
|
-
targetType = 'base/' + displayTemplate.baseType;
|
|
505
|
-
}
|
|
506
|
-
else if (displayTemplate.contentType) {
|
|
507
|
-
const contentType = await client.contentTypesGet({ path: { key: displayTemplate.contentType ?? '-' } });
|
|
508
|
-
itemPath = path.join(basePath, contentType.baseType, contentType.key);
|
|
509
|
-
typesPath = path.join(basePath, contentType.baseType, contentType.key);
|
|
510
|
-
targetType = 'content/' + displayTemplate.contentType;
|
|
511
|
-
}
|
|
512
|
-
if (!fs.existsSync(itemPath))
|
|
513
|
-
fs.mkdirSync(itemPath, { recursive: true });
|
|
514
|
-
// Write Style JSON
|
|
515
|
-
const filePath = path.join(itemPath, `${displayTemplate.key}.opti-style.json`);
|
|
538
|
+
// Create metadata
|
|
539
|
+
const { targetType, styleFilePath: filePath, helperFilePath: helperPath } = await createTemplateMetadata(client, displayTemplate, basePath, true);
|
|
540
|
+
// Build local style data
|
|
516
541
|
const outputTemplate = { ...displayTemplate };
|
|
517
542
|
if (outputTemplate.createdBy)
|
|
518
543
|
delete outputTemplate.createdBy;
|
|
@@ -522,6 +547,7 @@ const StylesPullCommand = {
|
|
|
522
547
|
delete outputTemplate.created;
|
|
523
548
|
if (outputTemplate.lastModified)
|
|
524
549
|
delete outputTemplate.lastModified;
|
|
550
|
+
// Write file to disk
|
|
525
551
|
if (fs.existsSync(filePath)) {
|
|
526
552
|
if (!force) {
|
|
527
553
|
if (cfg.debug)
|
|
@@ -539,80 +565,20 @@ const StylesPullCommand = {
|
|
|
539
565
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating style file for ${displayTemplate.key} in ${filePath}\n`));
|
|
540
566
|
fs.writeFileSync(filePath, JSON.stringify(outputTemplate, undefined, 2));
|
|
541
567
|
}
|
|
568
|
+
// Ensure we're tracking all files
|
|
542
569
|
if (!typeFiles[targetType]) {
|
|
543
570
|
typeFiles[targetType] = {
|
|
544
|
-
filePath:
|
|
571
|
+
filePath: helperPath,
|
|
545
572
|
templates: []
|
|
546
573
|
};
|
|
547
574
|
}
|
|
548
|
-
typeFiles[targetType].templates.push({ file: filePath, data: displayTemplate });
|
|
575
|
+
typeFiles[targetType].templates.push({ key: displayTemplate.key, file: filePath, data: displayTemplate });
|
|
549
576
|
return displayTemplate.key;
|
|
550
577
|
}))).filter(x => x);
|
|
551
578
|
//#endregion
|
|
552
579
|
//#region Create needed definition files
|
|
553
|
-
if (definitions)
|
|
554
|
-
|
|
555
|
-
for (const targetId of Object.getOwnPropertyNames(typeFiles)) {
|
|
556
|
-
const { filePath: typeFilePath, templates } = typeFiles[targetId];
|
|
557
|
-
if (fs.existsSync(typeFilePath)) {
|
|
558
|
-
if (!force) {
|
|
559
|
-
if (cfg.debug)
|
|
560
|
-
process.stdout.write(chalk.gray(`${figures.cross} Skipped writing definition file for ${targetId} - it already exists\n`));
|
|
561
|
-
continue;
|
|
562
|
-
}
|
|
563
|
-
if (cfg.debug)
|
|
564
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Overwriting definition file for ${targetId} - ${typeFilePath}\n`));
|
|
565
|
-
}
|
|
566
|
-
else if (cfg.debug)
|
|
567
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating definition file for ${targetId} - ${typeFilePath}\n`));
|
|
568
|
-
// Write Style definition
|
|
569
|
-
const imports = [
|
|
570
|
-
'import type { LayoutProps } from "@remkoj/optimizely-cms-react"',
|
|
571
|
-
'import type { ReactNode, JSX } from "react"'
|
|
572
|
-
];
|
|
573
|
-
const typeContents = [];
|
|
574
|
-
const props = [];
|
|
575
|
-
let typeId = targetId.split('/', 2)[1];
|
|
576
|
-
templates.forEach(({ file: displayTemplateFile, data: displayTemplate }) => {
|
|
577
|
-
const importPath = path.relative(path.dirname(typeFilePath), displayTemplateFile).replaceAll('\\', '/');
|
|
578
|
-
imports.push(`import type ${displayTemplate.key}Styles from "./${importPath}"`);
|
|
579
|
-
typeContents.push(`export type ${displayTemplate.key}Props = LayoutProps<typeof ${displayTemplate.key}Styles>`);
|
|
580
|
-
typeContents.push(`export type ${displayTemplate.key}ComponentProps<DT extends Record<string, any> = Record<string, any>> = {
|
|
581
|
-
data: DT
|
|
582
|
-
layoutProps: ${displayTemplate.key}Props | undefined
|
|
583
|
-
} & JSX.IntrinsicElements['div']`);
|
|
584
|
-
typeContents.push(`export type ${displayTemplate.key}Component<DT extends Record<string, any> = Record<string, any>> = (props: ${displayTemplate.key}ComponentProps<DT>) => ReactNode`);
|
|
585
|
-
typeContents.push('');
|
|
586
|
-
props.push(`${displayTemplate.key}Props`);
|
|
587
|
-
if (!typeId)
|
|
588
|
-
typeId = displayTemplate.nodeType ?? displayTemplate.baseType ?? displayTemplate.contentType;
|
|
589
|
-
});
|
|
590
|
-
if (typeId) {
|
|
591
|
-
typeId = ucFirst$1(typeId);
|
|
592
|
-
typeContents.push('');
|
|
593
|
-
typeContents.push(`export type ${typeId}LayoutProps = ${props.join(' | ')}
|
|
594
|
-
export type ${typeId}ComponentProps<DT extends Record<string, any> = Record<string, any>, LP extends ${typeId}LayoutProps = ${typeId}LayoutProps> = {
|
|
595
|
-
data: DT
|
|
596
|
-
layoutProps: LP | undefined
|
|
597
|
-
} & JSX.IntrinsicElements['div']
|
|
598
|
-
|
|
599
|
-
export type ${typeId}Component<DT extends Record<string, any> = Record<string, any>, LP extends ${typeId}LayoutProps = ${typeId}LayoutProps> = (props: ${typeId}ComponentProps<DT,LP>) => ReactNode
|
|
600
|
-
|
|
601
|
-
export function isDefaultProps(props?: ${typeId}LayoutProps | null) : props is ${templates.filter(t => t.data.isDefault).at(0)?.data?.key}Props
|
|
602
|
-
{
|
|
603
|
-
return props?.template == "${templates.filter(t => t.data.isDefault).at(0)?.data?.key}"
|
|
604
|
-
}`);
|
|
605
|
-
templates.forEach(t => {
|
|
606
|
-
typeContents.push(`
|
|
607
|
-
export function is${t.data.key}Props(props?: ${typeId}LayoutProps | null) : props is ${t.data.key}Props
|
|
608
|
-
{
|
|
609
|
-
return props?.template == "${t.data.key}"
|
|
610
|
-
}`);
|
|
611
|
-
});
|
|
612
|
-
}
|
|
613
|
-
fs.writeFileSync(typeFilePath, imports.join("\n") + "\n\n" + typeContents.join("\n"));
|
|
614
|
-
}
|
|
615
|
-
}
|
|
580
|
+
if (definitions)
|
|
581
|
+
await createDisplayTemplateHelpers(typeFiles);
|
|
616
582
|
//#endregion
|
|
617
583
|
process.stdout.write(chalk.green(chalk.bold(figures.tick + ` Created/updated style definitions for ${updatedTemplates.join(', ')}`)) + "\n");
|
|
618
584
|
}
|
|
@@ -622,6 +588,131 @@ function ucFirst$1(input) {
|
|
|
622
588
|
return input;
|
|
623
589
|
return input[0].toUpperCase() + input.substring(1);
|
|
624
590
|
}
|
|
591
|
+
async function createTemplateMetadata(client, displayTemplate, basePath, createPaths = true) {
|
|
592
|
+
let itemPath = undefined;
|
|
593
|
+
let targetType;
|
|
594
|
+
let typesPath;
|
|
595
|
+
const targetPrefix = getTemplateTarget(displayTemplate);
|
|
596
|
+
switch (targetPrefix) {
|
|
597
|
+
case 'node':
|
|
598
|
+
itemPath = path.join(basePath, 'nodes', displayTemplate.nodeType, displayTemplate.key);
|
|
599
|
+
typesPath = path.join(basePath, 'nodes', displayTemplate.nodeType);
|
|
600
|
+
targetType = targetPrefix + '/' + displayTemplate.nodeType;
|
|
601
|
+
break;
|
|
602
|
+
case 'base': {
|
|
603
|
+
const baseTypeSlug = baseTypeToSlug(displayTemplate.baseType);
|
|
604
|
+
itemPath = path.join(basePath, baseTypeSlug, 'styles', displayTemplate.key);
|
|
605
|
+
typesPath = path.join(basePath, baseTypeSlug, 'styles');
|
|
606
|
+
targetType = targetPrefix + '/' + baseTypeSlug;
|
|
607
|
+
break;
|
|
608
|
+
}
|
|
609
|
+
case 'content': {
|
|
610
|
+
const contentType = await client.contentTypesGet({ path: { key: displayTemplate.contentType ?? '-' } });
|
|
611
|
+
const baseTypeSlug = baseTypeToSlug(contentType.baseType);
|
|
612
|
+
itemPath = path.join(basePath, baseTypeSlug, contentType.key);
|
|
613
|
+
typesPath = path.join(basePath, baseTypeSlug, contentType.key);
|
|
614
|
+
targetType = targetPrefix + '/' + displayTemplate.contentType;
|
|
615
|
+
break;
|
|
616
|
+
}
|
|
617
|
+
default:
|
|
618
|
+
throw new Error("Unsupported display template type");
|
|
619
|
+
}
|
|
620
|
+
if (createPaths)
|
|
621
|
+
void await fsAsync.mkdir(itemPath, { recursive: true }).catch(e => {
|
|
622
|
+
console.log(e);
|
|
623
|
+
});
|
|
624
|
+
const styleFilePath = path.join(itemPath, `${displayTemplate.key}.opti-style.json`);
|
|
625
|
+
const helperFilePath = path.join(typesPath, 'displayTemplates.ts');
|
|
626
|
+
return { key: displayTemplate.key, itemPath, typesPath, targetType, styleFilePath, helperFilePath };
|
|
627
|
+
}
|
|
628
|
+
function baseTypeToSlug(baseType) {
|
|
629
|
+
return baseType.replace(/^\_*/, "").toLowerCase();
|
|
630
|
+
}
|
|
631
|
+
function getTemplateTarget(displayTemplate) {
|
|
632
|
+
if (displayTemplate.baseType && displayTemplate.baseType.length > 0)
|
|
633
|
+
return 'base';
|
|
634
|
+
if (displayTemplate.nodeType && displayTemplate.nodeType.length > 0)
|
|
635
|
+
return 'node';
|
|
636
|
+
if (displayTemplate.contentType && displayTemplate.contentType.length > 0)
|
|
637
|
+
return 'content';
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
async function createDisplayTemplateHelpers(typeFiles, force = false, debug = false) {
|
|
641
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Start creating displayTemplates.ts files\n`));
|
|
642
|
+
for (const targetId in typeFiles) {
|
|
643
|
+
await createDisplayTemplateHelper(typeFiles[targetId], targetId, force, debug);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
async function createDisplayTemplateHelper(typeFile, typeFileId, force = false, debug = false) {
|
|
647
|
+
const prefix = '//not-modified - Remove this line when making change to prevent it from being updated by the CLI tools';
|
|
648
|
+
const { filePath: typeFilePath, templates } = typeFile;
|
|
649
|
+
const shouldWrite = await fsAsync.readFile(typeFilePath, { encoding: "utf-8" }).then(data => data.startsWith('//not-modified')).catch((e) => {
|
|
650
|
+
if (e?.code === 'ENOENT')
|
|
651
|
+
return true;
|
|
652
|
+
if (debug)
|
|
653
|
+
process.stdout.write(chalk.redBright(chalk.bold(`${figures.cross} Unexpected error while reading display template file\n`)));
|
|
654
|
+
return false;
|
|
655
|
+
});
|
|
656
|
+
if (!shouldWrite) {
|
|
657
|
+
if (!force) {
|
|
658
|
+
if (debug)
|
|
659
|
+
process.stdout.write(chalk.gray(`${figures.cross} Skipped writing definition file for ${typeFileId} - it already exists and has been modified\n`));
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
else {
|
|
663
|
+
if (debug)
|
|
664
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Forcefully overwriting definition file for ${typeFileId} - ${typeFilePath}\n`));
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
else if (debug)
|
|
668
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating or updating definition file for ${typeFileId} - ${typeFilePath}\n`));
|
|
669
|
+
// Write Style definition
|
|
670
|
+
const imports = [
|
|
671
|
+
'import type { LayoutProps, LayoutPropsSettingKeys, LayoutPropsSettingValues, CmsComponentProps } from "@remkoj/optimizely-cms-react"',
|
|
672
|
+
'import type { JSX, ComponentType } from "react"'
|
|
673
|
+
];
|
|
674
|
+
const typeContents = [];
|
|
675
|
+
const props = [];
|
|
676
|
+
let typeId = typeFileId.split('/', 2)[1];
|
|
677
|
+
templates.forEach(({ file: displayTemplateFile, data: displayTemplate }) => {
|
|
678
|
+
const importPath = path.relative(path.dirname(typeFilePath), displayTemplateFile).replaceAll('\\', '/');
|
|
679
|
+
imports.push(`import type ${displayTemplate.key}Styles from "./${importPath}"`);
|
|
680
|
+
typeContents.push(`export type ${displayTemplate.key}Props = LayoutProps<typeof ${displayTemplate.key}Styles>`);
|
|
681
|
+
typeContents.push(`export type ${displayTemplate.key}Keys = LayoutPropsSettingKeys<${displayTemplate.key}Props>`);
|
|
682
|
+
typeContents.push(`export type ${displayTemplate.key}Options<K extends ${displayTemplate.key}Keys> = LayoutPropsSettingValues<${displayTemplate.key}Props, K>`);
|
|
683
|
+
typeContents.push(`export type ${displayTemplate.key}ComponentProps<DT extends Record<string, any> = Record<string, any>> = Omit<CmsComponentProps<DT, ${displayTemplate.key}Props>,'children'> & JSX.IntrinsicElements['div']`);
|
|
684
|
+
typeContents.push(`export type ${displayTemplate.key}Component<DT extends Record<string, any> = Record<string, any>> = ComponentType<${displayTemplate.key}ComponentProps<DT>>`);
|
|
685
|
+
typeContents.push('');
|
|
686
|
+
props.push(`${displayTemplate.key}Props`);
|
|
687
|
+
if (!typeId)
|
|
688
|
+
typeId = displayTemplate.nodeType ?? displayTemplate.baseType ?? displayTemplate.contentType;
|
|
689
|
+
});
|
|
690
|
+
if (typeId) {
|
|
691
|
+
typeId = ucFirst$1(typeId);
|
|
692
|
+
typeContents.push(`export type ${typeId}LayoutProps = ${props.join(' | ')}
|
|
693
|
+
export type ${typeId}LayoutKeys = LayoutPropsSettingKeys<${typeId}LayoutProps>
|
|
694
|
+
export type ${typeId}LayoutOptions<K extends ${typeId}LayoutKeys> = LayoutPropsSettingValues<${typeId}LayoutProps,K>
|
|
695
|
+
export type ${typeId}ComponentProps<DT extends Record<string, any> = Record<string, any>> = Omit<CmsComponentProps<DT, ${typeId}LayoutProps>,'children'> & JSX.IntrinsicElements['div']
|
|
696
|
+
export type ${typeId}Component<DT extends Record<string, any> = Record<string, any>> = ComponentType<${typeId}ComponentProps<DT>>`);
|
|
697
|
+
const defaultTemplate = templates.find(t => t.data.isDefault);
|
|
698
|
+
if (defaultTemplate) {
|
|
699
|
+
typeContents.push(`
|
|
700
|
+
export function isDefaultProps(props?: ${typeId}LayoutProps | null) : props is ${defaultTemplate.data?.key}Props
|
|
701
|
+
{
|
|
702
|
+
return props?.template == "${defaultTemplate.data?.key}"
|
|
703
|
+
}`);
|
|
704
|
+
}
|
|
705
|
+
templates.forEach(t => {
|
|
706
|
+
typeContents.push(`
|
|
707
|
+
export function is${t.data.key}Props(props?: ${typeId}LayoutProps | null) : props is ${t.data.key}Props
|
|
708
|
+
{
|
|
709
|
+
return props?.template == "${t.data.key}"
|
|
710
|
+
}`);
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
void await fsAsync.writeFile(typeFilePath, prefix + "\n" + imports.join("\n") + "\n\n" + typeContents.join("\n"));
|
|
714
|
+
return true;
|
|
715
|
+
}
|
|
625
716
|
|
|
626
717
|
// Node JS and 3rd Party libraries
|
|
627
718
|
const StylesCreateCommand = {
|
|
@@ -700,7 +791,7 @@ const StylesCreateCommand = {
|
|
|
700
791
|
fs.writeFileSync(path.join(basePath, styleFilePath), JSON.stringify(definition, undefined, 4));
|
|
701
792
|
process.stdout.write(chalk.yellowBright(chalk.bold(`\nWritten style defintion template to: ${path.normalize(path.join(basePath, styleFilePath))}\n`)));
|
|
702
793
|
if (await confirm({ message: "Do you want to publish this style into Optimizely CMS?" })) {
|
|
703
|
-
const response = await client.
|
|
794
|
+
const response = await client.displayTemplatesCreate({ body: definition }).catch(_ => undefined);
|
|
704
795
|
if (!response) {
|
|
705
796
|
process.stderr.write(chalk.redBright(chalk.bold(`\[ERROR] Unable to create style definition ${definition.displayName} in Optimizely CMS\n`)));
|
|
706
797
|
}
|
|
@@ -733,8 +824,7 @@ const TypesPullCommand = {
|
|
|
733
824
|
const client = createCmsClient(args);
|
|
734
825
|
const { contentTypes } = await getContentTypes(client, args);
|
|
735
826
|
const updatedTypes = contentTypes.map(contentType => {
|
|
736
|
-
const typePath =
|
|
737
|
-
const typeFile = path.join(typePath, `${contentType.key}.opti-type.json`);
|
|
827
|
+
const { typePath, typeFile } = getContentTypePaths(contentType, basePath);
|
|
738
828
|
if (!fs.existsSync(typePath))
|
|
739
829
|
fs.mkdirSync(typePath, { recursive: true });
|
|
740
830
|
if (fs.existsSync(typeFile) && !force) {
|
|
@@ -745,16 +835,28 @@ const TypesPullCommand = {
|
|
|
745
835
|
const outContentType = { ...contentType };
|
|
746
836
|
if (outContentType.source || outContentType.source == "")
|
|
747
837
|
delete outContentType.source;
|
|
748
|
-
if (outContentType.features)
|
|
749
|
-
|
|
750
|
-
if (outContentType.usage)
|
|
751
|
-
delete outContentType.usage;
|
|
838
|
+
//if (outContentType.features) delete outContentType.features
|
|
839
|
+
//if (outContentType.usage) delete outContentType.usage
|
|
752
840
|
if (outContentType.lastModifiedBy)
|
|
753
841
|
delete outContentType.lastModifiedBy;
|
|
754
842
|
if (outContentType.lastModified)
|
|
755
843
|
delete outContentType.lastModified;
|
|
756
844
|
if (outContentType.created)
|
|
757
845
|
delete outContentType.created;
|
|
846
|
+
for (const propName of Object.getOwnPropertyNames(outContentType.properties ?? {})) {
|
|
847
|
+
if (!["content", "contentReference"].includes(outContentType.properties[propName].type)) {
|
|
848
|
+
if (isEmptyArray(outContentType.properties[propName].allowedTypes))
|
|
849
|
+
delete outContentType.properties[propName].allowedTypes;
|
|
850
|
+
if (isEmptyArray(outContentType.properties[propName].restrictedTypes))
|
|
851
|
+
delete outContentType.properties[propName].restrictedTypes;
|
|
852
|
+
}
|
|
853
|
+
if (outContentType.properties[propName].type == 'array' && !["content", "contentReference"].includes(outContentType.properties[propName].items?.type)) {
|
|
854
|
+
if (isEmptyArray(outContentType.properties[propName].items.allowedTypes))
|
|
855
|
+
delete outContentType.properties[propName].items.allowedTypes;
|
|
856
|
+
if (isEmptyArray(outContentType.properties[propName].items.restrictedTypes))
|
|
857
|
+
delete outContentType.properties[propName].items.restrictedTypes;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
758
860
|
if (debug)
|
|
759
861
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Writing type definition for ${contentType.displayName} (${contentType.key})\n`));
|
|
760
862
|
fs.writeFileSync(typeFile, JSON.stringify(outContentType, undefined, 2));
|
|
@@ -763,6 +865,197 @@ const TypesPullCommand = {
|
|
|
763
865
|
process.stdout.write(chalk.green(chalk.bold(`${figures.tick} Created/updated type definitions for ${updatedTypes.join(', ')}\n`)));
|
|
764
866
|
}
|
|
765
867
|
};
|
|
868
|
+
function isEmptyArray(toTest) {
|
|
869
|
+
return toTest === null || !Array.isArray(toTest) || toTest.length === 0;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
const deepmerge$1 = createDeepMerge();
|
|
873
|
+
async function loadSchema(client, schemaName) {
|
|
874
|
+
const schemas = Array.isArray(schemaName) ? schemaName : [schemaName];
|
|
875
|
+
process.stdout.write(`${figures.arrowRight} Downloading current JSON Schema\n`);
|
|
876
|
+
const spec = await client.getOpenApiSpec();
|
|
877
|
+
const specSchemas = spec.components?.schemas ?? {};
|
|
878
|
+
process.stdout.write(` ${figures.tick} Complete\n`);
|
|
879
|
+
const result = [];
|
|
880
|
+
process.stdout.write(`\n${figures.arrowRight} Constructing schema for ${schemas.join(', ')}\n`);
|
|
881
|
+
for await (const schema of schemas)
|
|
882
|
+
if (specSchemas[schema]) {
|
|
883
|
+
const definitions = {};
|
|
884
|
+
const processedSchema = processSchema(specSchemas[schema], definitions, spec);
|
|
885
|
+
const jsonSchema = {
|
|
886
|
+
//"$schema": "https://json-schema.org/draft-07/schema",
|
|
887
|
+
"$id": new URL(`schema/${schema}`, client.getSchemaItemBase()).href,
|
|
888
|
+
type: "object",
|
|
889
|
+
title: schema,
|
|
890
|
+
...processedSchema,
|
|
891
|
+
definitions
|
|
892
|
+
};
|
|
893
|
+
result.push({
|
|
894
|
+
title: schema,
|
|
895
|
+
schema: postProcessDefintions(jsonSchema)
|
|
896
|
+
});
|
|
897
|
+
process.stdout.write(` ${figures.tick} Constructed schema of ${schema}\n`);
|
|
898
|
+
}
|
|
899
|
+
return result;
|
|
900
|
+
}
|
|
901
|
+
function getValidator(schemaObject) {
|
|
902
|
+
const ajv = new Ajv({
|
|
903
|
+
discriminator: true
|
|
904
|
+
});
|
|
905
|
+
addFormats(ajv);
|
|
906
|
+
return ajv.compile(schemaObject);
|
|
907
|
+
}
|
|
908
|
+
function postProcessDefintions(jsonSchema) {
|
|
909
|
+
if (!jsonSchema.definitions)
|
|
910
|
+
return jsonSchema;
|
|
911
|
+
for (const definitionName in jsonSchema.definitions) {
|
|
912
|
+
if ((definitionName.endsWith("Property") || definitionName.endsWith("ListItem")) && jsonSchema.definitions[definitionName].properties) {
|
|
913
|
+
const type = jsonSchema.definitions[definitionName].properties?.type;
|
|
914
|
+
if (isRefSchema(type)) {
|
|
915
|
+
const typeSchema = resolveRefSchema(type, jsonSchema);
|
|
916
|
+
if (typeSchema && typeSchema.type === 'string' && Array.isArray(typeSchema.enum)) {
|
|
917
|
+
let typeValue = definitionName.substring(0, definitionName.length - 8);
|
|
918
|
+
typeValue = typeValue[0].toLowerCase() + typeValue.substring(1);
|
|
919
|
+
typeValue = typeValue === 'list' ? 'array' : typeValue;
|
|
920
|
+
typeValue = typeValue === 'jsonString' ? 'json' : typeValue;
|
|
921
|
+
if (typeSchema.enum.includes(typeValue)) {
|
|
922
|
+
const newTypeDef = deepmerge$1({}, typeSchema);
|
|
923
|
+
newTypeDef.enum = newTypeDef.enum.filter((x) => x === typeValue);
|
|
924
|
+
jsonSchema.definitions[definitionName].properties.type = newTypeDef;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
return jsonSchema;
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* Schema normalization to tranform the schema from a .Net generated OpenAPI Schema
|
|
934
|
+
* to a AJV compatible JSON Schema
|
|
935
|
+
*
|
|
936
|
+
* @see https://ajv.js.org/
|
|
937
|
+
* @param schema
|
|
938
|
+
* @param defs
|
|
939
|
+
* @param spec
|
|
940
|
+
* @returns
|
|
941
|
+
*/
|
|
942
|
+
function processSchema(schema, defs, spec, mergeAllOf = true) {
|
|
943
|
+
if (Array.isArray(schema))
|
|
944
|
+
return schema.map(s => processSchema(s, defs, spec, mergeAllOf));
|
|
945
|
+
if (typeof schema !== 'object' || schema === null)
|
|
946
|
+
return schema;
|
|
947
|
+
const props = Object.getOwnPropertyNames(schema);
|
|
948
|
+
if (props.length === 1 && props[0] === '$ref') {
|
|
949
|
+
const ref = schema['$ref'];
|
|
950
|
+
if (isLocalRef(ref)) {
|
|
951
|
+
const refName = getLocalRefName(ref);
|
|
952
|
+
if (!defs[refName]) {
|
|
953
|
+
const refItem = resolveLocalRef(ref, spec);
|
|
954
|
+
defs[refName] = processSchema(refItem, defs, spec, mergeAllOf);
|
|
955
|
+
}
|
|
956
|
+
const newRef = `#/definitions/${refName}`;
|
|
957
|
+
return { "$ref": newRef };
|
|
958
|
+
}
|
|
959
|
+
else {
|
|
960
|
+
return schema;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
else if (mergeAllOf && props.includes('allOf') && Array.isArray(schema['allOf'])) {
|
|
964
|
+
// process all of
|
|
965
|
+
const newObject = props.reduce((generated, propName) => {
|
|
966
|
+
if (propName != 'allOf')
|
|
967
|
+
generated[propName] = schema[propName];
|
|
968
|
+
return generated;
|
|
969
|
+
}, {});
|
|
970
|
+
const allOfSchemas = schema['allOf'].map((subschema) => {
|
|
971
|
+
const resolvedSchema = isRefSchema(subschema) ? resolveRefSchema(subschema, spec) : subschema;
|
|
972
|
+
const resolved = processSchema(resolvedSchema, defs, spec, mergeAllOf);
|
|
973
|
+
return resolved;
|
|
974
|
+
});
|
|
975
|
+
const merged = allOfSchemas.reduce((previous, current) => deepmerge$1(previous, current), newObject);
|
|
976
|
+
if (newObject['description'])
|
|
977
|
+
merged['description'] = newObject['description'];
|
|
978
|
+
if (newObject['title'])
|
|
979
|
+
merged['title'] = newObject['title'];
|
|
980
|
+
return merged;
|
|
981
|
+
}
|
|
982
|
+
else {
|
|
983
|
+
const newSchema = {};
|
|
984
|
+
for (const key of props) {
|
|
985
|
+
if (key === 'pattern' && typeof schema[key] === 'string') {
|
|
986
|
+
newSchema[key] = safeCreateUnicodeRegExp(schema[key]);
|
|
987
|
+
/*} else if (key === 'readOnly' && schema[key] === true) {
|
|
988
|
+
return undefined*/
|
|
989
|
+
}
|
|
990
|
+
else if (['additionalProperties', 'required', 'properties', 'discriminator'].includes(key) && typeof schema[key] !== 'object') ;
|
|
991
|
+
else if (key === 'discriminator' && !Array.isArray(schema['oneOf'])) ;
|
|
992
|
+
else if (key === 'discriminator' && !schema['type']) {
|
|
993
|
+
newSchema.type = 'object';
|
|
994
|
+
}
|
|
995
|
+
else {
|
|
996
|
+
const keyVal = processSchema(schema[key], defs, spec, mergeAllOf);
|
|
997
|
+
if (keyVal !== undefined)
|
|
998
|
+
newSchema[key] = keyVal;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
if (newSchema['oneOf'] && Array.isArray(newSchema['oneOf']) && newSchema['nullable']) {
|
|
1002
|
+
delete newSchema['nullable'];
|
|
1003
|
+
}
|
|
1004
|
+
return newSchema;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
function isRefSchema(schema) {
|
|
1008
|
+
if (typeof schema != 'object' || schema == null)
|
|
1009
|
+
return false;
|
|
1010
|
+
return Object.getOwnPropertyNames(schema).length === 1 && typeof schema['$ref'] === 'string';
|
|
1011
|
+
}
|
|
1012
|
+
function resolveRefSchema(schema, spec) {
|
|
1013
|
+
if (!isRefSchema(schema))
|
|
1014
|
+
return undefined;
|
|
1015
|
+
return resolveLocalRef(schema['$ref'], spec);
|
|
1016
|
+
}
|
|
1017
|
+
function isLocalRef(ref) {
|
|
1018
|
+
return typeof ref === 'string' && ref.startsWith('#');
|
|
1019
|
+
}
|
|
1020
|
+
function getLocalRefName(ref) {
|
|
1021
|
+
if (!isLocalRef(ref))
|
|
1022
|
+
return undefined;
|
|
1023
|
+
const refPath = ref.substring(2).split('/');
|
|
1024
|
+
return refPath.at(refPath.length - 1);
|
|
1025
|
+
}
|
|
1026
|
+
function resolveLocalRef(ref, spec) {
|
|
1027
|
+
const refPath = ref.substring(2).split('/');
|
|
1028
|
+
return refPath.reduce((integrator, current) => {
|
|
1029
|
+
return integrator ? integrator[current] : undefined;
|
|
1030
|
+
}, spec);
|
|
1031
|
+
}
|
|
1032
|
+
const replaceEscapedChars = [
|
|
1033
|
+
[new RegExp('\\\\ ', 'g'), ' '],
|
|
1034
|
+
[new RegExp('\\\\!', 'g'), '!'],
|
|
1035
|
+
[new RegExp('\\\\"', 'g'), '"'],
|
|
1036
|
+
[new RegExp('\\\\#', 'g'), '#'],
|
|
1037
|
+
[new RegExp('\\\\%', 'g'), '%'],
|
|
1038
|
+
[new RegExp('\\\\&', 'g'), '&'],
|
|
1039
|
+
[new RegExp("\\\\'", 'g'), "'"],
|
|
1040
|
+
[new RegExp('\\\\,', 'g'), ','],
|
|
1041
|
+
[new RegExp('\\\\-', 'g'), '-'],
|
|
1042
|
+
[new RegExp('\\\\:', 'g'), ':'],
|
|
1043
|
+
[new RegExp('\\\\;', 'g'), ';'],
|
|
1044
|
+
[new RegExp('\\\\<', 'g'), '<'],
|
|
1045
|
+
[new RegExp('\\\\=', 'g'), '='],
|
|
1046
|
+
[new RegExp('\\\\>', 'g'), '>'],
|
|
1047
|
+
[new RegExp('\\\\@', 'g'), '@'],
|
|
1048
|
+
[new RegExp('\\\\_', 'g'), '_'],
|
|
1049
|
+
[new RegExp('\\\\`', 'g'), '`'],
|
|
1050
|
+
[new RegExp('\\\\~', 'g'), '~'],
|
|
1051
|
+
[new RegExp('\\\\[zZ]', 'g'), '$'],
|
|
1052
|
+
];
|
|
1053
|
+
function safeCreateUnicodeRegExp(pattern) {
|
|
1054
|
+
for (const unEscape of replaceEscapedChars) {
|
|
1055
|
+
pattern = pattern.replace(unEscape[0], unEscape[1]);
|
|
1056
|
+
}
|
|
1057
|
+
return pattern;
|
|
1058
|
+
}
|
|
766
1059
|
|
|
767
1060
|
const TypesPushCommand = {
|
|
768
1061
|
command: "types:push",
|
|
@@ -793,9 +1086,18 @@ const TypesPushCommand = {
|
|
|
793
1086
|
(!baseTypes || baseTypes.length == 0 || baseTypes.includes(data.definition.baseType)) &&
|
|
794
1087
|
(!types || types.length == 0 || types.includes(data.definition.key));
|
|
795
1088
|
});
|
|
1089
|
+
// Create validator
|
|
1090
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Loading OpenAPI Specification for validation\n`));
|
|
1091
|
+
const typeSchema = (await loadSchema(client, 'ContentType')).at(0)?.schema;
|
|
1092
|
+
const typeValidator = await getValidator(typeSchema);
|
|
796
1093
|
// Output selected types
|
|
797
|
-
const results = await Promise.all(typeDefinitions.map(type => {
|
|
1094
|
+
const results = await Promise.all(typeDefinitions.map(async (type) => {
|
|
1095
|
+
if (!type.definition.key) {
|
|
1096
|
+
process.stdout.write(chalk.yellowBright(` ${figures.arrowRight} Content Type has no key in ${type.file}\n`));
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
798
1099
|
process.stdout.write(chalk.yellowBright(` ${figures.arrowRight} Pushing ${type.definition.displayName} from ${type.file}\n`));
|
|
1100
|
+
// Filter unwanted fields from the ContentType definition
|
|
799
1101
|
const outType = { ...type.definition };
|
|
800
1102
|
if (outType.source)
|
|
801
1103
|
delete outType.source;
|
|
@@ -805,13 +1107,31 @@ const TypesPushCommand = {
|
|
|
805
1107
|
delete outType.lastModified;
|
|
806
1108
|
if (outType.lastModifiedBy || outType.lastModifiedBy == "")
|
|
807
1109
|
delete outType.lastModifiedBy;
|
|
808
|
-
if (outType.features)
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
.
|
|
1110
|
+
//if (outType.features) delete outType.features
|
|
1111
|
+
//if (outType.usage) delete outType.usage
|
|
1112
|
+
const validationResult = typeValidator(outType);
|
|
1113
|
+
if (validationResult || force) {
|
|
1114
|
+
if (!validationResult)
|
|
1115
|
+
process.stdout.write(chalk.yellowBright(` ${figures.arrowRight} Ignoring validation errors in ${type.file}\n`));
|
|
1116
|
+
const currentContentType = (await client.contentTypesGet({ path: { key: type.definition.key } }).catch(() => null));
|
|
1117
|
+
function buildError(e) {
|
|
1118
|
+
return { key: type.definition.key, type: type.definition, file: type.file, error: e };
|
|
1119
|
+
}
|
|
1120
|
+
function buildData(ct) {
|
|
1121
|
+
return { key: type.definition.key, type: ct, file: type.file };
|
|
1122
|
+
}
|
|
1123
|
+
return (currentContentType ?
|
|
1124
|
+
client.contentTypesPatch({ path: { key: type.definition.key }, body: outType }) :
|
|
1125
|
+
client.contentTypesCreate({ body: outType })).then(buildData).catch(buildError);
|
|
1126
|
+
}
|
|
1127
|
+
else {
|
|
1128
|
+
return {
|
|
1129
|
+
key: type.definition.key,
|
|
1130
|
+
type: type.definition,
|
|
1131
|
+
file: type.file,
|
|
1132
|
+
error: typeValidator.errors.map(x => x.message).join(', ')
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
815
1135
|
}));
|
|
816
1136
|
const overview = new Table({
|
|
817
1137
|
head: [
|
|
@@ -843,233 +1163,10 @@ const builder = yargs => {
|
|
|
843
1163
|
return updatedArgs;
|
|
844
1164
|
};
|
|
845
1165
|
function createTypeFolders(contentTypes, basePath, debug = false) {
|
|
846
|
-
|
|
847
|
-
const baseType = contentType.baseType ?? 'default';
|
|
848
|
-
// Create the type folder
|
|
849
|
-
const typePath = path.join(basePath, baseType, contentType.key);
|
|
850
|
-
if (!fs.existsSync(typePath)) {
|
|
851
|
-
fs.mkdirSync(typePath, { recursive: true });
|
|
852
|
-
if (debug)
|
|
853
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Created folder ${typePath} for Content-Type ${contentType.key}\n`));
|
|
854
|
-
}
|
|
855
|
-
// Check folders
|
|
856
|
-
if (!fs.statSync(typePath).isDirectory())
|
|
857
|
-
throw new Error(`The folder ${typePath} for Content-Type ${contentType.key} exists, but is not a directory!`);
|
|
858
|
-
return {
|
|
859
|
-
type: contentType.key,
|
|
860
|
-
path: typePath,
|
|
861
|
-
};
|
|
862
|
-
});
|
|
863
|
-
return folders;
|
|
1166
|
+
return contentTypes.map(contentType => getContentTypePaths(contentType, basePath, true, debug));
|
|
864
1167
|
}
|
|
865
1168
|
function getTypeFolder(list, type) {
|
|
866
|
-
return list.filter(x => x.type == type).at(0)
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
/**
|
|
870
|
-
* Keep track of all generated properties
|
|
871
|
-
*/
|
|
872
|
-
let generatedProps = [];
|
|
873
|
-
const NextJsQueriesCommand = {
|
|
874
|
-
command: "nextjs:fragments",
|
|
875
|
-
describe: "Create the GrapQL Fragments for a Next.JS / Optimizely Graph structure",
|
|
876
|
-
builder,
|
|
877
|
-
handler: async (args, opts) => {
|
|
878
|
-
generatedProps = [];
|
|
879
|
-
// Prepare
|
|
880
|
-
const { loadedContentTypes, createdTypeFolders } = opts || {};
|
|
881
|
-
const { components: basePath, _config: { debug }, force } = parseArgs(args);
|
|
882
|
-
const client = createCmsClient(args);
|
|
883
|
-
const { contentTypes, all: allContentTypes } = loadedContentTypes ?? await getContentTypes(client, args);
|
|
884
|
-
// Start process
|
|
885
|
-
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Generating GraphQL fragments for ${contentTypes.map(x => x.key).join(', ')}\n`));
|
|
886
|
-
const typeFolders = createdTypeFolders ?? createTypeFolders(contentTypes, basePath, debug);
|
|
887
|
-
const updatedTypes = contentTypes.map(contentType => {
|
|
888
|
-
const typePath = getTypeFolder(typeFolders, contentType.key);
|
|
889
|
-
return createGraphFragments(contentType, typePath, basePath, force, debug, allContentTypes, client.runtimeCmsVersion == OptiCmsVersion.CMS12);
|
|
890
|
-
}).filter(x => x).flat();
|
|
891
|
-
// Report outcome
|
|
892
|
-
if (updatedTypes.length > 0)
|
|
893
|
-
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Created/updated GraphQL fragments for ${updatedTypes.join(', ')}\n`));
|
|
894
|
-
else
|
|
895
|
-
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} No GraphQL fragments created/updated\n`));
|
|
896
|
-
if (!opts)
|
|
897
|
-
process.stdout.write(chalk.green(chalk.bold(figures.tick + " Done")) + "\n");
|
|
898
|
-
generatedProps = [];
|
|
899
|
-
}
|
|
900
|
-
};
|
|
901
|
-
function createGraphFragments(contentType, typePath, basePath, force, debug, contentTypes, forCms12 = false) {
|
|
902
|
-
const returnValue = [];
|
|
903
|
-
const baseType = contentType.baseType ?? 'default';
|
|
904
|
-
const baseQueryFile = path.join(typePath, `${contentType.key}.${baseType}.graphql`);
|
|
905
|
-
if (fs.existsSync(baseQueryFile)) {
|
|
906
|
-
if (force) {
|
|
907
|
-
if (debug)
|
|
908
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Overwriting ${contentType.displayName} (${contentType.key}) base fragment\n`));
|
|
909
|
-
}
|
|
910
|
-
else {
|
|
911
|
-
if (debug)
|
|
912
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.displayName} (${contentType.key}) base fragment - file already exists\n`));
|
|
913
|
-
return undefined;
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
else if (debug) {
|
|
917
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating ${contentType.displayName} (${contentType.key}) base fragment\n`));
|
|
918
|
-
}
|
|
919
|
-
const { fragment, propertyTypes } = createInitialFragment(contentType, false, undefined, forCms12);
|
|
920
|
-
fs.writeFileSync(baseQueryFile, fragment);
|
|
921
|
-
returnValue.push(contentType.key);
|
|
922
|
-
let dependencies = Array.isArray(propertyTypes) ? [...propertyTypes] : [];
|
|
923
|
-
while (Array.isArray(dependencies) && dependencies.length > 0) {
|
|
924
|
-
let newDependencies = [];
|
|
925
|
-
dependencies.forEach(dep => {
|
|
926
|
-
const propContentType = contentTypes.filter(x => x.key == dep[0])[0];
|
|
927
|
-
if (!propContentType) {
|
|
928
|
-
console.warn(`🟠 The content type ${dep[0]} has been referenced, but is not found in the Optimizely CMS instance`);
|
|
929
|
-
return;
|
|
930
|
-
}
|
|
931
|
-
const fullTypeName = forCms12 ? contentType.key + propContentType.key : propContentType.key;
|
|
932
|
-
const propertyFragmentFile = path.join(basePath, propContentType.baseType, propContentType.key, `${fullTypeName}.property.graphql`);
|
|
933
|
-
const propertyFragmentDir = path.dirname(propertyFragmentFile);
|
|
934
|
-
if (!fs.existsSync(propertyFragmentDir))
|
|
935
|
-
fs.mkdirSync(propertyFragmentDir, { recursive: true });
|
|
936
|
-
if (!fs.existsSync(propertyFragmentFile) || force) {
|
|
937
|
-
if (debug)
|
|
938
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Writing ${propContentType.displayName} (${propContentType.key}) property fragment\n`));
|
|
939
|
-
const propContentTypeInfo = createInitialFragment(propContentType, true, contentType, forCms12);
|
|
940
|
-
fs.writeFileSync(propertyFragmentFile, propContentTypeInfo.fragment);
|
|
941
|
-
returnValue.push(propContentType.key);
|
|
942
|
-
if (Array.isArray(propContentTypeInfo.propertyTypes))
|
|
943
|
-
newDependencies.push(...propContentTypeInfo.propertyTypes);
|
|
944
|
-
}
|
|
945
|
-
});
|
|
946
|
-
dependencies = newDependencies;
|
|
947
|
-
}
|
|
948
|
-
return returnValue.length > 0 ? returnValue : undefined;
|
|
949
|
-
}
|
|
950
|
-
function createInitialFragment(contentType, forProperty = false, forBaseType, forCms12 = false) {
|
|
951
|
-
const propertyTypes = [];
|
|
952
|
-
const fragmentFields = [];
|
|
953
|
-
const typeProps = contentType.properties ?? {};
|
|
954
|
-
Object.getOwnPropertyNames(typeProps).forEach(propKey => {
|
|
955
|
-
// Exclude system properties, which are not present in Optimizely Graph
|
|
956
|
-
if (['experience', 'section'].includes(contentType.baseType) && ['AdditionalData', 'UnstructuredData', 'Layout'].includes(propKey))
|
|
957
|
-
return;
|
|
958
|
-
// Exclude CMS 12 System Properties
|
|
959
|
-
if (forCms12 && ['Categories'].includes(propKey))
|
|
960
|
-
return;
|
|
961
|
-
const propType = typeProps[propKey].type;
|
|
962
|
-
const isConflict = generatedProps.some(x => x.propName == propKey && x.propType != propType);
|
|
963
|
-
const propName = isConflict ? `${contentType.key}${propKey}: ${propKey}` : propKey;
|
|
964
|
-
// Write the property
|
|
965
|
-
switch (propType) {
|
|
966
|
-
case IntegrationApi.PropertyDataType.ARRAY:
|
|
967
|
-
{
|
|
968
|
-
const typeData = typeProps[propKey];
|
|
969
|
-
switch (typeData.items.type) {
|
|
970
|
-
case IntegrationApi.PropertyDataType.INTEGER:
|
|
971
|
-
if (typeData.format == 'categorization')
|
|
972
|
-
fragmentFields.push(`${propName} { Id, Name, Description }`);
|
|
973
|
-
else
|
|
974
|
-
fragmentFields.push(propName);
|
|
975
|
-
break;
|
|
976
|
-
case IntegrationApi.PropertyDataType.STRING:
|
|
977
|
-
fragmentFields.push(propName);
|
|
978
|
-
break;
|
|
979
|
-
case IntegrationApi.PropertyDataType.CONTENT:
|
|
980
|
-
if (contentType.baseType == 'page' || contentType.baseType == 'experience')
|
|
981
|
-
fragmentFields.push(`${propName} { ...${forCms12 ? 'PageIContentListItem' : 'BlockData'} }`);
|
|
982
|
-
else
|
|
983
|
-
fragmentFields.push(`${propName} { ...IContentListItem }`);
|
|
984
|
-
break;
|
|
985
|
-
case IntegrationApi.PropertyDataType.COMPONENT:
|
|
986
|
-
const componentType = typeData.items.contentType;
|
|
987
|
-
switch (componentType) {
|
|
988
|
-
case 'link':
|
|
989
|
-
fragmentFields.push(`${propName} { ...LinkItemData }`);
|
|
990
|
-
break;
|
|
991
|
-
default:
|
|
992
|
-
{
|
|
993
|
-
const componentFragmentName = forCms12 ? contentType.key + componentType : componentType + 'Property';
|
|
994
|
-
fragmentFields.push(`${propName} { ...${componentFragmentName}Data }`);
|
|
995
|
-
propertyTypes.push([componentType, true]);
|
|
996
|
-
break;
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
break;
|
|
1000
|
-
case IntegrationApi.PropertyDataType.CONTENT_REFERENCE:
|
|
1001
|
-
fragmentFields.push(`${propName} { ...ReferenceData }`);
|
|
1002
|
-
break;
|
|
1003
|
-
default:
|
|
1004
|
-
fragmentFields.push(`${propName} { __typename }`);
|
|
1005
|
-
break;
|
|
1006
|
-
}
|
|
1007
|
-
break;
|
|
1008
|
-
}
|
|
1009
|
-
case IntegrationApi.PropertyDataType.STRING: {
|
|
1010
|
-
const propDetails = typeProps[propKey];
|
|
1011
|
-
switch (propDetails.format ?? "") {
|
|
1012
|
-
case 'html':
|
|
1013
|
-
fragmentFields.push(forCms12 ? `${propName} { Structure, Html }` : `${propName} { json, html }`);
|
|
1014
|
-
break;
|
|
1015
|
-
case 'shortString':
|
|
1016
|
-
case 'selectOne':
|
|
1017
|
-
case '':
|
|
1018
|
-
fragmentFields.push(propName);
|
|
1019
|
-
break;
|
|
1020
|
-
default:
|
|
1021
|
-
if (!isConflict)
|
|
1022
|
-
console.warn(chalk.redBright(`❗ Unsupported string format "${propDetails.format}" for ${contentType.key}.${propKey}; add it manually to the fragment if you need to access this field`));
|
|
1023
|
-
break;
|
|
1024
|
-
}
|
|
1025
|
-
break;
|
|
1026
|
-
}
|
|
1027
|
-
case IntegrationApi.PropertyDataType.URL:
|
|
1028
|
-
fragmentFields.push(forCms12 ? propName : `${propName} { ...LinkData }`);
|
|
1029
|
-
break;
|
|
1030
|
-
case IntegrationApi.PropertyDataType.CONTENT_REFERENCE:
|
|
1031
|
-
fragmentFields.push(`${propName} { ...ReferenceData }`);
|
|
1032
|
-
break;
|
|
1033
|
-
case IntegrationApi.PropertyDataType.COMPONENT: {
|
|
1034
|
-
const componentType = typeProps[propKey].contentType;
|
|
1035
|
-
if (forCms12 && componentType == "link") {
|
|
1036
|
-
fragmentFields.push(`${propName} { ...LinkItemData }`);
|
|
1037
|
-
}
|
|
1038
|
-
else {
|
|
1039
|
-
const componentFragmentName = forCms12 ? contentType.key + componentType : componentType + 'Property';
|
|
1040
|
-
fragmentFields.push(`${propName} { ...${componentFragmentName}Data }`);
|
|
1041
|
-
propertyTypes.push([componentType, true]);
|
|
1042
|
-
}
|
|
1043
|
-
break;
|
|
1044
|
-
}
|
|
1045
|
-
case IntegrationApi.PropertyDataType.BINARY:
|
|
1046
|
-
fragmentFields.push(`${propName} { ...BinaryData }`);
|
|
1047
|
-
break;
|
|
1048
|
-
default:
|
|
1049
|
-
fragmentFields.push(propName);
|
|
1050
|
-
break;
|
|
1051
|
-
}
|
|
1052
|
-
generatedProps.push({
|
|
1053
|
-
propType: typeProps[propKey].type,
|
|
1054
|
-
propName: propKey
|
|
1055
|
-
});
|
|
1056
|
-
});
|
|
1057
|
-
if (contentType.baseType == "experience")
|
|
1058
|
-
fragmentFields.push('...ExperienceData');
|
|
1059
|
-
if (fragmentFields.length == 0) {
|
|
1060
|
-
if (forCms12)
|
|
1061
|
-
fragmentFields.push('empty: _metadata: ContentLink { key: GuidValue }');
|
|
1062
|
-
else
|
|
1063
|
-
fragmentFields.push('empty: _metadata { key }');
|
|
1064
|
-
}
|
|
1065
|
-
const fragmentTarget = forProperty ? (forCms12 ? (forBaseType?.key ?? '') + contentType.key : contentType.key + 'Property') : contentType.key;
|
|
1066
|
-
const tpl = `fragment ${fragmentTarget}Data on ${fragmentTarget} {
|
|
1067
|
-
${fragmentFields.join("\n ")}
|
|
1068
|
-
}`;
|
|
1069
|
-
return {
|
|
1070
|
-
fragment: tpl,
|
|
1071
|
-
propertyTypes: propertyTypes.length == 0 ? null : propertyTypes
|
|
1072
|
-
};
|
|
1169
|
+
return list.filter(x => x.type == type).at(0);
|
|
1073
1170
|
}
|
|
1074
1171
|
|
|
1075
1172
|
function ucFirst(current) {
|
|
@@ -1105,8 +1202,8 @@ const NextJsComponentsCommand = {
|
|
|
1105
1202
|
process.stdout.write(chalk.green(chalk.bold(figures.tick + " Done")) + "\n");
|
|
1106
1203
|
}
|
|
1107
1204
|
};
|
|
1108
|
-
function createComponent(contentType,
|
|
1109
|
-
const componentFile
|
|
1205
|
+
function createComponent(contentType, typePathInfo, force, debug = false) {
|
|
1206
|
+
const { componentFile, path: typePath } = typePathInfo;
|
|
1110
1207
|
if (fs.existsSync(componentFile)) {
|
|
1111
1208
|
if (!force) {
|
|
1112
1209
|
if (debug)
|
|
@@ -1121,8 +1218,9 @@ function createComponent(contentType, typePath, force, debug = false) {
|
|
|
1121
1218
|
// Get type information & short-hands
|
|
1122
1219
|
const displayTemplate = getDisplayTemplateInfo$1(contentType, typePath);
|
|
1123
1220
|
const baseDisplayTemplate = getBaseTypeTemplateInfo(contentType, typePath);
|
|
1124
|
-
const
|
|
1125
|
-
const
|
|
1221
|
+
const normalizedBaseType = normalizeBaseType(contentType.baseType ?? 'part');
|
|
1222
|
+
const varName = `${contentType.key}${ucFirst(normalizedBaseType)}`;
|
|
1223
|
+
const tplFn = Templates[normalizedBaseType] ?? Templates['default'];
|
|
1126
1224
|
if (!tplFn) {
|
|
1127
1225
|
if (debug)
|
|
1128
1226
|
process.stdout.write(chalk.redBright(`${figures.cross} Skipping ${contentType.displayName} (${contentType.key}) component - no template for ${contentType.baseType} found\n`));
|
|
@@ -1146,6 +1244,11 @@ function getBaseTypeTemplateInfo(contentType, typePath) {
|
|
|
1146
1244
|
}
|
|
1147
1245
|
return undefined;
|
|
1148
1246
|
}
|
|
1247
|
+
function normalizeBaseType(baseType) {
|
|
1248
|
+
if (baseType.startsWith('_'))
|
|
1249
|
+
return baseType.substring(1);
|
|
1250
|
+
return baseType;
|
|
1251
|
+
}
|
|
1149
1252
|
function getDisplayTemplateInfo$1(contentType, typePath) {
|
|
1150
1253
|
const displayTemplatesFile = path.join(typePath, 'displayTemplates.ts');
|
|
1151
1254
|
if (fs.existsSync(displayTemplatesFile)) {
|
|
@@ -1159,24 +1262,50 @@ function getDisplayTemplateInfo$1(contentType, typePath) {
|
|
|
1159
1262
|
}
|
|
1160
1263
|
const Templates = {
|
|
1161
1264
|
// Default Template for all components without specifics
|
|
1162
|
-
default: (contentType, varName, displayTemplate, baseDisplayTemplate) => `import { type CmsComponent } from "@remkoj/optimizely-cms-react";
|
|
1265
|
+
default: (contentType, varName, displayTemplate, baseDisplayTemplate) => `import { CmsEditable, type CmsComponent } from "@remkoj/optimizely-cms-react/rsc";
|
|
1163
1266
|
import { ${contentType.key}DataFragmentDoc, type ${contentType.key}DataFragment } from "@/gql/graphql";${displayTemplate ? `
|
|
1164
1267
|
import { ${displayTemplate} } from "./displayTemplates";` : ''}${baseDisplayTemplate ? `
|
|
1165
1268
|
import { ${baseDisplayTemplate} } from "../styles/displayTemplates";` : ''}
|
|
1166
1269
|
|
|
1167
1270
|
/**
|
|
1168
1271
|
* ${contentType.displayName}
|
|
1272
|
+
* ---
|
|
1169
1273
|
* ${contentType.description}
|
|
1170
1274
|
*/
|
|
1171
|
-
export const ${varName} : CmsComponent<${contentType.key}DataFragment${(displayTemplate || baseDisplayTemplate) ? ', ' + [displayTemplate, baseDisplayTemplate].filter(x => x).join(" | ") : ''}> = ({ data${(displayTemplate || baseDisplayTemplate) ? ', layoutProps' : ''},
|
|
1275
|
+
export const ${varName} : CmsComponent<${contentType.key}DataFragment${(displayTemplate || baseDisplayTemplate) ? ', ' + [displayTemplate, baseDisplayTemplate].filter(x => x).join(" | ") : ''}> = ({ data${(displayTemplate || baseDisplayTemplate) ? ', layoutProps' : ''}, editProps }) => {
|
|
1172
1276
|
const componentName = '${contentType.displayName}'
|
|
1173
1277
|
const componentInfo = '${contentType.description?.replaceAll("'", "\\'") ?? ''}'
|
|
1174
|
-
return <
|
|
1278
|
+
return <CmsEditable className="w-full border-y border-y-solid border-y-slate-900 py-2 mb-4" {...editProps}>
|
|
1175
1279
|
<div className="font-bold italic">{ componentName }</div>
|
|
1176
1280
|
<div>{ componentInfo }</div>
|
|
1177
1281
|
{ Object.getOwnPropertyNames(data).length > 0 && <pre className="w-full overflow-x-hidden font-mono text-sm bg-slate-200 p-2 rounded-sm border border-solid border-slate-900 text-slate-900">{ JSON.stringify(data, undefined, 4) }</pre> }
|
|
1178
|
-
|
|
1179
|
-
|
|
1282
|
+
</CmsEditable>
|
|
1283
|
+
}
|
|
1284
|
+
${varName}.displayName = "${contentType.displayName} (${ucFirst(contentType.baseType)}/${contentType.key})"
|
|
1285
|
+
${varName}.getDataFragment = () => ['${contentType.key}Data', ${contentType.key}DataFragmentDoc]
|
|
1286
|
+
|
|
1287
|
+
export default ${varName}`,
|
|
1288
|
+
// Default Template for all section types
|
|
1289
|
+
section: (contentType, varName, displayTemplate, baseDisplayTemplate) => `import { CmsEditable, type CmsComponent } from "@remkoj/optimizely-cms-react/rsc";
|
|
1290
|
+
import { ${contentType.key}DataFragmentDoc, type ${contentType.key}DataFragment } from "@/gql/graphql";${displayTemplate ? `
|
|
1291
|
+
import { ${displayTemplate} } from "./displayTemplates";` : ''}${baseDisplayTemplate ? `
|
|
1292
|
+
import { ${baseDisplayTemplate} } from "../styles/displayTemplates";` : ''}
|
|
1293
|
+
|
|
1294
|
+
/**
|
|
1295
|
+
* ${contentType.displayName}
|
|
1296
|
+
* ---
|
|
1297
|
+
* ${contentType.description}
|
|
1298
|
+
*/
|
|
1299
|
+
export const ${varName} : CmsComponent<${contentType.key}DataFragment${(displayTemplate || baseDisplayTemplate) ? ', ' + [displayTemplate, baseDisplayTemplate].filter(x => x).join(" | ") : ''}> = ({ data${(displayTemplate || baseDisplayTemplate) ? ', layoutProps' : ''}, editProps, children }) => {
|
|
1300
|
+
const componentName = '${contentType.displayName}'
|
|
1301
|
+
const componentInfo = '${contentType.description?.replaceAll("'", "\\'") ?? ''}'
|
|
1302
|
+
return <CmsEditable className="w-full border-y border-y-solid border-y-slate-900 py-2 mb-4" {...editProps}>
|
|
1303
|
+
<div className="font-bold italic">{ componentName }</div>
|
|
1304
|
+
<div>{ componentInfo }</div>
|
|
1305
|
+
{ Object.getOwnPropertyNames(data).length > 0 && <pre className="w-full overflow-x-hidden font-mono text-sm bg-slate-200 p-2 rounded-sm border border-solid border-slate-900 text-slate-900">{ JSON.stringify(data, undefined, 4) }</pre> }
|
|
1306
|
+
${(displayTemplate || baseDisplayTemplate) ? '<pre className="w-full overflow-x-hidden font-mono text-sm bg-slate-200 p-2 rounded-sm border border-solid border-slate-900 text-slate-900">{ JSON.stringify(layoutProps, undefined, 4) }</pre>' : '{/* This component doesn\'t have layout options */}'}
|
|
1307
|
+
<div>{ children }</div>
|
|
1308
|
+
</CmsEditable>
|
|
1180
1309
|
}
|
|
1181
1310
|
${varName}.displayName = "${contentType.displayName} (${ucFirst(contentType.baseType)}/${contentType.key})"
|
|
1182
1311
|
${varName}.getDataFragment = () => ['${contentType.key}Data', ${contentType.key}DataFragmentDoc]
|
|
@@ -1184,26 +1313,32 @@ ${varName}.getDataFragment = () => ['${contentType.key}Data', ${contentType.key}
|
|
|
1184
1313
|
export default ${varName}`,
|
|
1185
1314
|
// Template for all page component types
|
|
1186
1315
|
page: (contentType, varName, displayTemplate) => `import { type OptimizelyNextPage as CmsComponent } from "@remkoj/optimizely-cms-nextjs";
|
|
1187
|
-
import { ${contentType.key}
|
|
1316
|
+
import { get${contentType.key}DataDocument, type get${contentType.key}DataQuery } from '@/gql/graphql'${displayTemplate ? `
|
|
1188
1317
|
import { ${displayTemplate} } from "./displayTemplates";` : ''}
|
|
1189
1318
|
import { getSdk } from "@/gql"
|
|
1190
1319
|
|
|
1191
1320
|
/**
|
|
1192
1321
|
* ${contentType.displayName}
|
|
1322
|
+
* ---
|
|
1193
1323
|
* ${contentType.description}
|
|
1324
|
+
*
|
|
1325
|
+
* This component uses the content query that is auto-generated with the Optimizely CMS Preset for GraphQL Codegen, if you need
|
|
1326
|
+
* to override this query create a GraphQL file (for example: \`get${contentType.key}Data.query.graphql\`) in the same folder as
|
|
1327
|
+
* this file. This file must include a GraphQL query with the name \`get${contentType.key}Data\`.
|
|
1328
|
+
*
|
|
1329
|
+
* [Documentation: Customizing queries](https://github.com/remkoj/optimizely-dxp-clients/blob/main/packages/optimizely-graph-functions/docs/customizing_queries.md)
|
|
1194
1330
|
*/
|
|
1195
|
-
export const ${varName} : CmsComponent
|
|
1331
|
+
export const ${varName} : CmsComponent<get${contentType.key}DataQuery${displayTemplate ? ', ' + displayTemplate : ''}> = ({ data${displayTemplate ? ', layoutProps' : ''} }) => {
|
|
1196
1332
|
const componentName = '${contentType.displayName}'
|
|
1197
1333
|
const componentInfo = '${contentType.description?.replaceAll("'", "\\'") ?? ''}'
|
|
1198
1334
|
return <div className="mx-auto px-2 container">
|
|
1199
1335
|
<div className="font-bold italic">{ componentName }</div>
|
|
1200
1336
|
<div>{ componentInfo }</div>
|
|
1201
1337
|
{ Object.getOwnPropertyNames(data).length > 0 && <pre className="w-full overflow-x-hidden font-mono text-sm bg-slate-200 p-2 rounded-sm border border-solid border-slate-900 text-slate-900">{ JSON.stringify(data, undefined, 4) }</pre> }
|
|
1202
|
-
{ children && <div className="flex flex-col mt-4 mx-4">{ children }</div>}
|
|
1203
1338
|
</div>
|
|
1204
1339
|
}
|
|
1205
1340
|
${varName}.displayName = "${contentType.displayName} (${ucFirst(contentType.baseType)}/${contentType.key})"
|
|
1206
|
-
${varName}.
|
|
1341
|
+
${varName}.getDataQuery = () => get${contentType.key}DataDocument
|
|
1207
1342
|
${varName}.getMetaData = async (contentLink, locale, client) => {
|
|
1208
1343
|
const sdk = getSdk(client);
|
|
1209
1344
|
// Add your metadata logic here
|
|
@@ -1213,23 +1348,36 @@ ${varName}.getMetaData = async (contentLink, locale, client) => {
|
|
|
1213
1348
|
export default ${varName}`,
|
|
1214
1349
|
// Template for all experience component types
|
|
1215
1350
|
experience: (contentType, varName, displayTemplate) => `import { type OptimizelyNextPage as CmsComponent } from "@remkoj/optimizely-cms-nextjs";
|
|
1216
|
-
import { ${contentType.key}
|
|
1351
|
+
import { ExperienceDataFragmentDoc, get${contentType.key}DataDocument, type get${contentType.key}DataQuery } from '@/gql/graphql'
|
|
1352
|
+
import { getFragmentData } from '@/gql/fragment-masking'
|
|
1217
1353
|
import { OptimizelyComposition, isNode, CmsEditable } from "@remkoj/optimizely-cms-react/rsc";${displayTemplate ? `
|
|
1218
1354
|
import { ${displayTemplate} } from "./displayTemplates";` : ''}
|
|
1219
1355
|
import { getSdk } from "@/gql/client"
|
|
1220
1356
|
|
|
1221
1357
|
/**
|
|
1222
1358
|
* ${contentType.displayName}
|
|
1359
|
+
* ---
|
|
1223
1360
|
* ${contentType.description}
|
|
1361
|
+
*
|
|
1362
|
+
* This component uses the content query that is auto-generated with the Optimizely CMS Preset for GraphQL Codegen, if you need
|
|
1363
|
+
* to override this query create the file \`${contentType.key}.query.graphql\` in the same folder as this file. This file then
|
|
1364
|
+
* must include a GraphQL query with the name \`get${contentType.key} Data\`.
|
|
1365
|
+
*
|
|
1366
|
+
* [Documentation: Customizing queries](https://github.com/remkoj/optimizely-dxp-clients/blob/main/packages/optimizely-graph-functions/docs/customizing_queries.md)
|
|
1224
1367
|
*/
|
|
1225
|
-
export const ${varName} : CmsComponent
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1368
|
+
export const ${varName} : CmsComponent<get${contentType.key}DataQuery${displayTemplate ? ', ' + displayTemplate : ''}> = ({ data${displayTemplate ? ', layoutProps' : ''}, ctx }) => {
|
|
1369
|
+
if (ctx) ctx.editableContentIsExperience = true
|
|
1370
|
+
const composition = getFragmentData(ExperienceDataFragmentDoc, data).composition
|
|
1371
|
+
const componentName = '${contentType.displayName}'
|
|
1372
|
+
const componentInfo = '${contentType.description?.replaceAll("'", "\\'") ?? ''}'
|
|
1373
|
+
return <CmsEditable as="div" className="mx-auto px-2 container" cmsFieldName="unstructuredData" ctx={ctx}>
|
|
1374
|
+
<div className="font-bold italic">{ componentName }</div>
|
|
1375
|
+
<div>{ componentInfo }</div>
|
|
1376
|
+
{ composition && isNode(composition) && <OptimizelyComposition node={composition} ctx={ctx} /> }
|
|
1377
|
+
</CmsEditable>
|
|
1230
1378
|
}
|
|
1231
1379
|
${varName}.displayName = "${contentType.displayName} (${ucFirst(contentType.baseType)}/${contentType.key})"
|
|
1232
|
-
${varName}.
|
|
1380
|
+
${varName}.getDataQuery = () => get${contentType.key}DataDocument
|
|
1233
1381
|
${varName}.getMetaData = async (contentLink, locale, client) => {
|
|
1234
1382
|
const sdk = getSdk(client);
|
|
1235
1383
|
// Add your metadata logic here
|
|
@@ -1247,7 +1395,7 @@ const NextJsVisualBuilderCommand = {
|
|
|
1247
1395
|
// Prepare
|
|
1248
1396
|
const { components: basePath, _config: { debug }, force } = parseArgs(args);
|
|
1249
1397
|
// Create the fall-back node and style defintions
|
|
1250
|
-
await StylesPullCommand.handler({ ...args, excludeNodeTypes: [], excludeTemplates: [], nodes: [], templates: [], templateTypes: ['node'], definitions: true });
|
|
1398
|
+
await StylesPullCommand.handler({ ...args, excludeNodeTypes: [], excludeTemplates: [], nodes: [], templates: [], templateTypes: ['node', 'base'], definitions: true });
|
|
1251
1399
|
createGenericNode(basePath, force, debug);
|
|
1252
1400
|
// Get all styles
|
|
1253
1401
|
const { styles } = await getStyles(createCmsClient(args), { ...args, excludeNodeTypes: [], excludeTemplates: [], nodes: [], templates: [], templateTypes: ['node', 'base'] });
|
|
@@ -1258,7 +1406,8 @@ const NextJsVisualBuilderCommand = {
|
|
|
1258
1406
|
});
|
|
1259
1407
|
// Process base styles
|
|
1260
1408
|
styles.filter(x => typeof (x.baseType) == 'string' && x.baseType.length > 0).map(styleDefinition => {
|
|
1261
|
-
const
|
|
1409
|
+
const baseType = (styleDefinition.baseType ?? '').startsWith('_') ? (styleDefinition.baseType ?? '').substring(1) : styleDefinition.baseType;
|
|
1410
|
+
const templatePath = path.join(basePath, baseType, 'styles', styleDefinition.key);
|
|
1262
1411
|
createSpecificNode(styleDefinition, templatePath, force, debug);
|
|
1263
1412
|
});
|
|
1264
1413
|
}
|
|
@@ -1278,13 +1427,30 @@ function createSpecificNode(template, templatePath, force = false, debug = false
|
|
|
1278
1427
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating ${template.displayName} (${template.key}) component\n`));
|
|
1279
1428
|
if (!fs.existsSync(templatePath))
|
|
1280
1429
|
fs.mkdirSync(templatePath, { recursive: true });
|
|
1430
|
+
const baseType = template.nodeType ?? template.baseType ?? 'unknown';
|
|
1281
1431
|
const displayTemplateName = getDisplayTemplateInfo(template, templatePath);
|
|
1282
|
-
const
|
|
1283
|
-
|
|
1432
|
+
const imports = ['import { extractSettings, type CmsLayoutComponent } from "@remkoj/optimizely-cms-react/rsc";'];
|
|
1433
|
+
const componentProps = [`className="vb:${baseType} vb:${baseType}:${template.key}"`];
|
|
1434
|
+
const componentArgs = ['layoutProps', 'children'];
|
|
1435
|
+
let componentType = 'div';
|
|
1436
|
+
if (displayTemplateName)
|
|
1437
|
+
imports.push(`import { ${displayTemplateName} } from "../displayTemplates";`);
|
|
1438
|
+
if (baseType.toLowerCase() == 'section') {
|
|
1439
|
+
imports.push('import { CmsEditable } from "@remkoj/optimizely-cms-react/rsc";');
|
|
1440
|
+
componentType = 'CmsEditable';
|
|
1441
|
+
componentProps.push('{ ...editProps }');
|
|
1442
|
+
componentArgs.push('editProps');
|
|
1443
|
+
}
|
|
1444
|
+
let style = '';
|
|
1445
|
+
if (baseType == "row")
|
|
1446
|
+
style = ` style={{ display: "flex", flexDirection: "row", justifyContent: "space-between", alignItems: "stretch" }}`;
|
|
1447
|
+
if (baseType == "column")
|
|
1448
|
+
style = ` style={{ display: "flex", flexDirection: "column", flexGrow: 1 }}`;
|
|
1449
|
+
const component = `${imports.join("\n")}
|
|
1284
1450
|
|
|
1285
|
-
export const ${template.key} : CmsLayoutComponent<${displayTemplateName ?? '{}'}> = ({
|
|
1451
|
+
export const ${template.key} : CmsLayoutComponent<${displayTemplateName ?? '{}'}> = ({ ${componentArgs.join(', ')} }) => {
|
|
1286
1452
|
const layout = extractSettings(layoutProps);
|
|
1287
|
-
return (
|
|
1453
|
+
return (<${componentType} ${componentProps.join(' ')}${style}>{ children }</${componentType}>);
|
|
1288
1454
|
}
|
|
1289
1455
|
|
|
1290
1456
|
export default ${template.key};`;
|
|
@@ -1304,13 +1470,19 @@ function createGenericNode(basePath, force, debug) {
|
|
|
1304
1470
|
else if (debug)
|
|
1305
1471
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating generic node component\n`));
|
|
1306
1472
|
const nodeContent = `import { CmsEditable, type CmsLayoutComponent } from '@remkoj/optimizely-cms-react/rsc'
|
|
1473
|
+
import { type CSSProperties } from 'react';
|
|
1307
1474
|
|
|
1308
|
-
export const VisualBuilderNode : CmsLayoutComponent = ({
|
|
1475
|
+
export const VisualBuilderNode : CmsLayoutComponent = ({ editProps, layoutProps, children }) =>
|
|
1309
1476
|
{
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1477
|
+
let className = \`vb:\${layoutProps?.layoutType}\`;
|
|
1478
|
+
let style : CSSProperties | undefined = undefined;
|
|
1479
|
+
if (layoutProps?.layoutType == "row")
|
|
1480
|
+
style = {display: "flex", flexDirection: "row"}
|
|
1481
|
+
if (layoutProps?.layoutType == "column")
|
|
1482
|
+
style = {display: "flex", flexDirection: "column"}
|
|
1483
|
+
if (layoutProps && layoutProps.layoutType == "section")
|
|
1484
|
+
return <CmsEditable as="div" className={ className } {...editProps}>{ children }</CmsEditable>
|
|
1485
|
+
return <div className={ className } style={ style }>{ children }</div>
|
|
1314
1486
|
}
|
|
1315
1487
|
|
|
1316
1488
|
export default VisualBuilderNode`;
|
|
@@ -1558,7 +1730,7 @@ const NextJsCreateCommand = {
|
|
|
1558
1730
|
await Promise.all([
|
|
1559
1731
|
TypesPullCommand.handler(args),
|
|
1560
1732
|
StylesPullCommand.handler({ ...args, excludeNodeTypes: [], excludeTemplates: [], nodes: [], templates: [], templateTypes: [], definitions: true }),
|
|
1561
|
-
|
|
1733
|
+
//createFragments.handler(args, { loadedContentTypes: getContentTypesResult, createdTypeFolders: folders })
|
|
1562
1734
|
]);
|
|
1563
1735
|
process.stdout.write(chalk.yellowBright(chalk.bold(figures.tick) + " Prepared types, styles and fragments") + "\n");
|
|
1564
1736
|
// Then create the components
|
|
@@ -1573,182 +1745,199 @@ const NextJsCreateCommand = {
|
|
|
1573
1745
|
}
|
|
1574
1746
|
};
|
|
1575
1747
|
|
|
1576
|
-
const
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
});
|
|
1601
|
-
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
const type = jsonSchema.definitions[definitionName].properties?.type;
|
|
1611
|
-
if (isRefSchema(type)) {
|
|
1612
|
-
const typeSchema = resolveRefSchema(type, jsonSchema);
|
|
1613
|
-
if (typeSchema && typeSchema.type === 'string' && Array.isArray(typeSchema.enum)) {
|
|
1614
|
-
let typeValue = definitionName.substring(0, definitionName.length - 8);
|
|
1615
|
-
typeValue = typeValue[0].toLowerCase() + typeValue.substring(1);
|
|
1616
|
-
typeValue = typeValue === 'list' ? 'array' : typeValue;
|
|
1617
|
-
typeValue = typeValue === 'jsonString' ? 'json' : typeValue;
|
|
1618
|
-
if (typeSchema.enum.includes(typeValue)) {
|
|
1619
|
-
const newTypeDef = deepmerge$1({}, typeSchema);
|
|
1620
|
-
newTypeDef.enum = newTypeDef.enum.filter((x) => x === typeValue);
|
|
1621
|
-
jsonSchema.definitions[definitionName].properties.type = newTypeDef;
|
|
1622
|
-
}
|
|
1623
|
-
}
|
|
1748
|
+
const NextJsFragmentsCommand = {
|
|
1749
|
+
command: "nextjs:fragments",
|
|
1750
|
+
describe: "Create the GrapQL Fragments for a Next.JS / Optimizely Graph structure",
|
|
1751
|
+
builder,
|
|
1752
|
+
handler: async (args, opts) => {
|
|
1753
|
+
// Prepare
|
|
1754
|
+
const { loadedContentTypes, createdTypeFolders } = opts || {};
|
|
1755
|
+
const { components: basePath, _config: { debug }, force } = parseArgs(args);
|
|
1756
|
+
const client = createCmsClient(args);
|
|
1757
|
+
const { contentTypes, all: allContentTypes } = loadedContentTypes ?? await getContentTypes(client, args);
|
|
1758
|
+
// Start process
|
|
1759
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Generating GraphQL fragments\n`));
|
|
1760
|
+
const typeFolders = createdTypeFolders ?? createTypeFolders(contentTypes, basePath, debug);
|
|
1761
|
+
const tracker = new Map();
|
|
1762
|
+
const dependencies = [];
|
|
1763
|
+
const updatedTypes = contentTypes.map(contentType => {
|
|
1764
|
+
const { written, propertyTypes } = createComponentFragments(contentType, getTypeFolder(typeFolders, contentType.key), force, debug, tracker);
|
|
1765
|
+
dependencies.push(...propertyTypes);
|
|
1766
|
+
return written ? contentType.key : undefined;
|
|
1767
|
+
}).filter(x => x);
|
|
1768
|
+
// Report outcome
|
|
1769
|
+
if (updatedTypes.length > 0)
|
|
1770
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Created/updated GraphQL fragments for ${updatedTypes.join(', ')}\n`));
|
|
1771
|
+
else
|
|
1772
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} No GraphQL fragments created/updated\n`));
|
|
1773
|
+
// Start property generation process
|
|
1774
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Generating GraphQL property fragments\n`));
|
|
1775
|
+
const generatedProps = createPropertyFragments(dependencies, allContentTypes, (contentType) => {
|
|
1776
|
+
if (!contentType.key)
|
|
1777
|
+
return undefined;
|
|
1778
|
+
let tf = getTypeFolder(typeFolders, contentType.key);
|
|
1779
|
+
if (!tf) {
|
|
1780
|
+
tf = getContentTypePaths(contentType, basePath, true, debug);
|
|
1781
|
+
typeFolders.push(tf);
|
|
1624
1782
|
}
|
|
1625
|
-
|
|
1783
|
+
return tf;
|
|
1784
|
+
}, force, debug);
|
|
1785
|
+
// Report outcome
|
|
1786
|
+
if (generatedProps.length > 0)
|
|
1787
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Created/updated GraphQL property fragments for ${generatedProps.join(', ')}\n`));
|
|
1788
|
+
else
|
|
1789
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} No GraphQL property fragments created/updated\n`));
|
|
1790
|
+
if (!opts)
|
|
1791
|
+
process.stdout.write(chalk.green(chalk.bold(figures.tick + " Done")) + "\n");
|
|
1626
1792
|
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
* @param defs
|
|
1636
|
-
* @param spec
|
|
1637
|
-
* @returns
|
|
1638
|
-
*/
|
|
1639
|
-
function processSchema(schema, defs, spec, mergeAllOf = true) {
|
|
1640
|
-
if (Array.isArray(schema))
|
|
1641
|
-
return schema.map(s => processSchema(s, defs, spec, mergeAllOf));
|
|
1642
|
-
if (typeof schema !== 'object' || schema === null)
|
|
1643
|
-
return schema;
|
|
1644
|
-
const props = Object.getOwnPropertyNames(schema);
|
|
1645
|
-
if (props.length === 1 && props[0] === '$ref') {
|
|
1646
|
-
const ref = schema['$ref'];
|
|
1647
|
-
if (isLocalRef(ref)) {
|
|
1648
|
-
const refName = getLocalRefName(ref);
|
|
1649
|
-
if (!defs[refName]) {
|
|
1650
|
-
const refItem = resolveLocalRef(ref, spec);
|
|
1651
|
-
defs[refName] = processSchema(refItem, defs, spec, mergeAllOf);
|
|
1652
|
-
}
|
|
1653
|
-
const newRef = `#/definitions/${refName}`;
|
|
1654
|
-
return { "$ref": newRef };
|
|
1793
|
+
};
|
|
1794
|
+
function createComponentFragments(contentType, typePath, force, debug, propertyTracker = new Map()) {
|
|
1795
|
+
const baseQueryFile = typePath.fragmentFile;
|
|
1796
|
+
let writeFragment = true;
|
|
1797
|
+
if (fs.existsSync(baseQueryFile)) {
|
|
1798
|
+
if (force) {
|
|
1799
|
+
if (debug)
|
|
1800
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Overwriting ${contentType.displayName} (${contentType.key}) base fragment\n`));
|
|
1655
1801
|
}
|
|
1656
1802
|
else {
|
|
1657
|
-
|
|
1803
|
+
if (debug)
|
|
1804
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.displayName} (${contentType.key}) base fragment - file already exists\n`));
|
|
1805
|
+
writeFragment = false;
|
|
1658
1806
|
}
|
|
1659
1807
|
}
|
|
1660
|
-
else if (
|
|
1661
|
-
|
|
1662
|
-
const newObject = props.reduce((generated, propName) => {
|
|
1663
|
-
if (propName != 'allOf')
|
|
1664
|
-
generated[propName] = schema[propName];
|
|
1665
|
-
return generated;
|
|
1666
|
-
}, {});
|
|
1667
|
-
const allOfSchemas = schema['allOf'].map((subschema) => {
|
|
1668
|
-
const resolvedSchema = isRefSchema(subschema) ? resolveRefSchema(subschema, spec) : subschema;
|
|
1669
|
-
const resolved = processSchema(resolvedSchema, defs, spec, mergeAllOf);
|
|
1670
|
-
return resolved;
|
|
1671
|
-
});
|
|
1672
|
-
const merged = allOfSchemas.reduce((previous, current) => deepmerge$1(previous, current), newObject);
|
|
1673
|
-
if (newObject['description'])
|
|
1674
|
-
merged['description'] = newObject['description'];
|
|
1675
|
-
if (newObject['title'])
|
|
1676
|
-
merged['title'] = newObject['title'];
|
|
1677
|
-
return merged;
|
|
1808
|
+
else if (debug) {
|
|
1809
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating ${contentType.displayName} (${contentType.key}) base fragment\n`));
|
|
1678
1810
|
}
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1811
|
+
let written = false;
|
|
1812
|
+
if (writeFragment) {
|
|
1813
|
+
const fragment = GraphQLGen.buildFragment(contentType, undefined, false, propertyTracker);
|
|
1814
|
+
fs.writeFileSync(baseQueryFile, fragment);
|
|
1815
|
+
written = true;
|
|
1816
|
+
}
|
|
1817
|
+
return { written, propertyTypes: GraphQLGen.getReferencedPropertyComponents(contentType) };
|
|
1818
|
+
}
|
|
1819
|
+
function createPropertyFragments(propertyTypesList, allContentTypes, selectTypeFolder, force = false, debug = false) {
|
|
1820
|
+
const returnValue = [];
|
|
1821
|
+
for (const propertyContentTypeKey of propertyTypesList.filter((v, i, a) => a.indexOf(v, i + 1) <= i)) {
|
|
1822
|
+
// Load the ContentType definition
|
|
1823
|
+
const contentType = allContentTypes.filter(x => x.key == propertyContentTypeKey).at(0);
|
|
1824
|
+
if (!contentType) {
|
|
1825
|
+
console.warn(`🟠 The content type ${propertyContentTypeKey} has been referenced, but is not found in the Optimizely CMS instance`);
|
|
1826
|
+
continue;
|
|
1827
|
+
}
|
|
1828
|
+
// Load the paths for this ContentType
|
|
1829
|
+
const depFolder = selectTypeFolder(contentType);
|
|
1830
|
+
if (!depFolder) {
|
|
1831
|
+
console.warn(`🟠 The content type ${propertyContentTypeKey} cannot be stored in the project`);
|
|
1832
|
+
continue;
|
|
1833
|
+
}
|
|
1834
|
+
const propertyFragmentFile = depFolder.propertyFragmentFile;
|
|
1835
|
+
let mustWrite = true;
|
|
1836
|
+
if (fs.existsSync(propertyFragmentFile)) {
|
|
1837
|
+
if (force) {
|
|
1838
|
+
if (debug)
|
|
1839
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Overwriting ${contentType.displayName} (${contentType.key}) property fragment\n`));
|
|
1691
1840
|
}
|
|
1692
1841
|
else {
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1842
|
+
if (debug)
|
|
1843
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.displayName} (${contentType.key}) property fragment - file already exists\n`));
|
|
1844
|
+
mustWrite = false;
|
|
1696
1845
|
}
|
|
1697
1846
|
}
|
|
1698
|
-
|
|
1847
|
+
else if (debug) {
|
|
1848
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating ${contentType.displayName} (${contentType.key}) property fragment\n`));
|
|
1849
|
+
}
|
|
1850
|
+
if (mustWrite) {
|
|
1851
|
+
const fragment = GraphQLGen.buildFragment(contentType, undefined, true);
|
|
1852
|
+
fs.writeFileSync(propertyFragmentFile, fragment);
|
|
1853
|
+
returnValue.push(contentType.key);
|
|
1854
|
+
}
|
|
1855
|
+
// Recurse down for properties that we're not yet rendering
|
|
1856
|
+
const referencedPropertyTypes = GraphQLGen.getReferencedPropertyComponents(contentType).filter(x => !propertyTypesList.includes(x));
|
|
1857
|
+
if (referencedPropertyTypes.length > 0) {
|
|
1858
|
+
if (debug)
|
|
1859
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Component property ${propertyContentTypeKey} uses components a property, recursing down\n`));
|
|
1860
|
+
const additionalProperties = createPropertyFragments(referencedPropertyTypes, allContentTypes, selectTypeFolder, force, debug);
|
|
1861
|
+
returnValue.push(...additionalProperties);
|
|
1862
|
+
}
|
|
1699
1863
|
}
|
|
1864
|
+
return returnValue;
|
|
1700
1865
|
}
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
return
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
}
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
}
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
pattern = pattern.replace(unEscape[0], unEscape[1]);
|
|
1866
|
+
|
|
1867
|
+
const NextJsQueriesCommand = {
|
|
1868
|
+
command: "nextjs:queries",
|
|
1869
|
+
describe: "Create the GrapQL Queries to use two queries to load content",
|
|
1870
|
+
builder: yargs => {
|
|
1871
|
+
const updatedArgs = contentTypesBuilder(yargs, { ...ContentTypesArgsDefaults, baseTypes: ['page', 'experience'] });
|
|
1872
|
+
updatedArgs.option('force', { alias: 'f', description: "Overwrite existing files", boolean: true, type: 'boolean', demandOption: false, default: false });
|
|
1873
|
+
return updatedArgs;
|
|
1874
|
+
},
|
|
1875
|
+
handler: async (args, opts) => {
|
|
1876
|
+
// Prepare
|
|
1877
|
+
const { loadedContentTypes, createdTypeFolders } = opts || {};
|
|
1878
|
+
const { components: basePath, _config: { debug }, force } = parseArgs(args);
|
|
1879
|
+
const client = createCmsClient(args);
|
|
1880
|
+
const { contentTypes, all: allContentTypes } = loadedContentTypes ?? await getContentTypes(client, args);
|
|
1881
|
+
// Start process
|
|
1882
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Generating GraphQL queries\n`));
|
|
1883
|
+
const dependencies = [];
|
|
1884
|
+
const typeFolders = createdTypeFolders ?? createTypeFolders(contentTypes, basePath, debug);
|
|
1885
|
+
const updatedTypes = contentTypes.map(contentType => {
|
|
1886
|
+
const typePath = getTypeFolder(typeFolders, contentType.key);
|
|
1887
|
+
const { written, propertyTypes } = createGraphQueries(contentType, typePath, force, debug);
|
|
1888
|
+
dependencies.push(...propertyTypes);
|
|
1889
|
+
return written ? contentType.key : undefined;
|
|
1890
|
+
}).filter(x => x).flat();
|
|
1891
|
+
// Report outcome
|
|
1892
|
+
if (updatedTypes.length > 0)
|
|
1893
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Created/updated GraphQL queries for ${updatedTypes.join(', ')}\n`));
|
|
1894
|
+
else
|
|
1895
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} No GraphQL queries created/updated\n`));
|
|
1896
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Generating GraphQL property fragments\n`));
|
|
1897
|
+
const generatedProps = createPropertyFragments(dependencies, allContentTypes, (contentType) => {
|
|
1898
|
+
if (!contentType.key)
|
|
1899
|
+
return undefined;
|
|
1900
|
+
let tf = getTypeFolder(typeFolders, contentType.key);
|
|
1901
|
+
if (!tf) {
|
|
1902
|
+
tf = getContentTypePaths(contentType, basePath, true, debug);
|
|
1903
|
+
typeFolders.push(tf);
|
|
1904
|
+
}
|
|
1905
|
+
return tf;
|
|
1906
|
+
}, force, debug);
|
|
1907
|
+
// Report outcome
|
|
1908
|
+
if (generatedProps.length > 0)
|
|
1909
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Created/updated GraphQL property fragments for ${generatedProps.join(', ')}\n`));
|
|
1910
|
+
else
|
|
1911
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} No GraphQL property fragments created/updated\n`));
|
|
1912
|
+
if (!opts)
|
|
1913
|
+
process.stdout.write(chalk.green(chalk.bold(figures.tick + " Done")) + "\n");
|
|
1750
1914
|
}
|
|
1751
|
-
|
|
1915
|
+
};
|
|
1916
|
+
function createGraphQueries(contentType, typePath, force, debug) {
|
|
1917
|
+
const baseQueryFile = typePath.queryFile;
|
|
1918
|
+
let mustWrite = true;
|
|
1919
|
+
if (fs.existsSync(baseQueryFile)) {
|
|
1920
|
+
if (force) {
|
|
1921
|
+
if (debug)
|
|
1922
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Overwriting ${contentType.displayName} (${contentType.key}) base fragment\n`));
|
|
1923
|
+
}
|
|
1924
|
+
else {
|
|
1925
|
+
if (debug)
|
|
1926
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.displayName} (${contentType.key}) base fragment - file already exists\n`));
|
|
1927
|
+
mustWrite = false;
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
else if (debug) {
|
|
1931
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating ${contentType.displayName} (${contentType.key}) base fragment\n`));
|
|
1932
|
+
}
|
|
1933
|
+
if (mustWrite) {
|
|
1934
|
+
const fragment = GraphQLGen.buildGetQuery(contentType, undefined, new Map());
|
|
1935
|
+
fs.writeFileSync(baseQueryFile, fragment);
|
|
1936
|
+
}
|
|
1937
|
+
return {
|
|
1938
|
+
written: mustWrite,
|
|
1939
|
+
propertyTypes: GraphQLGen.getReferencedPropertyComponents(contentType)
|
|
1940
|
+
};
|
|
1752
1941
|
}
|
|
1753
1942
|
|
|
1754
1943
|
const SchemaDownloadCommand = {
|
|
@@ -1769,12 +1958,12 @@ const SchemaDownloadCommand = {
|
|
|
1769
1958
|
const fullSchemaDir = path.normalize(path.join(projectPath, schemaDir));
|
|
1770
1959
|
const relativeSchemaDir = path.relative(projectPath, fullSchemaDir);
|
|
1771
1960
|
process.stdout.write(`\n${figures.arrowRight} Validating schema folder ${relativeSchemaDir}\n`);
|
|
1772
|
-
const schemaDirInfo = await
|
|
1961
|
+
const schemaDirInfo = await fsAsync.stat(fullSchemaDir).catch((e) => { if (e.code == 'ENOENT')
|
|
1773
1962
|
return undefined;
|
|
1774
1963
|
else
|
|
1775
1964
|
throw e; });
|
|
1776
1965
|
if (!schemaDirInfo) {
|
|
1777
|
-
await
|
|
1966
|
+
await fsAsync.mkdir(fullSchemaDir, { recursive: true });
|
|
1778
1967
|
}
|
|
1779
1968
|
else if (!schemaDirInfo.isDirectory()) {
|
|
1780
1969
|
process.stderr.write(chalk.redBright(chalk.bold(`${figures.cross} [Error] The schema directory exists, but is not a directory (${relativeSchemaDir})\n`)));
|
|
@@ -1787,7 +1976,7 @@ const SchemaDownloadCommand = {
|
|
|
1787
1976
|
const schemaFile = path.normalize(path.join(fullSchemaDir, `${schema.title.toLowerCase()}.schema.json`));
|
|
1788
1977
|
const relativePath = path.relative(projectPath, schemaFile);
|
|
1789
1978
|
try {
|
|
1790
|
-
await
|
|
1979
|
+
await fsAsync.writeFile(schemaFile, JSON.stringify(schema.schema, undefined, 2), {
|
|
1791
1980
|
encoding: "utf-8",
|
|
1792
1981
|
flag: force ? 'w' : 'wx'
|
|
1793
1982
|
});
|
|
@@ -1941,13 +2130,6 @@ function groupErrorsByKeyword(errors) {
|
|
|
1941
2130
|
toMerge.enum = [toMerge.enum.reduce((prev, current) => prev ? deepmerge(prev, current) : current, undefined)];
|
|
1942
2131
|
return toMerge;
|
|
1943
2132
|
}
|
|
1944
|
-
function getValidator(schemaObject) {
|
|
1945
|
-
const ajv = new Ajv({
|
|
1946
|
-
discriminator: true
|
|
1947
|
-
});
|
|
1948
|
-
addFormats(ajv);
|
|
1949
|
-
return ajv.compile(schemaObject);
|
|
1950
|
-
}
|
|
1951
2133
|
|
|
1952
2134
|
const SchemaVsCodeCommand = {
|
|
1953
2135
|
command: "schema:vscode",
|
|
@@ -2034,7 +2216,7 @@ function writeFileAsync(path, data) {
|
|
|
2034
2216
|
});
|
|
2035
2217
|
}
|
|
2036
2218
|
|
|
2037
|
-
var version = "6.0.0-
|
|
2219
|
+
var version = "6.0.0-pre9";
|
|
2038
2220
|
var name = "opti-cms";
|
|
2039
2221
|
var APP = {
|
|
2040
2222
|
version: version,
|
|
@@ -2059,17 +2241,22 @@ const CmsVersionCommand = {
|
|
|
2059
2241
|
throw error;
|
|
2060
2242
|
}
|
|
2061
2243
|
});
|
|
2244
|
+
const hasPreview2Info = versionInfo.results.preview2Data?.baseUrl ? true : false;
|
|
2062
2245
|
const info = new Table({
|
|
2063
2246
|
head: [
|
|
2064
2247
|
chalk.yellow(chalk.bold("Component")),
|
|
2065
2248
|
chalk.yellow(chalk.bold("Version"))
|
|
2066
2249
|
],
|
|
2067
|
-
colWidths: [
|
|
2250
|
+
colWidths: [30, 60],
|
|
2068
2251
|
colAligns: ["left", "left"]
|
|
2069
2252
|
});
|
|
2070
|
-
info.push(["
|
|
2253
|
+
info.push(["Base URL", versionInfo.baseUrl ?? client.cmsUrl.href]);
|
|
2254
|
+
if (hasPreview2Info)
|
|
2255
|
+
info.push(["Base URL (Preview 2)", versionInfo.results.preview2Data?.baseUrl ?? 'n/a']);
|
|
2071
2256
|
info.push(["Client API", client.apiVersion]);
|
|
2072
2257
|
info.push(["Service API", versionInfo.apiVersion]);
|
|
2258
|
+
if (hasPreview2Info)
|
|
2259
|
+
info.push(["Service API (Preview 2)", versionInfo.results.preview2Data?.apiVersion ?? 'n/a']);
|
|
2073
2260
|
info.push(["CMS Build", versionInfo.cmsVersion]);
|
|
2074
2261
|
info.push(["Service Build", versionInfo.serviceVersion]);
|
|
2075
2262
|
info.push(["SDK", APP.version]);
|
|
@@ -2192,7 +2379,7 @@ async function deleteContentItem(client, key, onlyChildren = false) {
|
|
|
2192
2379
|
process.stderr.write(chalk.redBright(` ${figures.cross}The ContentItem ${key} is a reserved item, skipping deletion\n`));
|
|
2193
2380
|
return removedCount;
|
|
2194
2381
|
}
|
|
2195
|
-
const deleteResult = await client.
|
|
2382
|
+
const deleteResult = await client.preview2ContentDelete({ path: { key }, query: { permanent: true } }).then(r => r.key).catch((e) => {
|
|
2196
2383
|
if (e.status == 404)
|
|
2197
2384
|
return key;
|
|
2198
2385
|
throw e;
|
|
@@ -2218,8 +2405,7 @@ async function resetSystemTypes(client) {
|
|
|
2218
2405
|
body: {
|
|
2219
2406
|
key: systemType,
|
|
2220
2407
|
properties: {}
|
|
2221
|
-
}
|
|
2222
|
-
query: { ignoreDataLossWarnings: true }
|
|
2408
|
+
}
|
|
2223
2409
|
}).catch((e) => e.status == 404 ? undefined : null);
|
|
2224
2410
|
if (newType)
|
|
2225
2411
|
resetTypes++;
|
|
@@ -2320,7 +2506,7 @@ async function getAllTypes(client, batchSize = 100) {
|
|
|
2320
2506
|
return actualItems;
|
|
2321
2507
|
}
|
|
2322
2508
|
async function getAllAssets(client, parentKey, batchSize = 100) {
|
|
2323
|
-
const items = await client.
|
|
2509
|
+
const items = await client.preview2ContentListAssets({ path: { key: parentKey }, query: { pageIndex: 0, pageSize: batchSize } }).catch((e) => {
|
|
2324
2510
|
if (e.status == 404) {
|
|
2325
2511
|
return {
|
|
2326
2512
|
items: [],
|
|
@@ -2335,7 +2521,7 @@ async function getAllAssets(client, parentKey, batchSize = 100) {
|
|
|
2335
2521
|
const actualItems = items.items ?? [];
|
|
2336
2522
|
const pageCount = Math.ceil(totalItemCount / pageSize);
|
|
2337
2523
|
for (let pageNr = 1; pageNr < pageCount; pageNr++) {
|
|
2338
|
-
const pageData = await client.
|
|
2524
|
+
const pageData = await client.preview2ContentListAssets({ path: { key: parentKey }, query: { pageIndex: pageNr, pageSize } }).catch((e) => {
|
|
2339
2525
|
if (e.status == 404) {
|
|
2340
2526
|
return {
|
|
2341
2527
|
items: [],
|
|
@@ -2350,7 +2536,7 @@ async function getAllAssets(client, parentKey, batchSize = 100) {
|
|
|
2350
2536
|
return actualItems;
|
|
2351
2537
|
}
|
|
2352
2538
|
async function getAllItems(client, parentKey, batchSize = 100) {
|
|
2353
|
-
const items = await client.
|
|
2539
|
+
const items = await client.preview2ContentListItems({ path: { key: parentKey }, query: { pageIndex: 0, pageSize: batchSize } }).catch((e) => {
|
|
2354
2540
|
if (e.status == 404) {
|
|
2355
2541
|
return {
|
|
2356
2542
|
items: [],
|
|
@@ -2365,7 +2551,7 @@ async function getAllItems(client, parentKey, batchSize = 100) {
|
|
|
2365
2551
|
const actualItems = items.items ?? [];
|
|
2366
2552
|
const pageCount = Math.ceil(totalItemCount / pageSize);
|
|
2367
2553
|
for (let pageNr = 1; pageNr < pageCount; pageNr++) {
|
|
2368
|
-
const pageData = await client.
|
|
2554
|
+
const pageData = await client.preview2ContentListItems({ path: { key: parentKey }, query: { pageIndex: pageNr, pageSize } }).catch((e) => {
|
|
2369
2555
|
if (e.status == 404) {
|
|
2370
2556
|
return {
|
|
2371
2557
|
items: [],
|
|
@@ -2387,6 +2573,7 @@ const commands = [
|
|
|
2387
2573
|
NextJsComponentsCommand,
|
|
2388
2574
|
NextJsCreateCommand,
|
|
2389
2575
|
NextJsFactoryCommand,
|
|
2576
|
+
NextJsFragmentsCommand,
|
|
2390
2577
|
NextJsQueriesCommand,
|
|
2391
2578
|
NextJsVisualBuilderCommand,
|
|
2392
2579
|
SchemaDownloadCommand,
|