@remkoj/optimizely-cms-cli 5.2.0 → 5.3.0
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 +232 -32
- package/dist/index.js +283 -92
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -3,11 +3,12 @@ import dotenv from 'dotenv';
|
|
|
3
3
|
import { expand } from 'dotenv-expand';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import fs from 'node:fs';
|
|
6
|
-
import { getCmsIntegrationApiConfigFromEnvironment, createClient, OptiCmsVersion,
|
|
6
|
+
import { getCmsIntegrationApiConfigFromEnvironment, createClient, ContentBaseType, OptiCmsVersion, PropertyDataType, ContentRoots } from '@remkoj/optimizely-cms-api';
|
|
7
7
|
import yargs from 'yargs';
|
|
8
8
|
import chalk from 'chalk';
|
|
9
9
|
import figures from 'figures';
|
|
10
10
|
import Table from 'cli-table3';
|
|
11
|
+
import { diff } from 'deep-object-diff';
|
|
11
12
|
import fsAsync from 'node:fs/promises';
|
|
12
13
|
import { input, select, confirm } from '@inquirer/prompts';
|
|
13
14
|
|
|
@@ -79,7 +80,7 @@ function createOptiCmsApp(scriptName, version, epilogue, envFiles, projectDir) {
|
|
|
79
80
|
console.error(msg + "\n");
|
|
80
81
|
if (error)
|
|
81
82
|
console.error(`[${chalk.bold(error.name ?? 'Error')}]: ${error.message ?? ''}\n`);
|
|
82
|
-
args.showHelp("error")
|
|
83
|
+
//args.showHelp("error")
|
|
83
84
|
});
|
|
84
85
|
}
|
|
85
86
|
function isDemanded(value) {
|
|
@@ -138,79 +139,6 @@ function createCmsClient(args) {
|
|
|
138
139
|
return client;
|
|
139
140
|
}
|
|
140
141
|
|
|
141
|
-
const StylesPushCommand = {
|
|
142
|
-
command: "styles:push",
|
|
143
|
-
describe: "Push Visual Builder style definitions into the CMS (create/replace)",
|
|
144
|
-
builder: (yargs) => {
|
|
145
|
-
yargs.option('excludeTemplates', { alias: 'e', description: "Exclude these templates", string: true, type: 'array', demandOption: false, default: [] });
|
|
146
|
-
yargs.option("templates", { alias: 't', description: "Select only these templates", string: true, type: 'array', demandOption: false, default: [] });
|
|
147
|
-
return yargs;
|
|
148
|
-
},
|
|
149
|
-
handler: async (args) => {
|
|
150
|
-
const { _config: cfg, excludeTemplates, templates, ...opts } = parseArgs(args);
|
|
151
|
-
const client = createCmsClient(args);
|
|
152
|
-
if (client.runtimeCmsVersion == OptiCmsVersion.CMS12) {
|
|
153
|
-
process.stdout.write(chalk.gray(`${figures.cross} Styles are not supported on CMS12\n`));
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Pushing (create/replace) DisplayStyles into Optimizely CMS\n`));
|
|
157
|
-
const styleDefinitionFiles = await glob("./**/*.opti-style.json", {
|
|
158
|
-
cwd: opts.components
|
|
159
|
-
});
|
|
160
|
-
const results = (await Promise.all(styleDefinitionFiles.map(async (styleDefinitionFile) => {
|
|
161
|
-
const filePath = path.normalize(path.join(opts.components, styleDefinitionFile));
|
|
162
|
-
const styleDefinition = tryReadJsonFile(filePath, cfg.debug);
|
|
163
|
-
const styleKey = styleDefinition.key;
|
|
164
|
-
if (!styleKey) {
|
|
165
|
-
process.stderr.write(chalk.redBright(`${chalk.bold(figures.cross)} The style definition in ${path.relative(opts.path, filePath)} does not have a key defined\n`));
|
|
166
|
-
return undefined;
|
|
167
|
-
}
|
|
168
|
-
if (excludeTemplates.includes(styleKey))
|
|
169
|
-
return undefined; // Skip excluded styles
|
|
170
|
-
if (templates.length > 0 && !templates.includes(styleKey))
|
|
171
|
-
return undefined; // Only include defined styles, if any
|
|
172
|
-
if (cfg.debug)
|
|
173
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Pushing: ${styleKey}\n`));
|
|
174
|
-
const newTemplate = await client.displayTemplates.displayTemplatesPut(styleKey, styleDefinition);
|
|
175
|
-
return newTemplate;
|
|
176
|
-
}))).filter(isNotNullOrUndefined);
|
|
177
|
-
const styles = new Table({
|
|
178
|
-
head: [
|
|
179
|
-
chalk.yellow(chalk.bold("Name")),
|
|
180
|
-
chalk.yellow(chalk.bold("Key")),
|
|
181
|
-
chalk.yellow(chalk.bold("Default")),
|
|
182
|
-
chalk.yellow(chalk.bold("Target"))
|
|
183
|
-
],
|
|
184
|
-
colWidths: [31, 20, 9, 20],
|
|
185
|
-
colAligns: ["left", "left", "center", "left"]
|
|
186
|
-
});
|
|
187
|
-
results.forEach(tpl => {
|
|
188
|
-
styles.push([
|
|
189
|
-
tpl.displayName,
|
|
190
|
-
tpl.key,
|
|
191
|
-
tpl.isDefault ? figures.tick : figures.cross,
|
|
192
|
-
tpl.contentType ? `${tpl.contentType} (C)` : tpl.baseType ? `${tpl.baseType} (B)` : `${tpl.nodeType} (N)`
|
|
193
|
-
]);
|
|
194
|
-
});
|
|
195
|
-
process.stdout.write(styles.toString() + "\n");
|
|
196
|
-
process.stdout.write(chalk.green(chalk.bold(figures.tick + " Done")) + "\n");
|
|
197
|
-
}
|
|
198
|
-
};
|
|
199
|
-
function tryReadJsonFile(filePath, debug = false) {
|
|
200
|
-
try {
|
|
201
|
-
if (debug)
|
|
202
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Reading style definition from ${filePath}\n`));
|
|
203
|
-
return JSON.parse(fs.readFileSync(filePath, { encoding: 'utf-8' }));
|
|
204
|
-
}
|
|
205
|
-
catch (e) {
|
|
206
|
-
process.stderr.write(chalk.redBright(`${chalk.bold(figures.cross)} Error while reading ${filePath}\n`));
|
|
207
|
-
}
|
|
208
|
-
return undefined;
|
|
209
|
-
}
|
|
210
|
-
function isNotNullOrUndefined(i) {
|
|
211
|
-
return i ? true : false;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
142
|
const ContentTypesArgsDefaults = {
|
|
215
143
|
excludeBaseTypes: [],
|
|
216
144
|
excludeTypes: ['folder', 'media', 'image', 'video'],
|
|
@@ -250,7 +178,7 @@ async function getContentTypes(client, args, pageSize = 100, allowSystem = false
|
|
|
250
178
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetched ${results.length} Content-Types from Optimizely CMS\n`));
|
|
251
179
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Filtering Content-Types based upon arguments\n`));
|
|
252
180
|
}
|
|
253
|
-
const validBaseTypes = getEnumOptions(
|
|
181
|
+
const validBaseTypes = getEnumOptions(ContentBaseType).map(x => x.toLowerCase());
|
|
254
182
|
if (cfg.debug)
|
|
255
183
|
process.stdout.write(`Allowing base types: ${validBaseTypes.join(', ')}\n`);
|
|
256
184
|
const allContentTypes = all ?
|
|
@@ -409,6 +337,269 @@ async function getStyleFilePath(definition, opts) {
|
|
|
409
337
|
throw new Error(`Unable to resolve the target for the DisplayTemplate: ${definition.key}`);
|
|
410
338
|
}
|
|
411
339
|
|
|
340
|
+
function normalizeMergePatch(value, omitKeys, requiredKeys = [], requiredSource) {
|
|
341
|
+
if (value === undefined || value === null)
|
|
342
|
+
return null;
|
|
343
|
+
if (Array.isArray(value))
|
|
344
|
+
return value.map(item => normalizeMergePatch(item, omitKeys));
|
|
345
|
+
if (typeof value !== 'object')
|
|
346
|
+
return value;
|
|
347
|
+
const output = Object.entries(value).reduce((acc, [key, entry]) => {
|
|
348
|
+
if (!omitKeys.includes(key))
|
|
349
|
+
acc[key] = normalizeMergePatch(entry, omitKeys);
|
|
350
|
+
return acc;
|
|
351
|
+
}, {});
|
|
352
|
+
const withRequiredKeys = requiredKeys.reduce((acc, key) => {
|
|
353
|
+
if (requiredSource && !Object.keys(acc).includes(key))
|
|
354
|
+
acc[key] = requiredSource[key];
|
|
355
|
+
return acc;
|
|
356
|
+
}, output);
|
|
357
|
+
return withRequiredKeys;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Computes a JSON Merge Patch (RFC 7396) that transforms `currentValue` into `newValue`.
|
|
361
|
+
*
|
|
362
|
+
* The resulting patch can be sent directly as the request body of an `application/merge-patch+json`
|
|
363
|
+
* request. Changed and added properties are included with their new value; removed properties are
|
|
364
|
+
* included with a `null` value (as required by RFC 7396); unchanged properties are omitted entirely.
|
|
365
|
+
*
|
|
366
|
+
* @see https://datatracker.ietf.org/doc/html/rfc7396
|
|
367
|
+
*
|
|
368
|
+
* @example Basic usage
|
|
369
|
+
* ```ts
|
|
370
|
+
* const current = { name: 'Alice', age: 30, role: 'user' }
|
|
371
|
+
* const updated = { name: 'Alice', age: 31 }
|
|
372
|
+
*
|
|
373
|
+
* generatePatch(current, updated)
|
|
374
|
+
* // => { age: 31, role: null }
|
|
375
|
+
* // `age` is replaced, `role` is removed (null), `name` is unchanged (omitted)
|
|
376
|
+
* ```
|
|
377
|
+
*
|
|
378
|
+
* @example Excluding keys from the patch
|
|
379
|
+
* ```ts
|
|
380
|
+
* const current = { id: '123', name: 'Alice', version: 1 }
|
|
381
|
+
* const updated = { id: '123', name: 'Bob', version: 2 }
|
|
382
|
+
*
|
|
383
|
+
* generatePatch(current, updated, { readonlyFields: ['id', 'version'] })
|
|
384
|
+
* // => { name: 'Bob' }
|
|
385
|
+
* // `id` and `version` are excluded even though they are present in the diff
|
|
386
|
+
* ```
|
|
387
|
+
*
|
|
388
|
+
* @example Always include required fields
|
|
389
|
+
* ```ts
|
|
390
|
+
* const current = { id: '123', name: 'Alice', version: 1 }
|
|
391
|
+
* const updated = { id: '123', name: 'Alice', version: 2 }
|
|
392
|
+
*
|
|
393
|
+
* generatePatch(current, updated, { requiredFields: ['id'] })
|
|
394
|
+
* // => { id: '123', version: 2 }
|
|
395
|
+
* // `id` is included even though it did not change
|
|
396
|
+
* ```
|
|
397
|
+
*
|
|
398
|
+
* @example Nested objects
|
|
399
|
+
* ```ts
|
|
400
|
+
* const current = { address: { city: 'Amsterdam', zip: '1000AA' } }
|
|
401
|
+
* const updated = { address: { city: 'Utrecht' } }
|
|
402
|
+
*
|
|
403
|
+
* generatePatch(current, updated)
|
|
404
|
+
* // => { address: { city: 'Utrecht', zip: null } }
|
|
405
|
+
* ```
|
|
406
|
+
*
|
|
407
|
+
* @param currentValue - The current state of the resource.
|
|
408
|
+
* @param newValue - The desired state of the resource.
|
|
409
|
+
* @param options - Optional configuration.
|
|
410
|
+
* @param options.readonlyFields - Keys that should never appear in the generated patch,
|
|
411
|
+
* regardless of whether they changed.
|
|
412
|
+
* @param options.requiredFields - Keys that should always appear in the generated patch,
|
|
413
|
+
* using values from `newValue`.
|
|
414
|
+
* @returns A {@link MergePatch} object ready to be serialised as an `application/merge-patch+json` body.
|
|
415
|
+
*/
|
|
416
|
+
function generatePatch(currentValue, newValue, options = {}) {
|
|
417
|
+
const requiredKeys = options.requiredFields ?? [];
|
|
418
|
+
const omitKeys = (options.readonlyFields ?? []).filter(key => !requiredKeys.includes(key));
|
|
419
|
+
const patch = diff(currentValue, newValue);
|
|
420
|
+
return normalizeMergePatch(patch, omitKeys, requiredKeys, newValue);
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Extracts all field paths from a {@link MergePatch} as dot-separated strings.
|
|
424
|
+
* Nested objects are flattened recursively; leaf values (including `null`) produce a path entry.
|
|
425
|
+
*
|
|
426
|
+
* @example
|
|
427
|
+
* ```ts
|
|
428
|
+
* getPatchFields({ a: { b: 0 }, c: 'value' })
|
|
429
|
+
* // => ['a.b', 'c']
|
|
430
|
+
* ```
|
|
431
|
+
*
|
|
432
|
+
* @param patch - The merge patch to extract fields from.
|
|
433
|
+
* @param prefix - Internal prefix used during recursion; omit when calling directly.
|
|
434
|
+
* @returns An array of dot-separated field path strings.
|
|
435
|
+
*/
|
|
436
|
+
function getPatchFields(patch, prefix = '') {
|
|
437
|
+
return Object.entries(patch).flatMap(([key, value]) => {
|
|
438
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
439
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
440
|
+
? getPatchFields(value, path)
|
|
441
|
+
: [path];
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* CLI command that synchronizes local `.opti-style.json` definitions with Optimizely CMS.
|
|
447
|
+
*
|
|
448
|
+
* Existing styles are patched, missing styles are created, and command options allow
|
|
449
|
+
* selecting or excluding template keys during the push.
|
|
450
|
+
*/
|
|
451
|
+
const StylesPushCommand = {
|
|
452
|
+
command: 'styles:push',
|
|
453
|
+
describe: 'Push Visual Builder style definitions into the CMS (create/patch)',
|
|
454
|
+
builder: (yargs) => {
|
|
455
|
+
yargs.option('excludeTemplates', {
|
|
456
|
+
alias: 'e',
|
|
457
|
+
description: 'Exclude these templates',
|
|
458
|
+
string: true,
|
|
459
|
+
type: 'array',
|
|
460
|
+
demandOption: false,
|
|
461
|
+
default: [],
|
|
462
|
+
});
|
|
463
|
+
yargs.option('templates', {
|
|
464
|
+
alias: 't',
|
|
465
|
+
description: 'Select only these templates',
|
|
466
|
+
string: true,
|
|
467
|
+
type: 'array',
|
|
468
|
+
demandOption: false,
|
|
469
|
+
default: [],
|
|
470
|
+
});
|
|
471
|
+
return yargs;
|
|
472
|
+
},
|
|
473
|
+
handler: async (args) => {
|
|
474
|
+
const { _config: { debug = false }, excludeTemplates, templates, ...opts } = parseArgs(args);
|
|
475
|
+
const client = createCmsClient(args);
|
|
476
|
+
if (client.runtimeCmsVersion == OptiCmsVersion.CMS12) {
|
|
477
|
+
process.stdout.write(chalk.gray(`${figures.cross} Styles are not supported on CMS12\n`));
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
const { styles: displayTemplates } = await getStyles(client, {
|
|
481
|
+
// Defaults
|
|
482
|
+
all: true,
|
|
483
|
+
baseTypes: [],
|
|
484
|
+
excludeBaseTypes: [],
|
|
485
|
+
excludeNodeTypes: [],
|
|
486
|
+
excludeTemplates: [],
|
|
487
|
+
excludeTypes: [],
|
|
488
|
+
nodes: [],
|
|
489
|
+
templates: [],
|
|
490
|
+
templateTypes: [],
|
|
491
|
+
types: [],
|
|
492
|
+
// Arguments from this method
|
|
493
|
+
...args,
|
|
494
|
+
}, 50);
|
|
495
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Pushing (create/patch) DisplayStyles into Optimizely CMS\n`));
|
|
496
|
+
// Listing all style files
|
|
497
|
+
const styleDefinitionFiles = await glob('./**/*.opti-style.json', {
|
|
498
|
+
cwd: opts.components,
|
|
499
|
+
});
|
|
500
|
+
// Helper function to read the files from disk
|
|
501
|
+
function readStyleFile(styleDefinitionFile) {
|
|
502
|
+
const filePath = path.normalize(path.join(opts.components, styleDefinitionFile));
|
|
503
|
+
const styleDefinition = tryReadJsonFile(filePath, debug);
|
|
504
|
+
return styleDefinition ? {
|
|
505
|
+
styleKey: styleDefinition.key,
|
|
506
|
+
styleDefinition,
|
|
507
|
+
styleFile: filePath
|
|
508
|
+
} : undefined;
|
|
509
|
+
}
|
|
510
|
+
// Helper function to filter the files read from disk
|
|
511
|
+
function filterStyleDefinition(data) {
|
|
512
|
+
if (!data || !data.styleKey)
|
|
513
|
+
return false;
|
|
514
|
+
const { styleKey } = data;
|
|
515
|
+
if (excludeTemplates.includes(styleKey)) {
|
|
516
|
+
if (debug)
|
|
517
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${styleKey} - explicitly excluded\n`));
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
// Skip if there a selectin and this template is not in it
|
|
521
|
+
if (templates.length > 0 && !templates.includes(styleKey)) {
|
|
522
|
+
if (debug)
|
|
523
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${styleKey} - not in selected list of templates\n`));
|
|
524
|
+
return false; // Only include defined styles, if any
|
|
525
|
+
}
|
|
526
|
+
return true;
|
|
527
|
+
}
|
|
528
|
+
// Processing each file
|
|
529
|
+
const results = await Promise.allSettled(styleDefinitionFiles
|
|
530
|
+
.map(readStyleFile)
|
|
531
|
+
.filter(filterStyleDefinition)
|
|
532
|
+
.map(async ({ styleKey, styleDefinition, styleFile }) => {
|
|
533
|
+
const displayTemplate = displayTemplates.find(dt => dt.key === styleKey);
|
|
534
|
+
// Confirm we're including
|
|
535
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} ${displayTemplate ? 'Updating' : 'Creating'} ${styleKey}\n`));
|
|
536
|
+
if (debug)
|
|
537
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} from ${styleFile || 'unknown source'}\n`));
|
|
538
|
+
// Patch or create the template
|
|
539
|
+
const newTemplate = await (displayTemplate ? (async () => {
|
|
540
|
+
const patch = generatePatch(displayTemplate, styleDefinition, {
|
|
541
|
+
readonlyFields: ['key', 'created', 'createdBy', 'lastModified', 'lastModifiedBy'],
|
|
542
|
+
});
|
|
543
|
+
if (!path || Object.entries(patch).length === 0)
|
|
544
|
+
return displayTemplate;
|
|
545
|
+
// @ts-expect-error: We're expecting an error here due to the CMS Logic not matching
|
|
546
|
+
// the OpenAPI Specification.
|
|
547
|
+
return client.displayTemplates.displayTemplatesPatch(styleKey, patch);
|
|
548
|
+
})() : client.displayTemplates.displayTemplatesCreate(displayTemplate));
|
|
549
|
+
// Validate the result
|
|
550
|
+
const unpatchedFields = generatePatch(newTemplate, styleDefinition, {
|
|
551
|
+
readonlyFields: ['key', 'created', 'createdBy', 'lastModified', 'lastModifiedBy']
|
|
552
|
+
});
|
|
553
|
+
if (unpatchedFields && Object.entries(unpatchedFields).length > 0)
|
|
554
|
+
throw new Error(`Creating/patching of displayTemplate failed, the following fields failed: ${getPatchFields(unpatchedFields).join('; ')}`);
|
|
555
|
+
// Return the template after Create/Patch
|
|
556
|
+
return newTemplate;
|
|
557
|
+
}));
|
|
558
|
+
const styles = new Table({
|
|
559
|
+
head: [
|
|
560
|
+
chalk.yellow(chalk.bold('Name')),
|
|
561
|
+
chalk.yellow(chalk.bold('Key')),
|
|
562
|
+
chalk.yellow(chalk.bold('Default')),
|
|
563
|
+
chalk.yellow(chalk.bold('Target')),
|
|
564
|
+
],
|
|
565
|
+
colWidths: [31, 20, 9, 20],
|
|
566
|
+
colAligns: ['left', 'left', 'center', 'left'],
|
|
567
|
+
});
|
|
568
|
+
results.forEach((result) => {
|
|
569
|
+
if (result.status === 'fulfilled') {
|
|
570
|
+
const tpl = result.value;
|
|
571
|
+
styles.push([
|
|
572
|
+
tpl.displayName,
|
|
573
|
+
tpl.key,
|
|
574
|
+
tpl.isDefault ? figures.tick : figures.cross,
|
|
575
|
+
tpl.contentType
|
|
576
|
+
? `${tpl.contentType} (C)`
|
|
577
|
+
: tpl.baseType
|
|
578
|
+
? `${tpl.baseType} (B)`
|
|
579
|
+
: `${tpl.nodeType} (N)`,
|
|
580
|
+
]);
|
|
581
|
+
}
|
|
582
|
+
else {
|
|
583
|
+
const error = result.reason;
|
|
584
|
+
process.stderr.write(`Error creating/updating style: ${error}`);
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
process.stdout.write(styles.toString() + '\n');
|
|
588
|
+
process.stdout.write(chalk.green(chalk.bold(figures.tick + ' Done')) + '\n');
|
|
589
|
+
},
|
|
590
|
+
};
|
|
591
|
+
function tryReadJsonFile(filePath, debug = false) {
|
|
592
|
+
try {
|
|
593
|
+
if (debug)
|
|
594
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Reading style definition from ${filePath}\n`));
|
|
595
|
+
return JSON.parse(fs.readFileSync(filePath, { encoding: 'utf-8' }));
|
|
596
|
+
}
|
|
597
|
+
catch (e) {
|
|
598
|
+
process.stderr.write(chalk.redBright(`${chalk.bold(figures.cross)} Error while reading ${filePath}\n`));
|
|
599
|
+
}
|
|
600
|
+
return undefined;
|
|
601
|
+
}
|
|
602
|
+
|
|
412
603
|
const StylesListCommand = {
|
|
413
604
|
command: "styles:list",
|
|
414
605
|
describe: "List Visual Builder style definitions from the CMS",
|
|
@@ -864,7 +1055,7 @@ const TypesPullCommand = {
|
|
|
864
1055
|
|
|
865
1056
|
const TypesPushCommand = {
|
|
866
1057
|
command: "types:push",
|
|
867
|
-
describe: "Push content type definition into Optimizely CMS (create /
|
|
1058
|
+
describe: "Push content type definition into Optimizely CMS (create / patch)",
|
|
868
1059
|
builder: (yargs) => {
|
|
869
1060
|
yargs.option('force', { alias: 'f', description: "Overwrite existing files", boolean: true, type: 'boolean', demandOption: false, default: false });
|
|
870
1061
|
yargs.option('excludeTypes', { alias: 'ect', description: "Exclude these content types", string: true, type: 'array', demandOption: false, default: [] });
|
|
@@ -877,7 +1068,7 @@ const TypesPushCommand = {
|
|
|
877
1068
|
const { _config: cfg, components: basePath, excludeBaseTypes, excludeTypes, baseTypes, types, force } = parseArgs(args);
|
|
878
1069
|
const client = createClient(cfg);
|
|
879
1070
|
// Find all type files
|
|
880
|
-
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Pushing (create/
|
|
1071
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Pushing (create/patch) Content Types into Optimizely CMS\n`));
|
|
881
1072
|
const typeDefinitionFiles = await glob("./**/*.opti-type.json", { cwd: basePath });
|
|
882
1073
|
// Read & filter all identified type files
|
|
883
1074
|
const typeDefinitions = typeDefinitionFiles.map(file => {
|
|
@@ -907,7 +1098,7 @@ const TypesPushCommand = {
|
|
|
907
1098
|
delete outType.features;
|
|
908
1099
|
if (outType.usage)
|
|
909
1100
|
delete outType.usage;
|
|
910
|
-
return client.contentTypes.
|
|
1101
|
+
return client.contentTypes.contentTypesPatch(outType.key, outType, force)
|
|
911
1102
|
.then(ct => { return { key: type.definition.key, type: ct, file: type.file }; })
|
|
912
1103
|
.catch(e => { return { key: type.definition.key, type: type.definition, file: type.file, error: e }; });
|
|
913
1104
|
}));
|
|
@@ -1090,11 +1281,11 @@ function renderProperties(contentType, generatedProps = [], forCms12 = false) {
|
|
|
1090
1281
|
const propName = isConflict ? `${contentType.key}${propKey}: ${propKey}` : propKey;
|
|
1091
1282
|
// Write the property
|
|
1092
1283
|
switch (propType) {
|
|
1093
|
-
case
|
|
1284
|
+
case PropertyDataType.ARRAY:
|
|
1094
1285
|
{
|
|
1095
1286
|
const typeData = typeProps[propKey];
|
|
1096
1287
|
switch (typeData.items.type) {
|
|
1097
|
-
case
|
|
1288
|
+
case PropertyDataType.INTEGER:
|
|
1098
1289
|
if (typeData.format == 'categorization') {
|
|
1099
1290
|
//fragmentFields.push(`${propName} { Id, Name, Description }`)
|
|
1100
1291
|
console.warn(chalk.redBright(`❗ Property ${propName} is a 'categorization', this is not supported. If you need to use this property, add it manually into the generated files`));
|
|
@@ -1102,16 +1293,16 @@ function renderProperties(contentType, generatedProps = [], forCms12 = false) {
|
|
|
1102
1293
|
else
|
|
1103
1294
|
fragmentFields.push(propName);
|
|
1104
1295
|
break;
|
|
1105
|
-
case
|
|
1296
|
+
case PropertyDataType.STRING:
|
|
1106
1297
|
fragmentFields.push(propName);
|
|
1107
1298
|
break;
|
|
1108
|
-
case
|
|
1299
|
+
case PropertyDataType.CONTENT:
|
|
1109
1300
|
if (contentType.baseType == 'page' || contentType.baseType == 'experience')
|
|
1110
1301
|
fragmentFields.push(`${propName} { ...${forCms12 ? 'PageIContentListItem' : 'BlockData'} }`);
|
|
1111
1302
|
else
|
|
1112
1303
|
fragmentFields.push(`${propName} { ...IContentListItem }`);
|
|
1113
1304
|
break;
|
|
1114
|
-
case
|
|
1305
|
+
case PropertyDataType.COMPONENT:
|
|
1115
1306
|
const componentType = typeData.items.contentType.split(':').pop();
|
|
1116
1307
|
switch (componentType) {
|
|
1117
1308
|
case 'link':
|
|
@@ -1126,7 +1317,7 @@ function renderProperties(contentType, generatedProps = [], forCms12 = false) {
|
|
|
1126
1317
|
}
|
|
1127
1318
|
}
|
|
1128
1319
|
break;
|
|
1129
|
-
case
|
|
1320
|
+
case PropertyDataType.CONTENT_REFERENCE:
|
|
1130
1321
|
fragmentFields.push(`${propName} { ...ReferenceData }`);
|
|
1131
1322
|
break;
|
|
1132
1323
|
default:
|
|
@@ -1135,7 +1326,7 @@ function renderProperties(contentType, generatedProps = [], forCms12 = false) {
|
|
|
1135
1326
|
}
|
|
1136
1327
|
break;
|
|
1137
1328
|
}
|
|
1138
|
-
case
|
|
1329
|
+
case PropertyDataType.STRING: {
|
|
1139
1330
|
const propDetails = typeProps[propKey];
|
|
1140
1331
|
switch (propDetails.format ?? "") {
|
|
1141
1332
|
case 'html':
|
|
@@ -1153,13 +1344,13 @@ function renderProperties(contentType, generatedProps = [], forCms12 = false) {
|
|
|
1153
1344
|
}
|
|
1154
1345
|
break;
|
|
1155
1346
|
}
|
|
1156
|
-
case
|
|
1347
|
+
case PropertyDataType.URL:
|
|
1157
1348
|
fragmentFields.push(forCms12 ? propName : `${propName} { ...LinkData }`);
|
|
1158
1349
|
break;
|
|
1159
|
-
case
|
|
1350
|
+
case PropertyDataType.CONTENT_REFERENCE:
|
|
1160
1351
|
fragmentFields.push(`${propName} { ...ReferenceData }`);
|
|
1161
1352
|
break;
|
|
1162
|
-
case
|
|
1353
|
+
case PropertyDataType.COMPONENT: {
|
|
1163
1354
|
const componentType = typeProps[propKey].contentType.split(':').pop();
|
|
1164
1355
|
if (componentType == "link") {
|
|
1165
1356
|
fragmentFields.push(`${propName} { ...LinkItemData }`);
|
|
@@ -1171,10 +1362,10 @@ function renderProperties(contentType, generatedProps = [], forCms12 = false) {
|
|
|
1171
1362
|
}
|
|
1172
1363
|
break;
|
|
1173
1364
|
}
|
|
1174
|
-
case
|
|
1365
|
+
case PropertyDataType.BINARY:
|
|
1175
1366
|
fragmentFields.push(propName);
|
|
1176
1367
|
break;
|
|
1177
|
-
case
|
|
1368
|
+
case PropertyDataType.CONTENT:
|
|
1178
1369
|
fragmentFields.push(`${propName} { ...${forCms12 ? 'PageIContentListItem' : 'BlockData'} }`);
|
|
1179
1370
|
break;
|
|
1180
1371
|
default:
|
|
@@ -2169,7 +2360,7 @@ const commands = [
|
|
|
2169
2360
|
CmsVersionCommand
|
|
2170
2361
|
];
|
|
2171
2362
|
|
|
2172
|
-
var version = "5.
|
|
2363
|
+
var version = "5.3.0";
|
|
2173
2364
|
var name = "opti-cms";
|
|
2174
2365
|
var APP = {
|
|
2175
2366
|
version: version,
|