@remkoj/optimizely-cms-cli 5.1.5 → 6.0.0-pre10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -17
- package/dist/index.js +1101 -636
- package/dist/index.js.map +1 -1
- package/package.json +33 -19
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { globSync, glob } from 'glob';
|
|
2
|
-
import
|
|
1
|
+
import { globSync, glob, globIterate } from 'glob';
|
|
2
|
+
import { config } from 'dotenv';
|
|
3
3
|
import { expand } from 'dotenv-expand';
|
|
4
|
-
import {
|
|
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';
|
|
@@ -10,6 +10,11 @@ import figures from 'figures';
|
|
|
10
10
|
import Table from 'cli-table3';
|
|
11
11
|
import fsAsync from 'node:fs/promises';
|
|
12
12
|
import { input, select, confirm } from '@inquirer/prompts';
|
|
13
|
+
import createDeepMerge from '@fastify/deepmerge';
|
|
14
|
+
import { Ajv } from 'ajv';
|
|
15
|
+
import addFormats from 'ajv-formats';
|
|
16
|
+
import GraphQLGen from '@remkoj/optimizely-graph-functions/contenttype-loader';
|
|
17
|
+
import deepEqual from 'fast-deep-equal';
|
|
13
18
|
|
|
14
19
|
/**
|
|
15
20
|
* Prepare the application context, by parsing the .env files in the main
|
|
@@ -18,19 +23,25 @@ import { input, select, confirm } from '@inquirer/prompts';
|
|
|
18
23
|
* @returns A string array with the files processed
|
|
19
24
|
*/
|
|
20
25
|
function prepare() {
|
|
21
|
-
const
|
|
22
|
-
|
|
26
|
+
const globResult = globSync(".env*");
|
|
27
|
+
const envFiles = globResult.sort((a, b) => b.length - a.length).filter(n => n == ".env" || n == ".env.local" || (process.env.NODE_ENV && n == `.env.${process.env.NODE_ENV}.local`));
|
|
28
|
+
expand(config({ path: envFiles, debug: false, quiet: true }));
|
|
23
29
|
return envFiles;
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
function createOptiCmsApp(scriptName, version, epilogue, envFiles) {
|
|
33
|
+
if (envFiles) {
|
|
34
|
+
process.stdout.write(chalk.bold(`✅ Loaded environment files:`) + "\n");
|
|
35
|
+
envFiles.forEach(envFile => {
|
|
36
|
+
process.stdout.write(` 👉 ${envFile}\n`);
|
|
37
|
+
});
|
|
38
|
+
process.stdout.write("\n");
|
|
39
|
+
}
|
|
27
40
|
let config;
|
|
28
41
|
try {
|
|
29
|
-
config =
|
|
42
|
+
config = readPartialEnvConfig();
|
|
30
43
|
}
|
|
31
44
|
catch (e) {
|
|
32
|
-
if (envFiles)
|
|
33
|
-
console.error(chalk.gray(`Included environment files: ${envFiles.join(', ')}`));
|
|
34
45
|
console.error(chalk.redBright(`${chalk.bold("[ERROR]:")} Error processing environment variables: ${e.message}`));
|
|
35
46
|
process.exit(1);
|
|
36
47
|
}
|
|
@@ -40,7 +51,7 @@ function createOptiCmsApp(scriptName, version, epilogue, envFiles) {
|
|
|
40
51
|
.usage('$0 <cmd> [args]')
|
|
41
52
|
.option("path", { alias: "p", description: "Application root folder", string: true, type: "string", demandOption: false, default: process.cwd() })
|
|
42
53
|
.option("components", { alias: "c", description: "Path to components folder", string: true, type: "string", demandOption: false, default: "./src/components/cms" })
|
|
43
|
-
.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 })
|
|
44
55
|
.option("client_id", { alias: "ci", description: "API Client ID", string: true, type: "string", demandOption: isDemanded(config.clientId), default: config.clientId })
|
|
45
56
|
.option('client_secret', { alias: "cs", description: "API Client Secrent", string: true, type: "string", demandOption: isDemanded(config.clientSecret), default: config.clientSecret })
|
|
46
57
|
.option('user_id', { alias: "u", description: "Impersonate user id", string: true, type: "string", demandOption: false, default: config.actAs })
|
|
@@ -52,12 +63,10 @@ function createOptiCmsApp(scriptName, version, epilogue, envFiles) {
|
|
|
52
63
|
.epilogue(`Copyright Remko Jantzen - 2023-${(new Date(Date.now())).getFullYear()}`)
|
|
53
64
|
.help()
|
|
54
65
|
.fail((msg, error, args) => {
|
|
55
|
-
if (envFiles)
|
|
56
|
-
console.error(chalk.gray(`Included environment files: ${envFiles.join(', ')}\n`));
|
|
57
66
|
if (msg)
|
|
58
|
-
console.error(msg + "\n");
|
|
67
|
+
console.error(chalk.redBright(msg) + "\n");
|
|
59
68
|
if (error)
|
|
60
|
-
console.error(`[${chalk.bold(error.name ?? 'Error')}]: ${error.message ?? ''}\n
|
|
69
|
+
console.error(chalk.redBright(`[${chalk.bold(error.name ?? 'Error')}]: ${error.message ?? ''}`) + '\n');
|
|
61
70
|
args.showHelp("error");
|
|
62
71
|
});
|
|
63
72
|
}
|
|
@@ -106,16 +115,23 @@ function parseArgs({ client_id, client_secret, cms_url, user_id, verbose, path:
|
|
|
106
115
|
}
|
|
107
116
|
|
|
108
117
|
function createCmsClient(args) {
|
|
109
|
-
const
|
|
110
|
-
const baseConfig =
|
|
118
|
+
const cfg = getCmsIntegrationApiOptions(args);
|
|
119
|
+
const baseConfig = readPartialEnvConfig();
|
|
111
120
|
const client = createClient({
|
|
112
121
|
...baseConfig,
|
|
113
122
|
...cfg
|
|
114
123
|
});
|
|
115
124
|
if (cfg.debug)
|
|
116
|
-
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`));
|
|
117
126
|
return client;
|
|
118
127
|
}
|
|
128
|
+
function getCmsIntegrationApiOptions(args) {
|
|
129
|
+
if (typeof args !== 'object' || args === null)
|
|
130
|
+
return undefined;
|
|
131
|
+
if (args.base && (typeof args.base === 'object') && (typeof args.base.href === 'string'))
|
|
132
|
+
return args;
|
|
133
|
+
return parseArgs(args)._config;
|
|
134
|
+
}
|
|
119
135
|
|
|
120
136
|
const StylesPushCommand = {
|
|
121
137
|
command: "styles:push",
|
|
@@ -150,7 +166,12 @@ const StylesPushCommand = {
|
|
|
150
166
|
return undefined; // Only include defined styles, if any
|
|
151
167
|
if (cfg.debug)
|
|
152
168
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Pushing: ${styleKey}\n`));
|
|
153
|
-
|
|
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 }));
|
|
154
175
|
return newTemplate;
|
|
155
176
|
}))).filter(isNotNullOrUndefined);
|
|
156
177
|
const styles = new Table({
|
|
@@ -190,9 +211,61 @@ function isNotNullOrUndefined(i) {
|
|
|
190
211
|
return i ? true : false;
|
|
191
212
|
}
|
|
192
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
|
+
|
|
193
266
|
const ContentTypesArgsDefaults = {
|
|
194
|
-
excludeBaseTypes: [],
|
|
195
|
-
excludeTypes: [
|
|
267
|
+
excludeBaseTypes: ['folder', 'media', 'image', 'video'],
|
|
268
|
+
excludeTypes: [],
|
|
196
269
|
baseTypes: [],
|
|
197
270
|
types: [],
|
|
198
271
|
all: false
|
|
@@ -205,72 +278,92 @@ const contentTypesBuilder = (yargs, defaults = ContentTypesArgsDefaults) => {
|
|
|
205
278
|
yargs.option('all', { alias: 'a', description: "Include non-supported base types", boolean: true, type: 'boolean', demandOption: false, default: defaults.all });
|
|
206
279
|
return yargs;
|
|
207
280
|
};
|
|
208
|
-
function
|
|
209
|
-
return Object.keys(enumObject).filter((item) => {
|
|
210
|
-
return isNaN(Number(item));
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
async function getContentTypes(client, args, pageSize = 100, allowSystem = false) {
|
|
281
|
+
async function getContentTypes(client, args, pageSize = 25, allowSystem = false, customFilter) {
|
|
214
282
|
const { _config: cfg, excludeBaseTypes, excludeTypes, baseTypes, types, all } = parseArgs(args);
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
let pagesRemaining = Math.ceil(resultsPage.totalItemCount / resultsPage.pageSize) - (resultsPage.pageIndex + 1);
|
|
221
|
-
while (pagesRemaining > 0 && results.length < resultsPage.totalItemCount) {
|
|
222
|
-
if (cfg.debug)
|
|
223
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetching page ${resultsPage.pageIndex + 2} of ${Math.ceil(resultsPage.totalItemCount / resultsPage.pageSize)} (${resultsPage.pageSize} items per page)\n`));
|
|
224
|
-
resultsPage = await client.contentTypes.contentTypesList(undefined, undefined, resultsPage.pageIndex + 1, resultsPage.pageSize);
|
|
225
|
-
results.push(...resultsPage.items);
|
|
226
|
-
pagesRemaining = Math.ceil(resultsPage.totalItemCount / resultsPage.pageSize) - (resultsPage.pageIndex + 1);
|
|
227
|
-
}
|
|
228
|
-
if (cfg.debug) {
|
|
229
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetched ${results.length} Content-Types from Optimizely CMS\n`));
|
|
230
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Filtering Content-Types based upon arguments\n`));
|
|
231
|
-
}
|
|
232
|
-
const validBaseTypes = getEnumOptions(IntegrationApi.ContentBaseType).map(x => x.toLowerCase());
|
|
233
|
-
const allContentTypes = all ?
|
|
234
|
-
// If we're returning all content types, including non-supported base types, make sure the base type is always set
|
|
235
|
-
results.map(contentType => {
|
|
236
|
-
return {
|
|
237
|
-
...contentType,
|
|
238
|
-
baseType: contentType.baseType ?? 'default'
|
|
239
|
-
};
|
|
240
|
-
}) :
|
|
241
|
-
// Otherwise filter out any non supported content base type
|
|
242
|
-
results.filter(contentType => {
|
|
243
|
-
const baseType = (contentType.baseType ?? 'default').toLowerCase();
|
|
244
|
-
const isValid = validBaseTypes.includes(baseType);
|
|
245
|
-
if (!isValid && cfg.debug)
|
|
246
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Removing ${contentType.key} as it has an unsupported base type: ${baseType}\n`));
|
|
247
|
-
return isValid;
|
|
248
|
-
});
|
|
249
|
-
const contentTypes = allContentTypes.filter(data => {
|
|
250
|
-
// Remove items based upon filters
|
|
251
|
-
const keepType = (!excludeBaseTypes.includes(data.baseType)) &&
|
|
252
|
-
(!excludeTypes.includes(data.key)) &&
|
|
253
|
-
(baseTypes.length == 0 || baseTypes.includes(data.baseType)) &&
|
|
254
|
-
(types.length == 0 || types.includes(data.key));
|
|
255
|
-
if (!keepType) {
|
|
283
|
+
const allContentTypes = [];
|
|
284
|
+
const filteredContentTypes = [];
|
|
285
|
+
for await (const contentType of getAllContentTypes(client, cfg.debug, pageSize)) {
|
|
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:')) {
|
|
256
288
|
if (cfg.debug)
|
|
257
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping
|
|
258
|
-
|
|
289
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.key} as it is a reference to external data in Optimizely Graph\n`));
|
|
290
|
+
continue;
|
|
259
291
|
}
|
|
260
|
-
//
|
|
261
|
-
|
|
292
|
+
// Build the unfiltered array
|
|
293
|
+
allContentTypes.push(contentType);
|
|
294
|
+
// Skip based upon base type filters
|
|
295
|
+
if (!shouldInclude(typeToSlug(contentType.baseType), baseTypes.map(x => typeToSlug(x)), excludeBaseTypes.map(x => typeToSlug(x)))) {
|
|
262
296
|
if (cfg.debug)
|
|
263
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping
|
|
264
|
-
|
|
297
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.key} as it has a restricted base type: ${contentType.baseType}\n`));
|
|
298
|
+
continue;
|
|
265
299
|
}
|
|
266
|
-
|
|
267
|
-
|
|
300
|
+
// Skip based upon type filters
|
|
301
|
+
if (!shouldInclude(contentType.key, types, excludeTypes)) {
|
|
302
|
+
if (cfg.debug)
|
|
303
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.key} as it has a restricted type: ${contentType.key}\n`));
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
// Skip based upon system filter
|
|
307
|
+
if (contentType.source == 'system' && !allowSystem) {
|
|
308
|
+
if (cfg.debug)
|
|
309
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Content-Type ${contentType.key} due to it being a system type\n`));
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
// Skip based upon custom filter
|
|
313
|
+
if (customFilter && !await customFilter(contentType)) {
|
|
314
|
+
if (cfg.debug)
|
|
315
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Content-Type ${contentType.key} due to the custom filter\n`));
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
// Add to list
|
|
319
|
+
filteredContentTypes.push(contentType);
|
|
320
|
+
}
|
|
268
321
|
if (cfg.debug)
|
|
269
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Applied content type filters, reduced from ${
|
|
270
|
-
return {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
322
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Applied content type filters, reduced from ${allContentTypes.length} to ${filteredContentTypes.length} items\n`));
|
|
323
|
+
return { all: allContentTypes, contentTypes: filteredContentTypes };
|
|
324
|
+
}
|
|
325
|
+
function shouldInclude(value, allow, disallow) {
|
|
326
|
+
// Item is allowed when either allow is not set, an empty array or has the value
|
|
327
|
+
const isAllowed = !Array.isArray(allow) || allow.length === 0 || allow.includes(value);
|
|
328
|
+
// Item is disallowed when and the array is set and includes the value
|
|
329
|
+
const isDisallowed = Array.isArray(disallow) && disallow.includes(value);
|
|
330
|
+
return isAllowed && !isDisallowed;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Retrieve all content types as an Async Generator, allowing processing of entries whilest they are being loaded from the CMS instance.
|
|
334
|
+
*
|
|
335
|
+
* @param client
|
|
336
|
+
* @param args
|
|
337
|
+
* @param pageSize
|
|
338
|
+
*/
|
|
339
|
+
async function* getAllContentTypes(client, debug = false, pageSize = 25) {
|
|
340
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Pulling Content Types from Optimizely CMS\n`));
|
|
341
|
+
let requestPageSize = pageSize;
|
|
342
|
+
let requestPageIndex = 0;
|
|
343
|
+
let totalItemCount = 0;
|
|
344
|
+
let totalPages = 0;
|
|
345
|
+
do {
|
|
346
|
+
const resultsPage = await client.contentTypesList({ query: { pageIndex: requestPageIndex, pageSize: requestPageSize } }).catch((_) => {
|
|
347
|
+
return {
|
|
348
|
+
items: [],
|
|
349
|
+
totalItemCount: 0,
|
|
350
|
+
pageIndex: requestPageIndex,
|
|
351
|
+
pageSize: requestPageSize
|
|
352
|
+
};
|
|
353
|
+
});
|
|
354
|
+
// Calculate fields for next page
|
|
355
|
+
totalItemCount = resultsPage.totalItemCount ?? 0;
|
|
356
|
+
requestPageSize = resultsPage.pageSize;
|
|
357
|
+
requestPageIndex = resultsPage.pageIndex + 1;
|
|
358
|
+
totalPages = Math.ceil(totalItemCount / requestPageSize);
|
|
359
|
+
// Debug output
|
|
360
|
+
if (debug)
|
|
361
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetched contentTypes page ${requestPageIndex} of ${totalPages} (${requestPageSize} items per page)\n`));
|
|
362
|
+
// Yield items
|
|
363
|
+
for (const contentType of (resultsPage.items ?? [])) {
|
|
364
|
+
yield contentType;
|
|
365
|
+
}
|
|
366
|
+
} while (requestPageIndex < totalPages);
|
|
274
367
|
}
|
|
275
368
|
|
|
276
369
|
const stylesBuilder = yargs => {
|
|
@@ -282,73 +375,90 @@ const stylesBuilder = yargs => {
|
|
|
282
375
|
newArgs.option("templateTypes", { alias: 'tt', description: "Select only these template types", choices: ['node', 'base', 'component'], type: 'array', demandOption: false, default: [] });
|
|
283
376
|
return newArgs;
|
|
284
377
|
};
|
|
285
|
-
async function getStyles(client, args, pageSize =
|
|
378
|
+
async function getStyles(client, args, pageSize = 25) {
|
|
286
379
|
if (client.runtimeCmsVersion == OptiCmsVersion.CMS12)
|
|
287
380
|
return { all: [], styles: [] };
|
|
288
381
|
const { _config: cfg, excludeBaseTypes, excludeTypes, excludeNodeTypes, excludeTemplates, baseTypes, types, nodes, templates, templateTypes } = parseArgs(args);
|
|
289
382
|
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Pulling Style-Definitions from Optimizely CMS\n`));
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
if (cfg.debug)
|
|
297
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetching page ${resultsPage.pageIndex + 2} of ${Math.ceil(resultsPage.totalItemCount / resultsPage.pageSize)} (${resultsPage.pageSize} items per page)\n`));
|
|
298
|
-
resultsPage = await client.displayTemplates.displayTemplatesList(resultsPage.pageIndex + 1, resultsPage.pageSize);
|
|
299
|
-
results.push(...resultsPage.items);
|
|
300
|
-
pagesRemaining = Math.ceil(resultsPage.totalItemCount / resultsPage.pageSize) - (resultsPage.pageIndex + 1);
|
|
301
|
-
}
|
|
302
|
-
if (cfg.debug) {
|
|
303
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetched ${results.length} Style-Definitions from Optimizely CMS\n`));
|
|
304
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Filtering Style-Definitions based upon arguments\n`));
|
|
305
|
-
}
|
|
306
|
-
const styles = results.filter(data => {
|
|
307
|
-
if (isExcluded(data.key, excludeTemplates, templates)) {
|
|
383
|
+
const allDisplayTemplates = [];
|
|
384
|
+
const filteredDisplayTemplates = [];
|
|
385
|
+
for await (const displayTemplate of getAllStyles(client, cfg.debug, pageSize)) {
|
|
386
|
+
allDisplayTemplates.push(displayTemplate);
|
|
387
|
+
const templateType = displayTemplate.baseType ? 'base' : displayTemplate.nodeType ? 'node' : displayTemplate.contentType ? 'component' : 'unknown';
|
|
388
|
+
if (isExcluded(displayTemplate.key, excludeTemplates, templates)) {
|
|
308
389
|
if (cfg.debug)
|
|
309
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${
|
|
310
|
-
|
|
390
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${displayTemplate.key} - Style defintion key filtering active\n`));
|
|
391
|
+
continue;
|
|
311
392
|
}
|
|
312
|
-
const templateType = data.baseType ? 'base' : data.nodeType ? 'node' : data.contentType ? 'component' : 'unknown';
|
|
313
393
|
if (isExcluded(templateType, [], templateTypes)) {
|
|
314
394
|
if (cfg.debug)
|
|
315
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${
|
|
316
|
-
|
|
395
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${displayTemplate.key} - Style type filtering is active\n`));
|
|
396
|
+
continue;
|
|
317
397
|
}
|
|
318
|
-
if (
|
|
398
|
+
if (displayTemplate.baseType && isExcluded(displayTemplate.baseType, excludeBaseTypes, baseTypes)) {
|
|
319
399
|
if (cfg.debug)
|
|
320
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${
|
|
321
|
-
|
|
400
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${displayTemplate.key} - Style is defined at base type level and base type filtering is active\n`));
|
|
401
|
+
continue;
|
|
322
402
|
}
|
|
323
|
-
if (
|
|
403
|
+
if (displayTemplate.contentType && isExcluded(displayTemplate.contentType, excludeTypes, types)) {
|
|
324
404
|
if (cfg.debug)
|
|
325
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${
|
|
326
|
-
|
|
405
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${displayTemplate.key} - Style is defined at component type level and component type filtering is active\n`));
|
|
406
|
+
continue;
|
|
327
407
|
}
|
|
328
408
|
if (templateType != 'component' && types.length > 0) {
|
|
329
409
|
if (cfg.debug)
|
|
330
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${
|
|
331
|
-
|
|
410
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${displayTemplate.key} - Style is targeting the ${templateType} level and component type selection is active\n`));
|
|
411
|
+
continue;
|
|
332
412
|
}
|
|
333
|
-
if (
|
|
413
|
+
if (displayTemplate.nodeType && isExcluded(displayTemplate.nodeType, excludeNodeTypes, nodes)) {
|
|
334
414
|
if (cfg.debug)
|
|
335
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${
|
|
336
|
-
|
|
415
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${displayTemplate.key} - Style is defined at node type level and node type filtering is active\n`));
|
|
416
|
+
continue;
|
|
337
417
|
}
|
|
338
418
|
if (templateType != 'node' && nodes.length > 0) {
|
|
339
419
|
if (cfg.debug)
|
|
340
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${
|
|
341
|
-
|
|
420
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${displayTemplate.key} - Style is targeting the ${templateType} level and node type selection is active\n`));
|
|
421
|
+
continue;
|
|
342
422
|
}
|
|
343
|
-
|
|
344
|
-
}
|
|
423
|
+
filteredDisplayTemplates.push(displayTemplate);
|
|
424
|
+
}
|
|
345
425
|
if (cfg.debug)
|
|
346
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Applied
|
|
426
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Applied style filters, reduced from ${allDisplayTemplates.length} to ${filteredDisplayTemplates.length} items\n`));
|
|
347
427
|
return {
|
|
348
|
-
all:
|
|
349
|
-
styles
|
|
428
|
+
all: allDisplayTemplates,
|
|
429
|
+
styles: filteredDisplayTemplates
|
|
350
430
|
};
|
|
351
431
|
}
|
|
432
|
+
async function* getAllStyles(client, debug = false, pageSize = 5) {
|
|
433
|
+
if (client.runtimeCmsVersion == OptiCmsVersion.CMS12)
|
|
434
|
+
return;
|
|
435
|
+
let requestPageSize = pageSize;
|
|
436
|
+
let requestPageIndex = 0;
|
|
437
|
+
let totalItemCount = 0;
|
|
438
|
+
let totalPages = 0;
|
|
439
|
+
do {
|
|
440
|
+
const resultsPage = await client.displayTemplatesList({ query: { pageIndex: requestPageIndex, pageSize: requestPageSize } }).catch((_) => {
|
|
441
|
+
return {
|
|
442
|
+
items: [],
|
|
443
|
+
totalItemCount: 0,
|
|
444
|
+
pageIndex: requestPageIndex,
|
|
445
|
+
pageSize: requestPageSize
|
|
446
|
+
};
|
|
447
|
+
});
|
|
448
|
+
// Calculate fields for next page
|
|
449
|
+
totalItemCount = resultsPage.totalItemCount ?? 0;
|
|
450
|
+
requestPageSize = resultsPage.pageSize;
|
|
451
|
+
requestPageIndex = resultsPage.pageIndex + 1;
|
|
452
|
+
totalPages = Math.ceil(totalItemCount / requestPageSize);
|
|
453
|
+
// Debug output
|
|
454
|
+
if (debug)
|
|
455
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetched displayTemplates page ${requestPageIndex} of ${totalPages} (${requestPageSize} items per page)\n`));
|
|
456
|
+
// Yield items
|
|
457
|
+
for (const displayTemplate of (resultsPage.items ?? [])) {
|
|
458
|
+
yield displayTemplate;
|
|
459
|
+
}
|
|
460
|
+
} while (requestPageIndex < totalPages);
|
|
461
|
+
}
|
|
352
462
|
function isExcluded(value, exclusions, inclusions) {
|
|
353
463
|
if (value == undefined || value == null)
|
|
354
464
|
return false;
|
|
@@ -364,18 +474,9 @@ async function getStyleFilePath(definition, opts) {
|
|
|
364
474
|
return `${opts?.contentBaseType}/${definition.contentType}/${definition.key}.opti-style.json`;
|
|
365
475
|
if (!opts?.client)
|
|
366
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");
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
let pagesRemaining = Math.ceil(resultsPage.totalItemCount / resultsPage.pageSize) - (resultsPage.pageIndex + 1);
|
|
371
|
-
while (pagesRemaining > 0 && contentTypes.length < resultsPage.totalItemCount) {
|
|
372
|
-
resultsPage = await opts.client.contentTypes.contentTypesList(undefined, undefined, resultsPage.pageIndex + 1, resultsPage.pageSize);
|
|
373
|
-
contentTypes.push(...resultsPage.items);
|
|
374
|
-
pagesRemaining = Math.ceil(resultsPage.totalItemCount / resultsPage.pageSize) - (resultsPage.pageIndex + 1);
|
|
375
|
-
}
|
|
376
|
-
const fetchedBaseType = contentTypes.filter(x => x.key == definition.contentType).map(x => x.baseType).at(0);
|
|
377
|
-
if (fetchedBaseType)
|
|
378
|
-
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`;
|
|
379
480
|
}
|
|
380
481
|
throw new Error(`Unable to resolve the target for the DisplayTemplate: ${definition.key}`);
|
|
381
482
|
}
|
|
@@ -487,20 +588,6 @@ function ucFirst$1(input) {
|
|
|
487
588
|
return input;
|
|
488
589
|
return input[0].toUpperCase() + input.substring(1);
|
|
489
590
|
}
|
|
490
|
-
function toTypeFilesList(client, templates, basePath, createPaths = true) {
|
|
491
|
-
return templates.reduce(async (prevList, displayTemplate) => {
|
|
492
|
-
const typeFiles = await prevList;
|
|
493
|
-
const { targetType, styleFilePath: filePath, helperFilePath: helperPath } = await createTemplateMetadata(client, displayTemplate, basePath, createPaths);
|
|
494
|
-
if (!typeFiles[targetType]) {
|
|
495
|
-
typeFiles[targetType] = {
|
|
496
|
-
filePath: helperPath,
|
|
497
|
-
templates: []
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
typeFiles[targetType].templates.push({ key: displayTemplate.key, file: filePath, data: displayTemplate });
|
|
501
|
-
return typeFiles;
|
|
502
|
-
}, Promise.resolve({}));
|
|
503
|
-
}
|
|
504
591
|
async function createTemplateMetadata(client, displayTemplate, basePath, createPaths = true) {
|
|
505
592
|
let itemPath = undefined;
|
|
506
593
|
let targetType;
|
|
@@ -512,17 +599,21 @@ async function createTemplateMetadata(client, displayTemplate, basePath, createP
|
|
|
512
599
|
typesPath = path.join(basePath, 'nodes', displayTemplate.nodeType);
|
|
513
600
|
targetType = targetPrefix + '/' + displayTemplate.nodeType;
|
|
514
601
|
break;
|
|
515
|
-
case 'base':
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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;
|
|
519
607
|
break;
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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);
|
|
524
614
|
targetType = targetPrefix + '/' + displayTemplate.contentType;
|
|
525
615
|
break;
|
|
616
|
+
}
|
|
526
617
|
default:
|
|
527
618
|
throw new Error("Unsupported display template type");
|
|
528
619
|
}
|
|
@@ -534,6 +625,9 @@ async function createTemplateMetadata(client, displayTemplate, basePath, createP
|
|
|
534
625
|
const helperFilePath = path.join(typesPath, 'displayTemplates.ts');
|
|
535
626
|
return { key: displayTemplate.key, itemPath, typesPath, targetType, styleFilePath, helperFilePath };
|
|
536
627
|
}
|
|
628
|
+
function baseTypeToSlug(baseType) {
|
|
629
|
+
return baseType.replace(/^\_*/, "").toLowerCase();
|
|
630
|
+
}
|
|
537
631
|
function getTemplateTarget(displayTemplate) {
|
|
538
632
|
if (displayTemplate.baseType && displayTemplate.baseType.length > 0)
|
|
539
633
|
return 'base';
|
|
@@ -574,8 +668,8 @@ async function createDisplayTemplateHelper(typeFile, typeFileId, force = false,
|
|
|
574
668
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating or updating definition file for ${typeFileId} - ${typeFilePath}\n`));
|
|
575
669
|
// Write Style definition
|
|
576
670
|
const imports = [
|
|
577
|
-
'import type { LayoutProps } from "@remkoj/optimizely-cms-react"',
|
|
578
|
-
'import type {
|
|
671
|
+
'import type { LayoutProps, LayoutPropsSettingKeys, LayoutPropsSettingValues, CmsComponentProps } from "@remkoj/optimizely-cms-react"',
|
|
672
|
+
'import type { JSX, ComponentType } from "react"'
|
|
579
673
|
];
|
|
580
674
|
const typeContents = [];
|
|
581
675
|
const props = [];
|
|
@@ -584,11 +678,10 @@ async function createDisplayTemplateHelper(typeFile, typeFileId, force = false,
|
|
|
584
678
|
const importPath = path.relative(path.dirname(typeFilePath), displayTemplateFile).replaceAll('\\', '/');
|
|
585
679
|
imports.push(`import type ${displayTemplate.key}Styles from "./${importPath}"`);
|
|
586
680
|
typeContents.push(`export type ${displayTemplate.key}Props = LayoutProps<typeof ${displayTemplate.key}Styles>`);
|
|
587
|
-
typeContents.push(`export type ${displayTemplate.key}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
}
|
|
591
|
-
typeContents.push(`export type ${displayTemplate.key}Component<DT extends Record<string, any> = Record<string, any>> = (props: ${displayTemplate.key}ComponentProps<DT>) => ReactNode`);
|
|
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>>`);
|
|
592
685
|
typeContents.push('');
|
|
593
686
|
props.push(`${displayTemplate.key}Props`);
|
|
594
687
|
if (!typeId)
|
|
@@ -596,14 +689,11 @@ async function createDisplayTemplateHelper(typeFile, typeFileId, force = false,
|
|
|
596
689
|
});
|
|
597
690
|
if (typeId) {
|
|
598
691
|
typeId = ucFirst$1(typeId);
|
|
599
|
-
typeContents.push('');
|
|
600
692
|
typeContents.push(`export type ${typeId}LayoutProps = ${props.join(' | ')}
|
|
601
|
-
export type ${typeId}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
export type ${typeId}Component<DT extends Record<string, any> = Record<string, any>, LP extends ${typeId}LayoutProps = ${typeId}LayoutProps> = (props: ${typeId}ComponentProps<DT,LP>) => ReactNode`);
|
|
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>>`);
|
|
607
697
|
const defaultTemplate = templates.find(t => t.data.isDefault);
|
|
608
698
|
if (defaultTemplate) {
|
|
609
699
|
typeContents.push(`
|
|
@@ -638,11 +728,11 @@ const StylesCreateCommand = {
|
|
|
638
728
|
process.stdout.write(chalk.gray(`${figures.cross} Styles are not supported on CMS12\n`));
|
|
639
729
|
return;
|
|
640
730
|
}
|
|
641
|
-
const allowedBaseTypes = ['section', '
|
|
731
|
+
const allowedBaseTypes = ['section', 'component', 'experience'];
|
|
642
732
|
// Prepare
|
|
643
733
|
process.stdout.write(chalk.yellowBright(chalk.bold(`Reading current information from Optimizely CMS\n`)));
|
|
644
734
|
const [{ contentTypes }, { styles }] = await Promise.all([
|
|
645
|
-
getContentTypes(client, { ...args, excludeBaseTypes: [], excludeTypes: [], baseTypes: allowedBaseTypes, types: [], all: false }),
|
|
735
|
+
getContentTypes(client, { ...args, excludeBaseTypes: [], excludeTypes: [], baseTypes: allowedBaseTypes, types: [], all: false }, 25, false, (ct) => ct.compositionBehaviors?.includes('elementEnabled') || ct.compositionBehaviors?.includes('sectionEnabled') || ct.baseType == 'experience' || ct.baseType == 'section'),
|
|
646
736
|
getStyles(client, { ...args, excludeBaseTypes: [], excludeNodeTypes: [], excludeTemplates: [], excludeTypes: [], baseTypes: allowedBaseTypes, types: [], nodes: [], components: '', templates: [], templateTypes: [], all: false })
|
|
647
737
|
]);
|
|
648
738
|
const styleKeys = styles.map(x => x.key);
|
|
@@ -650,27 +740,34 @@ const StylesCreateCommand = {
|
|
|
650
740
|
process.stdout.write(chalk.yellowBright(chalk.bold(`\nConfigure your style definition\n`)));
|
|
651
741
|
const key = await input({ message: "Identifier:", validate: (val) => { return val.match(/^[a-zA-Z][A-Za-z0-9\-_]*$/) != null && !styleKeys.includes(val); } });
|
|
652
742
|
const displayName = await input({ message: "Display name:" });
|
|
653
|
-
const type = (await select({
|
|
654
|
-
|
|
655
|
-
{ value: "
|
|
656
|
-
{ value: "
|
|
657
|
-
|
|
743
|
+
const type = (await select({
|
|
744
|
+
message: "Style target:", choices: [
|
|
745
|
+
{ value: "baseType", name: "Base type", description: "Target all Content-Types that inherit from the specified base type" },
|
|
746
|
+
{ value: "contentType", name: "Content type", description: "Target a specific Content-Type that supports styling" },
|
|
747
|
+
{ value: "nodeType", name: "Node", description: "Target a specific node type from a Section" }
|
|
748
|
+
]
|
|
749
|
+
}));
|
|
658
750
|
let typeId = "";
|
|
659
751
|
switch (type) {
|
|
660
752
|
case 'contentType':
|
|
661
753
|
typeId = await select({ message: "Content-Type:", choices: contentTypes.map(x => { return { value: x.key, description: x.description, name: x.displayName }; }) });
|
|
662
754
|
break;
|
|
663
755
|
case 'nodeType':
|
|
664
|
-
typeId = await select({
|
|
756
|
+
typeId = await select({
|
|
757
|
+
message: "Node type:", choices: [
|
|
665
758
|
{ value: "row", description: "Row", name: "Row" },
|
|
666
759
|
{ value: "column", description: "Column", name: "Column" }
|
|
667
|
-
]
|
|
760
|
+
]
|
|
761
|
+
});
|
|
668
762
|
break;
|
|
669
763
|
case 'baseType':
|
|
670
|
-
typeId = await select({
|
|
764
|
+
typeId = await select({
|
|
765
|
+
message: "Base type:", choices: [
|
|
766
|
+
{ value: "experience", description: "Target all experience types", name: "Experience" },
|
|
671
767
|
{ value: "section", description: "Target all section types", name: "Section" },
|
|
672
|
-
{ value: "
|
|
673
|
-
]
|
|
768
|
+
{ value: "component", description: "Target all component types", name: "Component" }
|
|
769
|
+
]
|
|
770
|
+
});
|
|
674
771
|
break;
|
|
675
772
|
}
|
|
676
773
|
const isDefault = await confirm({ message: "Should this style be marked as default?" });
|
|
@@ -690,8 +787,18 @@ const StylesCreateCommand = {
|
|
|
690
787
|
process.exit(0);
|
|
691
788
|
}
|
|
692
789
|
}
|
|
790
|
+
// @ToDo: Add properties
|
|
693
791
|
fs.writeFileSync(path.join(basePath, styleFilePath), JSON.stringify(definition, undefined, 4));
|
|
694
792
|
process.stdout.write(chalk.yellowBright(chalk.bold(`\nWritten style defintion template to: ${path.normalize(path.join(basePath, styleFilePath))}\n`)));
|
|
793
|
+
if (await confirm({ message: "Do you want to publish this style into Optimizely CMS?" })) {
|
|
794
|
+
const response = await client.displayTemplatesCreate({ body: definition }).catch(_ => undefined);
|
|
795
|
+
if (!response) {
|
|
796
|
+
process.stderr.write(chalk.redBright(chalk.bold(`\[ERROR] Unable to create style definition ${definition.displayName} in Optimizely CMS\n`)));
|
|
797
|
+
}
|
|
798
|
+
else {
|
|
799
|
+
process.stdout.write(chalk.yellowBright(chalk.bold(`\nCreated style definition ${definition.displayName} in Optimizely CMS\n`)));
|
|
800
|
+
}
|
|
801
|
+
}
|
|
695
802
|
process.stdout.write(chalk.yellowBright(`\n1. Add your properties to the 'settings' list in the file`));
|
|
696
803
|
process.stdout.write(chalk.yellowBright(`\n2. Run `));
|
|
697
804
|
process.stdout.write(chalk.whiteBright(`yarn opti-cms styles:push -t ${definition.key}`));
|
|
@@ -704,91 +811,6 @@ const StylesCreateCommand = {
|
|
|
704
811
|
}
|
|
705
812
|
};
|
|
706
813
|
|
|
707
|
-
const StylesDeleteCommand = {
|
|
708
|
-
command: "styles:delete",
|
|
709
|
-
describe: "Remove Visual Builder style definitions from the CMS",
|
|
710
|
-
builder: (yargs) => {
|
|
711
|
-
const newYargs = stylesBuilder(yargs);
|
|
712
|
-
newYargs.option('force', { alias: 'f', description: "Overwrite existing files", boolean: true, type: 'boolean', demandOption: false, default: false });
|
|
713
|
-
newYargs.option('withStyleFile', { alias: 'w', description: "Delete the .opti-style.json file as weill", boolean: true, type: 'boolean', demandOption: false, default: true });
|
|
714
|
-
newYargs.option("definitions", { alias: 'u', description: "Update typescript definitions", boolean: true, type: 'boolean', demandOption: false, default: true });
|
|
715
|
-
return newYargs;
|
|
716
|
-
},
|
|
717
|
-
handler: async (args) => {
|
|
718
|
-
const { components: basePath } = parseArgs(args);
|
|
719
|
-
const client = createCmsClient(args);
|
|
720
|
-
const { styles, all: allStyles } = await getStyles(client, args, 100);
|
|
721
|
-
if (styles.findIndex(x => x.isDefault) >= 0)
|
|
722
|
-
process.stdout.write(chalk.redBright(chalk.bold((`\n${figures.warning} You are deleting a default Display Template this may lead to unpredicted behavior.\n\n`))));
|
|
723
|
-
if (!args.force) {
|
|
724
|
-
process.stdout.write(`This will remove the following display templates\n`);
|
|
725
|
-
for (const style of styles) {
|
|
726
|
-
process.stdout.write(` ${figures.arrowRight} ${style.displayName || style.key} [Key: ${style.key}; Default: ${style.isDefault ? 'Yes' : 'No'}]\n`);
|
|
727
|
-
}
|
|
728
|
-
process.stdout.write(`\nRun with -f to actually preform removal\n`);
|
|
729
|
-
return;
|
|
730
|
-
}
|
|
731
|
-
const keysToDelete = styles.map(displayTemplate => displayTemplate.key);
|
|
732
|
-
const styleHelpers = await toTypeFilesList(client, allStyles, basePath, false);
|
|
733
|
-
for (const displayTemplate of styles) {
|
|
734
|
-
const { styleFilePath, targetType, itemPath, typesPath } = await createTemplateMetadata(client, displayTemplate, basePath, false);
|
|
735
|
-
// Remove style file
|
|
736
|
-
if (args.withStyleFile) {
|
|
737
|
-
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Removing *.opti-style.json file for ${displayTemplate.displayName} [${displayTemplate.key}]\n`));
|
|
738
|
-
await fsAsync.rm(styleFilePath, { force: false, recursive: false, maxRetries: 1 }).catch((e) => {
|
|
739
|
-
if (e?.code === 'ENOENT') // The file can't be deleted as it does not exist - that's the desired state
|
|
740
|
-
return;
|
|
741
|
-
throw e;
|
|
742
|
-
});
|
|
743
|
-
await fsAsync.rmdir(itemPath, { recursive: false, maxRetries: 1 }).catch((e) => {
|
|
744
|
-
if (e?.code === 'ENOTEMPTY') {
|
|
745
|
-
process.stdout.write(chalk.yellowBright(`${figures.warning} The folder is not empty, please remove ${itemPath} manually if needed\n`));
|
|
746
|
-
return;
|
|
747
|
-
}
|
|
748
|
-
else if (e?.code === 'ENOENT')
|
|
749
|
-
return;
|
|
750
|
-
else
|
|
751
|
-
throw e;
|
|
752
|
-
});
|
|
753
|
-
}
|
|
754
|
-
// Remove/update typescript helper
|
|
755
|
-
if (args.definitions && styleHelpers[targetType]) {
|
|
756
|
-
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Removing/updating displayTemplates.ts file for ${displayTemplate.displayName} [${displayTemplate.key}]\n`));
|
|
757
|
-
const remainingTemplates = styleHelpers[targetType].templates.filter(x => !keysToDelete.includes(x.key));
|
|
758
|
-
if (remainingTemplates.length === 0) {
|
|
759
|
-
await fsAsync.rm(styleHelpers[targetType].filePath, { force: false, recursive: false, maxRetries: 1 }).catch((e) => {
|
|
760
|
-
if (e?.code === 'ENOENT') // The file can't be deleted as it does not exist - that's the desired state
|
|
761
|
-
return;
|
|
762
|
-
throw e;
|
|
763
|
-
});
|
|
764
|
-
await fsAsync.rmdir(typesPath, { recursive: false, maxRetries: 1 }).catch((e) => {
|
|
765
|
-
if (e?.code === 'ENOTEMPTY') {
|
|
766
|
-
process.stdout.write(chalk.yellowBright(`${figures.warning} The folder is not empty, please remove ${typesPath} manually if needed\n`));
|
|
767
|
-
return;
|
|
768
|
-
}
|
|
769
|
-
else if (e?.code === 'ENOENT')
|
|
770
|
-
return;
|
|
771
|
-
else
|
|
772
|
-
throw e;
|
|
773
|
-
});
|
|
774
|
-
}
|
|
775
|
-
else {
|
|
776
|
-
const newEntry = {
|
|
777
|
-
filePath: styleHelpers[targetType].filePath,
|
|
778
|
-
templates: remainingTemplates
|
|
779
|
-
};
|
|
780
|
-
if (!await createDisplayTemplateHelper(newEntry, targetType, false, false))
|
|
781
|
-
process.stdout.write(chalk.yellowBright(`${figures.warning} The display template was not updated, please update ${newEntry.filePath} manually`));
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
// Actually remove from CMS
|
|
785
|
-
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Removing the display template ${displayTemplate.displayName} [${displayTemplate.key}] from Optimizely CMS\n`));
|
|
786
|
-
await client.displayTemplates.displayTemplatesDelete(displayTemplate.key);
|
|
787
|
-
}
|
|
788
|
-
process.stdout.write("\n" + chalk.green(chalk.bold(figures.tick + " Done")) + "\n");
|
|
789
|
-
}
|
|
790
|
-
};
|
|
791
|
-
|
|
792
814
|
const TypesPullCommand = {
|
|
793
815
|
command: "types:pull",
|
|
794
816
|
describe: "Pull content type definition files into the project",
|
|
@@ -802,8 +824,7 @@ const TypesPullCommand = {
|
|
|
802
824
|
const client = createCmsClient(args);
|
|
803
825
|
const { contentTypes } = await getContentTypes(client, args);
|
|
804
826
|
const updatedTypes = contentTypes.map(contentType => {
|
|
805
|
-
const typePath =
|
|
806
|
-
const typeFile = path.join(typePath, `${contentType.key}.opti-type.json`);
|
|
827
|
+
const { typePath, typeFile } = getContentTypePaths(contentType, basePath);
|
|
807
828
|
if (!fs.existsSync(typePath))
|
|
808
829
|
fs.mkdirSync(typePath, { recursive: true });
|
|
809
830
|
if (fs.existsSync(typeFile) && !force) {
|
|
@@ -814,16 +835,28 @@ const TypesPullCommand = {
|
|
|
814
835
|
const outContentType = { ...contentType };
|
|
815
836
|
if (outContentType.source || outContentType.source == "")
|
|
816
837
|
delete outContentType.source;
|
|
817
|
-
if (outContentType.features)
|
|
818
|
-
|
|
819
|
-
if (outContentType.usage)
|
|
820
|
-
delete outContentType.usage;
|
|
838
|
+
//if (outContentType.features) delete outContentType.features
|
|
839
|
+
//if (outContentType.usage) delete outContentType.usage
|
|
821
840
|
if (outContentType.lastModifiedBy)
|
|
822
841
|
delete outContentType.lastModifiedBy;
|
|
823
842
|
if (outContentType.lastModified)
|
|
824
843
|
delete outContentType.lastModified;
|
|
825
844
|
if (outContentType.created)
|
|
826
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
|
+
}
|
|
827
860
|
if (debug)
|
|
828
861
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Writing type definition for ${contentType.displayName} (${contentType.key})\n`));
|
|
829
862
|
fs.writeFileSync(typeFile, JSON.stringify(outContentType, undefined, 2));
|
|
@@ -832,6 +865,197 @@ const TypesPullCommand = {
|
|
|
832
865
|
process.stdout.write(chalk.green(chalk.bold(`${figures.tick} Created/updated type definitions for ${updatedTypes.join(', ')}\n`)));
|
|
833
866
|
}
|
|
834
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
|
+
}
|
|
835
1059
|
|
|
836
1060
|
const TypesPushCommand = {
|
|
837
1061
|
command: "types:push",
|
|
@@ -862,25 +1086,52 @@ const TypesPushCommand = {
|
|
|
862
1086
|
(!baseTypes || baseTypes.length == 0 || baseTypes.includes(data.definition.baseType)) &&
|
|
863
1087
|
(!types || types.length == 0 || types.includes(data.definition.key));
|
|
864
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);
|
|
865
1093
|
// Output selected types
|
|
866
|
-
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
|
+
}
|
|
867
1099
|
process.stdout.write(chalk.yellowBright(` ${figures.arrowRight} Pushing ${type.definition.displayName} from ${type.file}\n`));
|
|
1100
|
+
// Filter unwanted fields from the ContentType definition
|
|
868
1101
|
const outType = { ...type.definition };
|
|
869
|
-
if (outType.source
|
|
1102
|
+
if (outType.source)
|
|
870
1103
|
delete outType.source;
|
|
871
|
-
if (outType.created
|
|
1104
|
+
if (outType.created)
|
|
872
1105
|
delete outType.created;
|
|
873
|
-
if (outType.lastModified
|
|
1106
|
+
if (outType.lastModified)
|
|
874
1107
|
delete outType.lastModified;
|
|
875
1108
|
if (outType.lastModifiedBy || outType.lastModifiedBy == "")
|
|
876
1109
|
delete outType.lastModifiedBy;
|
|
877
|
-
if (outType.features)
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
.
|
|
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
|
+
}
|
|
884
1135
|
}));
|
|
885
1136
|
const overview = new Table({
|
|
886
1137
|
head: [
|
|
@@ -912,237 +1163,10 @@ const builder = yargs => {
|
|
|
912
1163
|
return updatedArgs;
|
|
913
1164
|
};
|
|
914
1165
|
function createTypeFolders(contentTypes, basePath, debug = false) {
|
|
915
|
-
|
|
916
|
-
const baseType = contentType.baseType ?? 'default';
|
|
917
|
-
// Create the type folder
|
|
918
|
-
const typePath = path.join(basePath, baseType, contentType.key);
|
|
919
|
-
if (!fs.existsSync(typePath)) {
|
|
920
|
-
fs.mkdirSync(typePath, { recursive: true });
|
|
921
|
-
if (debug)
|
|
922
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Created folder ${typePath} for Content-Type ${contentType.key}\n`));
|
|
923
|
-
}
|
|
924
|
-
// Check folders
|
|
925
|
-
if (!fs.statSync(typePath).isDirectory())
|
|
926
|
-
throw new Error(`The folder ${typePath} for Content-Type ${contentType.key} exists, but is not a directory!`);
|
|
927
|
-
return {
|
|
928
|
-
type: contentType.key,
|
|
929
|
-
path: typePath,
|
|
930
|
-
};
|
|
931
|
-
});
|
|
932
|
-
return folders;
|
|
1166
|
+
return contentTypes.map(contentType => getContentTypePaths(contentType, basePath, true, debug));
|
|
933
1167
|
}
|
|
934
1168
|
function getTypeFolder(list, type) {
|
|
935
|
-
return list.filter(x => x.type == type).at(0)
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
/**
|
|
939
|
-
* Keep track of all generated properties
|
|
940
|
-
*/
|
|
941
|
-
let generatedProps = [];
|
|
942
|
-
const NextJsFragmentsCommand = {
|
|
943
|
-
command: "nextjs:fragments",
|
|
944
|
-
describe: "Create the GrapQL Fragments for a Next.JS / Optimizely Graph structure",
|
|
945
|
-
builder,
|
|
946
|
-
handler: async (args, opts) => {
|
|
947
|
-
generatedProps = [];
|
|
948
|
-
// Prepare
|
|
949
|
-
const { loadedContentTypes, createdTypeFolders } = opts || {};
|
|
950
|
-
const { components: basePath, _config: { debug }, force } = parseArgs(args);
|
|
951
|
-
const client = createCmsClient(args);
|
|
952
|
-
const { contentTypes, all: allContentTypes } = loadedContentTypes ?? await getContentTypes(client, args);
|
|
953
|
-
// Start process
|
|
954
|
-
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Generating GraphQL fragments for ${contentTypes.map(x => x.key).join(', ')}\n`));
|
|
955
|
-
const typeFolders = createdTypeFolders ?? createTypeFolders(contentTypes, basePath, debug);
|
|
956
|
-
const updatedTypes = contentTypes.map(contentType => {
|
|
957
|
-
const typePath = getTypeFolder(typeFolders, contentType.key);
|
|
958
|
-
return createGraphFragments(contentType, typePath, basePath, force, debug, allContentTypes, client.runtimeCmsVersion == OptiCmsVersion.CMS12);
|
|
959
|
-
}).filter(x => x).flat();
|
|
960
|
-
// Report outcome
|
|
961
|
-
if (updatedTypes.length > 0)
|
|
962
|
-
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Created/updated GraphQL fragments for ${updatedTypes.join(', ')}\n`));
|
|
963
|
-
else
|
|
964
|
-
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} No GraphQL fragments created/updated\n`));
|
|
965
|
-
if (!opts)
|
|
966
|
-
process.stdout.write(chalk.green(chalk.bold(figures.tick + " Done")) + "\n");
|
|
967
|
-
generatedProps = [];
|
|
968
|
-
}
|
|
969
|
-
};
|
|
970
|
-
function createGraphFragments(contentType, typePath, basePath, force, debug, contentTypes, forCms12 = false) {
|
|
971
|
-
const returnValue = [];
|
|
972
|
-
const baseType = contentType.baseType ?? 'default';
|
|
973
|
-
const baseQueryFile = path.join(typePath, `${contentType.key}.${baseType}.graphql`);
|
|
974
|
-
if (fs.existsSync(baseQueryFile)) {
|
|
975
|
-
if (force) {
|
|
976
|
-
if (debug)
|
|
977
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Overwriting ${contentType.displayName} (${contentType.key}) base fragment\n`));
|
|
978
|
-
}
|
|
979
|
-
else {
|
|
980
|
-
if (debug)
|
|
981
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.displayName} (${contentType.key}) base fragment - file already exists\n`));
|
|
982
|
-
return undefined;
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
else if (debug) {
|
|
986
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating ${contentType.displayName} (${contentType.key}) base fragment\n`));
|
|
987
|
-
}
|
|
988
|
-
const { fragment, propertyTypes } = createInitialFragment(contentType, false, undefined, forCms12);
|
|
989
|
-
fs.writeFileSync(baseQueryFile, fragment);
|
|
990
|
-
returnValue.push(contentType.key);
|
|
991
|
-
let dependencies = Array.isArray(propertyTypes) ? [...propertyTypes] : [];
|
|
992
|
-
while (Array.isArray(dependencies) && dependencies.length > 0) {
|
|
993
|
-
let newDependencies = [];
|
|
994
|
-
dependencies.forEach(dep => {
|
|
995
|
-
const propContentType = contentTypes.filter(x => x.key == dep[0])[0];
|
|
996
|
-
if (!propContentType) {
|
|
997
|
-
console.warn(`🟠 The content type ${dep[0]} has been referenced, but is not found in the Optimizely CMS instance`);
|
|
998
|
-
return;
|
|
999
|
-
}
|
|
1000
|
-
const fullTypeName = forCms12 ? contentType.key + propContentType.key : propContentType.key;
|
|
1001
|
-
const propertyFragmentFile = path.join(basePath, propContentType.baseType, propContentType.key, `${fullTypeName}.property.graphql`);
|
|
1002
|
-
const propertyFragmentDir = path.dirname(propertyFragmentFile);
|
|
1003
|
-
if (!fs.existsSync(propertyFragmentDir))
|
|
1004
|
-
fs.mkdirSync(propertyFragmentDir, { recursive: true });
|
|
1005
|
-
if (!fs.existsSync(propertyFragmentFile) || force) {
|
|
1006
|
-
if (debug)
|
|
1007
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Writing ${propContentType.displayName} (${propContentType.key}) property fragment\n`));
|
|
1008
|
-
const propContentTypeInfo = createInitialFragment(propContentType, true, contentType, forCms12);
|
|
1009
|
-
fs.writeFileSync(propertyFragmentFile, propContentTypeInfo.fragment);
|
|
1010
|
-
returnValue.push(propContentType.key);
|
|
1011
|
-
if (Array.isArray(propContentTypeInfo.propertyTypes))
|
|
1012
|
-
newDependencies.push(...propContentTypeInfo.propertyTypes);
|
|
1013
|
-
}
|
|
1014
|
-
});
|
|
1015
|
-
dependencies = newDependencies;
|
|
1016
|
-
}
|
|
1017
|
-
return returnValue.length > 0 ? returnValue : undefined;
|
|
1018
|
-
}
|
|
1019
|
-
function createInitialFragment(contentType, forProperty = false, forBaseType, forCms12 = false) {
|
|
1020
|
-
const { fragmentFields, propertyTypes } = renderProperties(contentType, forCms12);
|
|
1021
|
-
const fragmentTarget = forProperty ? (forCms12 ? (forBaseType?.key ?? '') + contentType.key : contentType.key + 'Property') : contentType.key;
|
|
1022
|
-
const tpl = `fragment ${fragmentTarget}Data on ${fragmentTarget} {
|
|
1023
|
-
${fragmentFields.join("\n ")}
|
|
1024
|
-
}`;
|
|
1025
|
-
return {
|
|
1026
|
-
fragment: tpl,
|
|
1027
|
-
propertyTypes: propertyTypes.length == 0 ? null : propertyTypes
|
|
1028
|
-
};
|
|
1029
|
-
}
|
|
1030
|
-
function renderProperties(contentType, forCms12 = false) {
|
|
1031
|
-
const propertyTypes = [];
|
|
1032
|
-
const fragmentFields = [];
|
|
1033
|
-
const typeProps = contentType.properties ?? {};
|
|
1034
|
-
Object.getOwnPropertyNames(typeProps).forEach(propKey => {
|
|
1035
|
-
// Exclude system properties, which are not present in Optimizely Graph
|
|
1036
|
-
if (['experience', 'section'].includes(contentType.baseType) && ['AdditionalData', 'UnstructuredData', 'Layout'].includes(propKey))
|
|
1037
|
-
return;
|
|
1038
|
-
// Exclude CMS 12 System Properties
|
|
1039
|
-
if (forCms12 && ['Categories'].includes(propKey))
|
|
1040
|
-
return;
|
|
1041
|
-
const propType = typeProps[propKey].type;
|
|
1042
|
-
const isConflict = generatedProps.some(x => x.propName == propKey && x.propType != propType);
|
|
1043
|
-
const propName = isConflict ? `${contentType.key}${propKey}: ${propKey}` : propKey;
|
|
1044
|
-
// Write the property
|
|
1045
|
-
switch (propType) {
|
|
1046
|
-
case IntegrationApi.PropertyDataType.ARRAY:
|
|
1047
|
-
{
|
|
1048
|
-
const typeData = typeProps[propKey];
|
|
1049
|
-
switch (typeData.items.type) {
|
|
1050
|
-
case IntegrationApi.PropertyDataType.INTEGER:
|
|
1051
|
-
if (typeData.format == 'categorization')
|
|
1052
|
-
fragmentFields.push(`${propName} { Id, Name, Description }`);
|
|
1053
|
-
else
|
|
1054
|
-
fragmentFields.push(propName);
|
|
1055
|
-
break;
|
|
1056
|
-
case IntegrationApi.PropertyDataType.STRING:
|
|
1057
|
-
fragmentFields.push(propName);
|
|
1058
|
-
break;
|
|
1059
|
-
case IntegrationApi.PropertyDataType.CONTENT:
|
|
1060
|
-
if (contentType.baseType == 'page' || contentType.baseType == 'experience')
|
|
1061
|
-
fragmentFields.push(`${propName} { ...${forCms12 ? 'PageIContentListItem' : 'BlockData'} }`);
|
|
1062
|
-
else
|
|
1063
|
-
fragmentFields.push(`${propName} { ...IContentListItem }`);
|
|
1064
|
-
break;
|
|
1065
|
-
case IntegrationApi.PropertyDataType.COMPONENT:
|
|
1066
|
-
const componentType = typeData.items.contentType;
|
|
1067
|
-
switch (componentType) {
|
|
1068
|
-
case 'link':
|
|
1069
|
-
fragmentFields.push(`${propName} { ...LinkItemData }`);
|
|
1070
|
-
break;
|
|
1071
|
-
default:
|
|
1072
|
-
{
|
|
1073
|
-
const componentFragmentName = forCms12 ? contentType.key + componentType : componentType + 'Property';
|
|
1074
|
-
fragmentFields.push(`${propName} { ...${componentFragmentName}Data }`);
|
|
1075
|
-
propertyTypes.push([componentType, true]);
|
|
1076
|
-
break;
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
break;
|
|
1080
|
-
case IntegrationApi.PropertyDataType.CONTENT_REFERENCE:
|
|
1081
|
-
fragmentFields.push(`${propName} { ...ReferenceData }`);
|
|
1082
|
-
break;
|
|
1083
|
-
default:
|
|
1084
|
-
fragmentFields.push(`${propName} { __typename }`);
|
|
1085
|
-
break;
|
|
1086
|
-
}
|
|
1087
|
-
break;
|
|
1088
|
-
}
|
|
1089
|
-
case IntegrationApi.PropertyDataType.STRING: {
|
|
1090
|
-
const propDetails = typeProps[propKey];
|
|
1091
|
-
switch (propDetails.format ?? "") {
|
|
1092
|
-
case 'html':
|
|
1093
|
-
fragmentFields.push(forCms12 ? `${propName} { Structure, Html }` : `${propName} { json, html }`);
|
|
1094
|
-
break;
|
|
1095
|
-
case 'shortString':
|
|
1096
|
-
case 'selectOne':
|
|
1097
|
-
case '':
|
|
1098
|
-
fragmentFields.push(propName);
|
|
1099
|
-
break;
|
|
1100
|
-
default:
|
|
1101
|
-
if (!isConflict)
|
|
1102
|
-
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`));
|
|
1103
|
-
break;
|
|
1104
|
-
}
|
|
1105
|
-
break;
|
|
1106
|
-
}
|
|
1107
|
-
case IntegrationApi.PropertyDataType.URL:
|
|
1108
|
-
fragmentFields.push(forCms12 ? propName : `${propName} { ...LinkData }`);
|
|
1109
|
-
break;
|
|
1110
|
-
case IntegrationApi.PropertyDataType.CONTENT_REFERENCE:
|
|
1111
|
-
fragmentFields.push(`${propName} { ...ReferenceData }`);
|
|
1112
|
-
break;
|
|
1113
|
-
case IntegrationApi.PropertyDataType.COMPONENT: {
|
|
1114
|
-
const componentType = typeProps[propKey].contentType;
|
|
1115
|
-
if (forCms12 && componentType == "link") {
|
|
1116
|
-
fragmentFields.push(`${propName} { ...LinkItemData }`);
|
|
1117
|
-
}
|
|
1118
|
-
else {
|
|
1119
|
-
const componentFragmentName = forCms12 ? contentType.key + componentType : componentType + 'Property';
|
|
1120
|
-
fragmentFields.push(`${propName} { ...${componentFragmentName}Data }`);
|
|
1121
|
-
propertyTypes.push([componentType, true]);
|
|
1122
|
-
}
|
|
1123
|
-
break;
|
|
1124
|
-
}
|
|
1125
|
-
case IntegrationApi.PropertyDataType.BINARY:
|
|
1126
|
-
fragmentFields.push(`${propName} { ...BinaryData }`);
|
|
1127
|
-
break;
|
|
1128
|
-
default:
|
|
1129
|
-
fragmentFields.push(propName);
|
|
1130
|
-
break;
|
|
1131
|
-
}
|
|
1132
|
-
generatedProps.push({
|
|
1133
|
-
propType: typeProps[propKey].type,
|
|
1134
|
-
propName: propKey
|
|
1135
|
-
});
|
|
1136
|
-
});
|
|
1137
|
-
if (contentType.baseType == "experience")
|
|
1138
|
-
fragmentFields.push('...ExperienceData');
|
|
1139
|
-
if (fragmentFields.length == 0) {
|
|
1140
|
-
if (forCms12)
|
|
1141
|
-
fragmentFields.push('empty: _metadata: ContentLink { key: GuidValue }');
|
|
1142
|
-
else
|
|
1143
|
-
fragmentFields.push('empty: _metadata { key }');
|
|
1144
|
-
}
|
|
1145
|
-
return { fragmentFields, propertyTypes };
|
|
1169
|
+
return list.filter(x => x.type == type).at(0);
|
|
1146
1170
|
}
|
|
1147
1171
|
|
|
1148
1172
|
function ucFirst(current) {
|
|
@@ -1178,8 +1202,8 @@ const NextJsComponentsCommand = {
|
|
|
1178
1202
|
process.stdout.write(chalk.green(chalk.bold(figures.tick + " Done")) + "\n");
|
|
1179
1203
|
}
|
|
1180
1204
|
};
|
|
1181
|
-
function createComponent(contentType,
|
|
1182
|
-
const componentFile
|
|
1205
|
+
function createComponent(contentType, typePathInfo, force, debug = false) {
|
|
1206
|
+
const { componentFile, path: typePath } = typePathInfo;
|
|
1183
1207
|
if (fs.existsSync(componentFile)) {
|
|
1184
1208
|
if (!force) {
|
|
1185
1209
|
if (debug)
|
|
@@ -1194,8 +1218,9 @@ function createComponent(contentType, typePath, force, debug = false) {
|
|
|
1194
1218
|
// Get type information & short-hands
|
|
1195
1219
|
const displayTemplate = getDisplayTemplateInfo$1(contentType, typePath);
|
|
1196
1220
|
const baseDisplayTemplate = getBaseTypeTemplateInfo(contentType, typePath);
|
|
1197
|
-
const
|
|
1198
|
-
const
|
|
1221
|
+
const normalizedBaseType = normalizeBaseType(contentType.baseType ?? 'part');
|
|
1222
|
+
const varName = `${contentType.key}${ucFirst(normalizedBaseType)}`;
|
|
1223
|
+
const tplFn = Templates[normalizedBaseType] ?? Templates['default'];
|
|
1199
1224
|
if (!tplFn) {
|
|
1200
1225
|
if (debug)
|
|
1201
1226
|
process.stdout.write(chalk.redBright(`${figures.cross} Skipping ${contentType.displayName} (${contentType.key}) component - no template for ${contentType.baseType} found\n`));
|
|
@@ -1219,6 +1244,11 @@ function getBaseTypeTemplateInfo(contentType, typePath) {
|
|
|
1219
1244
|
}
|
|
1220
1245
|
return undefined;
|
|
1221
1246
|
}
|
|
1247
|
+
function normalizeBaseType(baseType) {
|
|
1248
|
+
if (baseType.startsWith('_'))
|
|
1249
|
+
return baseType.substring(1);
|
|
1250
|
+
return baseType;
|
|
1251
|
+
}
|
|
1222
1252
|
function getDisplayTemplateInfo$1(contentType, typePath) {
|
|
1223
1253
|
const displayTemplatesFile = path.join(typePath, 'displayTemplates.ts');
|
|
1224
1254
|
if (fs.existsSync(displayTemplatesFile)) {
|
|
@@ -1239,6 +1269,7 @@ import { ${baseDisplayTemplate} } from "../styles/displayTemplates";` : ''}
|
|
|
1239
1269
|
|
|
1240
1270
|
/**
|
|
1241
1271
|
* ${contentType.displayName}
|
|
1272
|
+
* ---
|
|
1242
1273
|
* ${contentType.description}
|
|
1243
1274
|
*/
|
|
1244
1275
|
export const ${varName} : CmsComponent<${contentType.key}DataFragment${(displayTemplate || baseDisplayTemplate) ? ', ' + [displayTemplate, baseDisplayTemplate].filter(x => x).join(" | ") : ''}> = ({ data${(displayTemplate || baseDisplayTemplate) ? ', layoutProps' : ''}, editProps }) => {
|
|
@@ -1253,18 +1284,51 @@ export const ${varName} : CmsComponent<${contentType.key}DataFragment${(displayT
|
|
|
1253
1284
|
${varName}.displayName = "${contentType.displayName} (${ucFirst(contentType.baseType)}/${contentType.key})"
|
|
1254
1285
|
${varName}.getDataFragment = () => ['${contentType.key}Data', ${contentType.key}DataFragmentDoc]
|
|
1255
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>
|
|
1309
|
+
}
|
|
1310
|
+
${varName}.displayName = "${contentType.displayName} (${ucFirst(contentType.baseType)}/${contentType.key})"
|
|
1311
|
+
${varName}.getDataFragment = () => ['${contentType.key}Data', ${contentType.key}DataFragmentDoc]
|
|
1312
|
+
|
|
1256
1313
|
export default ${varName}`,
|
|
1257
1314
|
// Template for all page component types
|
|
1258
1315
|
page: (contentType, varName, displayTemplate) => `import { type OptimizelyNextPage as CmsComponent } from "@remkoj/optimizely-cms-nextjs";
|
|
1259
|
-
import { ${contentType.key}
|
|
1316
|
+
import { get${contentType.key}DataDocument, type get${contentType.key}DataQuery } from '@/gql/graphql'${displayTemplate ? `
|
|
1260
1317
|
import { ${displayTemplate} } from "./displayTemplates";` : ''}
|
|
1261
1318
|
import { getSdk } from "@/gql"
|
|
1262
1319
|
|
|
1263
1320
|
/**
|
|
1264
1321
|
* ${contentType.displayName}
|
|
1322
|
+
* ---
|
|
1265
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)
|
|
1266
1330
|
*/
|
|
1267
|
-
export const ${varName} : CmsComponent
|
|
1331
|
+
export const ${varName} : CmsComponent<get${contentType.key}DataQuery${displayTemplate ? ', ' + displayTemplate : ''}> = ({ data${displayTemplate ? ', layoutProps' : ''} }) => {
|
|
1268
1332
|
const componentName = '${contentType.displayName}'
|
|
1269
1333
|
const componentInfo = '${contentType.description?.replaceAll("'", "\\'") ?? ''}'
|
|
1270
1334
|
return <div className="mx-auto px-2 container">
|
|
@@ -1274,7 +1338,7 @@ export const ${varName} : CmsComponent<${contentType.key}DataFragment${displayTe
|
|
|
1274
1338
|
</div>
|
|
1275
1339
|
}
|
|
1276
1340
|
${varName}.displayName = "${contentType.displayName} (${ucFirst(contentType.baseType)}/${contentType.key})"
|
|
1277
|
-
${varName}.
|
|
1341
|
+
${varName}.getDataQuery = () => get${contentType.key}DataDocument
|
|
1278
1342
|
${varName}.getMetaData = async (contentLink, locale, client) => {
|
|
1279
1343
|
const sdk = getSdk(client);
|
|
1280
1344
|
// Add your metadata logic here
|
|
@@ -1284,24 +1348,36 @@ ${varName}.getMetaData = async (contentLink, locale, client) => {
|
|
|
1284
1348
|
export default ${varName}`,
|
|
1285
1349
|
// Template for all experience component types
|
|
1286
1350
|
experience: (contentType, varName, displayTemplate) => `import { type OptimizelyNextPage as CmsComponent } from "@remkoj/optimizely-cms-nextjs";
|
|
1287
|
-
import {
|
|
1288
|
-
import {
|
|
1351
|
+
import { ExperienceDataFragmentDoc, get${contentType.key}DataDocument, type get${contentType.key}DataQuery } from '@/gql/graphql'
|
|
1352
|
+
import { getFragmentData } from '@/gql/fragment-masking'
|
|
1289
1353
|
import { OptimizelyComposition, isNode, CmsEditable } from "@remkoj/optimizely-cms-react/rsc";${displayTemplate ? `
|
|
1290
1354
|
import { ${displayTemplate} } from "./displayTemplates";` : ''}
|
|
1291
|
-
import { getSdk } from "@/gql"
|
|
1355
|
+
import { getSdk } from "@/gql/client"
|
|
1292
1356
|
|
|
1293
1357
|
/**
|
|
1294
1358
|
* ${contentType.displayName}
|
|
1359
|
+
* ---
|
|
1295
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)
|
|
1296
1367
|
*/
|
|
1297
|
-
export const ${varName} : CmsComponent
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
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>
|
|
1302
1378
|
}
|
|
1303
1379
|
${varName}.displayName = "${contentType.displayName} (${ucFirst(contentType.baseType)}/${contentType.key})"
|
|
1304
|
-
${varName}.
|
|
1380
|
+
${varName}.getDataQuery = () => get${contentType.key}DataDocument
|
|
1305
1381
|
${varName}.getMetaData = async (contentLink, locale, client) => {
|
|
1306
1382
|
const sdk = getSdk(client);
|
|
1307
1383
|
// Add your metadata logic here
|
|
@@ -1319,7 +1395,7 @@ const NextJsVisualBuilderCommand = {
|
|
|
1319
1395
|
// Prepare
|
|
1320
1396
|
const { components: basePath, _config: { debug }, force } = parseArgs(args);
|
|
1321
1397
|
// Create the fall-back node and style defintions
|
|
1322
|
-
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 });
|
|
1323
1399
|
createGenericNode(basePath, force, debug);
|
|
1324
1400
|
// Get all styles
|
|
1325
1401
|
const { styles } = await getStyles(createCmsClient(args), { ...args, excludeNodeTypes: [], excludeTemplates: [], nodes: [], templates: [], templateTypes: ['node', 'base'] });
|
|
@@ -1330,7 +1406,8 @@ const NextJsVisualBuilderCommand = {
|
|
|
1330
1406
|
});
|
|
1331
1407
|
// Process base styles
|
|
1332
1408
|
styles.filter(x => typeof (x.baseType) == 'string' && x.baseType.length > 0).map(styleDefinition => {
|
|
1333
|
-
const
|
|
1409
|
+
const baseType = (styleDefinition.baseType ?? '').startsWith('_') ? (styleDefinition.baseType ?? '').substring(1) : styleDefinition.baseType;
|
|
1410
|
+
const templatePath = path.join(basePath, baseType, 'styles', styleDefinition.key);
|
|
1334
1411
|
createSpecificNode(styleDefinition, templatePath, force, debug);
|
|
1335
1412
|
});
|
|
1336
1413
|
}
|
|
@@ -1364,11 +1441,16 @@ function createSpecificNode(template, templatePath, force = false, debug = false
|
|
|
1364
1441
|
componentProps.push('{ ...editProps }');
|
|
1365
1442
|
componentArgs.push('editProps');
|
|
1366
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 }}`;
|
|
1367
1449
|
const component = `${imports.join("\n")}
|
|
1368
1450
|
|
|
1369
1451
|
export const ${template.key} : CmsLayoutComponent<${displayTemplateName ?? '{}'}> = ({ ${componentArgs.join(', ')} }) => {
|
|
1370
1452
|
const layout = extractSettings(layoutProps);
|
|
1371
|
-
return (<${componentType} ${componentProps.join(' ')}>{ children }</${componentType}>);
|
|
1453
|
+
return (<${componentType} ${componentProps.join(' ')}${style}>{ children }</${componentType}>);
|
|
1372
1454
|
}
|
|
1373
1455
|
|
|
1374
1456
|
export default ${template.key};`;
|
|
@@ -1388,13 +1470,19 @@ function createGenericNode(basePath, force, debug) {
|
|
|
1388
1470
|
else if (debug)
|
|
1389
1471
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating generic node component\n`));
|
|
1390
1472
|
const nodeContent = `import { CmsEditable, type CmsLayoutComponent } from '@remkoj/optimizely-cms-react/rsc'
|
|
1473
|
+
import { type CSSProperties } from 'react';
|
|
1391
1474
|
|
|
1392
1475
|
export const VisualBuilderNode : CmsLayoutComponent = ({ editProps, layoutProps, children }) =>
|
|
1393
1476
|
{
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
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>
|
|
1398
1486
|
}
|
|
1399
1487
|
|
|
1400
1488
|
export default VisualBuilderNode`;
|
|
@@ -1642,7 +1730,7 @@ const NextJsCreateCommand = {
|
|
|
1642
1730
|
await Promise.all([
|
|
1643
1731
|
TypesPullCommand.handler(args),
|
|
1644
1732
|
StylesPullCommand.handler({ ...args, excludeNodeTypes: [], excludeTemplates: [], nodes: [], templates: [], templateTypes: [], definitions: true }),
|
|
1645
|
-
|
|
1733
|
+
//createFragments.handler(args, { loadedContentTypes: getContentTypesResult, createdTypeFolders: folders })
|
|
1646
1734
|
]);
|
|
1647
1735
|
process.stdout.write(chalk.yellowBright(chalk.bold(figures.tick) + " Prepared types, styles and fragments") + "\n");
|
|
1648
1736
|
// Then create the components
|
|
@@ -1657,6 +1745,125 @@ const NextJsCreateCommand = {
|
|
|
1657
1745
|
}
|
|
1658
1746
|
};
|
|
1659
1747
|
|
|
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);
|
|
1782
|
+
}
|
|
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");
|
|
1792
|
+
}
|
|
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`));
|
|
1801
|
+
}
|
|
1802
|
+
else {
|
|
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;
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
else if (debug) {
|
|
1809
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating ${contentType.displayName} (${contentType.key}) base fragment\n`));
|
|
1810
|
+
}
|
|
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`));
|
|
1840
|
+
}
|
|
1841
|
+
else {
|
|
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;
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
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
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
return returnValue;
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1660
1867
|
const NextJsQueriesCommand = {
|
|
1661
1868
|
command: "nextjs:queries",
|
|
1662
1869
|
describe: "Create the GrapQL Queries to use two queries to load content",
|
|
@@ -1672,24 +1879,43 @@ const NextJsQueriesCommand = {
|
|
|
1672
1879
|
const client = createCmsClient(args);
|
|
1673
1880
|
const { contentTypes, all: allContentTypes } = loadedContentTypes ?? await getContentTypes(client, args);
|
|
1674
1881
|
// Start process
|
|
1675
|
-
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Generating GraphQL
|
|
1882
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Generating GraphQL queries\n`));
|
|
1883
|
+
const dependencies = [];
|
|
1676
1884
|
const typeFolders = createdTypeFolders ?? createTypeFolders(contentTypes, basePath, debug);
|
|
1677
1885
|
const updatedTypes = contentTypes.map(contentType => {
|
|
1678
1886
|
const typePath = getTypeFolder(typeFolders, contentType.key);
|
|
1679
|
-
|
|
1887
|
+
const { written, propertyTypes } = createGraphQueries(contentType, typePath, force, debug);
|
|
1888
|
+
dependencies.push(...propertyTypes);
|
|
1889
|
+
return written ? contentType.key : undefined;
|
|
1680
1890
|
}).filter(x => x).flat();
|
|
1681
1891
|
// Report outcome
|
|
1682
1892
|
if (updatedTypes.length > 0)
|
|
1683
|
-
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Created/updated GraphQL
|
|
1893
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Created/updated GraphQL queries for ${updatedTypes.join(', ')}\n`));
|
|
1684
1894
|
else
|
|
1685
|
-
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} No GraphQL
|
|
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`));
|
|
1686
1912
|
if (!opts)
|
|
1687
1913
|
process.stdout.write(chalk.green(chalk.bold(figures.tick + " Done")) + "\n");
|
|
1688
1914
|
}
|
|
1689
1915
|
};
|
|
1690
|
-
function createGraphQueries(contentType, typePath,
|
|
1691
|
-
const
|
|
1692
|
-
|
|
1916
|
+
function createGraphQueries(contentType, typePath, force, debug) {
|
|
1917
|
+
const baseQueryFile = typePath.queryFile;
|
|
1918
|
+
let mustWrite = true;
|
|
1693
1919
|
if (fs.existsSync(baseQueryFile)) {
|
|
1694
1920
|
if (force) {
|
|
1695
1921
|
if (debug)
|
|
@@ -1698,72 +1924,305 @@ function createGraphQueries(contentType, typePath, basePath, force, debug, conte
|
|
|
1698
1924
|
else {
|
|
1699
1925
|
if (debug)
|
|
1700
1926
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.displayName} (${contentType.key}) base fragment - file already exists\n`));
|
|
1701
|
-
|
|
1927
|
+
mustWrite = false;
|
|
1702
1928
|
}
|
|
1703
1929
|
}
|
|
1704
1930
|
else if (debug) {
|
|
1705
1931
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating ${contentType.displayName} (${contentType.key}) base fragment\n`));
|
|
1706
1932
|
}
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
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
|
+
};
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
const SchemaDownloadCommand = {
|
|
1944
|
+
command: "schema:download",
|
|
1945
|
+
describe: "Create JSON schema files for the specified types, which you can use to configure JSON validation in your IDE of choice.",
|
|
1946
|
+
builder: (yargs) => {
|
|
1947
|
+
const newYargs = yargs;
|
|
1948
|
+
newYargs.option('schemaDir', { alias: 'd', description: "The schema path relative to your project root directory", string: true, type: 'string', demandOption: false, default: './.schema' });
|
|
1949
|
+
newYargs.option('schemas', { alias: 's', description: "The schema's to download", array: true, type: 'string', demandOption: false, default: ['DisplayTemplate', 'ContentType'] });
|
|
1950
|
+
newYargs.option('force', { alias: 'f', description: "Overwrite existing files", boolean: true, type: 'boolean', demandOption: false, default: false });
|
|
1951
|
+
return newYargs;
|
|
1952
|
+
},
|
|
1953
|
+
async handler(args, opts) {
|
|
1954
|
+
const { _config: cmsConfig, path: projectPath, force, schemaDir, schemas } = parseArgs(args);
|
|
1955
|
+
const client = createCmsClient(cmsConfig);
|
|
1956
|
+
// Loading Schema
|
|
1957
|
+
const schemaDefs = await loadSchema(client, schemas);
|
|
1958
|
+
const fullSchemaDir = path.normalize(path.join(projectPath, schemaDir));
|
|
1959
|
+
const relativeSchemaDir = path.relative(projectPath, fullSchemaDir);
|
|
1960
|
+
process.stdout.write(`\n${figures.arrowRight} Validating schema folder ${relativeSchemaDir}\n`);
|
|
1961
|
+
const schemaDirInfo = await fsAsync.stat(fullSchemaDir).catch((e) => { if (e.code == 'ENOENT')
|
|
1962
|
+
return undefined;
|
|
1963
|
+
else
|
|
1964
|
+
throw e; });
|
|
1965
|
+
if (!schemaDirInfo) {
|
|
1966
|
+
await fsAsync.mkdir(fullSchemaDir, { recursive: true });
|
|
1967
|
+
}
|
|
1968
|
+
else if (!schemaDirInfo.isDirectory()) {
|
|
1969
|
+
process.stderr.write(chalk.redBright(chalk.bold(`${figures.cross} [Error] The schema directory exists, but is not a directory (${relativeSchemaDir})\n`)));
|
|
1970
|
+
process.exit(1);
|
|
1971
|
+
}
|
|
1972
|
+
process.stdout.write(chalk.greenBright(` ${figures.tick} `));
|
|
1973
|
+
process.stdout.write(`Schema directory exists\n`);
|
|
1974
|
+
process.stdout.write(`\n${figures.arrowRight} Writing schema files\n`);
|
|
1975
|
+
for (const schema of schemaDefs) {
|
|
1976
|
+
const schemaFile = path.normalize(path.join(fullSchemaDir, `${schema.title.toLowerCase()}.schema.json`));
|
|
1977
|
+
const relativePath = path.relative(projectPath, schemaFile);
|
|
1978
|
+
try {
|
|
1979
|
+
await fsAsync.writeFile(schemaFile, JSON.stringify(schema.schema, undefined, 2), {
|
|
1980
|
+
encoding: "utf-8",
|
|
1981
|
+
flag: force ? 'w' : 'wx'
|
|
1982
|
+
});
|
|
1983
|
+
process.stdout.write(chalk.greenBright(` ${figures.tick} `));
|
|
1984
|
+
process.stdout.write(`Written the ${schema.title} schema to ${relativePath}\n`);
|
|
1718
1985
|
}
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Writing ${propContentType.displayName} (${propContentType.key}) property fragment\n`));
|
|
1727
|
-
const propContentTypeInfo = createInitialFragment(propContentType, true, contentType, forCms12);
|
|
1728
|
-
fs.writeFileSync(propertyFragmentFile, propContentTypeInfo.fragment);
|
|
1729
|
-
returnValue.push(propContentType.key);
|
|
1730
|
-
if (Array.isArray(propContentTypeInfo.propertyTypes))
|
|
1731
|
-
newDependencies.push(...propContentTypeInfo.propertyTypes);
|
|
1986
|
+
catch (err) {
|
|
1987
|
+
if (!force && err.code === 'EEXIST') {
|
|
1988
|
+
process.stdout.write(chalk.yellowBright(` ${figures.info} `));
|
|
1989
|
+
process.stdout.write(`The schema ${schema.title} already exists in ${relativePath}, run with -f to overwrite\n`);
|
|
1990
|
+
}
|
|
1991
|
+
else
|
|
1992
|
+
process.stderr.write(chalk.redBright(chalk.bold(`${figures.cross} [Error] ${err.toString()}\n`)));
|
|
1732
1993
|
}
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
};
|
|
1997
|
+
|
|
1998
|
+
const SchemaListCommand = {
|
|
1999
|
+
command: "schema:list",
|
|
2000
|
+
describe: "List all schema's that are available within the SaaS CMS instance",
|
|
2001
|
+
builder: (yargs) => {
|
|
2002
|
+
const newYargs = yargs;
|
|
2003
|
+
return newYargs;
|
|
2004
|
+
},
|
|
2005
|
+
async handler(args, opts) {
|
|
2006
|
+
const client = createCmsClient(args);
|
|
2007
|
+
process.stdout.write(`\n${figures.arrowRight} Downloading OpenAPI Specification\n`);
|
|
2008
|
+
const spec = await client.getOpenApiSpec();
|
|
2009
|
+
process.stdout.write(chalk.greenBright(` ${figures.tick} `));
|
|
2010
|
+
process.stdout.write(`The ${spec.info.title} (Version: ${spec.info.version}) specification has been downloaded.\n`);
|
|
2011
|
+
const specSchemas = (spec?.components?.schemas ?? {});
|
|
2012
|
+
const schemas = new Table({
|
|
2013
|
+
head: [
|
|
2014
|
+
chalk.yellow(chalk.bold("Key")),
|
|
2015
|
+
chalk.yellow(chalk.bold("Description"))
|
|
2016
|
+
],
|
|
2017
|
+
colWidths: [35, 60],
|
|
2018
|
+
colAligns: ["left", "left"],
|
|
2019
|
+
wordWrap: true,
|
|
2020
|
+
wrapOnWordBoundary: true
|
|
1733
2021
|
});
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
2022
|
+
for (const key in specSchemas)
|
|
2023
|
+
if (key) {
|
|
2024
|
+
schemas.push([key, specSchemas[key].description?.replaceAll(/\s+/g, ' ') ?? `-`]);
|
|
2025
|
+
}
|
|
2026
|
+
process.stdout.write("\n" + schemas.toString() + "\n");
|
|
2027
|
+
process.stdout.write(chalk.green(chalk.bold(figures.tick + " Done")) + "\n");
|
|
2028
|
+
}
|
|
2029
|
+
};
|
|
2030
|
+
|
|
2031
|
+
const deepmerge = createDeepMerge();
|
|
2032
|
+
const SchemaValidateCommand = {
|
|
2033
|
+
command: "schema:validate",
|
|
2034
|
+
describe: "Validate the opti-type.json & opti-style.json files",
|
|
2035
|
+
builder: (yargs) => {
|
|
2036
|
+
const newYargs = yargs;
|
|
2037
|
+
//newYargs.option('schemas', { alias: 's', description: "The schema's to download", array: true, type: 'string', demandOption: false, default: ['DisplayTemplate', 'ContentType'] })
|
|
2038
|
+
//newYargs.option('force', { alias: 'f', description: "Overwrite existing files", boolean: true, type: 'boolean', demandOption: false, default: false })
|
|
2039
|
+
//newYargs.option("definitions", { alias: 'd', description: "Create/overwrite typescript definitions", boolean: true, type: 'boolean', demandOption: false, default: true })
|
|
2040
|
+
return newYargs;
|
|
2041
|
+
},
|
|
2042
|
+
handler: async (args) => {
|
|
2043
|
+
const projectPath = args.path;
|
|
2044
|
+
const client = createCmsClient(args);
|
|
2045
|
+
const schemas = await loadSchema(client, ['DisplayTemplate', 'ContentType']);
|
|
2046
|
+
process.stdout.write(``);
|
|
2047
|
+
const styleSchema = schemas.find(x => x.title == 'DisplayTemplate')?.schema;
|
|
2048
|
+
const typeSchema = schemas.find(x => x.title == 'ContentType')?.schema;
|
|
2049
|
+
const [styleValidator, typeValidator] = await Promise.all([getValidator(styleSchema), getValidator(typeSchema)]);
|
|
2050
|
+
process.stdout.write(`\n${figures.arrowRight} Validating display templates (*.opti-style.json) and content types (*.opti-type.json).\n`);
|
|
2051
|
+
const patterns = ["./**/*.opti-type.json", "./**/*.opti-style.json"];
|
|
2052
|
+
const globMatches = globIterate(patterns, { cwd: projectPath, absolute: true });
|
|
2053
|
+
let allOk = true;
|
|
2054
|
+
for await (const fileMatch of globMatches) {
|
|
2055
|
+
try {
|
|
2056
|
+
const content = JSON.parse(fs.readFileSync(fileMatch).toString());
|
|
2057
|
+
let errors = undefined;
|
|
2058
|
+
if (fileMatch.endsWith('.opti-type.json') && !typeValidator(content)) {
|
|
2059
|
+
errors = typeValidator.errors;
|
|
2060
|
+
}
|
|
2061
|
+
else if (fileMatch.endsWith('.opti-style.json') && !styleValidator(content)) {
|
|
2062
|
+
errors = styleValidator.errors;
|
|
2063
|
+
}
|
|
2064
|
+
if (errors) {
|
|
2065
|
+
allOk = false;
|
|
2066
|
+
process.stdout.write(chalk.redBright(chalk.bold(`\n${figures.cross} ${path.relative(projectPath, fileMatch)}\n`)));
|
|
2067
|
+
const groupedErrors = groupErrors(errors);
|
|
2068
|
+
for (const instancepath in groupedErrors) {
|
|
2069
|
+
for (const key in groupedErrors[instancepath]) {
|
|
2070
|
+
const errorList = groupedErrors[instancepath][key].filter((v, i, a) => a.findIndex(pv => deepEqual(v, pv)) === i);
|
|
2071
|
+
for (const error of errorList) {
|
|
2072
|
+
process.stdout.write(` Node ${instancepath} ${error.message}\n`);
|
|
2073
|
+
switch (error.keyword) {
|
|
2074
|
+
case 'enum':
|
|
2075
|
+
process.stdout.write(` Allowed values: ${error.params.allowedValues.map((x) => x.toString()).join(', ')}\n`);
|
|
2076
|
+
break;
|
|
2077
|
+
case 'oneOf':
|
|
2078
|
+
const matching = Array.isArray(error.params.passingSchemas) ? error.params.passingSchemas : [];
|
|
2079
|
+
if (matching.length === 0)
|
|
2080
|
+
process.stdout.write(` Currently none matching\n`);
|
|
2081
|
+
else
|
|
2082
|
+
process.stdout.write(` Currently matching ${matching.map((x) => x.toString()).join(", ")}\n`);
|
|
2083
|
+
break;
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
catch (e) {
|
|
2091
|
+
allOk = false;
|
|
2092
|
+
process.stdout.write(chalk.redBright(chalk.bold(`${figures.cross} ${path.relative(projectPath, fileMatch)}\n`)));
|
|
2093
|
+
process.stdout.write(` ${e.message}\n`);
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
process.stdout.write(`\n`);
|
|
2097
|
+
if (allOk)
|
|
2098
|
+
process.stdout.write(chalk.greenBright(`${figures.tick} All types & styles match the schema\n`));
|
|
2099
|
+
else {
|
|
2100
|
+
process.stdout.write(chalk.redBright(`${figures.cross} Some types & styles contain errors\n`));
|
|
2101
|
+
process.exit(1);
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
};
|
|
2105
|
+
function groupErrors(errors) {
|
|
2106
|
+
const errorrsByInstance = groupErrorsByPath(errors);
|
|
2107
|
+
const instancePaths = Object.getOwnPropertyNames(errorrsByInstance);
|
|
2108
|
+
return instancePaths.reduce((previous, current) => {
|
|
2109
|
+
previous[current] = groupErrorsByKeyword(errorrsByInstance[current]);
|
|
2110
|
+
return previous;
|
|
2111
|
+
}, {});
|
|
2112
|
+
}
|
|
2113
|
+
function groupErrorsByPath(errors) {
|
|
2114
|
+
return errors.reduce((previous, current) => {
|
|
2115
|
+
const instancePath = current.instancePath == "" ? "[ROOT]" : current.instancePath;
|
|
2116
|
+
const partial = {};
|
|
2117
|
+
partial[instancePath] = [current];
|
|
2118
|
+
return deepmerge(previous, partial);
|
|
2119
|
+
}, {});
|
|
2120
|
+
}
|
|
2121
|
+
function groupErrorsByKeyword(errors) {
|
|
2122
|
+
const toMerge = errors.reduce((previous, current) => {
|
|
2123
|
+
const keyword = current.keyword;
|
|
2124
|
+
const partial = {};
|
|
2125
|
+
partial[keyword] = [current];
|
|
2126
|
+
return deepmerge(previous, partial);
|
|
2127
|
+
}, {});
|
|
2128
|
+
// Enum errors must be merged
|
|
2129
|
+
if (toMerge.enum)
|
|
2130
|
+
toMerge.enum = [toMerge.enum.reduce((prev, current) => prev ? deepmerge(prev, current) : current, undefined)];
|
|
2131
|
+
return toMerge;
|
|
1765
2132
|
}
|
|
1766
2133
|
|
|
2134
|
+
const SchemaVsCodeCommand = {
|
|
2135
|
+
command: "schema:vscode",
|
|
2136
|
+
describe: "Configure schema validation for opti-type.json & opti-style.json files by VSCode in the current project folder",
|
|
2137
|
+
builder: (yargs) => {
|
|
2138
|
+
const newYargs = yargs;
|
|
2139
|
+
//newYargs.option('schemas', { alias: 's', description: "The schema's to download", array: true, type: 'string', demandOption: false, default: ['DisplayTemplate', 'ContentType'] })
|
|
2140
|
+
//newYargs.option('force', { alias: 'f', description: "Overwrite existing files", boolean: true, type: 'boolean', demandOption: false, default: false })
|
|
2141
|
+
//newYargs.option("definitions", { alias: 'd', description: "Create/overwrite typescript definitions", boolean: true, type: 'boolean', demandOption: false, default: true })
|
|
2142
|
+
return newYargs;
|
|
2143
|
+
},
|
|
2144
|
+
handler: async (args) => {
|
|
2145
|
+
const projectPath = args.path;
|
|
2146
|
+
const client = createCmsClient(args);
|
|
2147
|
+
const schemas = await loadSchema(client, ['DisplayTemplate', 'ContentType']);
|
|
2148
|
+
process.stdout.write(`\n${figures.arrowRight} Writing schema files\n`);
|
|
2149
|
+
for (const schema of schemas) {
|
|
2150
|
+
const schemaFile = path.join(projectPath, '.vscode', `${schema.title.toLowerCase()}.schema.json`);
|
|
2151
|
+
void await writeFileAsync(schemaFile, JSON.stringify(schema.schema, undefined, 2));
|
|
2152
|
+
process.stdout.write(` ${figures.tick} Written the ${schema.title} schema to ${path.relative(projectPath, schemaFile)}\n`);
|
|
2153
|
+
}
|
|
2154
|
+
process.stdout.write(`\n${figures.arrowRight} Updating folder settings\n`);
|
|
2155
|
+
const settingsFile = path.join(projectPath, '.vscode', 'settings.json');
|
|
2156
|
+
const settings = (await readJsonFileAsync(settingsFile).catch(() => undefined)) ?? {};
|
|
2157
|
+
settings['json.validate.enable'] = true;
|
|
2158
|
+
settings['json.schemaDownload.enable'] = true;
|
|
2159
|
+
settings['json.schemas'] = settings['json.schemas'] || [];
|
|
2160
|
+
const hasContentType = settings['json.schemas'].findIndex(v => v.fileMatch.findIndex(fm => fm.endsWith('.opti-type.json')) >= 0) >= 0;
|
|
2161
|
+
const hasStyleDefinition = settings['json.schemas'].findIndex(v => v.fileMatch.findIndex(fm => fm.endsWith('.opti-style.json')) >= 0) >= 0;
|
|
2162
|
+
const hasSchemaType = settings['json.schemas'].findIndex(v => v.fileMatch.findIndex(fm => fm.endsWith('.schema.json')) >= 0) >= 0;
|
|
2163
|
+
if (!hasContentType)
|
|
2164
|
+
settings['json.schemas'].push({
|
|
2165
|
+
"fileMatch": [
|
|
2166
|
+
"**/*.opti-type.json"
|
|
2167
|
+
],
|
|
2168
|
+
"url": "./.vscode/contenttype.schema.json"
|
|
2169
|
+
});
|
|
2170
|
+
if (!hasStyleDefinition)
|
|
2171
|
+
settings['json.schemas'].push({
|
|
2172
|
+
"fileMatch": [
|
|
2173
|
+
"**/*.opti-style.json"
|
|
2174
|
+
],
|
|
2175
|
+
"url": "./.vscode/displaytemplate.schema.json"
|
|
2176
|
+
});
|
|
2177
|
+
if (!hasSchemaType)
|
|
2178
|
+
settings['json.schemas'].push({
|
|
2179
|
+
"fileMatch": [
|
|
2180
|
+
"**/*.schema.json"
|
|
2181
|
+
],
|
|
2182
|
+
"url": "https://json-schema.org/draft-07/schema"
|
|
2183
|
+
});
|
|
2184
|
+
if (settings['json.schemas'].length == 0)
|
|
2185
|
+
delete settings['json.schemas'];
|
|
2186
|
+
await writeFileAsync(settingsFile, JSON.stringify(settings, undefined, 2));
|
|
2187
|
+
}
|
|
2188
|
+
};
|
|
2189
|
+
function readJsonFileAsync(path) {
|
|
2190
|
+
return new Promise((resolve, reject) => fs.readFile(path, (err, data) => {
|
|
2191
|
+
if (err) {
|
|
2192
|
+
if (err?.code === 'ENOENT')
|
|
2193
|
+
resolve(undefined);
|
|
2194
|
+
else
|
|
2195
|
+
reject(err);
|
|
2196
|
+
}
|
|
2197
|
+
else {
|
|
2198
|
+
try {
|
|
2199
|
+
const d = data.toString();
|
|
2200
|
+
resolve(JSON.parse(d));
|
|
2201
|
+
}
|
|
2202
|
+
catch (e) {
|
|
2203
|
+
reject(e);
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
}));
|
|
2207
|
+
}
|
|
2208
|
+
function writeFileAsync(path, data) {
|
|
2209
|
+
return new Promise((resolve, reject) => {
|
|
2210
|
+
fs.writeFile(path, data, (err) => {
|
|
2211
|
+
if (err)
|
|
2212
|
+
reject(err);
|
|
2213
|
+
else
|
|
2214
|
+
resolve();
|
|
2215
|
+
});
|
|
2216
|
+
});
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
var version = "6.0.0-pre10";
|
|
2220
|
+
var name = "opti-cms";
|
|
2221
|
+
var APP = {
|
|
2222
|
+
version: version,
|
|
2223
|
+
name: name
|
|
2224
|
+
};
|
|
2225
|
+
|
|
1767
2226
|
const CmsVersionCommand = {
|
|
1768
2227
|
command: "cms:version",
|
|
1769
2228
|
aliases: "$0",
|
|
@@ -1782,18 +2241,25 @@ const CmsVersionCommand = {
|
|
|
1782
2241
|
throw error;
|
|
1783
2242
|
}
|
|
1784
2243
|
});
|
|
2244
|
+
const hasPreview2Info = versionInfo.results.preview2Data?.baseUrl ? true : false;
|
|
1785
2245
|
const info = new Table({
|
|
1786
2246
|
head: [
|
|
1787
2247
|
chalk.yellow(chalk.bold("Component")),
|
|
1788
2248
|
chalk.yellow(chalk.bold("Version"))
|
|
1789
2249
|
],
|
|
1790
|
-
colWidths: [
|
|
2250
|
+
colWidths: [30, 60],
|
|
1791
2251
|
colAligns: ["left", "left"]
|
|
1792
2252
|
});
|
|
1793
|
-
info.push(["
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
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']);
|
|
2256
|
+
info.push(["Client API", client.apiVersion]);
|
|
2257
|
+
info.push(["Service API", versionInfo.apiVersion]);
|
|
2258
|
+
if (hasPreview2Info)
|
|
2259
|
+
info.push(["Service API (Preview 2)", versionInfo.results.preview2Data?.apiVersion ?? 'n/a']);
|
|
2260
|
+
info.push(["CMS Build", versionInfo.cmsVersion]);
|
|
2261
|
+
info.push(["Service Build", versionInfo.serviceVersion]);
|
|
2262
|
+
info.push(["SDK", APP.version]);
|
|
1797
2263
|
process.stdout.write(info.toString() + "\n");
|
|
1798
2264
|
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Optimizely CMS Status: ${versionInfo.status}\n`));
|
|
1799
2265
|
process.stdout.write(chalk.green(chalk.bold(figures.tick + " Done")) + "\n");
|
|
@@ -1913,7 +2379,7 @@ async function deleteContentItem(client, key, onlyChildren = false) {
|
|
|
1913
2379
|
process.stderr.write(chalk.redBright(` ${figures.cross}The ContentItem ${key} is a reserved item, skipping deletion\n`));
|
|
1914
2380
|
return removedCount;
|
|
1915
2381
|
}
|
|
1916
|
-
const deleteResult = await client.
|
|
2382
|
+
const deleteResult = await client.preview2ContentDelete({ path: { key }, query: { permanent: true } }).then(r => r.key).catch((e) => {
|
|
1917
2383
|
if (e.status == 404)
|
|
1918
2384
|
return key;
|
|
1919
2385
|
throw e;
|
|
@@ -1934,10 +2400,13 @@ async function resetSystemTypes(client) {
|
|
|
1934
2400
|
for (const systemType of reservedTypes) {
|
|
1935
2401
|
if (client.debug)
|
|
1936
2402
|
process.stdout.write(chalk.gray(` ${figures.arrowRight} Resetting ${systemType} by removing all attributes\n`));
|
|
1937
|
-
const newType = await client.
|
|
1938
|
-
key: systemType,
|
|
1939
|
-
|
|
1940
|
-
|
|
2403
|
+
const newType = await client.contentTypesPatch({
|
|
2404
|
+
path: { key: systemType },
|
|
2405
|
+
body: {
|
|
2406
|
+
key: systemType,
|
|
2407
|
+
properties: {}
|
|
2408
|
+
}
|
|
2409
|
+
}).catch((e) => e.status == 404 ? undefined : null);
|
|
1941
2410
|
if (newType)
|
|
1942
2411
|
resetTypes++;
|
|
1943
2412
|
}
|
|
@@ -1949,7 +2418,7 @@ async function removeContentTypes(client) {
|
|
|
1949
2418
|
for (let contentType of contentTypes.filter(item => item.source == '' && !reservedTypes.includes(item.key))) {
|
|
1950
2419
|
if (client.debug)
|
|
1951
2420
|
process.stdout.write(` ${chalk.blueBright(figures.arrowRight)} Removing content type ${contentType.displayName} (${contentType.key})\n`);
|
|
1952
|
-
const result = await client.
|
|
2421
|
+
const result = await client.contentTypesDelete({ path: { key: contentType.key } }).catch((e) => {
|
|
1953
2422
|
if (e.status == 404)
|
|
1954
2423
|
return contentType;
|
|
1955
2424
|
return null;
|
|
@@ -1965,7 +2434,7 @@ async function removeDisplayTemplates(client) {
|
|
|
1965
2434
|
for (const template of allTemplates) {
|
|
1966
2435
|
if (client.debug)
|
|
1967
2436
|
process.stdout.write(` ${chalk.blueBright(figures.arrowRight)} Removing display template ${template.displayName} (${template.key})\n`);
|
|
1968
|
-
const deleteResult = client.
|
|
2437
|
+
const deleteResult = client.displayTemplatesDelete({ path: { key: template.key } }).catch((e) => {
|
|
1969
2438
|
if (e.status == 404)
|
|
1970
2439
|
return template;
|
|
1971
2440
|
console.log(e.body);
|
|
@@ -1977,7 +2446,7 @@ async function removeDisplayTemplates(client) {
|
|
|
1977
2446
|
return removedTemplateCount;
|
|
1978
2447
|
}
|
|
1979
2448
|
async function getAllTemplates(client, batchSize = 100) {
|
|
1980
|
-
const items = await client.
|
|
2449
|
+
const items = await client.displayTemplatesList({ query: { pageIndex: 0, pageSize: batchSize } }).catch((e) => {
|
|
1981
2450
|
if (e.status == 404) {
|
|
1982
2451
|
return {
|
|
1983
2452
|
items: [],
|
|
@@ -1992,7 +2461,7 @@ async function getAllTemplates(client, batchSize = 100) {
|
|
|
1992
2461
|
const actualItems = items.items ?? [];
|
|
1993
2462
|
const pageCount = Math.ceil(totalItemCount / pageSize);
|
|
1994
2463
|
for (let pageNr = 1; pageNr < pageCount; pageNr++) {
|
|
1995
|
-
const pageData = await client.
|
|
2464
|
+
const pageData = await client.displayTemplatesList({ query: { pageIndex: pageNr, pageSize } }).catch((e) => {
|
|
1996
2465
|
if (e.status == 404) {
|
|
1997
2466
|
return {
|
|
1998
2467
|
items: [],
|
|
@@ -2007,7 +2476,7 @@ async function getAllTemplates(client, batchSize = 100) {
|
|
|
2007
2476
|
return actualItems;
|
|
2008
2477
|
}
|
|
2009
2478
|
async function getAllTypes(client, batchSize = 100) {
|
|
2010
|
-
const items = await client.
|
|
2479
|
+
const items = await client.contentTypesList({ query: { pageIndex: 0, pageSize: batchSize } }).catch((e) => {
|
|
2011
2480
|
if (e.status == 404) {
|
|
2012
2481
|
return {
|
|
2013
2482
|
items: [],
|
|
@@ -2022,7 +2491,7 @@ async function getAllTypes(client, batchSize = 100) {
|
|
|
2022
2491
|
const actualItems = items.items ?? [];
|
|
2023
2492
|
const pageCount = Math.ceil(totalItemCount / pageSize);
|
|
2024
2493
|
for (let pageNr = 1; pageNr < pageCount; pageNr++) {
|
|
2025
|
-
const pageData = await client.
|
|
2494
|
+
const pageData = await client.contentTypesList({ query: { pageIndex: pageNr, pageSize } }).catch((e) => {
|
|
2026
2495
|
if (e.status == 404) {
|
|
2027
2496
|
return {
|
|
2028
2497
|
items: [],
|
|
@@ -2037,7 +2506,7 @@ async function getAllTypes(client, batchSize = 100) {
|
|
|
2037
2506
|
return actualItems;
|
|
2038
2507
|
}
|
|
2039
2508
|
async function getAllAssets(client, parentKey, batchSize = 100) {
|
|
2040
|
-
const items = await client.
|
|
2509
|
+
const items = await client.preview2ContentListAssets({ path: { key: parentKey }, query: { pageIndex: 0, pageSize: batchSize } }).catch((e) => {
|
|
2041
2510
|
if (e.status == 404) {
|
|
2042
2511
|
return {
|
|
2043
2512
|
items: [],
|
|
@@ -2052,7 +2521,7 @@ async function getAllAssets(client, parentKey, batchSize = 100) {
|
|
|
2052
2521
|
const actualItems = items.items ?? [];
|
|
2053
2522
|
const pageCount = Math.ceil(totalItemCount / pageSize);
|
|
2054
2523
|
for (let pageNr = 1; pageNr < pageCount; pageNr++) {
|
|
2055
|
-
const pageData = await client.
|
|
2524
|
+
const pageData = await client.preview2ContentListAssets({ path: { key: parentKey }, query: { pageIndex: pageNr, pageSize } }).catch((e) => {
|
|
2056
2525
|
if (e.status == 404) {
|
|
2057
2526
|
return {
|
|
2058
2527
|
items: [],
|
|
@@ -2067,7 +2536,7 @@ async function getAllAssets(client, parentKey, batchSize = 100) {
|
|
|
2067
2536
|
return actualItems;
|
|
2068
2537
|
}
|
|
2069
2538
|
async function getAllItems(client, parentKey, batchSize = 100) {
|
|
2070
|
-
const items = await client.
|
|
2539
|
+
const items = await client.preview2ContentListItems({ path: { key: parentKey }, query: { pageIndex: 0, pageSize: batchSize } }).catch((e) => {
|
|
2071
2540
|
if (e.status == 404) {
|
|
2072
2541
|
return {
|
|
2073
2542
|
items: [],
|
|
@@ -2082,7 +2551,7 @@ async function getAllItems(client, parentKey, batchSize = 100) {
|
|
|
2082
2551
|
const actualItems = items.items ?? [];
|
|
2083
2552
|
const pageCount = Math.ceil(totalItemCount / pageSize);
|
|
2084
2553
|
for (let pageNr = 1; pageNr < pageCount; pageNr++) {
|
|
2085
|
-
const pageData = await client.
|
|
2554
|
+
const pageData = await client.preview2ContentListItems({ path: { key: parentKey }, query: { pageIndex: pageNr, pageSize } }).catch((e) => {
|
|
2086
2555
|
if (e.status == 404) {
|
|
2087
2556
|
return {
|
|
2088
2557
|
items: [],
|
|
@@ -2099,30 +2568,26 @@ async function getAllItems(client, parentKey, batchSize = 100) {
|
|
|
2099
2568
|
|
|
2100
2569
|
// Style processing
|
|
2101
2570
|
const commands = [
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
StylesListCommand,
|
|
2105
|
-
StylesPullCommand,
|
|
2106
|
-
StylesPushCommand,
|
|
2107
|
-
TypesPullCommand,
|
|
2108
|
-
TypesPushCommand,
|
|
2571
|
+
CmsResetCommand,
|
|
2572
|
+
CmsVersionCommand,
|
|
2109
2573
|
NextJsComponentsCommand,
|
|
2110
2574
|
NextJsCreateCommand,
|
|
2111
2575
|
NextJsFactoryCommand,
|
|
2112
2576
|
NextJsFragmentsCommand,
|
|
2113
2577
|
NextJsQueriesCommand,
|
|
2114
2578
|
NextJsVisualBuilderCommand,
|
|
2115
|
-
|
|
2116
|
-
|
|
2579
|
+
SchemaDownloadCommand,
|
|
2580
|
+
SchemaListCommand,
|
|
2581
|
+
SchemaValidateCommand,
|
|
2582
|
+
SchemaVsCodeCommand,
|
|
2583
|
+
StylesCreateCommand,
|
|
2584
|
+
StylesListCommand,
|
|
2585
|
+
StylesPullCommand,
|
|
2586
|
+
StylesPushCommand,
|
|
2587
|
+
TypesPullCommand,
|
|
2588
|
+
TypesPushCommand
|
|
2117
2589
|
];
|
|
2118
2590
|
|
|
2119
|
-
var version = "5.1.5";
|
|
2120
|
-
var name = "opti-cms";
|
|
2121
|
-
var APP = {
|
|
2122
|
-
version: version,
|
|
2123
|
-
name: name
|
|
2124
|
-
};
|
|
2125
|
-
|
|
2126
2591
|
async function main() {
|
|
2127
2592
|
const envFiles = prepare();
|
|
2128
2593
|
const app = createOptiCmsApp(APP.name, APP.version, undefined, envFiles);
|
|
@@ -2132,7 +2597,7 @@ async function main() {
|
|
|
2132
2597
|
}
|
|
2133
2598
|
catch {
|
|
2134
2599
|
//We're ignoring error here, as yargs will already generate the "nice output" for it
|
|
2135
|
-
//console.log
|
|
2600
|
+
//console.log('Caught error')
|
|
2136
2601
|
}
|
|
2137
2602
|
}
|
|
2138
2603
|
main();
|