@remkoj/optimizely-cms-cli 6.0.0-pre9 → 6.0.0-rc.1
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 +348 -38
- package/dist/index.js +910 -460
- package/dist/index.js.map +1 -1
- package/package.json +17 -15
package/dist/index.js
CHANGED
|
@@ -1,35 +1,52 @@
|
|
|
1
1
|
import { globSync, glob, globIterate } from 'glob';
|
|
2
2
|
import { config } from 'dotenv';
|
|
3
3
|
import { expand } from 'dotenv-expand';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import fs from 'node:fs';
|
|
4
6
|
import { readPartialEnvConfig, createClient, OptiCmsVersion, ContentRoots } from '@remkoj/optimizely-cms-api';
|
|
5
7
|
import yargs from 'yargs';
|
|
6
8
|
import chalk from 'chalk';
|
|
7
|
-
import path from 'node:path';
|
|
8
|
-
import fs from 'node:fs';
|
|
9
9
|
import figures from 'figures';
|
|
10
10
|
import Table from 'cli-table3';
|
|
11
|
+
import slugify from '@sindresorhus/slugify';
|
|
12
|
+
import { diff } from 'deep-object-diff';
|
|
11
13
|
import fsAsync from 'node:fs/promises';
|
|
12
14
|
import { input, select, confirm } from '@inquirer/prompts';
|
|
13
15
|
import createDeepMerge from '@fastify/deepmerge';
|
|
14
16
|
import { Ajv } from 'ajv';
|
|
15
17
|
import addFormats from 'ajv-formats';
|
|
16
|
-
import
|
|
18
|
+
import { DocumentGenerator, PropertyCollisionTracker } from '@remkoj/optimizely-graph-functions/generate';
|
|
17
19
|
import deepEqual from 'fast-deep-equal';
|
|
18
20
|
|
|
21
|
+
function getProjectDir() {
|
|
22
|
+
let testPath = process.cwd();
|
|
23
|
+
let hasPackageJson = false;
|
|
24
|
+
do {
|
|
25
|
+
hasPackageJson = fs.statSync(path.join(testPath, 'package.json'), { throwIfNoEntry: false })?.isFile() ?? false;
|
|
26
|
+
if (!hasPackageJson)
|
|
27
|
+
testPath = path.normalize(path.join(testPath, '..'));
|
|
28
|
+
} while (!hasPackageJson && testPath.length > 2);
|
|
29
|
+
if (!hasPackageJson) {
|
|
30
|
+
throw new Error('No package.json found!');
|
|
31
|
+
}
|
|
32
|
+
return testPath;
|
|
33
|
+
}
|
|
19
34
|
/**
|
|
20
35
|
* Prepare the application context, by parsing the .env files in the main
|
|
21
36
|
* application directory.
|
|
22
37
|
*
|
|
23
38
|
* @returns A string array with the files processed
|
|
24
39
|
*/
|
|
25
|
-
function prepare() {
|
|
26
|
-
const
|
|
27
|
-
const envFiles =
|
|
40
|
+
function prepare(pDir) {
|
|
41
|
+
const projectDir = pDir ?? getProjectDir();
|
|
42
|
+
const envFiles = globSync(".env*", { cwd: projectDir })
|
|
43
|
+
.sort((a, b) => b.length - a.length)
|
|
44
|
+
.filter(n => n == ".env" || n == ".env.local" || (process.env.NODE_ENV && n == `.env.${process.env.NODE_ENV}.local`));
|
|
28
45
|
expand(config({ path: envFiles, debug: false, quiet: true }));
|
|
29
46
|
return envFiles;
|
|
30
47
|
}
|
|
31
48
|
|
|
32
|
-
function createOptiCmsApp(scriptName, version, epilogue, envFiles) {
|
|
49
|
+
function createOptiCmsApp(scriptName, version, epilogue, envFiles, projectDir) {
|
|
33
50
|
if (envFiles) {
|
|
34
51
|
process.stdout.write(chalk.bold(`✅ Loaded environment files:`) + "\n");
|
|
35
52
|
envFiles.forEach(envFile => {
|
|
@@ -45,11 +62,12 @@ function createOptiCmsApp(scriptName, version, epilogue, envFiles) {
|
|
|
45
62
|
console.error(chalk.redBright(`${chalk.bold("[ERROR]:")} Error processing environment variables: ${e.message}`));
|
|
46
63
|
process.exit(1);
|
|
47
64
|
}
|
|
65
|
+
const defaultPath = projectDir ?? process.cwd();
|
|
48
66
|
return yargs(process.argv)
|
|
49
67
|
.scriptName(scriptName)
|
|
50
68
|
.version(version)
|
|
51
69
|
.usage('$0 <cmd> [args]')
|
|
52
|
-
.option("path", { alias: "p", description: "Application root folder", string: true, type: "string", demandOption: false, default:
|
|
70
|
+
.option("path", { alias: "p", description: "Application root folder", string: true, type: "string", demandOption: false, default: defaultPath })
|
|
53
71
|
.option("components", { alias: "c", description: "Path to components folder", string: true, type: "string", demandOption: false, default: "./src/components/cms" })
|
|
54
72
|
.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 })
|
|
55
73
|
.option("client_id", { alias: "ci", description: "API Client ID", string: true, type: "string", demandOption: isDemanded(config.clientId), default: config.clientId })
|
|
@@ -133,84 +151,6 @@ function getCmsIntegrationApiOptions(args) {
|
|
|
133
151
|
return parseArgs(args)._config;
|
|
134
152
|
}
|
|
135
153
|
|
|
136
|
-
const StylesPushCommand = {
|
|
137
|
-
command: "styles:push",
|
|
138
|
-
describe: "Push Visual Builder style definitions into the CMS (create/replace)",
|
|
139
|
-
builder: (yargs) => {
|
|
140
|
-
yargs.option('excludeTemplates', { alias: 'e', description: "Exclude these templates", string: true, type: 'array', demandOption: false, default: [] });
|
|
141
|
-
yargs.option("templates", { alias: 't', description: "Select only these templates", string: true, type: 'array', demandOption: false, default: [] });
|
|
142
|
-
return yargs;
|
|
143
|
-
},
|
|
144
|
-
handler: async (args) => {
|
|
145
|
-
const { _config: cfg, excludeTemplates, templates, ...opts } = parseArgs(args);
|
|
146
|
-
const client = createCmsClient(args);
|
|
147
|
-
if (client.runtimeCmsVersion == OptiCmsVersion.CMS12) {
|
|
148
|
-
process.stdout.write(chalk.gray(`${figures.cross} Styles are not supported on CMS12\n`));
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Pushing (create/replace) DisplayStyles into Optimizely CMS\n`));
|
|
152
|
-
const styleDefinitionFiles = await glob("./**/*.opti-style.json", {
|
|
153
|
-
cwd: opts.components
|
|
154
|
-
});
|
|
155
|
-
const results = (await Promise.all(styleDefinitionFiles.map(async (styleDefinitionFile) => {
|
|
156
|
-
const filePath = path.normalize(path.join(opts.components, styleDefinitionFile));
|
|
157
|
-
const styleDefinition = tryReadJsonFile(filePath, cfg.debug);
|
|
158
|
-
const styleKey = styleDefinition.key;
|
|
159
|
-
if (!styleKey) {
|
|
160
|
-
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`));
|
|
161
|
-
return undefined;
|
|
162
|
-
}
|
|
163
|
-
if (excludeTemplates.includes(styleKey))
|
|
164
|
-
return undefined; // Skip excluded styles
|
|
165
|
-
if (templates.length > 0 && !templates.includes(styleKey))
|
|
166
|
-
return undefined; // Only include defined styles, if any
|
|
167
|
-
if (cfg.debug)
|
|
168
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Pushing: ${styleKey}\n`));
|
|
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 }));
|
|
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
154
|
/**
|
|
215
155
|
* This file contains tools that allow using the project that we're targeting
|
|
216
156
|
*/
|
|
@@ -223,8 +163,12 @@ function isNotNullOrUndefined(i) {
|
|
|
223
163
|
* @returns
|
|
224
164
|
*/
|
|
225
165
|
function getContentTypePaths(contentType, basePath, createFolder = false, debug = false) {
|
|
226
|
-
const baseTypeSlug =
|
|
227
|
-
|
|
166
|
+
const baseTypeSlug = keyToSlug(contentType.baseType, {
|
|
167
|
+
defaultKey: 'global',
|
|
168
|
+
stripLeadingUnderscore: true
|
|
169
|
+
});
|
|
170
|
+
const typeSlug = keyToSlug(contentType.key);
|
|
171
|
+
const typePath = path.join(basePath, baseTypeSlug, typeSlug);
|
|
228
172
|
if (createFolder) {
|
|
229
173
|
if (!fs.existsSync(typePath)) {
|
|
230
174
|
fs.mkdirSync(typePath, { recursive: true });
|
|
@@ -235,10 +179,10 @@ function getContentTypePaths(contentType, basePath, createFolder = false, debug
|
|
|
235
179
|
if (!fs.statSync(typePath).isDirectory())
|
|
236
180
|
throw new Error(`The folder ${typePath} for Content-Type ${contentType.key} exists, but is not a directory!`);
|
|
237
181
|
}
|
|
238
|
-
const typeFile = path.join(typePath, `${
|
|
239
|
-
const fragmentFile = path.join(typePath, `${
|
|
240
|
-
const propertyFragmentFile = path.join(typePath, `${
|
|
241
|
-
const queryFile = path.join(typePath,
|
|
182
|
+
const typeFile = path.join(typePath, `${typeSlug}.opti-type.json`);
|
|
183
|
+
const fragmentFile = path.join(typePath, `${typeSlug}.${baseTypeSlug}.graphql`);
|
|
184
|
+
const propertyFragmentFile = path.join(typePath, `${typeSlug}.property.graphql`);
|
|
185
|
+
const queryFile = path.join(typePath, `${typeSlug}.query.graphql`);
|
|
242
186
|
const componentFile = path.join(typePath, `index.tsx`);
|
|
243
187
|
return {
|
|
244
188
|
type: contentType.key,
|
|
@@ -251,16 +195,167 @@ function getContentTypePaths(contentType, basePath, createFolder = false, debug
|
|
|
251
195
|
queryFile
|
|
252
196
|
};
|
|
253
197
|
}
|
|
198
|
+
function getStyleFilePathsSync(displayTemplate, contentBaseType = '_Component', basePath = './src/components/cms', createFolder = false) {
|
|
199
|
+
const displayTemplateType = getDisplayTemplateType(displayTemplate);
|
|
200
|
+
let target;
|
|
201
|
+
let groupPath;
|
|
202
|
+
switch (displayTemplateType) {
|
|
203
|
+
case 'base':
|
|
204
|
+
groupPath = path.join(keyToSlug(displayTemplate.baseType, { stripLeadingUnderscore: true }), 'styles');
|
|
205
|
+
target = displayTemplate.baseType;
|
|
206
|
+
break;
|
|
207
|
+
case 'node':
|
|
208
|
+
groupPath = path.join('nodes', keyToSlug(displayTemplate.nodeType));
|
|
209
|
+
target = displayTemplate.nodeType;
|
|
210
|
+
break;
|
|
211
|
+
case 'component':
|
|
212
|
+
const baseType = contentBaseType;
|
|
213
|
+
target = displayTemplate.contentType;
|
|
214
|
+
groupPath = path.join(keyToSlug(baseType, { stripLeadingUnderscore: true }), keyToSlug(displayTemplate.contentType, { stripLeadingGroup: true }));
|
|
215
|
+
break;
|
|
216
|
+
default:
|
|
217
|
+
throw new Error(`Unsupported DisplayTemplate target for ${displayTemplate.key}`);
|
|
218
|
+
}
|
|
219
|
+
const displayTemplateKeySlug = keyToSlug(displayTemplate.key ?? 'displayTemplate', { stripLeadingGroup: true });
|
|
220
|
+
const groupFolder = path.join(basePath, groupPath);
|
|
221
|
+
const styleFolder = displayTemplateType === 'component' ? groupFolder : path.join(groupFolder, displayTemplateKeySlug);
|
|
222
|
+
if (createFolder && !fs.existsSync(styleFolder))
|
|
223
|
+
fs.mkdirSync(styleFolder, { recursive: true });
|
|
224
|
+
return {
|
|
225
|
+
styleFile: path.join(styleFolder, displayTemplateKeySlug + '.opti-style.json'),
|
|
226
|
+
helperFile: path.join(groupFolder, 'displayTemplates.ts'),
|
|
227
|
+
identifier: `${displayTemplateType}/${target}`,
|
|
228
|
+
helperFolder: groupFolder,
|
|
229
|
+
styleFolder
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
async function getStyleFilePaths(definition, opts) {
|
|
233
|
+
let defintionBaseType = undefined;
|
|
234
|
+
if (definition.contentType && !opts.contentBaseType && opts.client) {
|
|
235
|
+
const contentType = await opts.client.contentTypesGet({ path: { key: definition.contentType } }).catch(() => undefined);
|
|
236
|
+
defintionBaseType = contentType?.baseType;
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
defintionBaseType = opts.contentBaseType;
|
|
240
|
+
}
|
|
241
|
+
return getStyleFilePathsSync(definition, defintionBaseType, opts.basePath, opts.createFolder);
|
|
242
|
+
}
|
|
254
243
|
/**
|
|
244
|
+
* Simple logic to create path slugs for storing ContentType related files on
|
|
245
|
+
* disk.
|
|
255
246
|
*
|
|
256
|
-
* @param
|
|
247
|
+
* @param typeKey
|
|
248
|
+
* @param stripLeadingUnderscore
|
|
257
249
|
* @returns
|
|
258
250
|
*/
|
|
259
|
-
function
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
251
|
+
function keyToSlug(typeKey, options = {}) {
|
|
252
|
+
// Destruct the configuration into the parts we need
|
|
253
|
+
const { stripLeadingGroup = true, defaultKey = 'unknown', stripLeadingUnderscore = false, ...slugifyBaseConfig } = options;
|
|
254
|
+
const slugifyConfig = {
|
|
255
|
+
...slugifyBaseConfig,
|
|
256
|
+
preserveLeadingUnderscore: !stripLeadingUnderscore
|
|
257
|
+
};
|
|
258
|
+
// First make sure we have a valid input type
|
|
259
|
+
if (!(typeof typeKey === 'string' || typeKey === undefined || typeKey === null))
|
|
260
|
+
throw new Error(`Invalid typeKey provided, expected an optional string, received a value of type ${typeof typeKey}`);
|
|
261
|
+
// Then, take the prefix out, if any and required
|
|
262
|
+
const toSlugify = stripLeadingGroup && typeKey?.indexOf(":") >= 0 ? typeKey.split(":", 2).at(1) : typeKey;
|
|
263
|
+
// Finally slugify the result
|
|
264
|
+
return slugify(toSlugify ?? defaultKey, slugifyConfig);
|
|
265
|
+
}
|
|
266
|
+
function getDisplayTemplateType(displayTemplate) {
|
|
267
|
+
const templateType = displayTemplate.baseType ? 'base' : displayTemplate.nodeType ? 'node' : displayTemplate.contentType ? 'component' : 'unknown';
|
|
268
|
+
return templateType;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Test if the value must be included in a set, by defining allowed values and blocked values. A value
|
|
273
|
+
* is considered be eligible to be included when it's both allowed an not disallowed.
|
|
274
|
+
*
|
|
275
|
+
* @param value The value to test
|
|
276
|
+
* @param allow The list of allowed values, when not provided, or and empty array, all
|
|
277
|
+
* values will be allowed.
|
|
278
|
+
* @param disallow The list of explicitly disallowed values, yielding an "all allowed but
|
|
279
|
+
* these" operation
|
|
280
|
+
* @param compareSlugified When set to `true`, and after test with `includes()` on the allow and
|
|
281
|
+
* disallow did not yield a match, will try to match by converting the
|
|
282
|
+
* values to string (using the `.toString()` method), then slugify them with
|
|
283
|
+
* the `keyToSlug` method and then comparing the outcomes.
|
|
284
|
+
* @param slugifyOptions The options to provide to the `keyToSlug` method, this will be ignored
|
|
285
|
+
* unless you set the `compareSlugified` parameter to `true`
|
|
286
|
+
* @returns If the value should be included in the result, based upon the `allow` and
|
|
287
|
+
* `disallow` configuration.
|
|
288
|
+
*
|
|
289
|
+
* @see {@link keyToSlug}
|
|
290
|
+
*/
|
|
291
|
+
function shouldInclude(value, allow, disallow, compareSlugified = false, slugifyOptions) {
|
|
292
|
+
// Do not include null or undefined values
|
|
293
|
+
if (value === null || value === undefined)
|
|
294
|
+
return false;
|
|
295
|
+
// Item is allowed when either allow is not set, an empty array or has the value
|
|
296
|
+
const isAllowed = !Array.isArray(allow) || allow.length === 0 || allow.includes(value) || (compareSlugified && allow.some(av => keyToSlug(av.toString(), slugifyOptions) === keyToSlug(value.toString(), slugifyOptions)));
|
|
297
|
+
// Item is disallowed when and the array is set and includes the value
|
|
298
|
+
const isDisallowed = Array.isArray(disallow) && (disallow.includes(value) || (compareSlugified && disallow.some(av => keyToSlug(av.toString(), slugifyOptions) === keyToSlug(value.toString(), slugifyOptions))));
|
|
299
|
+
// Return the outcome
|
|
300
|
+
return isAllowed && !isDisallowed;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Test if the provided content type is a contract
|
|
304
|
+
*
|
|
305
|
+
* @param contentType The ContentType to test
|
|
306
|
+
* @returns `true` if the ContentType is a contract, `false` otherwise
|
|
307
|
+
* @see {@link IntegrationApi.ContentType}
|
|
308
|
+
*/
|
|
309
|
+
function isContract(contentType) {
|
|
310
|
+
if (typeof contentType !== 'object' || contentType === null)
|
|
311
|
+
return false;
|
|
312
|
+
return contentType.isContract || contentType.source === 'globalcontract';
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Test if the provided content type comes from the Graph content source (e.g. external content)
|
|
316
|
+
*
|
|
317
|
+
* @param contentType The ContentType to test
|
|
318
|
+
* @returns `true` if the ContentType is a graph type, `false` otherwise
|
|
319
|
+
* @see {@link IntegrationApi.ContentType}
|
|
320
|
+
*/
|
|
321
|
+
function isGraphType(contentType) {
|
|
322
|
+
if (typeof contentType !== 'object' || contentType === null)
|
|
323
|
+
return false;
|
|
324
|
+
return (contentType.key && typeof (contentType.key) === 'string' && contentType.key.startsWith('graph:')) || contentType.source === 'graph';
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Test if the provided content type is a folder, which is a structural element for editors, but
|
|
328
|
+
* does not represent anything significant when working with ContentType
|
|
329
|
+
*
|
|
330
|
+
* @param contentType The ContentType to test
|
|
331
|
+
* @returns `true` if the ContentType is a folder, `false` otherwise
|
|
332
|
+
* @see {@link IntegrationApi.ContentType}
|
|
333
|
+
*/
|
|
334
|
+
function isFolder(contentType) {
|
|
335
|
+
if (typeof contentType !== 'object' || contentType === null)
|
|
336
|
+
return false;
|
|
337
|
+
return contentType.baseType === '_folder';
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Test if the provided content type is a system type
|
|
341
|
+
*
|
|
342
|
+
* @param contentType The ContentType to test
|
|
343
|
+
* @returns `true` if the ContentType is a folder, `false` otherwise
|
|
344
|
+
* @see {@link IntegrationApi.ContentType}
|
|
345
|
+
*/
|
|
346
|
+
function isSystemType(contentType) {
|
|
347
|
+
if (typeof contentType !== 'object' || contentType === null)
|
|
348
|
+
return false;
|
|
349
|
+
return contentType.source === 'system';
|
|
350
|
+
}
|
|
351
|
+
function isNonEmptyArray(toTest) {
|
|
352
|
+
return Array.isArray(toTest) && toTest.length > 0;
|
|
353
|
+
}
|
|
354
|
+
function isEmptyArray(toTest) {
|
|
355
|
+
return toTest === null || !Array.isArray(toTest) || toTest.length === 0;
|
|
356
|
+
}
|
|
357
|
+
function isDefined(toTest) {
|
|
358
|
+
return toTest !== null && toTest !== undefined;
|
|
264
359
|
}
|
|
265
360
|
|
|
266
361
|
const ContentTypesArgsDefaults = {
|
|
@@ -283,28 +378,41 @@ async function getContentTypes(client, args, pageSize = 25, allowSystem = false,
|
|
|
283
378
|
const allContentTypes = [];
|
|
284
379
|
const filteredContentTypes = [];
|
|
285
380
|
for await (const contentType of getAllContentTypes(client, cfg.debug, pageSize)) {
|
|
286
|
-
// Skip
|
|
287
|
-
if (!all && contentType
|
|
381
|
+
// Skip contracts by default as they're abstract classes and cannot be used to directly store data.
|
|
382
|
+
if (!all && isContract(contentType)) {
|
|
383
|
+
if (cfg.debug)
|
|
384
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.key || contentType.displayName || "unnamed type"} as it is a contract and cannot be instantiated directly, use --all to include\n`));
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
// Skip content types mapped against Graph data, these should be used with their source type in Graph,
|
|
388
|
+
// not the reference in CMS
|
|
389
|
+
if (!all && isGraphType(contentType)) {
|
|
390
|
+
if (cfg.debug)
|
|
391
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.key} as it is a reference to external data in Optimizely Graph, use --all to include\n`));
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
// Skip folder types as these are non-data carrying types in the instance.
|
|
395
|
+
if (!all && isFolder(contentType)) {
|
|
288
396
|
if (cfg.debug)
|
|
289
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.key} as it is a
|
|
397
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.key} as it is a folder, use --all to include\n`));
|
|
290
398
|
continue;
|
|
291
399
|
}
|
|
292
400
|
// Build the unfiltered array
|
|
293
401
|
allContentTypes.push(contentType);
|
|
294
402
|
// Skip based upon base type filters
|
|
295
|
-
if (!shouldInclude(
|
|
403
|
+
if (!shouldInclude(contentType.baseType, baseTypes, excludeBaseTypes, true)) {
|
|
296
404
|
if (cfg.debug)
|
|
297
405
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.key} as it has a restricted base type: ${contentType.baseType}\n`));
|
|
298
406
|
continue;
|
|
299
407
|
}
|
|
300
408
|
// Skip based upon type filters
|
|
301
|
-
if (!shouldInclude(contentType.key, types, excludeTypes)) {
|
|
409
|
+
if (!shouldInclude(contentType.key, types, excludeTypes, true)) {
|
|
302
410
|
if (cfg.debug)
|
|
303
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.key} as it
|
|
411
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.key} as it is a restricted type (${types.join(', ')})(${excludeTypes.join(', ')})\n`));
|
|
304
412
|
continue;
|
|
305
413
|
}
|
|
306
414
|
// Skip based upon system filter
|
|
307
|
-
if (
|
|
415
|
+
if (!allowSystem && isSystemType(contentType)) {
|
|
308
416
|
if (cfg.debug)
|
|
309
417
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Content-Type ${contentType.key} due to it being a system type\n`));
|
|
310
418
|
continue;
|
|
@@ -322,13 +430,6 @@ async function getContentTypes(client, args, pageSize = 25, allowSystem = false,
|
|
|
322
430
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Applied content type filters, reduced from ${allContentTypes.length} to ${filteredContentTypes.length} items\n`));
|
|
323
431
|
return { all: allContentTypes, contentTypes: filteredContentTypes };
|
|
324
432
|
}
|
|
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
433
|
/**
|
|
333
434
|
* Retrieve all content types as an Async Generator, allowing processing of entries whilest they are being loaded from the CMS instance.
|
|
334
435
|
*
|
|
@@ -376,50 +477,60 @@ const stylesBuilder = yargs => {
|
|
|
376
477
|
return newArgs;
|
|
377
478
|
};
|
|
378
479
|
async function getStyles(client, args, pageSize = 25) {
|
|
379
|
-
|
|
380
|
-
return { all: [], styles: [] };
|
|
381
|
-
const { _config: cfg, excludeBaseTypes, excludeTypes, excludeNodeTypes, excludeTemplates, baseTypes, types, nodes, templates, templateTypes } = parseArgs(args);
|
|
480
|
+
const { _config: cfg, excludeBaseTypes: disallowBaseTypes, excludeTypes: disallowTypes, excludeNodeTypes: disallowNodeTypes, excludeTemplates: disallowTemplates, baseTypes: allowBaseTypes, types: allowTypes, nodes: allowNodeTypes, templates: allowTemplates, templateTypes: baseAllowTemplateTypes } = parseArgs(args);
|
|
382
481
|
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Pulling Style-Definitions from Optimizely CMS\n`));
|
|
482
|
+
const allowTemplateTypes = [];
|
|
483
|
+
if (!Array.isArray(baseAllowTemplateTypes) || baseAllowTemplateTypes.length === 0) {
|
|
484
|
+
if (isNonEmptyArray(allowBaseTypes)) {
|
|
485
|
+
console.log('BT', allowBaseTypes);
|
|
486
|
+
if (cfg.debug)
|
|
487
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Base picking filter is active, adjusting template type filter to pick templates targeting a base type\n`));
|
|
488
|
+
allowTemplateTypes.push('base');
|
|
489
|
+
}
|
|
490
|
+
if (isNonEmptyArray(allowNodeTypes)) {
|
|
491
|
+
if (cfg.debug)
|
|
492
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Node type filter active, adjusting template type filter\n`));
|
|
493
|
+
allowTemplateTypes.push('node');
|
|
494
|
+
}
|
|
495
|
+
if (isNonEmptyArray(allowTypes)) {
|
|
496
|
+
if (cfg.debug)
|
|
497
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Component type filter active, adjusting template type filter\n`));
|
|
498
|
+
allowTemplateTypes.push('component');
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
allowTemplateTypes.push(...baseAllowTemplateTypes);
|
|
503
|
+
}
|
|
383
504
|
const allDisplayTemplates = [];
|
|
384
505
|
const filteredDisplayTemplates = [];
|
|
385
506
|
for await (const displayTemplate of getAllStyles(client, cfg.debug, pageSize)) {
|
|
386
507
|
allDisplayTemplates.push(displayTemplate);
|
|
387
|
-
const templateType = displayTemplate
|
|
388
|
-
if (
|
|
508
|
+
const templateType = getDisplayTemplateType(displayTemplate);
|
|
509
|
+
if (!shouldInclude(displayTemplate.key, allowTemplates, disallowTemplates)) {
|
|
389
510
|
if (cfg.debug)
|
|
390
511
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${displayTemplate.key} - Style defintion key filtering active\n`));
|
|
391
512
|
continue;
|
|
392
513
|
}
|
|
393
|
-
if (
|
|
514
|
+
if (!shouldInclude(templateType, allowTemplateTypes)) {
|
|
394
515
|
if (cfg.debug)
|
|
395
516
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${displayTemplate.key} - Style type filtering is active\n`));
|
|
396
517
|
continue;
|
|
397
518
|
}
|
|
398
|
-
if (
|
|
519
|
+
if (templateType === 'base' && !shouldInclude(displayTemplate.baseType, allowBaseTypes, disallowBaseTypes)) {
|
|
399
520
|
if (cfg.debug)
|
|
400
521
|
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
522
|
continue;
|
|
402
523
|
}
|
|
403
|
-
if (
|
|
524
|
+
if (templateType === 'component' && !shouldInclude(displayTemplate.contentType, allowTypes, disallowTypes)) {
|
|
404
525
|
if (cfg.debug)
|
|
405
526
|
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
527
|
continue;
|
|
407
528
|
}
|
|
408
|
-
if (templateType
|
|
409
|
-
if (cfg.debug)
|
|
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;
|
|
412
|
-
}
|
|
413
|
-
if (displayTemplate.nodeType && isExcluded(displayTemplate.nodeType, excludeNodeTypes, nodes)) {
|
|
529
|
+
if (templateType === 'node' && !shouldInclude(displayTemplate.nodeType, allowNodeTypes, disallowNodeTypes)) {
|
|
414
530
|
if (cfg.debug)
|
|
415
531
|
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
532
|
continue;
|
|
417
533
|
}
|
|
418
|
-
if (templateType != 'node' && nodes.length > 0) {
|
|
419
|
-
if (cfg.debug)
|
|
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;
|
|
422
|
-
}
|
|
423
534
|
filteredDisplayTemplates.push(displayTemplate);
|
|
424
535
|
}
|
|
425
536
|
if (cfg.debug)
|
|
@@ -430,8 +541,6 @@ async function getStyles(client, args, pageSize = 25) {
|
|
|
430
541
|
};
|
|
431
542
|
}
|
|
432
543
|
async function* getAllStyles(client, debug = false, pageSize = 5) {
|
|
433
|
-
if (client.runtimeCmsVersion == OptiCmsVersion.CMS12)
|
|
434
|
-
return;
|
|
435
544
|
let requestPageSize = pageSize;
|
|
436
545
|
let requestPageIndex = 0;
|
|
437
546
|
let totalItemCount = 0;
|
|
@@ -459,26 +568,268 @@ async function* getAllStyles(client, debug = false, pageSize = 5) {
|
|
|
459
568
|
}
|
|
460
569
|
} while (requestPageIndex < totalPages);
|
|
461
570
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
571
|
+
class TypeFilesList extends Map {
|
|
572
|
+
getDisplayTemplateByKey(displayTemplateKey) {
|
|
573
|
+
for (const groupKey of this.keys()) {
|
|
574
|
+
const templates = this.get(groupKey)?.templates || [];
|
|
575
|
+
const displayTemplate = templates.find(x => x.key === displayTemplateKey);
|
|
576
|
+
if (displayTemplate)
|
|
577
|
+
return displayTemplate.data;
|
|
578
|
+
}
|
|
579
|
+
return undefined;
|
|
580
|
+
}
|
|
581
|
+
getDisplayTemplatePathsByKey(displayTemplateKey) {
|
|
582
|
+
for (const identifier of this.keys()) {
|
|
583
|
+
const groupInfo = this.get(identifier);
|
|
584
|
+
const templates = groupInfo?.templates || [];
|
|
585
|
+
const helperFile = groupInfo?.filePath;
|
|
586
|
+
const helperFolder = groupInfo?.fileFolder;
|
|
587
|
+
const info = templates.find(x => x.key === displayTemplateKey);
|
|
588
|
+
if (info)
|
|
589
|
+
return {
|
|
590
|
+
styleFile: info.file,
|
|
591
|
+
styleFolder: info.folder,
|
|
592
|
+
identifier,
|
|
593
|
+
helperFile,
|
|
594
|
+
helperFolder
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
return undefined;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
async function toTypeFilesList(displayTemplates, client, basePath) {
|
|
601
|
+
if (isNonEmptyArray(displayTemplates)) {
|
|
602
|
+
// Get all content types we need to get the type file lists
|
|
603
|
+
const targetContentTypes = (await Promise.allSettled(displayTemplates.map(async (displayTemplate) => {
|
|
604
|
+
if (!displayTemplate.contentType)
|
|
605
|
+
return undefined;
|
|
606
|
+
return client.contentTypesGet({ path: { key: displayTemplate.contentType } });
|
|
607
|
+
}))).map(x => x.status == 'fulfilled' ? x.value : undefined).filter(isDefined);
|
|
608
|
+
// Simple helper to get the base type from the list
|
|
609
|
+
function getBaseTypeOf(contentTypeKey) {
|
|
610
|
+
if (!contentTypeKey)
|
|
611
|
+
return undefined;
|
|
612
|
+
return targetContentTypes.find(x => x.key === contentTypeKey)?.baseType;
|
|
613
|
+
}
|
|
614
|
+
// Now reduce the list into the Map we need
|
|
615
|
+
return displayTemplates.reduce((aggregator, displayTemplate) => {
|
|
616
|
+
const contentTypeBaseType = getBaseTypeOf(displayTemplate.contentType);
|
|
617
|
+
const { identifier, helperFile, helperFolder, styleFile, styleFolder } = getStyleFilePathsSync(displayTemplate, contentTypeBaseType, basePath, true);
|
|
618
|
+
const info = aggregator.get(identifier) ?? { filePath: helperFile, fileFolder: helperFolder, templates: [] };
|
|
619
|
+
info.templates.push({ key: displayTemplate.key, file: styleFile, data: displayTemplate, folder: styleFolder });
|
|
620
|
+
aggregator.set(identifier, info);
|
|
621
|
+
return aggregator;
|
|
622
|
+
}, new TypeFilesList());
|
|
623
|
+
}
|
|
624
|
+
return new TypeFilesList();
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function normalizeMergePatch(value, omitKeys, requiredKeys = [], requiredSource) {
|
|
628
|
+
if (value === undefined || value === null)
|
|
629
|
+
return null;
|
|
630
|
+
if (Array.isArray(value))
|
|
631
|
+
return value.map(item => normalizeMergePatch(item, omitKeys));
|
|
632
|
+
if (typeof value !== 'object')
|
|
633
|
+
return value;
|
|
634
|
+
const output = Object.entries(value).reduce((acc, [key, entry]) => {
|
|
635
|
+
if (!omitKeys.includes(key))
|
|
636
|
+
acc[key] = normalizeMergePatch(entry, omitKeys);
|
|
637
|
+
return acc;
|
|
638
|
+
}, {});
|
|
639
|
+
const withRequiredKeys = requiredKeys.reduce((acc, key) => {
|
|
640
|
+
if (requiredSource && !Object.keys(acc).includes(key))
|
|
641
|
+
acc[key] = requiredSource[key];
|
|
642
|
+
return acc;
|
|
643
|
+
}, output);
|
|
644
|
+
return withRequiredKeys;
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Computes a JSON Merge Patch (RFC 7396) that transforms `currentValue` into `newValue`.
|
|
648
|
+
*
|
|
649
|
+
* The resulting patch can be sent directly as the request body of an `application/merge-patch+json`
|
|
650
|
+
* request. Changed and added properties are included with their new value; removed properties are
|
|
651
|
+
* included with a `null` value (as required by RFC 7396); unchanged properties are omitted entirely.
|
|
652
|
+
*
|
|
653
|
+
* @see https://datatracker.ietf.org/doc/html/rfc7396
|
|
654
|
+
*
|
|
655
|
+
* @example Basic usage
|
|
656
|
+
* ```ts
|
|
657
|
+
* const current = { name: 'Alice', age: 30, role: 'user' }
|
|
658
|
+
* const updated = { name: 'Alice', age: 31 }
|
|
659
|
+
*
|
|
660
|
+
* generatePatch(current, updated)
|
|
661
|
+
* // => { age: 31, role: null }
|
|
662
|
+
* // `age` is replaced, `role` is removed (null), `name` is unchanged (omitted)
|
|
663
|
+
* ```
|
|
664
|
+
*
|
|
665
|
+
* @example Excluding keys from the patch
|
|
666
|
+
* ```ts
|
|
667
|
+
* const current = { id: '123', name: 'Alice', version: 1 }
|
|
668
|
+
* const updated = { id: '123', name: 'Bob', version: 2 }
|
|
669
|
+
*
|
|
670
|
+
* generatePatch(current, updated, { readonlyFields: ['id', 'version'] })
|
|
671
|
+
* // => { name: 'Bob' }
|
|
672
|
+
* // `id` and `version` are excluded even though they are present in the diff
|
|
673
|
+
* ```
|
|
674
|
+
*
|
|
675
|
+
* @example Always include required fields
|
|
676
|
+
* ```ts
|
|
677
|
+
* const current = { id: '123', name: 'Alice', version: 1 }
|
|
678
|
+
* const updated = { id: '123', name: 'Alice', version: 2 }
|
|
679
|
+
*
|
|
680
|
+
* generatePatch(current, updated, { requiredFields: ['id'] })
|
|
681
|
+
* // => { id: '123', version: 2 }
|
|
682
|
+
* // `id` is included even though it did not change
|
|
683
|
+
* ```
|
|
684
|
+
*
|
|
685
|
+
* @example Nested objects
|
|
686
|
+
* ```ts
|
|
687
|
+
* const current = { address: { city: 'Amsterdam', zip: '1000AA' } }
|
|
688
|
+
* const updated = { address: { city: 'Utrecht' } }
|
|
689
|
+
*
|
|
690
|
+
* generatePatch(current, updated)
|
|
691
|
+
* // => { address: { city: 'Utrecht', zip: null } }
|
|
692
|
+
* ```
|
|
693
|
+
*
|
|
694
|
+
* @param currentValue - The current state of the resource.
|
|
695
|
+
* @param newValue - The desired state of the resource.
|
|
696
|
+
* @param options - Optional configuration.
|
|
697
|
+
* @param options.readonlyFields - Keys that should never appear in the generated patch,
|
|
698
|
+
* regardless of whether they changed.
|
|
699
|
+
* @param options.requiredFields - Keys that should always appear in the generated patch,
|
|
700
|
+
* using values from `newValue`.
|
|
701
|
+
* @returns A {@link MergePatch} object ready to be serialised as an `application/merge-patch+json` body.
|
|
702
|
+
*/
|
|
703
|
+
function generatePatch(currentValue, newValue, options = {}) {
|
|
704
|
+
const requiredKeys = options.requiredFields ?? [];
|
|
705
|
+
const omitKeys = (options.readonlyFields ?? []).filter(key => !requiredKeys.includes(key));
|
|
706
|
+
const patch = diff(currentValue, newValue);
|
|
707
|
+
return normalizeMergePatch(patch, omitKeys, requiredKeys, newValue);
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Extracts all field paths from a {@link MergePatch} as dot-separated strings.
|
|
711
|
+
* Nested objects are flattened recursively; leaf values (including `null`) produce a path entry.
|
|
712
|
+
*
|
|
713
|
+
* @example
|
|
714
|
+
* ```ts
|
|
715
|
+
* getPatchFields({ a: { b: 0 }, c: 'value' })
|
|
716
|
+
* // => ['a.b', 'c']
|
|
717
|
+
* ```
|
|
718
|
+
*
|
|
719
|
+
* @param patch - The merge patch to extract fields from.
|
|
720
|
+
* @param prefix - Internal prefix used during recursion; omit when calling directly.
|
|
721
|
+
* @returns An array of dot-separated field path strings.
|
|
722
|
+
*/
|
|
723
|
+
function getPatchFields(patch, prefix = '') {
|
|
724
|
+
return Object.entries(patch).flatMap(([key, value]) => {
|
|
725
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
726
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
727
|
+
? getPatchFields(value, path)
|
|
728
|
+
: [path];
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
const StylesPushCommand = {
|
|
733
|
+
command: "styles:push",
|
|
734
|
+
describe: "Push Visual Builder style definitions into the CMS (create/replace)",
|
|
735
|
+
builder: (yargs) => {
|
|
736
|
+
yargs.option('excludeTemplates', { alias: 'e', description: "Exclude these templates", string: true, type: 'array', demandOption: false, default: [] });
|
|
737
|
+
yargs.option("templates", { alias: 't', description: "Select only these templates", string: true, type: 'array', demandOption: false, default: [] });
|
|
738
|
+
return yargs;
|
|
739
|
+
},
|
|
740
|
+
handler: async (args) => {
|
|
741
|
+
const { _config: cfg, excludeTemplates, templates, ...opts } = parseArgs(args);
|
|
742
|
+
const client = createCmsClient(args);
|
|
743
|
+
const { styles: displayTemplates } = await getStyles(client, {
|
|
744
|
+
all: false,
|
|
745
|
+
baseTypes: [],
|
|
746
|
+
excludeBaseTypes: [],
|
|
747
|
+
excludeNodeTypes: [],
|
|
748
|
+
excludeTemplates: [],
|
|
749
|
+
excludeTypes: [],
|
|
750
|
+
nodes: [],
|
|
751
|
+
templates: [],
|
|
752
|
+
templateTypes: [],
|
|
753
|
+
types: [],
|
|
754
|
+
...args
|
|
755
|
+
}, 50);
|
|
756
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Pushing (create/replace) DisplayStyles into Optimizely CMS\n`));
|
|
757
|
+
const styleDefinitionFiles = await glob("./**/*.opti-style.json", {
|
|
758
|
+
cwd: opts.components
|
|
759
|
+
});
|
|
760
|
+
const results = (await Promise.allSettled(styleDefinitionFiles.map(async (styleDefinitionFile) => {
|
|
761
|
+
const filePath = path.normalize(path.join(opts.components, styleDefinitionFile));
|
|
762
|
+
const styleDefinition = tryReadJsonFile(filePath, cfg.debug);
|
|
763
|
+
const styleKey = styleDefinition.key;
|
|
764
|
+
if (!styleKey) {
|
|
765
|
+
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`));
|
|
766
|
+
return undefined;
|
|
767
|
+
}
|
|
768
|
+
if (excludeTemplates.includes(styleKey))
|
|
769
|
+
return undefined; // Skip excluded styles
|
|
770
|
+
if (templates.length > 0 && !templates.includes(styleKey))
|
|
771
|
+
return undefined; // Only include defined styles, if any
|
|
772
|
+
if (cfg.debug)
|
|
773
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Pushing: ${styleKey}\n`));
|
|
774
|
+
// Try to fetch the current template
|
|
775
|
+
const currentTemplate = displayTemplates.find(dt => dt.key === styleKey);
|
|
776
|
+
// Create / Replace the current template
|
|
777
|
+
const newTemplate = await (currentTemplate ? (async () => {
|
|
778
|
+
const patch = generatePatch(currentTemplate, styleDefinition, {
|
|
779
|
+
readonlyFields: ['key', 'created', 'lastModified', 'createdBy', 'lastModifiedBy']
|
|
780
|
+
});
|
|
781
|
+
if (!path || Object.entries(patch).length === 0)
|
|
782
|
+
return currentTemplate;
|
|
783
|
+
// @ts-expect-error There's a mis-match between the logic in the CMS and the contents of the
|
|
784
|
+
// OpenAPI Spec file.
|
|
785
|
+
return client.displayTemplatesPatch({ path: { key: styleKey }, body: patch });
|
|
786
|
+
})() :
|
|
787
|
+
client.displayTemplatesCreate({ body: styleDefinition }));
|
|
788
|
+
const missedFields = generatePatch(newTemplate, currentTemplate, {
|
|
789
|
+
readonlyFields: ['key', 'created', 'lastModified', 'createdBy', 'lastModifiedBy']
|
|
790
|
+
});
|
|
791
|
+
if (missedFields && Object.keys(missedFields).length > 0)
|
|
792
|
+
throw new Error(`The Display template ${styleKey} failed to update properties: ${getPatchFields(missedFields).join('; ')}`);
|
|
793
|
+
return newTemplate;
|
|
794
|
+
})));
|
|
795
|
+
const styles = new Table({
|
|
796
|
+
head: [
|
|
797
|
+
chalk.yellow(chalk.bold("Name")),
|
|
798
|
+
chalk.yellow(chalk.bold("Key")),
|
|
799
|
+
chalk.yellow(chalk.bold("Default")),
|
|
800
|
+
chalk.yellow(chalk.bold("Target"))
|
|
801
|
+
],
|
|
802
|
+
colWidths: [31, 20, 9, 20],
|
|
803
|
+
colAligns: ["left", "left", "center", "left"]
|
|
804
|
+
});
|
|
805
|
+
results.forEach(result => {
|
|
806
|
+
if (result.status === 'fulfilled') {
|
|
807
|
+
const tpl = result.value;
|
|
808
|
+
styles.push([
|
|
809
|
+
tpl.displayName,
|
|
810
|
+
tpl.key,
|
|
811
|
+
tpl.isDefault ? figures.tick : figures.cross,
|
|
812
|
+
tpl.contentType ? `${tpl.contentType} (C)` : tpl.baseType ? `${tpl.baseType} (B)` : `${tpl.nodeType} (N)`
|
|
813
|
+
]);
|
|
814
|
+
}
|
|
815
|
+
else {
|
|
816
|
+
process.stderr.write(`Error processing DisplayTemplate: ${result.reason}\n`);
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
process.stdout.write(styles.toString() + "\n");
|
|
820
|
+
process.stdout.write(chalk.green(chalk.bold(figures.tick + " Done")) + "\n");
|
|
821
|
+
}
|
|
822
|
+
};
|
|
823
|
+
function tryReadJsonFile(filePath, debug = false) {
|
|
824
|
+
try {
|
|
825
|
+
if (debug)
|
|
826
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Reading style definition from ${filePath}\n`));
|
|
827
|
+
return JSON.parse(fs.readFileSync(filePath, { encoding: 'utf-8' }));
|
|
828
|
+
}
|
|
829
|
+
catch (e) {
|
|
830
|
+
process.stderr.write(chalk.redBright(`${chalk.bold(figures.cross)} Error while reading ${filePath}\n`));
|
|
480
831
|
}
|
|
481
|
-
|
|
832
|
+
return undefined;
|
|
482
833
|
}
|
|
483
834
|
|
|
484
835
|
const StylesListCommand = {
|
|
@@ -514,6 +865,14 @@ const StylesListCommand = {
|
|
|
514
865
|
}
|
|
515
866
|
};
|
|
516
867
|
|
|
868
|
+
function ucFirst(current) {
|
|
869
|
+
if (typeof current != 'string')
|
|
870
|
+
throw new Error("Only strings can be transformed");
|
|
871
|
+
if (current == "")
|
|
872
|
+
return current;
|
|
873
|
+
return current[0]?.toUpperCase() + current.substring(1);
|
|
874
|
+
}
|
|
875
|
+
|
|
517
876
|
const StylesPullCommand = {
|
|
518
877
|
command: "styles:pull",
|
|
519
878
|
describe: "Create Visual Builder style definitions from the CMS",
|
|
@@ -524,124 +883,59 @@ const StylesPullCommand = {
|
|
|
524
883
|
return newYargs;
|
|
525
884
|
},
|
|
526
885
|
handler: async (args) => {
|
|
527
|
-
const { _config: cfg, components: basePath, force
|
|
886
|
+
const { _config: cfg, components: basePath, force} = parseArgs(args);
|
|
528
887
|
const client = createCmsClient(args);
|
|
529
|
-
if (client.runtimeCmsVersion == OptiCmsVersion.CMS12) {
|
|
530
|
-
process.stdout.write(chalk.gray(`${figures.cross} Styles are not supported on CMS12\n`));
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
888
|
const { styles: filteredResults } = await getStyles(client, args);
|
|
534
|
-
//#region Create & Write opti-style.json files
|
|
535
889
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Start creating .opti-style.json files\n`));
|
|
536
|
-
const
|
|
537
|
-
const updatedTemplates =
|
|
538
|
-
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
delete outputTemplate.lastModifiedBy;
|
|
546
|
-
if (outputTemplate.created)
|
|
547
|
-
delete outputTemplate.created;
|
|
548
|
-
if (outputTemplate.lastModified)
|
|
549
|
-
delete outputTemplate.lastModified;
|
|
550
|
-
// Write file to disk
|
|
551
|
-
if (fs.existsSync(filePath)) {
|
|
552
|
-
if (!force) {
|
|
553
|
-
if (cfg.debug)
|
|
554
|
-
process.stdout.write(chalk.gray(`${figures.cross} Skipping style file for ${displayTemplate.key} - File already exists\n`));
|
|
555
|
-
}
|
|
556
|
-
else {
|
|
557
|
-
if (cfg.debug) {
|
|
558
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Overwriting style file for ${displayTemplate.key}\n`));
|
|
559
|
-
}
|
|
560
|
-
fs.writeFileSync(filePath, JSON.stringify(outputTemplate, undefined, 2));
|
|
561
|
-
}
|
|
890
|
+
const styleFiles = await toTypeFilesList(filteredResults, client, basePath);
|
|
891
|
+
const updatedTemplates = [];
|
|
892
|
+
for (const groupIdentifier of styleFiles.keys()) {
|
|
893
|
+
const displayTemplateGroup = styleFiles.get(groupIdentifier);
|
|
894
|
+
for (const { file: filePath, data: displayTemplate } of (displayTemplateGroup?.templates || [])) {
|
|
895
|
+
// Write JSON to disk
|
|
896
|
+
var updatedJson = await createDisplayTemplateFile(displayTemplate, filePath, force, cfg.debug);
|
|
897
|
+
if (updatedJson)
|
|
898
|
+
updatedTemplates.push(displayTemplate.key);
|
|
562
899
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
fs.writeFileSync(filePath, JSON.stringify(outputTemplate, undefined, 2));
|
|
567
|
-
}
|
|
568
|
-
// Ensure we're tracking all files
|
|
569
|
-
if (!typeFiles[targetType]) {
|
|
570
|
-
typeFiles[targetType] = {
|
|
571
|
-
filePath: helperPath,
|
|
572
|
-
templates: []
|
|
573
|
-
};
|
|
574
|
-
}
|
|
575
|
-
typeFiles[targetType].templates.push({ key: displayTemplate.key, file: filePath, data: displayTemplate });
|
|
576
|
-
return displayTemplate.key;
|
|
577
|
-
}))).filter(x => x);
|
|
578
|
-
//#endregion
|
|
579
|
-
//#region Create needed definition files
|
|
580
|
-
if (definitions)
|
|
581
|
-
await createDisplayTemplateHelpers(typeFiles);
|
|
582
|
-
//#endregion
|
|
900
|
+
// Write template to disk
|
|
901
|
+
void await createDisplayTemplateHelper(displayTemplateGroup, groupIdentifier, force, cfg.debug);
|
|
902
|
+
}
|
|
583
903
|
process.stdout.write(chalk.green(chalk.bold(figures.tick + ` Created/updated style definitions for ${updatedTemplates.join(', ')}`)) + "\n");
|
|
584
904
|
}
|
|
585
905
|
};
|
|
586
|
-
function
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
const baseTypeSlug = baseTypeToSlug(displayTemplate.baseType);
|
|
604
|
-
itemPath = path.join(basePath, baseTypeSlug, 'styles', displayTemplate.key);
|
|
605
|
-
typesPath = path.join(basePath, baseTypeSlug, 'styles');
|
|
606
|
-
targetType = targetPrefix + '/' + baseTypeSlug;
|
|
607
|
-
break;
|
|
906
|
+
async function createDisplayTemplateFile(displayTemplate, filePath, force = false, debug = false) {
|
|
907
|
+
let updated = false;
|
|
908
|
+
// Build local style data
|
|
909
|
+
const outputTemplate = { ...displayTemplate };
|
|
910
|
+
if (outputTemplate.createdBy)
|
|
911
|
+
delete outputTemplate.createdBy;
|
|
912
|
+
if (outputTemplate.lastModifiedBy)
|
|
913
|
+
delete outputTemplate.lastModifiedBy;
|
|
914
|
+
if (outputTemplate.created)
|
|
915
|
+
delete outputTemplate.created;
|
|
916
|
+
if (outputTemplate.lastModified)
|
|
917
|
+
delete outputTemplate.lastModified;
|
|
918
|
+
// Write file to disk
|
|
919
|
+
if (fs.existsSync(filePath)) {
|
|
920
|
+
if (!force) {
|
|
921
|
+
if (debug)
|
|
922
|
+
process.stdout.write(chalk.gray(`${figures.cross} Skipping style file for ${displayTemplate.key} - File already exists\n`));
|
|
608
923
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
break;
|
|
924
|
+
else {
|
|
925
|
+
if (debug) {
|
|
926
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Overwriting style file for ${displayTemplate.key}\n`));
|
|
927
|
+
}
|
|
928
|
+
fs.writeFileSync(filePath, JSON.stringify(outputTemplate, undefined, 2));
|
|
929
|
+
updated = true;
|
|
616
930
|
}
|
|
617
|
-
default:
|
|
618
|
-
throw new Error("Unsupported display template type");
|
|
619
931
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
const helperFilePath = path.join(typesPath, 'displayTemplates.ts');
|
|
626
|
-
return { key: displayTemplate.key, itemPath, typesPath, targetType, styleFilePath, helperFilePath };
|
|
627
|
-
}
|
|
628
|
-
function baseTypeToSlug(baseType) {
|
|
629
|
-
return baseType.replace(/^\_*/, "").toLowerCase();
|
|
630
|
-
}
|
|
631
|
-
function getTemplateTarget(displayTemplate) {
|
|
632
|
-
if (displayTemplate.baseType && displayTemplate.baseType.length > 0)
|
|
633
|
-
return 'base';
|
|
634
|
-
if (displayTemplate.nodeType && displayTemplate.nodeType.length > 0)
|
|
635
|
-
return 'node';
|
|
636
|
-
if (displayTemplate.contentType && displayTemplate.contentType.length > 0)
|
|
637
|
-
return 'content';
|
|
638
|
-
return null;
|
|
639
|
-
}
|
|
640
|
-
async function createDisplayTemplateHelpers(typeFiles, force = false, debug = false) {
|
|
641
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Start creating displayTemplates.ts files\n`));
|
|
642
|
-
for (const targetId in typeFiles) {
|
|
643
|
-
await createDisplayTemplateHelper(typeFiles[targetId], targetId, force, debug);
|
|
932
|
+
else {
|
|
933
|
+
if (debug)
|
|
934
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating style file for ${displayTemplate.key} in ${filePath}\n`));
|
|
935
|
+
fs.writeFileSync(filePath, JSON.stringify(outputTemplate, undefined, 2));
|
|
936
|
+
updated = true;
|
|
644
937
|
}
|
|
938
|
+
return updated;
|
|
645
939
|
}
|
|
646
940
|
async function createDisplayTemplateHelper(typeFile, typeFileId, force = false, debug = false) {
|
|
647
941
|
const prefix = '//not-modified - Remove this line when making change to prevent it from being updated by the CLI tools';
|
|
@@ -680,20 +974,20 @@ async function createDisplayTemplateHelper(typeFile, typeFileId, force = false,
|
|
|
680
974
|
typeContents.push(`export type ${displayTemplate.key}Props = LayoutProps<typeof ${displayTemplate.key}Styles>`);
|
|
681
975
|
typeContents.push(`export type ${displayTemplate.key}Keys = LayoutPropsSettingKeys<${displayTemplate.key}Props>`);
|
|
682
976
|
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,
|
|
684
|
-
typeContents.push(`export type ${displayTemplate.key}Component<DT extends Record<string,
|
|
977
|
+
typeContents.push(`export type ${displayTemplate.key}ComponentProps<DT extends Record<string, unknown> = Record<string, unknown>> = Omit<CmsComponentProps<DT, ${displayTemplate.key}Props>,'children'> & JSX.IntrinsicElements['div']`);
|
|
978
|
+
typeContents.push(`export type ${displayTemplate.key}Component<DT extends Record<string, unknown> = Record<string, unknown>> = ComponentType<${displayTemplate.key}ComponentProps<DT>>`);
|
|
685
979
|
typeContents.push('');
|
|
686
980
|
props.push(`${displayTemplate.key}Props`);
|
|
687
981
|
if (!typeId)
|
|
688
982
|
typeId = displayTemplate.nodeType ?? displayTemplate.baseType ?? displayTemplate.contentType;
|
|
689
983
|
});
|
|
690
984
|
if (typeId) {
|
|
691
|
-
typeId = ucFirst
|
|
985
|
+
typeId = ucFirst(typeId);
|
|
692
986
|
typeContents.push(`export type ${typeId}LayoutProps = ${props.join(' | ')}
|
|
693
987
|
export type ${typeId}LayoutKeys = LayoutPropsSettingKeys<${typeId}LayoutProps>
|
|
694
988
|
export type ${typeId}LayoutOptions<K extends ${typeId}LayoutKeys> = LayoutPropsSettingValues<${typeId}LayoutProps,K>
|
|
695
|
-
export type ${typeId}ComponentProps<DT extends Record<string,
|
|
696
|
-
export type ${typeId}Component<DT extends Record<string,
|
|
989
|
+
export type ${typeId}ComponentProps<DT extends Record<string, unknown> = Record<string, unknown>> = Omit<CmsComponentProps<DT, ${typeId}LayoutProps>,'children'> & JSX.IntrinsicElements['div']
|
|
990
|
+
export type ${typeId}Component<DT extends Record<string, unknown> = Record<string, unknown>> = ComponentType<${typeId}ComponentProps<DT>>`);
|
|
697
991
|
const defaultTemplate = templates.find(t => t.data.isDefault);
|
|
698
992
|
if (defaultTemplate) {
|
|
699
993
|
typeContents.push(`
|
|
@@ -724,10 +1018,6 @@ const StylesCreateCommand = {
|
|
|
724
1018
|
handler: async (args) => {
|
|
725
1019
|
const { components: basePath } = parseArgs(args);
|
|
726
1020
|
const client = createCmsClient(args);
|
|
727
|
-
if (client.runtimeCmsVersion == OptiCmsVersion.CMS12) {
|
|
728
|
-
process.stdout.write(chalk.gray(`${figures.cross} Styles are not supported on CMS12\n`));
|
|
729
|
-
return;
|
|
730
|
-
}
|
|
731
1021
|
const allowedBaseTypes = ['section', 'component', 'experience'];
|
|
732
1022
|
// Prepare
|
|
733
1023
|
process.stdout.write(chalk.yellowBright(chalk.bold(`Reading current information from Optimizely CMS\n`)));
|
|
@@ -776,10 +1066,10 @@ const StylesCreateCommand = {
|
|
|
776
1066
|
definition[type] = typeId;
|
|
777
1067
|
definition['settings'] = {};
|
|
778
1068
|
const contentBaseType = type == "contentType" ? contentTypes.filter(x => x.key == typeId).map(x => x.baseType).at(0) : undefined;
|
|
779
|
-
const styleFilePath = await
|
|
780
|
-
if (!fs.existsSync(
|
|
781
|
-
fs.mkdirSync(
|
|
782
|
-
if (fs.existsSync(
|
|
1069
|
+
const { styleFile: styleFilePath, styleFolder: styleFileFolder } = await getStyleFilePaths(definition, { contentBaseType, client, basePath });
|
|
1070
|
+
if (!fs.existsSync(styleFileFolder))
|
|
1071
|
+
fs.mkdirSync(styleFileFolder, { recursive: true });
|
|
1072
|
+
if (fs.existsSync(styleFilePath)) {
|
|
783
1073
|
const overwrite = await confirm({ message: "The style definition file already exists, do you want to overwrite it?" });
|
|
784
1074
|
if (!overwrite) {
|
|
785
1075
|
process.stdout.write("\n");
|
|
@@ -788,8 +1078,8 @@ const StylesCreateCommand = {
|
|
|
788
1078
|
}
|
|
789
1079
|
}
|
|
790
1080
|
// @ToDo: Add properties
|
|
791
|
-
fs.writeFileSync(
|
|
792
|
-
process.stdout.write(chalk.yellowBright(chalk.bold(`\nWritten style defintion template to: ${path.normalize(
|
|
1081
|
+
fs.writeFileSync(styleFilePath, JSON.stringify(definition, undefined, 4));
|
|
1082
|
+
process.stdout.write(chalk.yellowBright(chalk.bold(`\nWritten style defintion template to: ${path.normalize(styleFilePath)}\n`)));
|
|
793
1083
|
if (await confirm({ message: "Do you want to publish this style into Optimizely CMS?" })) {
|
|
794
1084
|
const response = await client.displayTemplatesCreate({ body: definition }).catch(_ => undefined);
|
|
795
1085
|
if (!response) {
|
|
@@ -811,6 +1101,91 @@ const StylesCreateCommand = {
|
|
|
811
1101
|
}
|
|
812
1102
|
};
|
|
813
1103
|
|
|
1104
|
+
const StylesDeleteCommand = {
|
|
1105
|
+
command: "styles:delete",
|
|
1106
|
+
describe: "Remove Visual Builder style definitions from the CMS",
|
|
1107
|
+
builder: (yargs) => {
|
|
1108
|
+
const newYargs = stylesBuilder(yargs);
|
|
1109
|
+
newYargs.option('force', { alias: 'f', description: "Overwrite existing files", boolean: true, type: 'boolean', demandOption: false, default: false });
|
|
1110
|
+
newYargs.option('withStyleFile', { alias: 'w', description: "Delete the .opti-style.json file as weill", boolean: true, type: 'boolean', demandOption: false, default: true });
|
|
1111
|
+
newYargs.option("definitions", { alias: 'u', description: "Update typescript definitions", boolean: true, type: 'boolean', demandOption: false, default: true });
|
|
1112
|
+
return newYargs;
|
|
1113
|
+
},
|
|
1114
|
+
handler: async (args) => {
|
|
1115
|
+
const { components: basePath } = parseArgs(args);
|
|
1116
|
+
const client = createCmsClient(args);
|
|
1117
|
+
const { styles, all: allStyles } = await getStyles(client, args, 100);
|
|
1118
|
+
if (styles.findIndex(x => x.isDefault) >= 0)
|
|
1119
|
+
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`))));
|
|
1120
|
+
if (!args.force) {
|
|
1121
|
+
process.stdout.write(`This will remove the following display templates\n`);
|
|
1122
|
+
for (const style of styles) {
|
|
1123
|
+
process.stdout.write(` ${figures.arrowRight} ${style.displayName || style.key} [Key: ${style.key}; Default: ${style.isDefault ? 'Yes' : 'No'}]\n`);
|
|
1124
|
+
}
|
|
1125
|
+
process.stdout.write(`\nRun with -f to actually preform removal\n`);
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
const keysToDelete = styles.map(displayTemplate => displayTemplate.key);
|
|
1129
|
+
const styleHelpers = await toTypeFilesList(allStyles, client, basePath);
|
|
1130
|
+
for (const displayTemplate of styles) {
|
|
1131
|
+
const { styleFile: styleFilePath, styleFolder: itemPath, identifier: targetType, helperFolder: typesPath } = styleHelpers.getDisplayTemplatePathsByKey(displayTemplate.key);
|
|
1132
|
+
// Remove style file
|
|
1133
|
+
if (args.withStyleFile) {
|
|
1134
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Removing *.opti-style.json file for ${displayTemplate.displayName} [${displayTemplate.key}]\n`));
|
|
1135
|
+
await fsAsync.rm(styleFilePath, { force: false, recursive: false, maxRetries: 1 }).catch((e) => {
|
|
1136
|
+
if (e?.code === 'ENOENT') // The file can't be deleted as it does not exist - that's the desired state
|
|
1137
|
+
return;
|
|
1138
|
+
throw e;
|
|
1139
|
+
});
|
|
1140
|
+
await fsAsync.rmdir(itemPath, { recursive: false, maxRetries: 1 }).catch((e) => {
|
|
1141
|
+
if (e?.code === 'ENOTEMPTY') {
|
|
1142
|
+
process.stdout.write(chalk.yellowBright(`${figures.warning} The folder is not empty, please remove ${itemPath} manually if needed\n`));
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
else if (e?.code === 'ENOENT')
|
|
1146
|
+
return;
|
|
1147
|
+
else
|
|
1148
|
+
throw e;
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
// Remove/update typescript helper
|
|
1152
|
+
if (args.definitions && styleHelpers.has(targetType)) {
|
|
1153
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Removing/updating displayTemplates.ts file for ${displayTemplate.displayName} [${displayTemplate.key}]\n`));
|
|
1154
|
+
const remainingTemplates = styleHelpers[targetType].templates.filter(x => !keysToDelete.includes(x.key));
|
|
1155
|
+
if (remainingTemplates.length === 0) {
|
|
1156
|
+
await fsAsync.rm(styleHelpers[targetType].filePath, { force: false, recursive: false, maxRetries: 1 }).catch((e) => {
|
|
1157
|
+
if (e?.code === 'ENOENT') // The file can't be deleted as it does not exist - that's the desired state
|
|
1158
|
+
return;
|
|
1159
|
+
throw e;
|
|
1160
|
+
});
|
|
1161
|
+
await fsAsync.rmdir(typesPath, { recursive: false, maxRetries: 1 }).catch((e) => {
|
|
1162
|
+
if (e?.code === 'ENOTEMPTY') {
|
|
1163
|
+
process.stdout.write(chalk.yellowBright(`${figures.warning} The folder is not empty, please remove ${typesPath} manually if needed\n`));
|
|
1164
|
+
return;
|
|
1165
|
+
}
|
|
1166
|
+
else if (e?.code === 'ENOENT')
|
|
1167
|
+
return;
|
|
1168
|
+
else
|
|
1169
|
+
throw e;
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
else {
|
|
1173
|
+
const newEntry = {
|
|
1174
|
+
filePath: styleHelpers[targetType].filePath,
|
|
1175
|
+
templates: remainingTemplates
|
|
1176
|
+
};
|
|
1177
|
+
if (!await createDisplayTemplateHelper(newEntry, targetType, false, false))
|
|
1178
|
+
process.stdout.write(chalk.yellowBright(`${figures.warning} The display template was not updated, please update ${newEntry.filePath} manually`));
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
// Actually remove from CMS
|
|
1182
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Removing the display template ${displayTemplate.displayName} [${displayTemplate.key}] from Optimizely CMS\n`));
|
|
1183
|
+
void await client.displayTemplatesDelete({ path: { key: displayTemplate.key } });
|
|
1184
|
+
}
|
|
1185
|
+
process.stdout.write("\n" + chalk.green(chalk.bold(figures.tick + " Done")) + "\n");
|
|
1186
|
+
}
|
|
1187
|
+
};
|
|
1188
|
+
|
|
814
1189
|
const TypesPullCommand = {
|
|
815
1190
|
command: "types:pull",
|
|
816
1191
|
describe: "Pull content type definition files into the project",
|
|
@@ -820,13 +1195,13 @@ const TypesPullCommand = {
|
|
|
820
1195
|
return newArgs;
|
|
821
1196
|
},
|
|
822
1197
|
handler: async (args) => {
|
|
823
|
-
const { _config: { debug }, components: basePath, force } = parseArgs(args);
|
|
1198
|
+
const { _config: { debug }, components: basePath, path: projectPath, force } = parseArgs(args);
|
|
824
1199
|
const client = createCmsClient(args);
|
|
825
1200
|
const { contentTypes } = await getContentTypes(client, args);
|
|
826
1201
|
const updatedTypes = contentTypes.map(contentType => {
|
|
827
|
-
const {
|
|
828
|
-
if (!fs.existsSync(
|
|
829
|
-
fs.mkdirSync(
|
|
1202
|
+
const { path: path$1, typeFile } = getContentTypePaths(contentType, basePath);
|
|
1203
|
+
if (!fs.existsSync(path$1))
|
|
1204
|
+
fs.mkdirSync(path$1, { recursive: true });
|
|
830
1205
|
if (fs.existsSync(typeFile) && !force) {
|
|
831
1206
|
if (debug)
|
|
832
1207
|
process.stdout.write(chalk.yellow(`${figures.cross} Skipping type definition for ${contentType.displayName} (${contentType.key}) - File already exists\n`));
|
|
@@ -835,9 +1210,7 @@ const TypesPullCommand = {
|
|
|
835
1210
|
const outContentType = { ...contentType };
|
|
836
1211
|
if (outContentType.source || outContentType.source == "")
|
|
837
1212
|
delete outContentType.source;
|
|
838
|
-
|
|
839
|
-
//if (outContentType.usage) delete outContentType.usage
|
|
840
|
-
if (outContentType.lastModifiedBy)
|
|
1213
|
+
if (outContentType.lastModifiedBy || outContentType.lastModifiedBy == "")
|
|
841
1214
|
delete outContentType.lastModifiedBy;
|
|
842
1215
|
if (outContentType.lastModified)
|
|
843
1216
|
delete outContentType.lastModified;
|
|
@@ -858,16 +1231,13 @@ const TypesPullCommand = {
|
|
|
858
1231
|
}
|
|
859
1232
|
}
|
|
860
1233
|
if (debug)
|
|
861
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Writing type definition for ${contentType.displayName} (${contentType.key})\n`));
|
|
1234
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Writing type definition for ${contentType.displayName} (${contentType.key}) into ${path.relative(projectPath, path$1)}\n`));
|
|
862
1235
|
fs.writeFileSync(typeFile, JSON.stringify(outContentType, undefined, 2));
|
|
863
1236
|
return contentType.key;
|
|
864
1237
|
}).filter(x => x);
|
|
865
1238
|
process.stdout.write(chalk.green(chalk.bold(`${figures.tick} Created/updated type definitions for ${updatedTypes.join(', ')}\n`)));
|
|
866
1239
|
}
|
|
867
1240
|
};
|
|
868
|
-
function isEmptyArray(toTest) {
|
|
869
|
-
return toTest === null || !Array.isArray(toTest) || toTest.length === 0;
|
|
870
|
-
}
|
|
871
1241
|
|
|
872
1242
|
const deepmerge$1 = createDeepMerge();
|
|
873
1243
|
async function loadSchema(client, schemaName) {
|
|
@@ -1169,14 +1539,6 @@ function getTypeFolder(list, type) {
|
|
|
1169
1539
|
return list.filter(x => x.type == type).at(0);
|
|
1170
1540
|
}
|
|
1171
1541
|
|
|
1172
|
-
function ucFirst(current) {
|
|
1173
|
-
if (typeof current != 'string')
|
|
1174
|
-
throw new Error("Only strings can be transformed");
|
|
1175
|
-
if (current == "")
|
|
1176
|
-
return current;
|
|
1177
|
-
return current[0]?.toUpperCase() + current.substring(1);
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
1542
|
const NextJsComponentsCommand = {
|
|
1181
1543
|
command: "nextjs:components",
|
|
1182
1544
|
describe: "Create the React Components for a Next.JS / Optimizely Graph structure",
|
|
@@ -1286,8 +1648,9 @@ ${varName}.getDataFragment = () => ['${contentType.key}Data', ${contentType.key}
|
|
|
1286
1648
|
|
|
1287
1649
|
export default ${varName}`,
|
|
1288
1650
|
// 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}
|
|
1651
|
+
section: (contentType, varName, displayTemplate, baseDisplayTemplate) => `import { OptimizelyComposition, isNode, CmsEditable, type CmsComponent } from "@remkoj/optimizely-cms-react/rsc";
|
|
1652
|
+
import { get${contentType.key}DataDocument, type get${contentType.key}DataQuery, SectionCompositionDataFragmentDoc } from "@/gql/graphql";
|
|
1653
|
+
import { getFragmentData } from "@/gql/fragment-masking";${displayTemplate ? `
|
|
1291
1654
|
import { ${displayTemplate} } from "./displayTemplates";` : ''}${baseDisplayTemplate ? `
|
|
1292
1655
|
import { ${baseDisplayTemplate} } from "../styles/displayTemplates";` : ''}
|
|
1293
1656
|
|
|
@@ -1296,19 +1659,25 @@ import { ${baseDisplayTemplate} } from "../styles/displayTemplates";` : ''}
|
|
|
1296
1659
|
* ---
|
|
1297
1660
|
* ${contentType.description}
|
|
1298
1661
|
*/
|
|
1299
|
-
export const ${varName} : CmsComponent
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
</
|
|
1662
|
+
export const ${varName} : CmsComponent<get${contentType.key}DataQuery${(displayTemplate || baseDisplayTemplate) ? ', ' + [displayTemplate, baseDisplayTemplate].filter(x => x).join(" | ") : ''}> = ({ data${(displayTemplate || baseDisplayTemplate) ? ', layoutProps' : ''}, editProps, children, ctx }) => {
|
|
1663
|
+
// If we're rendering stand-alone we'll get a composition back, with ourself included If we're
|
|
1664
|
+
// rendering as part of an experience, we'll get the data directly. So handle both cases.
|
|
1665
|
+
if (!data?._metadata && children) return children;
|
|
1666
|
+
|
|
1667
|
+
const componentName = '${contentType.displayName}'
|
|
1668
|
+
const componentInfo = '${contentType.description?.replaceAll("'", "\\'") ?? ''}'
|
|
1669
|
+
const composition = getFragmentData(SectionCompositionDataFragmentDoc, data)?.composition;
|
|
1670
|
+
return <CmsEditable className="w-full border-y border-y-solid border-y-slate-900 py-2 mb-4" {...editProps}>
|
|
1671
|
+
<div className="font-bold italic">{ componentName }</div>
|
|
1672
|
+
<div>{ componentInfo }</div>
|
|
1673
|
+
{ 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> }
|
|
1674
|
+
${(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 */}'}
|
|
1675
|
+
{children && <div>{ children }</div>}
|
|
1676
|
+
{composition && isNode(composition) && <OptimizelyComposition node={composition} ctx={ctx} />}
|
|
1677
|
+
</CmsEditable>
|
|
1309
1678
|
}
|
|
1310
1679
|
${varName}.displayName = "${contentType.displayName} (${ucFirst(contentType.baseType)}/${contentType.key})"
|
|
1311
|
-
${varName}.
|
|
1680
|
+
${varName}.getDataQuery = () => get${contentType.key}DataDocument
|
|
1312
1681
|
|
|
1313
1682
|
export default ${varName}`,
|
|
1314
1683
|
// Template for all page component types
|
|
@@ -1401,13 +1770,13 @@ const NextJsVisualBuilderCommand = {
|
|
|
1401
1770
|
const { styles } = await getStyles(createCmsClient(args), { ...args, excludeNodeTypes: [], excludeTemplates: [], nodes: [], templates: [], templateTypes: ['node', 'base'] });
|
|
1402
1771
|
// Process node styles
|
|
1403
1772
|
styles.filter(x => typeof (x.nodeType) == 'string' && x.nodeType.length > 0).map(styleDefintion => {
|
|
1404
|
-
const templatePath = path.join(basePath, 'nodes', styleDefintion.nodeType, styleDefintion.key);
|
|
1773
|
+
const templatePath = path.join(basePath, 'nodes', keyToSlug(styleDefintion.nodeType), keyToSlug(styleDefintion.key));
|
|
1405
1774
|
createSpecificNode(styleDefintion, templatePath, force, debug);
|
|
1406
1775
|
});
|
|
1407
1776
|
// Process base styles
|
|
1408
1777
|
styles.filter(x => typeof (x.baseType) == 'string' && x.baseType.length > 0).map(styleDefinition => {
|
|
1409
1778
|
const baseType = (styleDefinition.baseType ?? '').startsWith('_') ? (styleDefinition.baseType ?? '').substring(1) : styleDefinition.baseType;
|
|
1410
|
-
const templatePath = path.join(basePath, baseType, 'styles', styleDefinition.key);
|
|
1779
|
+
const templatePath = path.join(basePath, baseType, 'styles', keyToSlug(styleDefinition.key));
|
|
1411
1780
|
createSpecificNode(styleDefinition, templatePath, force, debug);
|
|
1412
1781
|
});
|
|
1413
1782
|
}
|
|
@@ -1417,14 +1786,14 @@ function createSpecificNode(template, templatePath, force = false, debug = false
|
|
|
1417
1786
|
if (fs.existsSync(nodeFile)) {
|
|
1418
1787
|
if (!force) {
|
|
1419
1788
|
if (debug)
|
|
1420
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${template.displayName} (${template.key})
|
|
1789
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${template.displayName} (${template.key}) node - file already exists\n`));
|
|
1421
1790
|
return undefined;
|
|
1422
1791
|
}
|
|
1423
1792
|
if (debug)
|
|
1424
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Overwriting ${template.displayName} (${template.key})
|
|
1793
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Overwriting ${template.displayName} (${template.key}) node\n`));
|
|
1425
1794
|
}
|
|
1426
1795
|
else if (debug)
|
|
1427
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating ${template.displayName} (${template.key})
|
|
1796
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating ${template.displayName} (${template.key}) node\n`));
|
|
1428
1797
|
if (!fs.existsSync(templatePath))
|
|
1429
1798
|
fs.mkdirSync(templatePath, { recursive: true });
|
|
1430
1799
|
const baseType = template.nodeType ?? template.baseType ?? 'unknown';
|
|
@@ -1502,6 +1871,7 @@ function getDisplayTemplateInfo(template, typePath) {
|
|
|
1502
1871
|
|
|
1503
1872
|
const ROOT_FACTORY_KEY = ".";
|
|
1504
1873
|
const FACTORY_FILE_NAME = "index.ts";
|
|
1874
|
+
const reservedNames = ["loading.tsx", "loading.jsx", "suspense.tsx", "suspense.jsx"];
|
|
1505
1875
|
const NextJsFactoryCommand = {
|
|
1506
1876
|
command: "nextjs:factory",
|
|
1507
1877
|
describe: "Create the ComponentFactory for a Next.JS / Optimizely Graph structure",
|
|
@@ -1510,108 +1880,102 @@ const NextJsFactoryCommand = {
|
|
|
1510
1880
|
const { components: basePath, force, _config: { debug } } = parseArgs(args);
|
|
1511
1881
|
if (debug)
|
|
1512
1882
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Start generating component factories\n`));
|
|
1883
|
+
// Get & filter all component files
|
|
1884
|
+
const clientComponents = [];
|
|
1513
1885
|
const components = globSync(["./**/*.jsx", "./**/*.tsx"], {
|
|
1514
1886
|
cwd: basePath
|
|
1515
1887
|
}).map(p => p.split(path.sep)).filter(p => {
|
|
1888
|
+
// Get the filename
|
|
1889
|
+
const fileName = p.at(p.length - 1);
|
|
1890
|
+
if (!fileName)
|
|
1891
|
+
return false;
|
|
1516
1892
|
// Consider components in a file starting with "_" as a partial
|
|
1517
|
-
if (
|
|
1893
|
+
if (fileName.startsWith('_') == true)
|
|
1518
1894
|
return false;
|
|
1519
1895
|
// Consider components in a folder named "partials" as a partial
|
|
1520
|
-
if (p.
|
|
1896
|
+
if (p.some(folder => folder === 'partials'))
|
|
1521
1897
|
return false;
|
|
1522
1898
|
// Skip the special loader component
|
|
1523
|
-
if (
|
|
1899
|
+
if (reservedNames.includes(fileName))
|
|
1524
1900
|
return false;
|
|
1525
1901
|
// Check if the file has a default export
|
|
1526
1902
|
const fileBuffer = fs.readFileSync(path.join(basePath, p.join(path.sep)));
|
|
1527
1903
|
const hasDefaultExport = fileBuffer.includes('export default');
|
|
1904
|
+
if (fileBuffer.includes('use client'))
|
|
1905
|
+
clientComponents.push(p.join(path.sep));
|
|
1528
1906
|
if (!hasDefaultExport) {
|
|
1529
1907
|
process.stdout.write(chalk.redBright(`${figures.warning} No default export in ${p.join(path.sep)} - ignoring file\n`));
|
|
1530
1908
|
return false;
|
|
1531
1909
|
}
|
|
1532
1910
|
return true;
|
|
1533
1911
|
});
|
|
1912
|
+
// Report what we have found
|
|
1534
1913
|
if (debug)
|
|
1535
1914
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Identified ${components.length} components in ${basePath}\n`));
|
|
1915
|
+
// Build factory / component structure
|
|
1536
1916
|
const componentFactoryDefintions = new Map();
|
|
1537
1917
|
components.forEach(component => {
|
|
1918
|
+
const componentDir = path.dirname(path.join(...component));
|
|
1919
|
+
path.basename(path.join(...component));
|
|
1920
|
+
// Determine component target
|
|
1921
|
+
const componentKey = getComponentKey(component, basePath);
|
|
1922
|
+
let componentVariant = (component.length > 1 ? component.at(component.length - 1) ?? 'default' : 'default').replace('index', 'default');
|
|
1923
|
+
componentVariant = path.basename(componentVariant, path.extname(componentVariant));
|
|
1924
|
+
// Get factory information
|
|
1538
1925
|
const factorySegments = component.length > 2 ? component.slice(0, -2) : [ROOT_FACTORY_KEY];
|
|
1539
|
-
const factoryKey =
|
|
1926
|
+
const factoryKey = path.posix.join(...factorySegments);
|
|
1540
1927
|
const factoryFile = path.join(factoryKey, FACTORY_FILE_NAME);
|
|
1541
|
-
//
|
|
1928
|
+
// Check dynamic & suspense
|
|
1929
|
+
const isClientComponent = clientComponents.includes(component.join(path.sep));
|
|
1930
|
+
const useDynamic = [
|
|
1931
|
+
path.join(basePath, componentDir, 'loading.tsx'),
|
|
1932
|
+
path.join(basePath, componentDir, 'loading.jsx')
|
|
1933
|
+
].some(fs.existsSync);
|
|
1934
|
+
const useSuspense = [
|
|
1935
|
+
path.join(basePath, componentDir, 'suspense.tsx'),
|
|
1936
|
+
path.join(basePath, componentDir, 'suspense.jsx')
|
|
1937
|
+
].some(fs.existsSync);
|
|
1938
|
+
// Prepare data
|
|
1939
|
+
const componentImport = component.length == 1 ?
|
|
1940
|
+
`.${path.posix.sep}${path.basename(component[0], path.extname(component[0]))}` :
|
|
1941
|
+
`.${path.posix.sep}${path.posix.relative(factoryKey, componentDir)}${componentVariant !== 'default' ? path.posix.sep + componentVariant : ''}`;
|
|
1942
|
+
const loaderImport = useDynamic ? componentImport + path.posix.sep + 'loading' : undefined;
|
|
1943
|
+
const suspenseImport = useSuspense ? componentImport + path.posix.sep + 'suspense' : undefined;
|
|
1944
|
+
const componentVariablesBase = componentKey + (componentVariant !== 'default' ? processName(componentVariant) : '');
|
|
1945
|
+
// Add to the factory
|
|
1542
1946
|
const factory = componentFactoryDefintions.get(factoryKey) || { file: factoryFile, entries: [], subfactories: [] };
|
|
1543
|
-
const useSuspense = fs.existsSync(path.join(basePath, component.slice(0, -1).join(path.sep), 'loading.tsx')) || fs.existsSync(path.join(basePath, component.slice(0, -1).join(path.sep), 'loading.jsx'));
|
|
1544
|
-
if (useSuspense && debug)
|
|
1545
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Components in ${component.slice(0, -1).join(path.sep)} will use suspense\n`));
|
|
1546
|
-
const componentSegments = component.slice(-2).map(p => {
|
|
1547
|
-
const entry = p.substring(0, p.length - path.extname(p).length);
|
|
1548
|
-
return entry.toLowerCase() == "index" ? null : entry;
|
|
1549
|
-
}).filter(x => x);
|
|
1550
|
-
const componentImport = "./" + componentSegments.join('/');
|
|
1551
|
-
const loaderImport = useSuspense ? "./" + component.slice(-2).map((p, i, a) => {
|
|
1552
|
-
const entry = p.substring(0, p.length - path.extname(p).length);
|
|
1553
|
-
if (i == a.length - 1)
|
|
1554
|
-
return 'loading';
|
|
1555
|
-
return entry.toLowerCase() == "index" ? null : entry;
|
|
1556
|
-
}).join('/') : undefined;
|
|
1557
1947
|
factory.entries.push({
|
|
1948
|
+
key: componentKey,
|
|
1949
|
+
variant: componentVariant,
|
|
1558
1950
|
import: componentImport,
|
|
1559
|
-
variable:
|
|
1560
|
-
key: componentSegments.join('/') == "node" ? "Node" : componentSegments.join('/'),
|
|
1951
|
+
variable: componentVariablesBase + 'Component',
|
|
1561
1952
|
loaderImport,
|
|
1562
|
-
loaderVariable:
|
|
1953
|
+
loaderVariable: useDynamic ? componentVariablesBase + 'Loader' : undefined,
|
|
1954
|
+
suspenseImport,
|
|
1955
|
+
suspenseVariable: useSuspense ? componentVariablesBase + 'Placeholder' : undefined,
|
|
1956
|
+
isClient: isClientComponent
|
|
1563
1957
|
});
|
|
1564
1958
|
componentFactoryDefintions.set(factoryKey, factory);
|
|
1565
|
-
//
|
|
1959
|
+
// Add/update parent factories
|
|
1566
1960
|
const parentSegements = factoryKey == ROOT_FACTORY_KEY ? factorySegments.slice(0, -1) : [ROOT_FACTORY_KEY, ...factorySegments.slice(0, -1)];
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
prefix: processName(factorySegments.slice(-1).at(0)),
|
|
1571
|
-
variable: factorySegments.map(processName).join("") + "Factory"
|
|
1572
|
-
};
|
|
1573
|
-
for (let i = parentSegements.length; i > 0; i--) {
|
|
1574
|
-
const parentFactoryKey = parentSegements.slice(0, i).filter(x => x != ROOT_FACTORY_KEY).join(path.sep) || ROOT_FACTORY_KEY;
|
|
1961
|
+
parentSegements.forEach((_, idx, data) => {
|
|
1962
|
+
const parentFactoryKey = path.posix.join(...data.slice(0, idx + 1)) || ROOT_FACTORY_KEY;
|
|
1963
|
+
const childFactoryKey = factorySegments.slice(idx, idx + 1).at(0);
|
|
1575
1964
|
const parentFactory = componentFactoryDefintions.get(parentFactoryKey) ?? { file: path.join(parentFactoryKey, FACTORY_FILE_NAME), entries: [], subfactories: [] };
|
|
1576
|
-
if (!parentFactory.subfactories.some(x => x.key
|
|
1577
|
-
parentFactory.subfactories.push(
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
prefix: processName(parentFactoryKey.split(path.sep).slice(-1).at(0)),
|
|
1584
|
-
variable: parentFactoryKey.split(path.sep).map(processName).join("") + "Factory"
|
|
1585
|
-
};
|
|
1965
|
+
if (!parentFactory.subfactories.some(x => x.key === childFactoryKey)) {
|
|
1966
|
+
parentFactory.subfactories.push({
|
|
1967
|
+
key: childFactoryKey,
|
|
1968
|
+
import: './' + childFactoryKey,
|
|
1969
|
+
variable: processName(childFactoryKey) + 'Factory'
|
|
1970
|
+
});
|
|
1971
|
+
componentFactoryDefintions.set(parentFactoryKey, parentFactory);
|
|
1586
1972
|
}
|
|
1587
|
-
}
|
|
1588
|
-
});
|
|
1589
|
-
const mainFactory = componentFactoryDefintions.get(ROOT_FACTORY_KEY);
|
|
1590
|
-
if (mainFactory) {
|
|
1591
|
-
if (debug)
|
|
1592
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Updating prefixes within RootFactory\n`));
|
|
1593
|
-
mainFactory.subfactories = mainFactory.subfactories.map(subFactory => {
|
|
1594
|
-
if (typeof (subFactory.prefix == 'string'))
|
|
1595
|
-
switch (subFactory.prefix) {
|
|
1596
|
-
case "Video":
|
|
1597
|
-
case "Image":
|
|
1598
|
-
subFactory.prefix = ["Media", subFactory.prefix, "Component"];
|
|
1599
|
-
break;
|
|
1600
|
-
case "Experience":
|
|
1601
|
-
subFactory.prefix = [subFactory.prefix, "Page"];
|
|
1602
|
-
break;
|
|
1603
|
-
case "Element":
|
|
1604
|
-
subFactory.prefix = ["Component"];
|
|
1605
|
-
break;
|
|
1606
|
-
case "Media":
|
|
1607
|
-
subFactory.prefix = [subFactory.prefix, "Component"];
|
|
1608
|
-
break;
|
|
1609
|
-
}
|
|
1610
|
-
return subFactory;
|
|
1611
1973
|
});
|
|
1612
|
-
}
|
|
1974
|
+
});
|
|
1975
|
+
// Report factory file count
|
|
1613
1976
|
if (debug)
|
|
1614
1977
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Finished preparing ${componentFactoryDefintions.size} factories, start writing\n`));
|
|
1978
|
+
// Iterate over the factories and create them
|
|
1615
1979
|
let updateCounter = 0;
|
|
1616
1980
|
for (const key of componentFactoryDefintions.keys()) {
|
|
1617
1981
|
const factory = componentFactoryDefintions.get(key);
|
|
@@ -1635,6 +1999,36 @@ const NextJsFactoryCommand = {
|
|
|
1635
1999
|
process.stdout.write("\n");
|
|
1636
2000
|
}
|
|
1637
2001
|
};
|
|
2002
|
+
/**
|
|
2003
|
+
* Get the component key that must be used for the variable in the factory
|
|
2004
|
+
* this looks first at the component-type file.
|
|
2005
|
+
*
|
|
2006
|
+
* @param component The file path broken into parts
|
|
2007
|
+
* @returns The key to use
|
|
2008
|
+
*/
|
|
2009
|
+
function getComponentKey(component, componentsRootDir) {
|
|
2010
|
+
// Prepare context
|
|
2011
|
+
//const componentFile = path.basename(path.join(...component));
|
|
2012
|
+
const componentDir = path.dirname(path.join(...component));
|
|
2013
|
+
// Calculate the fallback value
|
|
2014
|
+
const baseName = processName(component.length == 1 ? ucFirst(path.basename(component[0], path.extname(component[0]))) : component.at(component.length - 2));
|
|
2015
|
+
// Try to read the key from the opti-type.json file in the same folder
|
|
2016
|
+
const definitions = globSync("*.opti-type.json", { cwd: path.join(componentsRootDir, componentDir) });
|
|
2017
|
+
//console.log(definitions, componentDir);
|
|
2018
|
+
if (definitions.length == 1) {
|
|
2019
|
+
try {
|
|
2020
|
+
const data = JSON.parse(fs.readFileSync(path.join(componentsRootDir, componentDir, definitions[0])).toString('utf8'));
|
|
2021
|
+
//console.log('Updating to', data.key)
|
|
2022
|
+
if (data.key)
|
|
2023
|
+
return data.key;
|
|
2024
|
+
}
|
|
2025
|
+
catch {
|
|
2026
|
+
// Ignored on purpose
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
// Return the fallback value
|
|
2030
|
+
return baseName;
|
|
2031
|
+
}
|
|
1638
2032
|
function shouldWriteFactory(factoryFile, force = false, debug = false) {
|
|
1639
2033
|
if (!fs.existsSync(factoryFile)) {
|
|
1640
2034
|
process.stdout.write(chalk.green(`${figures.tick} Creating new factory file: ${factoryFile}\n`));
|
|
@@ -1660,59 +2054,64 @@ function processName(input) {
|
|
|
1660
2054
|
return nameSegements.map(ucFirst).join('');
|
|
1661
2055
|
}
|
|
1662
2056
|
function generateFactory(factoryInfo, factoryKey) {
|
|
1663
|
-
|
|
2057
|
+
// Get the factory name
|
|
1664
2058
|
const factoryName = factoryKey.split(path.sep).map(processName).join("") + "Factory";
|
|
1665
|
-
|
|
1666
|
-
const
|
|
1667
|
-
const
|
|
2059
|
+
// Get the components and sub-factories, sorted by key to minimize changes between runs
|
|
2060
|
+
const components = [...factoryInfo.entries].sort((a, b) => { return a.key < b.key ? -1 : a.key > b.key ? 1 : 0; });
|
|
2061
|
+
const subFactories = [...factoryInfo.subfactories].sort((a, b) => { return a.key < b.key ? -1 : a.key > b.key ? 1 : 0; });
|
|
2062
|
+
// Check if there's at least one component that uses next/dynamic
|
|
2063
|
+
const hasDynamic = factoryInfo.entries.some(x => x.loaderImport);
|
|
2064
|
+
const hasClientComponents = factoryInfo.entries.some(x => x.isClient);
|
|
2065
|
+
// The intro for the factory
|
|
2066
|
+
const factoryIntro = `// Auto generated dictionary
|
|
1668
2067
|
// @not-modified => When this line is removed, the "force" parameter of the CLI tool is required to overwrite this file
|
|
1669
|
-
import { type ComponentTypeDictionary } from
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
${
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
${
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
}
|
|
1714
|
-
|
|
1715
|
-
return
|
|
2068
|
+
import { type ComponentTypeDictionary } from '@remkoj/optimizely-cms-react';${hasDynamic || hasClientComponents ? `
|
|
2069
|
+
import dynamic from 'next/dynamic';` : ''}`;
|
|
2070
|
+
// The outry for the factory
|
|
2071
|
+
const factoryOutro = `// Export dictionary
|
|
2072
|
+
export default ${factoryName};`;
|
|
2073
|
+
// The imports of the factory
|
|
2074
|
+
const factoryImports = [...components, ...subFactories].map(x => {
|
|
2075
|
+
const imports = [];
|
|
2076
|
+
if (x.loaderImport)
|
|
2077
|
+
imports.push(`import ${x.loaderVariable} from '${x.loaderImport}';`);
|
|
2078
|
+
else if (!x.isClient)
|
|
2079
|
+
imports.push(`import ${x.variable} from '${x.import}';`);
|
|
2080
|
+
if (x.suspenseImport)
|
|
2081
|
+
imports.push(`import ${x.suspenseVariable} from '${x.suspenseImport}';`);
|
|
2082
|
+
return imports.length > 0 ? imports.join('\n') : undefined;
|
|
2083
|
+
}).filter(x => x).join('\n');
|
|
2084
|
+
// The dynamic imports of the factory
|
|
2085
|
+
const dynamicImports = hasDynamic ? `// Lazy load components that have a loading file, this only affects client components
|
|
2086
|
+
// See https://nextjs.org/docs/app/guides/lazy-loading#importing-server-components
|
|
2087
|
+
// for more details on how this affects server components in Next.js
|
|
2088
|
+
` + components.filter(x => x.loaderImport).map(x => {
|
|
2089
|
+
return `const ${x.variable} = dynamic(() => import('${x.import}'), {
|
|
2090
|
+
ssr: true,
|
|
2091
|
+
loading: ${x.loaderVariable}
|
|
2092
|
+
});`;
|
|
2093
|
+
}).join('\n') : undefined;
|
|
2094
|
+
// The client imports of the factory
|
|
2095
|
+
const clientComponents = hasClientComponents ? `// Lazy load client components even without loader specified
|
|
2096
|
+
` + components.filter(x => x.isClient && !x.loaderImport).map(x => {
|
|
2097
|
+
return `const ${x.variable} = dynamic(() => import('${x.import}'), { ssr: true });`;
|
|
2098
|
+
}).join('\n') : undefined;
|
|
2099
|
+
// The actual entries for the factory
|
|
2100
|
+
const factoryEntries = [...components.map(x => {
|
|
2101
|
+
return ` {
|
|
2102
|
+
type: '${x.key}',${x.variant && x.variant !== 'default' ? `
|
|
2103
|
+
variant: '${x.variant}',` : ''}
|
|
2104
|
+
component: ${x.variable}${x.suspenseVariable ? `,
|
|
2105
|
+
useSuspense: true,
|
|
2106
|
+
loader: ${x.suspenseVariable}` : ''}${x.isClient ? `,
|
|
2107
|
+
isClient: true` : ''}
|
|
2108
|
+
}`;
|
|
2109
|
+
}), ...subFactories.map(x => ` ...${x.variable}`)];
|
|
2110
|
+
// The body of the factory
|
|
2111
|
+
const factoryBody = `// Build dictionary
|
|
2112
|
+
export const ${factoryName} : ComponentTypeDictionary = [${factoryEntries.length > 0 ? '\n' + factoryEntries.join(',\n') + '\n' : ''}];`;
|
|
2113
|
+
// Combine everything into one string
|
|
2114
|
+
return [factoryIntro, factoryImports, dynamicImports, clientComponents, factoryBody, factoryOutro].filter(x => (x?.length || 0) > 0).join('\n\n') + '\n';
|
|
1716
2115
|
}
|
|
1717
2116
|
|
|
1718
2117
|
const NextJsCreateCommand = {
|
|
@@ -1752,16 +2151,23 @@ const NextJsFragmentsCommand = {
|
|
|
1752
2151
|
handler: async (args, opts) => {
|
|
1753
2152
|
// Prepare
|
|
1754
2153
|
const { loadedContentTypes, createdTypeFolders } = opts || {};
|
|
1755
|
-
const { components: basePath, _config: { debug }, force } = parseArgs(args);
|
|
2154
|
+
const { components: basePath, _config: { debug }, force, path: appPath } = parseArgs(args);
|
|
1756
2155
|
const client = createCmsClient(args);
|
|
2156
|
+
// Get content types
|
|
1757
2157
|
const { contentTypes, all: allContentTypes } = loadedContentTypes ?? await getContentTypes(client, args);
|
|
2158
|
+
const allContentTypesMap = new Map();
|
|
2159
|
+
allContentTypes.forEach(x => {
|
|
2160
|
+
if (x.key)
|
|
2161
|
+
allContentTypesMap.set(x.key, x);
|
|
2162
|
+
});
|
|
2163
|
+
const generator = new DocumentGenerator(allContentTypesMap);
|
|
1758
2164
|
// Start process
|
|
1759
2165
|
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Generating GraphQL fragments\n`));
|
|
1760
2166
|
const typeFolders = createdTypeFolders ?? createTypeFolders(contentTypes, basePath, debug);
|
|
1761
|
-
const tracker = new
|
|
2167
|
+
const tracker = new PropertyCollisionTracker(appPath);
|
|
1762
2168
|
const dependencies = [];
|
|
1763
2169
|
const updatedTypes = contentTypes.map(contentType => {
|
|
1764
|
-
const { written, propertyTypes } = createComponentFragments(contentType, getTypeFolder(typeFolders, contentType.key), force, debug, tracker);
|
|
2170
|
+
const { written, propertyTypes } = createComponentFragments(contentType, getTypeFolder(typeFolders, contentType.key), force, debug, tracker, generator);
|
|
1765
2171
|
dependencies.push(...propertyTypes);
|
|
1766
2172
|
return written ? contentType.key : undefined;
|
|
1767
2173
|
}).filter(x => x);
|
|
@@ -1781,7 +2187,7 @@ const NextJsFragmentsCommand = {
|
|
|
1781
2187
|
typeFolders.push(tf);
|
|
1782
2188
|
}
|
|
1783
2189
|
return tf;
|
|
1784
|
-
}, force, debug);
|
|
2190
|
+
}, force, debug, tracker, generator);
|
|
1785
2191
|
// Report outcome
|
|
1786
2192
|
if (generatedProps.length > 0)
|
|
1787
2193
|
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Created/updated GraphQL property fragments for ${generatedProps.join(', ')}\n`));
|
|
@@ -1791,7 +2197,7 @@ const NextJsFragmentsCommand = {
|
|
|
1791
2197
|
process.stdout.write(chalk.green(chalk.bold(figures.tick + " Done")) + "\n");
|
|
1792
2198
|
}
|
|
1793
2199
|
};
|
|
1794
|
-
function createComponentFragments(contentType, typePath, force, debug, propertyTracker = new Map()) {
|
|
2200
|
+
function createComponentFragments(contentType, typePath, force, debug, propertyTracker = new Map(), generator) {
|
|
1795
2201
|
const baseQueryFile = typePath.fragmentFile;
|
|
1796
2202
|
let writeFragment = true;
|
|
1797
2203
|
if (fs.existsSync(baseQueryFile)) {
|
|
@@ -1810,13 +2216,13 @@ function createComponentFragments(contentType, typePath, force, debug, propertyT
|
|
|
1810
2216
|
}
|
|
1811
2217
|
let written = false;
|
|
1812
2218
|
if (writeFragment) {
|
|
1813
|
-
const fragment =
|
|
2219
|
+
const fragment = generator.buildFragment(contentType, undefined, false, propertyTracker);
|
|
1814
2220
|
fs.writeFileSync(baseQueryFile, fragment);
|
|
1815
2221
|
written = true;
|
|
1816
2222
|
}
|
|
1817
|
-
return { written, propertyTypes:
|
|
2223
|
+
return { written, propertyTypes: DocumentGenerator.getReferencedPropertyComponents(contentType) };
|
|
1818
2224
|
}
|
|
1819
|
-
function createPropertyFragments(propertyTypesList, allContentTypes, selectTypeFolder, force = false, debug = false) {
|
|
2225
|
+
function createPropertyFragments(propertyTypesList, allContentTypes, selectTypeFolder, force = false, debug = false, propertyTracker = new Map(), generator) {
|
|
1820
2226
|
const returnValue = [];
|
|
1821
2227
|
for (const propertyContentTypeKey of propertyTypesList.filter((v, i, a) => a.indexOf(v, i + 1) <= i)) {
|
|
1822
2228
|
// Load the ContentType definition
|
|
@@ -1848,16 +2254,16 @@ function createPropertyFragments(propertyTypesList, allContentTypes, selectTypeF
|
|
|
1848
2254
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating ${contentType.displayName} (${contentType.key}) property fragment\n`));
|
|
1849
2255
|
}
|
|
1850
2256
|
if (mustWrite) {
|
|
1851
|
-
const fragment =
|
|
2257
|
+
const fragment = generator.buildFragment(contentType, undefined, true, propertyTracker);
|
|
1852
2258
|
fs.writeFileSync(propertyFragmentFile, fragment);
|
|
1853
2259
|
returnValue.push(contentType.key);
|
|
1854
2260
|
}
|
|
1855
2261
|
// Recurse down for properties that we're not yet rendering
|
|
1856
|
-
const referencedPropertyTypes =
|
|
2262
|
+
const referencedPropertyTypes = DocumentGenerator.getReferencedPropertyComponents(contentType).filter(x => !propertyTypesList.includes(x));
|
|
1857
2263
|
if (referencedPropertyTypes.length > 0) {
|
|
1858
2264
|
if (debug)
|
|
1859
2265
|
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);
|
|
2266
|
+
const additionalProperties = createPropertyFragments(referencedPropertyTypes, allContentTypes, selectTypeFolder, force, debug, propertyTracker, generator);
|
|
1861
2267
|
returnValue.push(...additionalProperties);
|
|
1862
2268
|
}
|
|
1863
2269
|
}
|
|
@@ -1875,16 +2281,23 @@ const NextJsQueriesCommand = {
|
|
|
1875
2281
|
handler: async (args, opts) => {
|
|
1876
2282
|
// Prepare
|
|
1877
2283
|
const { loadedContentTypes, createdTypeFolders } = opts || {};
|
|
1878
|
-
const { components: basePath, _config: { debug }, force } = parseArgs(args);
|
|
2284
|
+
const { components: basePath, _config: { debug }, force, path: appPath } = parseArgs(args);
|
|
1879
2285
|
const client = createCmsClient(args);
|
|
1880
2286
|
const { contentTypes, all: allContentTypes } = loadedContentTypes ?? await getContentTypes(client, args);
|
|
2287
|
+
const allContentTypesMap = new Map();
|
|
2288
|
+
allContentTypes.forEach(x => {
|
|
2289
|
+
if (x.key)
|
|
2290
|
+
allContentTypesMap.set(x.key, x);
|
|
2291
|
+
});
|
|
2292
|
+
const generator = new DocumentGenerator(allContentTypesMap);
|
|
2293
|
+
const propertyTracker = new PropertyCollisionTracker(appPath);
|
|
1881
2294
|
// Start process
|
|
1882
2295
|
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Generating GraphQL queries\n`));
|
|
1883
2296
|
const dependencies = [];
|
|
1884
2297
|
const typeFolders = createdTypeFolders ?? createTypeFolders(contentTypes, basePath, debug);
|
|
1885
2298
|
const updatedTypes = contentTypes.map(contentType => {
|
|
1886
2299
|
const typePath = getTypeFolder(typeFolders, contentType.key);
|
|
1887
|
-
const { written, propertyTypes } = createGraphQueries(contentType, typePath, force, debug);
|
|
2300
|
+
const { written, propertyTypes } = createGraphQueries(contentType, typePath, force, debug, propertyTracker, generator);
|
|
1888
2301
|
dependencies.push(...propertyTypes);
|
|
1889
2302
|
return written ? contentType.key : undefined;
|
|
1890
2303
|
}).filter(x => x).flat();
|
|
@@ -1903,7 +2316,7 @@ const NextJsQueriesCommand = {
|
|
|
1903
2316
|
typeFolders.push(tf);
|
|
1904
2317
|
}
|
|
1905
2318
|
return tf;
|
|
1906
|
-
}, force, debug);
|
|
2319
|
+
}, force, debug, propertyTracker, generator);
|
|
1907
2320
|
// Report outcome
|
|
1908
2321
|
if (generatedProps.length > 0)
|
|
1909
2322
|
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Created/updated GraphQL property fragments for ${generatedProps.join(', ')}\n`));
|
|
@@ -1913,7 +2326,7 @@ const NextJsQueriesCommand = {
|
|
|
1913
2326
|
process.stdout.write(chalk.green(chalk.bold(figures.tick + " Done")) + "\n");
|
|
1914
2327
|
}
|
|
1915
2328
|
};
|
|
1916
|
-
function createGraphQueries(contentType, typePath, force, debug) {
|
|
2329
|
+
function createGraphQueries(contentType, typePath, force, debug, tracker, generator) {
|
|
1917
2330
|
const baseQueryFile = typePath.queryFile;
|
|
1918
2331
|
let mustWrite = true;
|
|
1919
2332
|
if (fs.existsSync(baseQueryFile)) {
|
|
@@ -1931,12 +2344,12 @@ function createGraphQueries(contentType, typePath, force, debug) {
|
|
|
1931
2344
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating ${contentType.displayName} (${contentType.key}) base fragment\n`));
|
|
1932
2345
|
}
|
|
1933
2346
|
if (mustWrite) {
|
|
1934
|
-
const fragment =
|
|
2347
|
+
const fragment = generator.buildGetQuery(contentType, undefined, tracker);
|
|
1935
2348
|
fs.writeFileSync(baseQueryFile, fragment);
|
|
1936
2349
|
}
|
|
1937
2350
|
return {
|
|
1938
2351
|
written: mustWrite,
|
|
1939
|
-
propertyTypes:
|
|
2352
|
+
propertyTypes: DocumentGenerator.getReferencedPropertyComponents(contentType)
|
|
1940
2353
|
};
|
|
1941
2354
|
}
|
|
1942
2355
|
|
|
@@ -2216,7 +2629,46 @@ function writeFileAsync(path, data) {
|
|
|
2216
2629
|
});
|
|
2217
2630
|
}
|
|
2218
2631
|
|
|
2219
|
-
|
|
2632
|
+
const MigrateCommand = {
|
|
2633
|
+
command: "project:migrate",
|
|
2634
|
+
describe: "Automate the directory naming convention update",
|
|
2635
|
+
builder,
|
|
2636
|
+
async handler(args, opts) {
|
|
2637
|
+
const { components: componentsPath} = parseArgs(args);
|
|
2638
|
+
const files = await glob(['*/**/*'], { cwd: componentsPath });
|
|
2639
|
+
const operations = [];
|
|
2640
|
+
for (const file of files) {
|
|
2641
|
+
const fullPath = path.join(componentsPath, file);
|
|
2642
|
+
const pathInfo = await fsAsync.stat(fullPath);
|
|
2643
|
+
if (pathInfo.isDirectory()) { // We only need to rename directories
|
|
2644
|
+
const dirName = path.basename(fullPath);
|
|
2645
|
+
const dirParent = path.dirname(fullPath);
|
|
2646
|
+
const newDirName = keyToSlug(dirName);
|
|
2647
|
+
// We're ignoring casing as changing that will confuse Git
|
|
2648
|
+
if (dirName.toLowerCase() !== newDirName.toLowerCase()) {
|
|
2649
|
+
process.stdout.write(`${figures.arrowRight} Updating ${file}\n`);
|
|
2650
|
+
const newFullPath = path.join(dirParent, newDirName);
|
|
2651
|
+
operations.push({ from: fullPath, to: newFullPath });
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
if (pathInfo.isFile() && (fullPath.endsWith('.opti-style.json') || fullPath.endsWith('.opti-type.json'))) {
|
|
2655
|
+
const fileName = path.basename(fullPath);
|
|
2656
|
+
const newFileName = keyToSlug(fileName, { preserveCharacters: ['.'] });
|
|
2657
|
+
if (fileName.toLowerCase() !== newFileName.toLowerCase()) {
|
|
2658
|
+
const filePath = path.dirname(fullPath);
|
|
2659
|
+
const newFullPath = path.join(filePath, newFileName);
|
|
2660
|
+
operations.unshift({ from: fullPath, to: newFullPath });
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
for (const operation of operations) {
|
|
2665
|
+
void await fsAsync.rename(operation.from, operation.to);
|
|
2666
|
+
}
|
|
2667
|
+
NextJsFactoryCommand.handler(args);
|
|
2668
|
+
}
|
|
2669
|
+
};
|
|
2670
|
+
|
|
2671
|
+
var version = "6.0.0-rc.1";
|
|
2220
2672
|
var name = "opti-cms";
|
|
2221
2673
|
var APP = {
|
|
2222
2674
|
version: version,
|
|
@@ -2241,7 +2693,6 @@ const CmsVersionCommand = {
|
|
|
2241
2693
|
throw error;
|
|
2242
2694
|
}
|
|
2243
2695
|
});
|
|
2244
|
-
const hasPreview2Info = versionInfo.results.preview2Data?.baseUrl ? true : false;
|
|
2245
2696
|
const info = new Table({
|
|
2246
2697
|
head: [
|
|
2247
2698
|
chalk.yellow(chalk.bold("Component")),
|
|
@@ -2251,12 +2702,8 @@ const CmsVersionCommand = {
|
|
|
2251
2702
|
colAligns: ["left", "left"]
|
|
2252
2703
|
});
|
|
2253
2704
|
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
2705
|
info.push(["Client API", client.apiVersion]);
|
|
2257
2706
|
info.push(["Service API", versionInfo.apiVersion]);
|
|
2258
|
-
if (hasPreview2Info)
|
|
2259
|
-
info.push(["Service API (Preview 2)", versionInfo.results.preview2Data?.apiVersion ?? 'n/a']);
|
|
2260
2707
|
info.push(["CMS Build", versionInfo.cmsVersion]);
|
|
2261
2708
|
info.push(["Service Build", versionInfo.serviceVersion]);
|
|
2262
2709
|
info.push(["SDK", APP.version]);
|
|
@@ -2379,7 +2826,7 @@ async function deleteContentItem(client, key, onlyChildren = false) {
|
|
|
2379
2826
|
process.stderr.write(chalk.redBright(` ${figures.cross}The ContentItem ${key} is a reserved item, skipping deletion\n`));
|
|
2380
2827
|
return removedCount;
|
|
2381
2828
|
}
|
|
2382
|
-
const deleteResult = await client.
|
|
2829
|
+
const deleteResult = await client.contentDelete({ path: { key }, headers: { "cms-permanent-delete": true } }).then(r => r.key).catch((e) => {
|
|
2383
2830
|
if (e.status == 404)
|
|
2384
2831
|
return key;
|
|
2385
2832
|
throw e;
|
|
@@ -2506,7 +2953,7 @@ async function getAllTypes(client, batchSize = 100) {
|
|
|
2506
2953
|
return actualItems;
|
|
2507
2954
|
}
|
|
2508
2955
|
async function getAllAssets(client, parentKey, batchSize = 100) {
|
|
2509
|
-
const items = await client.
|
|
2956
|
+
const items = await client.contentListAssets({ path: { key: parentKey }, query: { pageIndex: 0, pageSize: batchSize } }).catch((e) => {
|
|
2510
2957
|
if (e.status == 404) {
|
|
2511
2958
|
return {
|
|
2512
2959
|
items: [],
|
|
@@ -2521,7 +2968,7 @@ async function getAllAssets(client, parentKey, batchSize = 100) {
|
|
|
2521
2968
|
const actualItems = items.items ?? [];
|
|
2522
2969
|
const pageCount = Math.ceil(totalItemCount / pageSize);
|
|
2523
2970
|
for (let pageNr = 1; pageNr < pageCount; pageNr++) {
|
|
2524
|
-
const pageData = await client.
|
|
2971
|
+
const pageData = await client.contentListAssets({ path: { key: parentKey }, query: { pageIndex: pageNr, pageSize } }).catch((e) => {
|
|
2525
2972
|
if (e.status == 404) {
|
|
2526
2973
|
return {
|
|
2527
2974
|
items: [],
|
|
@@ -2536,7 +2983,7 @@ async function getAllAssets(client, parentKey, batchSize = 100) {
|
|
|
2536
2983
|
return actualItems;
|
|
2537
2984
|
}
|
|
2538
2985
|
async function getAllItems(client, parentKey, batchSize = 100) {
|
|
2539
|
-
const items = await client.
|
|
2986
|
+
const items = await client.contentListItems({ path: { key: parentKey }, query: { pageIndex: 0, pageSize: batchSize } }).catch((e) => {
|
|
2540
2987
|
if (e.status == 404) {
|
|
2541
2988
|
return {
|
|
2542
2989
|
items: [],
|
|
@@ -2551,7 +2998,7 @@ async function getAllItems(client, parentKey, batchSize = 100) {
|
|
|
2551
2998
|
const actualItems = items.items ?? [];
|
|
2552
2999
|
const pageCount = Math.ceil(totalItemCount / pageSize);
|
|
2553
3000
|
for (let pageNr = 1; pageNr < pageCount; pageNr++) {
|
|
2554
|
-
const pageData = await client.
|
|
3001
|
+
const pageData = await client.contentListItems({ path: { key: parentKey }, query: { pageIndex: pageNr, pageSize } }).catch((e) => {
|
|
2555
3002
|
if (e.status == 404) {
|
|
2556
3003
|
return {
|
|
2557
3004
|
items: [],
|
|
@@ -2584,20 +3031,23 @@ const commands = [
|
|
|
2584
3031
|
StylesListCommand,
|
|
2585
3032
|
StylesPullCommand,
|
|
2586
3033
|
StylesPushCommand,
|
|
3034
|
+
StylesDeleteCommand,
|
|
2587
3035
|
TypesPullCommand,
|
|
2588
|
-
TypesPushCommand
|
|
3036
|
+
TypesPushCommand,
|
|
3037
|
+
MigrateCommand
|
|
2589
3038
|
];
|
|
2590
3039
|
|
|
2591
3040
|
async function main() {
|
|
2592
|
-
const
|
|
2593
|
-
const
|
|
3041
|
+
const projectDir = getProjectDir();
|
|
3042
|
+
const envFiles = prepare(projectDir);
|
|
3043
|
+
const app = createOptiCmsApp(APP.name, APP.version, undefined, envFiles, projectDir);
|
|
2594
3044
|
app.command(commands);
|
|
2595
3045
|
try {
|
|
2596
3046
|
await app.parse(process.argv.slice(2));
|
|
2597
3047
|
}
|
|
2598
3048
|
catch {
|
|
2599
3049
|
//We're ignoring error here, as yargs will already generate the "nice output" for it
|
|
2600
|
-
//console.log('Caught error')
|
|
3050
|
+
//console.log ('Caught error')
|
|
2601
3051
|
}
|
|
2602
3052
|
}
|
|
2603
3053
|
main();
|