@remkoj/optimizely-cms-cli 6.0.0-pre3 → 6.0.0-pre5
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 +6 -12
- package/dist/index.js +586 -138
- package/dist/index.js.map +1 -1
- package/package.json +7 -3
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { globSync, glob } from 'glob';
|
|
1
|
+
import { globSync, glob, globIterate } from 'glob';
|
|
2
2
|
import { config } from 'dotenv';
|
|
3
3
|
import { expand } from 'dotenv-expand';
|
|
4
|
-
import { getCmsIntegrationApiConfigFromEnvironment, createClient, OptiCmsVersion, IntegrationApi, ContentRoots } from '@remkoj/optimizely-cms-api';
|
|
4
|
+
import { readPartialEnvConfig, getCmsIntegrationApiConfigFromEnvironment, createClient, OptiCmsVersion, IntegrationApi, 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';
|
|
@@ -9,6 +9,10 @@ import fs from 'node:fs';
|
|
|
9
9
|
import figures from 'figures';
|
|
10
10
|
import Table from 'cli-table3';
|
|
11
11
|
import { input, select, confirm } from '@inquirer/prompts';
|
|
12
|
+
import createDeepMerge from '@fastify/deepmerge';
|
|
13
|
+
import { Ajv } from 'ajv';
|
|
14
|
+
import addFormats from 'ajv-formats';
|
|
15
|
+
import deepEqual from 'fast-deep-equal';
|
|
12
16
|
|
|
13
17
|
/**
|
|
14
18
|
* Prepare the application context, by parsing the .env files in the main
|
|
@@ -17,19 +21,25 @@ import { input, select, confirm } from '@inquirer/prompts';
|
|
|
17
21
|
* @returns A string array with the files processed
|
|
18
22
|
*/
|
|
19
23
|
function prepare() {
|
|
20
|
-
const
|
|
21
|
-
|
|
24
|
+
const globResult = globSync(".env*");
|
|
25
|
+
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`));
|
|
26
|
+
expand(config({ path: envFiles, debug: false, quiet: true }));
|
|
22
27
|
return envFiles;
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
function createOptiCmsApp(scriptName, version, epilogue, envFiles) {
|
|
31
|
+
if (envFiles) {
|
|
32
|
+
process.stdout.write(chalk.bold(`✅ Loaded environment files:`) + "\n");
|
|
33
|
+
envFiles.forEach(envFile => {
|
|
34
|
+
process.stdout.write(` 👉 ${envFile}\n`);
|
|
35
|
+
});
|
|
36
|
+
process.stdout.write("\n");
|
|
37
|
+
}
|
|
26
38
|
let config;
|
|
27
39
|
try {
|
|
28
|
-
config =
|
|
40
|
+
config = readPartialEnvConfig();
|
|
29
41
|
}
|
|
30
42
|
catch (e) {
|
|
31
|
-
if (envFiles)
|
|
32
|
-
console.error(chalk.gray(`Included environment files: ${envFiles.join(', ')}`));
|
|
33
43
|
console.error(chalk.redBright(`${chalk.bold("[ERROR]:")} Error processing environment variables: ${e.message}`));
|
|
34
44
|
process.exit(1);
|
|
35
45
|
}
|
|
@@ -51,12 +61,10 @@ function createOptiCmsApp(scriptName, version, epilogue, envFiles) {
|
|
|
51
61
|
.epilogue(`Copyright Remko Jantzen - 2023-${(new Date(Date.now())).getFullYear()}`)
|
|
52
62
|
.help()
|
|
53
63
|
.fail((msg, error, args) => {
|
|
54
|
-
if (envFiles)
|
|
55
|
-
console.error(chalk.gray(`Included environment files: ${envFiles.join(', ')}\n`));
|
|
56
64
|
if (msg)
|
|
57
|
-
console.error(msg + "\n");
|
|
65
|
+
console.error(chalk.redBright(msg) + "\n");
|
|
58
66
|
if (error)
|
|
59
|
-
console.error(`[${chalk.bold(error.name ?? 'Error')}]: ${error.message ?? ''}\n
|
|
67
|
+
console.error(chalk.redBright(`[${chalk.bold(error.name ?? 'Error')}]: ${error.message ?? ''}`) + '\n');
|
|
60
68
|
args.showHelp("error");
|
|
61
69
|
});
|
|
62
70
|
}
|
|
@@ -202,67 +210,98 @@ function getEnumOptions(enumObject) {
|
|
|
202
210
|
return isNaN(Number(item));
|
|
203
211
|
});
|
|
204
212
|
}
|
|
205
|
-
async function getContentTypes(client, args, pageSize =
|
|
213
|
+
async function getContentTypes(client, args, pageSize = 5, allowSystem = false, customFilter) {
|
|
206
214
|
const { _config: cfg, excludeBaseTypes, excludeTypes, baseTypes, types, all } = parseArgs(args);
|
|
207
|
-
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Pulling Content Types from Optimizely CMS\n`));
|
|
208
|
-
if (cfg.debug)
|
|
209
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetching page 1 of ? (${pageSize} items per page)\n`));
|
|
210
|
-
let resultsPage = await client.contentTypesList({ query: { pageIndex: 0, pageSize } });
|
|
211
|
-
const results = resultsPage.items ?? [];
|
|
212
|
-
let pagesRemaining = Math.ceil(resultsPage.totalItemCount / resultsPage.pageSize) - (resultsPage.pageIndex + 1);
|
|
213
|
-
while (pagesRemaining > 0 && results.length < resultsPage.totalItemCount) {
|
|
214
|
-
if (cfg.debug)
|
|
215
|
-
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`));
|
|
216
|
-
resultsPage = await client.contentTypesList({ query: { pageIndex: resultsPage.pageIndex + 1, pageSize: resultsPage.pageSize } });
|
|
217
|
-
results.push(...resultsPage.items);
|
|
218
|
-
pagesRemaining = Math.ceil(resultsPage.totalItemCount / resultsPage.pageSize) - (resultsPage.pageIndex + 1);
|
|
219
|
-
}
|
|
220
|
-
if (cfg.debug) {
|
|
221
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetched ${results.length} Content-Types from Optimizely CMS\n`));
|
|
222
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Filtering Content-Types based upon arguments\n`));
|
|
223
|
-
}
|
|
224
215
|
const validBaseTypes = getEnumOptions(IntegrationApi.ContentBaseType).map(x => x.toLowerCase());
|
|
225
|
-
const allContentTypes =
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const keepType = (!excludeBaseTypes.includes(data.baseType)) &&
|
|
244
|
-
(!excludeTypes.includes(data.key)) &&
|
|
245
|
-
(baseTypes.length == 0 || baseTypes.includes(data.baseType)) &&
|
|
246
|
-
(types.length == 0 || types.includes(data.key));
|
|
247
|
-
if (!keepType) {
|
|
216
|
+
const allContentTypes = [];
|
|
217
|
+
const filteredContentTypes = [];
|
|
218
|
+
for await (const contentType of getAllContentTypes(client, cfg.debug, pageSize)) {
|
|
219
|
+
const normalizedContentType = { ...contentType, baseType: contentType.baseType ?? 'default' };
|
|
220
|
+
// Build the "All" data set
|
|
221
|
+
if (all)
|
|
222
|
+
allContentTypes.push(normalizedContentType);
|
|
223
|
+
else if (validBaseTypes.includes(normalizedContentType.baseType.toLowerCase()))
|
|
224
|
+
allContentTypes.push(normalizedContentType);
|
|
225
|
+
else if (cfg.debug) {
|
|
226
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${normalizedContentType.key} as it has an unsupported base type: ${normalizedContentType.baseType}\n`));
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
// Skip based upon base type filters
|
|
233
|
+
if (!shouldInclude(normalizedContentType.baseType, baseTypes, excludeBaseTypes)) {
|
|
248
234
|
if (cfg.debug)
|
|
249
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping
|
|
250
|
-
|
|
235
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${normalizedContentType.key} as it has a restricted base type: ${normalizedContentType.baseType}\n`));
|
|
236
|
+
continue;
|
|
251
237
|
}
|
|
252
|
-
// Skip
|
|
253
|
-
if (
|
|
238
|
+
// Skip based upon type filters
|
|
239
|
+
if (!shouldInclude(normalizedContentType.key, types, excludeTypes)) {
|
|
254
240
|
if (cfg.debug)
|
|
255
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping
|
|
256
|
-
|
|
241
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${normalizedContentType.key} as it has a restricted type: ${normalizedContentType.key}\n`));
|
|
242
|
+
continue;
|
|
257
243
|
}
|
|
258
|
-
|
|
259
|
-
|
|
244
|
+
// Skip based upon system filter
|
|
245
|
+
if (normalizedContentType.source == 'system' && !allowSystem) {
|
|
246
|
+
if (cfg.debug)
|
|
247
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Content-Type ${normalizedContentType.key} due to it being a system type\n`));
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
// Skip based upon custom filter
|
|
251
|
+
if (customFilter && !await customFilter(normalizedContentType)) {
|
|
252
|
+
if (cfg.debug)
|
|
253
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Content-Type ${normalizedContentType.key} due to the custom filter\n`));
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
// Add to list
|
|
257
|
+
filteredContentTypes.push(normalizedContentType);
|
|
258
|
+
}
|
|
260
259
|
if (cfg.debug)
|
|
261
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Applied content type filters, reduced from ${
|
|
262
|
-
return {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
260
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Applied content type filters, reduced from ${allContentTypes.length} to ${filteredContentTypes.length} items\n`));
|
|
261
|
+
return { all: allContentTypes, contentTypes: filteredContentTypes };
|
|
262
|
+
}
|
|
263
|
+
function shouldInclude(value, allow, disallow) {
|
|
264
|
+
// Item is allowed when either allow is not set, an empty array or has the value
|
|
265
|
+
const isAllowed = !Array.isArray(allow) || allow.length === 0 || allow.includes(value);
|
|
266
|
+
// Item is disallowed when and the array is set and includes the value
|
|
267
|
+
const isDisallowed = Array.isArray(disallow) && disallow.includes(value);
|
|
268
|
+
return isAllowed && !isDisallowed;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Retrieve all content types as an Async Generator, allowing processing of entries whilest they are being loaded from the CMS instance.
|
|
272
|
+
*
|
|
273
|
+
* @param client
|
|
274
|
+
* @param args
|
|
275
|
+
* @param pageSize
|
|
276
|
+
*/
|
|
277
|
+
async function* getAllContentTypes(client, debug = false, pageSize = 25) {
|
|
278
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Pulling Content Types from Optimizely CMS\n`));
|
|
279
|
+
let requestPageSize = pageSize;
|
|
280
|
+
let requestPageIndex = 0;
|
|
281
|
+
let totalItemCount = 0;
|
|
282
|
+
let totalPages = 0;
|
|
283
|
+
do {
|
|
284
|
+
const resultsPage = await client.contentTypesList({ query: { pageIndex: requestPageIndex, pageSize: requestPageSize } }).catch((_) => {
|
|
285
|
+
return {
|
|
286
|
+
items: [],
|
|
287
|
+
totalItemCount: 0,
|
|
288
|
+
pageIndex: requestPageIndex,
|
|
289
|
+
pageSize: requestPageSize
|
|
290
|
+
};
|
|
291
|
+
});
|
|
292
|
+
// Calculate fields for next page
|
|
293
|
+
totalItemCount = resultsPage.totalItemCount ?? 0;
|
|
294
|
+
requestPageSize = resultsPage.pageSize;
|
|
295
|
+
requestPageIndex = resultsPage.pageIndex + 1;
|
|
296
|
+
totalPages = Math.ceil(totalItemCount / requestPageSize);
|
|
297
|
+
// Debug output
|
|
298
|
+
if (debug)
|
|
299
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetched contentTypes page ${requestPageIndex} of ${totalPages} (${requestPageSize} items per page)\n`));
|
|
300
|
+
// Yield items
|
|
301
|
+
for (const contentType of (resultsPage.items ?? [])) {
|
|
302
|
+
yield contentType;
|
|
303
|
+
}
|
|
304
|
+
} while (requestPageIndex < totalPages);
|
|
266
305
|
}
|
|
267
306
|
|
|
268
307
|
const stylesBuilder = yargs => {
|
|
@@ -274,73 +313,90 @@ const stylesBuilder = yargs => {
|
|
|
274
313
|
newArgs.option("templateTypes", { alias: 'tt', description: "Select only these template types", choices: ['node', 'base', 'component'], type: 'array', demandOption: false, default: [] });
|
|
275
314
|
return newArgs;
|
|
276
315
|
};
|
|
277
|
-
async function getStyles(client, args, pageSize =
|
|
316
|
+
async function getStyles(client, args, pageSize = 25) {
|
|
278
317
|
if (client.runtimeCmsVersion == OptiCmsVersion.CMS12)
|
|
279
318
|
return { all: [], styles: [] };
|
|
280
319
|
const { _config: cfg, excludeBaseTypes, excludeTypes, excludeNodeTypes, excludeTemplates, baseTypes, types, nodes, templates, templateTypes } = parseArgs(args);
|
|
281
320
|
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Pulling Style-Definitions from Optimizely CMS\n`));
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if (cfg.debug)
|
|
289
|
-
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`));
|
|
290
|
-
resultsPage = await client.displayTemplatesList({ query: { pageIndex: resultsPage.pageIndex + 1, pageSize: resultsPage.pageSize } });
|
|
291
|
-
results.push(...resultsPage.items);
|
|
292
|
-
pagesRemaining = Math.ceil(resultsPage.totalItemCount / resultsPage.pageSize) - (resultsPage.pageIndex + 1);
|
|
293
|
-
}
|
|
294
|
-
if (cfg.debug) {
|
|
295
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetched ${results.length} Style-Definitions from Optimizely CMS\n`));
|
|
296
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Filtering Style-Definitions based upon arguments\n`));
|
|
297
|
-
}
|
|
298
|
-
const styles = results.filter(data => {
|
|
299
|
-
if (isExcluded(data.key, excludeTemplates, templates)) {
|
|
321
|
+
const allDisplayTemplates = [];
|
|
322
|
+
const filteredDisplayTemplates = [];
|
|
323
|
+
for await (const displayTemplate of getAllStyles(client, cfg.debug, pageSize)) {
|
|
324
|
+
allDisplayTemplates.push(displayTemplate);
|
|
325
|
+
const templateType = displayTemplate.baseType ? 'base' : displayTemplate.nodeType ? 'node' : displayTemplate.contentType ? 'component' : 'unknown';
|
|
326
|
+
if (isExcluded(displayTemplate.key, excludeTemplates, templates)) {
|
|
300
327
|
if (cfg.debug)
|
|
301
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${
|
|
302
|
-
|
|
328
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${displayTemplate.key} - Style defintion key filtering active\n`));
|
|
329
|
+
continue;
|
|
303
330
|
}
|
|
304
|
-
const templateType = data.baseType ? 'base' : data.nodeType ? 'node' : data.contentType ? 'component' : 'unknown';
|
|
305
331
|
if (isExcluded(templateType, [], templateTypes)) {
|
|
306
332
|
if (cfg.debug)
|
|
307
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${
|
|
308
|
-
|
|
333
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${displayTemplate.key} - Style type filtering is active\n`));
|
|
334
|
+
continue;
|
|
309
335
|
}
|
|
310
|
-
if (
|
|
336
|
+
if (displayTemplate.baseType && isExcluded(displayTemplate.baseType, excludeBaseTypes, baseTypes)) {
|
|
311
337
|
if (cfg.debug)
|
|
312
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${
|
|
313
|
-
|
|
338
|
+
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`));
|
|
339
|
+
continue;
|
|
314
340
|
}
|
|
315
|
-
if (
|
|
341
|
+
if (displayTemplate.contentType && isExcluded(displayTemplate.contentType, excludeTypes, types)) {
|
|
316
342
|
if (cfg.debug)
|
|
317
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${
|
|
318
|
-
|
|
343
|
+
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`));
|
|
344
|
+
continue;
|
|
319
345
|
}
|
|
320
346
|
if (templateType != 'component' && types.length > 0) {
|
|
321
347
|
if (cfg.debug)
|
|
322
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${
|
|
323
|
-
|
|
348
|
+
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`));
|
|
349
|
+
continue;
|
|
324
350
|
}
|
|
325
|
-
if (
|
|
351
|
+
if (displayTemplate.nodeType && isExcluded(displayTemplate.nodeType, excludeNodeTypes, nodes)) {
|
|
326
352
|
if (cfg.debug)
|
|
327
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${
|
|
328
|
-
|
|
353
|
+
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`));
|
|
354
|
+
continue;
|
|
329
355
|
}
|
|
330
356
|
if (templateType != 'node' && nodes.length > 0) {
|
|
331
357
|
if (cfg.debug)
|
|
332
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${
|
|
333
|
-
|
|
358
|
+
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`));
|
|
359
|
+
continue;
|
|
334
360
|
}
|
|
335
|
-
|
|
336
|
-
}
|
|
361
|
+
filteredDisplayTemplates.push(displayTemplate);
|
|
362
|
+
}
|
|
337
363
|
if (cfg.debug)
|
|
338
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Applied
|
|
364
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Applied style filters, reduced from ${allDisplayTemplates.length} to ${filteredDisplayTemplates.length} items\n`));
|
|
339
365
|
return {
|
|
340
|
-
all:
|
|
341
|
-
styles
|
|
366
|
+
all: allDisplayTemplates,
|
|
367
|
+
styles: filteredDisplayTemplates
|
|
342
368
|
};
|
|
343
369
|
}
|
|
370
|
+
async function* getAllStyles(client, debug = false, pageSize = 5) {
|
|
371
|
+
if (client.runtimeCmsVersion == OptiCmsVersion.CMS12)
|
|
372
|
+
return;
|
|
373
|
+
let requestPageSize = pageSize;
|
|
374
|
+
let requestPageIndex = 0;
|
|
375
|
+
let totalItemCount = 0;
|
|
376
|
+
let totalPages = 0;
|
|
377
|
+
do {
|
|
378
|
+
const resultsPage = await client.displayTemplatesList({ query: { pageIndex: requestPageIndex, pageSize: requestPageSize } }).catch((_) => {
|
|
379
|
+
return {
|
|
380
|
+
items: [],
|
|
381
|
+
totalItemCount: 0,
|
|
382
|
+
pageIndex: requestPageIndex,
|
|
383
|
+
pageSize: requestPageSize
|
|
384
|
+
};
|
|
385
|
+
});
|
|
386
|
+
// Calculate fields for next page
|
|
387
|
+
totalItemCount = resultsPage.totalItemCount ?? 0;
|
|
388
|
+
requestPageSize = resultsPage.pageSize;
|
|
389
|
+
requestPageIndex = resultsPage.pageIndex + 1;
|
|
390
|
+
totalPages = Math.ceil(totalItemCount / requestPageSize);
|
|
391
|
+
// Debug output
|
|
392
|
+
if (debug)
|
|
393
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetched displayTemplates page ${requestPageIndex} of ${totalPages} (${requestPageSize} items per page)\n`));
|
|
394
|
+
// Yield items
|
|
395
|
+
for (const displayTemplate of (resultsPage.items ?? [])) {
|
|
396
|
+
yield displayTemplate;
|
|
397
|
+
}
|
|
398
|
+
} while (requestPageIndex < totalPages);
|
|
399
|
+
}
|
|
344
400
|
function isExcluded(value, exclusions, inclusions) {
|
|
345
401
|
if (value == undefined || value == null)
|
|
346
402
|
return false;
|
|
@@ -504,7 +560,7 @@ const StylesPullCommand = {
|
|
|
504
560
|
// Write Style definition
|
|
505
561
|
const imports = [
|
|
506
562
|
'import type { LayoutProps } from "@remkoj/optimizely-cms-react"',
|
|
507
|
-
'import type { ReactNode } from "react"'
|
|
563
|
+
'import type { ReactNode, JSX } from "react"'
|
|
508
564
|
];
|
|
509
565
|
const typeContents = [];
|
|
510
566
|
const props = [];
|
|
@@ -573,11 +629,11 @@ const StylesCreateCommand = {
|
|
|
573
629
|
process.stdout.write(chalk.gray(`${figures.cross} Styles are not supported on CMS12\n`));
|
|
574
630
|
return;
|
|
575
631
|
}
|
|
576
|
-
const allowedBaseTypes = ['section', '
|
|
632
|
+
const allowedBaseTypes = ['section', 'component', 'experience'];
|
|
577
633
|
// Prepare
|
|
578
634
|
process.stdout.write(chalk.yellowBright(chalk.bold(`Reading current information from Optimizely CMS\n`)));
|
|
579
635
|
const [{ contentTypes }, { styles }] = await Promise.all([
|
|
580
|
-
getContentTypes(client, { ...args, excludeBaseTypes: [], excludeTypes: [], baseTypes: allowedBaseTypes, types: [], all: false }),
|
|
636
|
+
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'),
|
|
581
637
|
getStyles(client, { ...args, excludeBaseTypes: [], excludeNodeTypes: [], excludeTemplates: [], excludeTypes: [], baseTypes: allowedBaseTypes, types: [], nodes: [], components: '', templates: [], templateTypes: [], all: false })
|
|
582
638
|
]);
|
|
583
639
|
const styleKeys = styles.map(x => x.key);
|
|
@@ -585,27 +641,34 @@ const StylesCreateCommand = {
|
|
|
585
641
|
process.stdout.write(chalk.yellowBright(chalk.bold(`\nConfigure your style definition\n`)));
|
|
586
642
|
const key = await input({ message: "Identifier:", validate: (val) => { return val.match(/^[a-zA-Z][A-Za-z0-9\-_]*$/) != null && !styleKeys.includes(val); } });
|
|
587
643
|
const displayName = await input({ message: "Display name:" });
|
|
588
|
-
const type = (await select({
|
|
589
|
-
|
|
590
|
-
{ value: "
|
|
591
|
-
{ value: "
|
|
592
|
-
|
|
644
|
+
const type = (await select({
|
|
645
|
+
message: "Style target:", choices: [
|
|
646
|
+
{ value: "baseType", name: "Base type", description: "Target all Content-Types that inherit from the specified base type" },
|
|
647
|
+
{ value: "contentType", name: "Content type", description: "Target a specific Content-Type that supports styling" },
|
|
648
|
+
{ value: "nodeType", name: "Node", description: "Target a specific node type from a Section" }
|
|
649
|
+
]
|
|
650
|
+
}));
|
|
593
651
|
let typeId = "";
|
|
594
652
|
switch (type) {
|
|
595
653
|
case 'contentType':
|
|
596
654
|
typeId = await select({ message: "Content-Type:", choices: contentTypes.map(x => { return { value: x.key, description: x.description, name: x.displayName }; }) });
|
|
597
655
|
break;
|
|
598
656
|
case 'nodeType':
|
|
599
|
-
typeId = await select({
|
|
657
|
+
typeId = await select({
|
|
658
|
+
message: "Node type:", choices: [
|
|
600
659
|
{ value: "row", description: "Row", name: "Row" },
|
|
601
660
|
{ value: "column", description: "Column", name: "Column" }
|
|
602
|
-
]
|
|
661
|
+
]
|
|
662
|
+
});
|
|
603
663
|
break;
|
|
604
664
|
case 'baseType':
|
|
605
|
-
typeId = await select({
|
|
665
|
+
typeId = await select({
|
|
666
|
+
message: "Base type:", choices: [
|
|
667
|
+
{ value: "experience", description: "Target all experience types", name: "Experience" },
|
|
606
668
|
{ value: "section", description: "Target all section types", name: "Section" },
|
|
607
|
-
{ value: "
|
|
608
|
-
]
|
|
669
|
+
{ value: "component", description: "Target all component types", name: "Component" }
|
|
670
|
+
]
|
|
671
|
+
});
|
|
609
672
|
break;
|
|
610
673
|
}
|
|
611
674
|
const isDefault = await confirm({ message: "Should this style be marked as default?" });
|
|
@@ -625,8 +688,18 @@ const StylesCreateCommand = {
|
|
|
625
688
|
process.exit(0);
|
|
626
689
|
}
|
|
627
690
|
}
|
|
691
|
+
// @ToDo: Add properties
|
|
628
692
|
fs.writeFileSync(path.join(basePath, styleFilePath), JSON.stringify(definition, undefined, 4));
|
|
629
693
|
process.stdout.write(chalk.yellowBright(chalk.bold(`\nWritten style defintion template to: ${path.normalize(path.join(basePath, styleFilePath))}\n`)));
|
|
694
|
+
if (await confirm({ message: "Do you want to publish this style into Optimizely CMS?" })) {
|
|
695
|
+
const response = await client.displayTemplatesPut({ body: definition, path: { key: definition.key } }).catch(_ => undefined);
|
|
696
|
+
if (!response) {
|
|
697
|
+
process.stderr.write(chalk.redBright(chalk.bold(`\[ERROR] Unable to create style definition ${definition.displayName} in Optimizely CMS\n`)));
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
process.stdout.write(chalk.yellowBright(chalk.bold(`\nCreated style definition ${definition.displayName} in Optimizely CMS\n`)));
|
|
701
|
+
}
|
|
702
|
+
}
|
|
630
703
|
process.stdout.write(chalk.yellowBright(`\n1. Add your properties to the 'settings' list in the file`));
|
|
631
704
|
process.stdout.write(chalk.yellowBright(`\n2. Run `));
|
|
632
705
|
process.stdout.write(chalk.whiteBright(`yarn opti-cms styles:push -t ${definition.key}`));
|
|
@@ -1132,20 +1205,19 @@ ${varName}.getMetaData = async (contentLink, locale, client) => {
|
|
|
1132
1205
|
export default ${varName}`,
|
|
1133
1206
|
// Template for all experience component types
|
|
1134
1207
|
experience: (contentType, varName, displayTemplate) => `import { type OptimizelyNextPage as CmsComponent } from "@remkoj/optimizely-cms-nextjs";
|
|
1135
|
-
import {
|
|
1136
|
-
import { ExperienceDataFragmentDoc, CompositionDataFragmentDoc, ${contentType.key}DataFragmentDoc, type ${contentType.key}DataFragment } from "@/gql/graphql";
|
|
1208
|
+
import { ${contentType.key}DataFragmentDoc, type ExperienceDataFragment, type ${contentType.key}DataFragment } from "@/gql/graphql";
|
|
1137
1209
|
import { OptimizelyComposition, isNode, CmsEditable } from "@remkoj/optimizely-cms-react/rsc";${displayTemplate ? `
|
|
1138
1210
|
import { ${displayTemplate} } from "./displayTemplates";` : ''}
|
|
1139
|
-
import { getSdk } from "@/gql"
|
|
1211
|
+
import { getSdk } from "@/gql/client"
|
|
1140
1212
|
|
|
1141
1213
|
/**
|
|
1142
1214
|
* ${contentType.displayName}
|
|
1143
1215
|
* ${contentType.description}
|
|
1144
1216
|
*/
|
|
1145
1217
|
export const ${varName} : CmsComponent<${contentType.key}DataFragment${displayTemplate ? ', ' + displayTemplate : ''}> = ({ data${displayTemplate ? ', layoutProps' : ''}, ctx }) => {
|
|
1146
|
-
const composition =
|
|
1218
|
+
const composition = (data as ExperienceDataFragment)?.composition
|
|
1147
1219
|
return <CmsEditable as="div" className="mx-auto px-2 container" cmsFieldName="unstructuredData" ctx={ctx}>
|
|
1148
|
-
{ composition && isNode(composition) && <OptimizelyComposition node={composition} /> }
|
|
1220
|
+
{ composition && isNode(composition) && <OptimizelyComposition node={composition} ctx={ctx} /> }
|
|
1149
1221
|
</CmsEditable>
|
|
1150
1222
|
}
|
|
1151
1223
|
${varName}.displayName = "${contentType.displayName} (${ucFirst(contentType.baseType)}/${contentType.key})"
|
|
@@ -1493,6 +1565,386 @@ const NextJsCreateCommand = {
|
|
|
1493
1565
|
}
|
|
1494
1566
|
};
|
|
1495
1567
|
|
|
1568
|
+
const deepmerge$1 = createDeepMerge();
|
|
1569
|
+
async function loadSchema(client, schemaName) {
|
|
1570
|
+
const schemas = Array.isArray(schemaName) ? schemaName : [schemaName];
|
|
1571
|
+
process.stdout.write(`${figures.arrowRight} Downloading current JSON Schema\n`);
|
|
1572
|
+
const spec = await client.getOpenApiSpec();
|
|
1573
|
+
const specSchemas = spec.components?.schemas ?? {};
|
|
1574
|
+
process.stdout.write(` ${figures.tick} Complete\n`);
|
|
1575
|
+
const result = [];
|
|
1576
|
+
process.stdout.write(`\n${figures.arrowRight} Constructing schema for ${schemas.join(', ')}\n`);
|
|
1577
|
+
for await (const schema of schemas)
|
|
1578
|
+
if (specSchemas[schema]) {
|
|
1579
|
+
const definitions = {};
|
|
1580
|
+
const processedSchema = processSchema(specSchemas[schema], definitions, spec);
|
|
1581
|
+
const jsonSchema = {
|
|
1582
|
+
//"$schema": "https://json-schema.org/draft-07/schema",
|
|
1583
|
+
"$id": `https://api.cms.optimizely.com/schema/${schema}`,
|
|
1584
|
+
type: "object",
|
|
1585
|
+
title: schema,
|
|
1586
|
+
...processedSchema,
|
|
1587
|
+
definitions
|
|
1588
|
+
};
|
|
1589
|
+
result.push({
|
|
1590
|
+
title: schema,
|
|
1591
|
+
schema: postProcessDefintions(jsonSchema)
|
|
1592
|
+
});
|
|
1593
|
+
process.stdout.write(` ${figures.tick} Constructed schema of ${schema}\n`);
|
|
1594
|
+
}
|
|
1595
|
+
return result;
|
|
1596
|
+
}
|
|
1597
|
+
function postProcessDefintions(jsonSchema) {
|
|
1598
|
+
if (!jsonSchema.definitions)
|
|
1599
|
+
return jsonSchema;
|
|
1600
|
+
for (const definitionName in jsonSchema.definitions) {
|
|
1601
|
+
if ((definitionName.endsWith("Property") || definitionName.endsWith("ListItem")) && jsonSchema.definitions[definitionName].properties) {
|
|
1602
|
+
const type = jsonSchema.definitions[definitionName].properties?.type;
|
|
1603
|
+
if (isRefSchema(type)) {
|
|
1604
|
+
const typeSchema = resolveRefSchema(type, jsonSchema);
|
|
1605
|
+
if (typeSchema && typeSchema.type === 'string' && Array.isArray(typeSchema.enum)) {
|
|
1606
|
+
let typeValue = definitionName.substring(0, definitionName.length - 8);
|
|
1607
|
+
typeValue = typeValue[0].toLowerCase() + typeValue.substring(1);
|
|
1608
|
+
typeValue = typeValue === 'list' ? 'array' : typeValue;
|
|
1609
|
+
typeValue = typeValue === 'jsonString' ? 'json' : typeValue;
|
|
1610
|
+
if (typeSchema.enum.includes(typeValue)) {
|
|
1611
|
+
const newTypeDef = deepmerge$1({}, typeSchema);
|
|
1612
|
+
newTypeDef.enum = newTypeDef.enum.filter((x) => x === typeValue);
|
|
1613
|
+
jsonSchema.definitions[definitionName].properties.type = newTypeDef;
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
return jsonSchema;
|
|
1620
|
+
}
|
|
1621
|
+
/**
|
|
1622
|
+
* Schema normalization to tranform the schema from a .Net generated OpenAPI Schema
|
|
1623
|
+
* to a AJV compatible JSON Schema
|
|
1624
|
+
*
|
|
1625
|
+
* @see https://ajv.js.org/
|
|
1626
|
+
* @param schema
|
|
1627
|
+
* @param defs
|
|
1628
|
+
* @param spec
|
|
1629
|
+
* @returns
|
|
1630
|
+
*/
|
|
1631
|
+
function processSchema(schema, defs, spec, mergeAllOf = true) {
|
|
1632
|
+
if (Array.isArray(schema))
|
|
1633
|
+
return schema.map(s => processSchema(s, defs, spec, mergeAllOf));
|
|
1634
|
+
if (typeof schema !== 'object' || schema === null)
|
|
1635
|
+
return schema;
|
|
1636
|
+
const props = Object.getOwnPropertyNames(schema);
|
|
1637
|
+
if (props.length === 1 && props[0] === '$ref') {
|
|
1638
|
+
const ref = schema['$ref'];
|
|
1639
|
+
if (isLocalRef(ref)) {
|
|
1640
|
+
const refName = getLocalRefName(ref);
|
|
1641
|
+
if (!defs[refName]) {
|
|
1642
|
+
const refItem = resolveLocalRef(ref, spec);
|
|
1643
|
+
defs[refName] = processSchema(refItem, defs, spec, mergeAllOf);
|
|
1644
|
+
}
|
|
1645
|
+
const newRef = `#/definitions/${refName}`;
|
|
1646
|
+
return { "$ref": newRef };
|
|
1647
|
+
}
|
|
1648
|
+
else {
|
|
1649
|
+
return schema;
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
else if (mergeAllOf && props.includes('allOf') && Array.isArray(schema['allOf'])) {
|
|
1653
|
+
// process all of
|
|
1654
|
+
const newObject = props.reduce((generated, propName) => {
|
|
1655
|
+
if (propName != 'allOf')
|
|
1656
|
+
generated[propName] = schema[propName];
|
|
1657
|
+
return generated;
|
|
1658
|
+
}, {});
|
|
1659
|
+
const allOfSchemas = schema['allOf'].map((subschema) => {
|
|
1660
|
+
const resolvedSchema = isRefSchema(subschema) ? resolveRefSchema(subschema, spec) : subschema;
|
|
1661
|
+
const resolved = processSchema(resolvedSchema, defs, spec, mergeAllOf);
|
|
1662
|
+
return resolved;
|
|
1663
|
+
});
|
|
1664
|
+
const merged = allOfSchemas.reduce((previous, current) => deepmerge$1(previous, current), newObject);
|
|
1665
|
+
if (newObject['description'])
|
|
1666
|
+
merged['description'] = newObject['description'];
|
|
1667
|
+
if (newObject['title'])
|
|
1668
|
+
merged['title'] = newObject['title'];
|
|
1669
|
+
return merged;
|
|
1670
|
+
}
|
|
1671
|
+
else {
|
|
1672
|
+
const newSchema = {};
|
|
1673
|
+
for (const key of props) {
|
|
1674
|
+
if (key === 'pattern' && typeof schema[key] === 'string') {
|
|
1675
|
+
newSchema[key] = safeCreateUnicodeRegExp(schema[key]);
|
|
1676
|
+
/*} else if (key === 'readOnly' && schema[key] === true) {
|
|
1677
|
+
return undefined*/
|
|
1678
|
+
}
|
|
1679
|
+
else if (['additionalProperties', 'required', 'properties', 'discriminator'].includes(key) && typeof schema[key] !== 'object') ;
|
|
1680
|
+
else if (key === 'discriminator' && !Array.isArray(schema['oneOf'])) ;
|
|
1681
|
+
else if (key === 'discriminator' && !schema['type']) {
|
|
1682
|
+
newSchema.type = 'object';
|
|
1683
|
+
}
|
|
1684
|
+
else {
|
|
1685
|
+
const keyVal = processSchema(schema[key], defs, spec, mergeAllOf);
|
|
1686
|
+
if (keyVal !== undefined)
|
|
1687
|
+
newSchema[key] = keyVal;
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
return newSchema;
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
function isRefSchema(schema) {
|
|
1694
|
+
if (typeof schema != 'object' || schema == null)
|
|
1695
|
+
return false;
|
|
1696
|
+
return Object.getOwnPropertyNames(schema).length === 1 && typeof schema['$ref'] === 'string';
|
|
1697
|
+
}
|
|
1698
|
+
function resolveRefSchema(schema, spec) {
|
|
1699
|
+
if (!isRefSchema(schema))
|
|
1700
|
+
return undefined;
|
|
1701
|
+
return resolveLocalRef(schema['$ref'], spec);
|
|
1702
|
+
}
|
|
1703
|
+
function isLocalRef(ref) {
|
|
1704
|
+
return typeof ref === 'string' && ref.startsWith('#');
|
|
1705
|
+
}
|
|
1706
|
+
function getLocalRefName(ref) {
|
|
1707
|
+
if (!isLocalRef(ref))
|
|
1708
|
+
return undefined;
|
|
1709
|
+
const refPath = ref.substring(2).split('/');
|
|
1710
|
+
return refPath.at(refPath.length - 1);
|
|
1711
|
+
}
|
|
1712
|
+
function resolveLocalRef(ref, spec) {
|
|
1713
|
+
const refPath = ref.substring(2).split('/');
|
|
1714
|
+
return refPath.reduce((integrator, current) => {
|
|
1715
|
+
return integrator ? integrator[current] : undefined;
|
|
1716
|
+
}, spec);
|
|
1717
|
+
}
|
|
1718
|
+
const replaceEscapedChars = [
|
|
1719
|
+
[new RegExp('\\\\ ', 'g'), ' '],
|
|
1720
|
+
[new RegExp('\\\\!', 'g'), '!'],
|
|
1721
|
+
[new RegExp('\\\\"', 'g'), '"'],
|
|
1722
|
+
[new RegExp('\\\\#', 'g'), '#'],
|
|
1723
|
+
[new RegExp('\\\\%', 'g'), '%'],
|
|
1724
|
+
[new RegExp('\\\\&', 'g'), '&'],
|
|
1725
|
+
[new RegExp("\\\\'", 'g'), "'"],
|
|
1726
|
+
[new RegExp('\\\\,', 'g'), ','],
|
|
1727
|
+
[new RegExp('\\\\-', 'g'), '-'],
|
|
1728
|
+
[new RegExp('\\\\:', 'g'), ':'],
|
|
1729
|
+
[new RegExp('\\\\;', 'g'), ';'],
|
|
1730
|
+
[new RegExp('\\\\<', 'g'), '<'],
|
|
1731
|
+
[new RegExp('\\\\=', 'g'), '='],
|
|
1732
|
+
[new RegExp('\\\\>', 'g'), '>'],
|
|
1733
|
+
[new RegExp('\\\\@', 'g'), '@'],
|
|
1734
|
+
[new RegExp('\\\\_', 'g'), '_'],
|
|
1735
|
+
[new RegExp('\\\\`', 'g'), '`'],
|
|
1736
|
+
[new RegExp('\\\\~', 'g'), '~'],
|
|
1737
|
+
[new RegExp('\\\\[zZ]', 'g'), '$'],
|
|
1738
|
+
];
|
|
1739
|
+
function safeCreateUnicodeRegExp(pattern) {
|
|
1740
|
+
for (const unEscape of replaceEscapedChars) {
|
|
1741
|
+
pattern = pattern.replace(unEscape[0], unEscape[1]);
|
|
1742
|
+
}
|
|
1743
|
+
return pattern;
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
const SchemaVsCodeCommand = {
|
|
1747
|
+
command: "schema:vscode",
|
|
1748
|
+
describe: "Configure schema validation for opti-type.json & opti-style.json files by VSCode in the current project folder",
|
|
1749
|
+
builder: (yargs) => {
|
|
1750
|
+
const newYargs = yargs;
|
|
1751
|
+
//newYargs.option('schemas', { alias: 's', description: "The schema's to download", array: true, type: 'string', demandOption: false, default: ['DisplayTemplate', 'ContentType'] })
|
|
1752
|
+
//newYargs.option('force', { alias: 'f', description: "Overwrite existing files", boolean: true, type: 'boolean', demandOption: false, default: false })
|
|
1753
|
+
//newYargs.option("definitions", { alias: 'd', description: "Create/overwrite typescript definitions", boolean: true, type: 'boolean', demandOption: false, default: true })
|
|
1754
|
+
return newYargs;
|
|
1755
|
+
},
|
|
1756
|
+
handler: async (args) => {
|
|
1757
|
+
const projectPath = args.path;
|
|
1758
|
+
const client = createCmsClient(args);
|
|
1759
|
+
const schemas = await loadSchema(client, ['DisplayTemplate', 'ContentType']);
|
|
1760
|
+
process.stdout.write(`\n${figures.arrowRight} Writing schema files\n`);
|
|
1761
|
+
for (const schema of schemas) {
|
|
1762
|
+
const schemaFile = path.join(projectPath, '.vscode', `${schema.title.toLowerCase()}.schema.json`);
|
|
1763
|
+
void await writeFileAsync(schemaFile, JSON.stringify(schema.schema, undefined, 2));
|
|
1764
|
+
process.stdout.write(` ${figures.tick} Written the ${schema.title} schema to ${path.relative(projectPath, schemaFile)}\n`);
|
|
1765
|
+
}
|
|
1766
|
+
process.stdout.write(`\n${figures.arrowRight} Updating folder settings\n`);
|
|
1767
|
+
const settingsFile = path.join(projectPath, '.vscode', 'settings.json');
|
|
1768
|
+
const settings = (await readJsonFileAsync(settingsFile).catch(() => undefined)) ?? {};
|
|
1769
|
+
settings['json.validate.enable'] = true;
|
|
1770
|
+
settings['json.schemaDownload.enable'] = true;
|
|
1771
|
+
settings['json.schemas'] = settings['json.schemas'] || [];
|
|
1772
|
+
const hasContentType = settings['json.schemas'].findIndex(v => v.fileMatch.findIndex(fm => fm.endsWith('.opti-type.json')) >= 0) >= 0;
|
|
1773
|
+
const hasStyleDefinition = settings['json.schemas'].findIndex(v => v.fileMatch.findIndex(fm => fm.endsWith('.opti-style.json')) >= 0) >= 0;
|
|
1774
|
+
const hasSchemaType = settings['json.schemas'].findIndex(v => v.fileMatch.findIndex(fm => fm.endsWith('.schema.json')) >= 0) >= 0;
|
|
1775
|
+
if (!hasContentType)
|
|
1776
|
+
settings['json.schemas'].push({
|
|
1777
|
+
"fileMatch": [
|
|
1778
|
+
"**/*.opti-type.json"
|
|
1779
|
+
],
|
|
1780
|
+
"url": "./.vscode/contenttype.schema.json"
|
|
1781
|
+
});
|
|
1782
|
+
if (!hasStyleDefinition)
|
|
1783
|
+
settings['json.schemas'].push({
|
|
1784
|
+
"fileMatch": [
|
|
1785
|
+
"**/*.opti-style.json"
|
|
1786
|
+
],
|
|
1787
|
+
"url": "./.vscode/displaytemplate.schema.json"
|
|
1788
|
+
});
|
|
1789
|
+
if (!hasSchemaType)
|
|
1790
|
+
settings['json.schemas'].push({
|
|
1791
|
+
"fileMatch": [
|
|
1792
|
+
"**/*.schema.json"
|
|
1793
|
+
],
|
|
1794
|
+
"url": "https://json-schema.org/draft-07/schema"
|
|
1795
|
+
});
|
|
1796
|
+
if (settings['json.schemas'].length == 0)
|
|
1797
|
+
delete settings['json.schemas'];
|
|
1798
|
+
await writeFileAsync(settingsFile, JSON.stringify(settings, undefined, 2));
|
|
1799
|
+
}
|
|
1800
|
+
};
|
|
1801
|
+
function readJsonFileAsync(path) {
|
|
1802
|
+
return new Promise((resolve, reject) => fs.readFile(path, (err, data) => {
|
|
1803
|
+
if (err) {
|
|
1804
|
+
if (err?.code === 'ENOENT')
|
|
1805
|
+
resolve(undefined);
|
|
1806
|
+
else
|
|
1807
|
+
reject(err);
|
|
1808
|
+
}
|
|
1809
|
+
else {
|
|
1810
|
+
try {
|
|
1811
|
+
const d = data.toString();
|
|
1812
|
+
resolve(JSON.parse(d));
|
|
1813
|
+
}
|
|
1814
|
+
catch (e) {
|
|
1815
|
+
reject(e);
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
}));
|
|
1819
|
+
}
|
|
1820
|
+
function writeFileAsync(path, data) {
|
|
1821
|
+
return new Promise((resolve, reject) => {
|
|
1822
|
+
fs.writeFile(path, data, (err) => {
|
|
1823
|
+
if (err)
|
|
1824
|
+
reject(err);
|
|
1825
|
+
else
|
|
1826
|
+
resolve();
|
|
1827
|
+
});
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
const deepmerge = createDeepMerge();
|
|
1832
|
+
const SchemaValidateCommand = {
|
|
1833
|
+
command: "schema:validate",
|
|
1834
|
+
describe: "Validate the opti-type.json & opti-style.json files",
|
|
1835
|
+
builder: (yargs) => {
|
|
1836
|
+
const newYargs = yargs;
|
|
1837
|
+
//newYargs.option('schemas', { alias: 's', description: "The schema's to download", array: true, type: 'string', demandOption: false, default: ['DisplayTemplate', 'ContentType'] })
|
|
1838
|
+
//newYargs.option('force', { alias: 'f', description: "Overwrite existing files", boolean: true, type: 'boolean', demandOption: false, default: false })
|
|
1839
|
+
//newYargs.option("definitions", { alias: 'd', description: "Create/overwrite typescript definitions", boolean: true, type: 'boolean', demandOption: false, default: true })
|
|
1840
|
+
return newYargs;
|
|
1841
|
+
},
|
|
1842
|
+
handler: async (args) => {
|
|
1843
|
+
const projectPath = args.path;
|
|
1844
|
+
const client = createCmsClient(args);
|
|
1845
|
+
const schemas = await loadSchema(client, ['DisplayTemplate', 'ContentType']);
|
|
1846
|
+
process.stdout.write(``);
|
|
1847
|
+
const styleSchema = schemas.find(x => x.title == 'DisplayTemplate')?.schema;
|
|
1848
|
+
const typeSchema = schemas.find(x => x.title == 'ContentType')?.schema;
|
|
1849
|
+
const [styleValidator, typeValidator] = await Promise.all([getValidator(styleSchema), getValidator(typeSchema)]);
|
|
1850
|
+
process.stdout.write(`\n${figures.arrowRight} Validating display templates (*.opti-style.json) and content types (*.opti-type.json).\n`);
|
|
1851
|
+
const patterns = ["./**/*.opti-type.json", "./**/*.opti-style.json"];
|
|
1852
|
+
const globMatches = globIterate(patterns, { cwd: projectPath, absolute: true });
|
|
1853
|
+
let allOk = true;
|
|
1854
|
+
for await (const fileMatch of globMatches) {
|
|
1855
|
+
try {
|
|
1856
|
+
const content = JSON.parse(fs.readFileSync(fileMatch).toString());
|
|
1857
|
+
let errors = undefined;
|
|
1858
|
+
if (fileMatch.endsWith('.opti-type.json') && !typeValidator(content)) {
|
|
1859
|
+
errors = typeValidator.errors;
|
|
1860
|
+
}
|
|
1861
|
+
else if (fileMatch.endsWith('.opti-style.json') && !styleValidator(content)) {
|
|
1862
|
+
errors = styleValidator.errors;
|
|
1863
|
+
}
|
|
1864
|
+
if (errors) {
|
|
1865
|
+
allOk = false;
|
|
1866
|
+
process.stdout.write(chalk.redBright(chalk.bold(`\n${figures.cross} ${path.relative(projectPath, fileMatch)}\n`)));
|
|
1867
|
+
const groupedErrors = groupErrors(errors);
|
|
1868
|
+
for (const instancepath in groupedErrors) {
|
|
1869
|
+
for (const key in groupedErrors[instancepath]) {
|
|
1870
|
+
const errorList = groupedErrors[instancepath][key].filter((v, i, a) => a.findIndex(pv => deepEqual(v, pv)) === i);
|
|
1871
|
+
for (const error of errorList) {
|
|
1872
|
+
process.stdout.write(` Node ${instancepath} ${error.message}\n`);
|
|
1873
|
+
switch (error.keyword) {
|
|
1874
|
+
case 'enum':
|
|
1875
|
+
process.stdout.write(` Allowed values: ${error.params.allowedValues.map((x) => x.toString()).join(', ')}\n`);
|
|
1876
|
+
break;
|
|
1877
|
+
case 'oneOf':
|
|
1878
|
+
const matching = Array.isArray(error.params.passingSchemas) ? error.params.passingSchemas : [];
|
|
1879
|
+
if (matching.length === 0)
|
|
1880
|
+
process.stdout.write(` Currently none matching\n`);
|
|
1881
|
+
else
|
|
1882
|
+
process.stdout.write(` Currently matching ${matching.map((x) => x.toString()).join(", ")}\n`);
|
|
1883
|
+
break;
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
catch (e) {
|
|
1891
|
+
allOk = false;
|
|
1892
|
+
process.stdout.write(chalk.redBright(chalk.bold(`${figures.cross} ${path.relative(projectPath, fileMatch)}\n`)));
|
|
1893
|
+
process.stdout.write(` ${e.message}\n`);
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
process.stdout.write(`\n`);
|
|
1897
|
+
if (allOk)
|
|
1898
|
+
process.stdout.write(chalk.greenBright(`${figures.tick} All types & styles match the schema\n`));
|
|
1899
|
+
else {
|
|
1900
|
+
process.stdout.write(chalk.redBright(`${figures.cross} Some types & styles contain errors\n`));
|
|
1901
|
+
process.exit(1);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
};
|
|
1905
|
+
function groupErrors(errors) {
|
|
1906
|
+
const errorrsByInstance = groupErrorsByPath(errors);
|
|
1907
|
+
const instancePaths = Object.getOwnPropertyNames(errorrsByInstance);
|
|
1908
|
+
return instancePaths.reduce((previous, current) => {
|
|
1909
|
+
previous[current] = groupErrorsByKeyword(errorrsByInstance[current]);
|
|
1910
|
+
return previous;
|
|
1911
|
+
}, {});
|
|
1912
|
+
}
|
|
1913
|
+
function groupErrorsByPath(errors) {
|
|
1914
|
+
return errors.reduce((previous, current) => {
|
|
1915
|
+
const instancePath = current.instancePath == "" ? "[ROOT]" : current.instancePath;
|
|
1916
|
+
const partial = {};
|
|
1917
|
+
partial[instancePath] = [current];
|
|
1918
|
+
return deepmerge(previous, partial);
|
|
1919
|
+
}, {});
|
|
1920
|
+
}
|
|
1921
|
+
function groupErrorsByKeyword(errors) {
|
|
1922
|
+
const toMerge = errors.reduce((previous, current) => {
|
|
1923
|
+
const keyword = current.keyword;
|
|
1924
|
+
const partial = {};
|
|
1925
|
+
partial[keyword] = [current];
|
|
1926
|
+
return deepmerge(previous, partial);
|
|
1927
|
+
}, {});
|
|
1928
|
+
// Enum errors must be merged
|
|
1929
|
+
if (toMerge.enum)
|
|
1930
|
+
toMerge.enum = [toMerge.enum.reduce((prev, current) => prev ? deepmerge(prev, current) : current, undefined)];
|
|
1931
|
+
return toMerge;
|
|
1932
|
+
}
|
|
1933
|
+
function getValidator(schemaObject) {
|
|
1934
|
+
const ajv = new Ajv({
|
|
1935
|
+
discriminator: true
|
|
1936
|
+
});
|
|
1937
|
+
addFormats(ajv);
|
|
1938
|
+
return ajv.compile(schemaObject);
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
var version = "6.0.0-pre4";
|
|
1942
|
+
var name = "opti-cms";
|
|
1943
|
+
var APP = {
|
|
1944
|
+
version: version,
|
|
1945
|
+
name: name
|
|
1946
|
+
};
|
|
1947
|
+
|
|
1496
1948
|
const CmsVersionCommand = {
|
|
1497
1949
|
command: "cms:version",
|
|
1498
1950
|
aliases: "$0",
|
|
@@ -1520,10 +1972,11 @@ const CmsVersionCommand = {
|
|
|
1520
1972
|
colAligns: ["left", "left"]
|
|
1521
1973
|
});
|
|
1522
1974
|
info.push(["Instance", client.cmsUrl.host]);
|
|
1523
|
-
info.push(["Client", client.apiVersion]);
|
|
1524
|
-
info.push(["API", versionInfo.apiVersion]);
|
|
1525
|
-
info.push(["CMS", versionInfo.cmsVersion]);
|
|
1526
|
-
info.push(["Service", versionInfo.serviceVersion]);
|
|
1975
|
+
info.push(["Client API", client.apiVersion]);
|
|
1976
|
+
info.push(["Service API", versionInfo.apiVersion]);
|
|
1977
|
+
info.push(["CMS Build", versionInfo.cmsVersion]);
|
|
1978
|
+
info.push(["Service Build", versionInfo.serviceVersion]);
|
|
1979
|
+
info.push(["SDK", APP.version]);
|
|
1527
1980
|
process.stdout.write(info.toString() + "\n");
|
|
1528
1981
|
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Optimizely CMS Status: ${versionInfo.status}\n`));
|
|
1529
1982
|
process.stdout.write(chalk.green(chalk.bold(figures.tick + " Done")) + "\n");
|
|
@@ -1845,16 +2298,11 @@ const commands = [
|
|
|
1845
2298
|
NextJsVisualBuilderCommand,
|
|
1846
2299
|
NextJsFactoryCommand,
|
|
1847
2300
|
CmsResetCommand,
|
|
1848
|
-
CmsVersionCommand
|
|
2301
|
+
CmsVersionCommand,
|
|
2302
|
+
SchemaVsCodeCommand,
|
|
2303
|
+
SchemaValidateCommand
|
|
1849
2304
|
];
|
|
1850
2305
|
|
|
1851
|
-
var version = "6.0.0-pre3";
|
|
1852
|
-
var name = "opti-cms";
|
|
1853
|
-
var APP = {
|
|
1854
|
-
version: version,
|
|
1855
|
-
name: name
|
|
1856
|
-
};
|
|
1857
|
-
|
|
1858
2306
|
async function main() {
|
|
1859
2307
|
const envFiles = prepare();
|
|
1860
2308
|
const app = createOptiCmsApp(APP.name, APP.version, undefined, envFiles);
|
|
@@ -1864,7 +2312,7 @@ async function main() {
|
|
|
1864
2312
|
}
|
|
1865
2313
|
catch {
|
|
1866
2314
|
//We're ignoring error here, as yargs will already generate the "nice output" for it
|
|
1867
|
-
//console.log
|
|
2315
|
+
//console.log('Caught error')
|
|
1868
2316
|
}
|
|
1869
2317
|
}
|
|
1870
2318
|
main();
|