@remkoj/optimizely-cms-cli 2.0.0 → 2.0.1-pre1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +744 -293
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
package/dist/index.js
CHANGED
|
@@ -55,11 +55,20 @@ function isDemanded(value) {
|
|
|
55
55
|
* @param param0 The parameters from the Command Line application
|
|
56
56
|
* @returns The arguments, with the Optimizely CMS Client parameters transformed into a configuration object
|
|
57
57
|
*/
|
|
58
|
-
function parseArgs({ client_id, client_secret, cms_url, user_id, verbose, path: argsPath, components: argsComponents, ...args }) {
|
|
58
|
+
function parseArgs({ client_id, client_secret, cms_url, user_id, verbose, path: argsPath, components: argsComponents, ...args }, createBasePath = true) {
|
|
59
59
|
const appPath = path.isAbsolute(argsPath) ? argsPath : path.normalize(path.join(process.cwd(), argsPath));
|
|
60
60
|
const componentDir = path.normalize(path.join(argsPath, argsComponents));
|
|
61
61
|
if (!componentDir.startsWith(argsPath))
|
|
62
62
|
throw new Error(`The component directory ${componentDir} is outside the application directory (${appPath})`);
|
|
63
|
+
if (createBasePath && !fs.existsSync(componentDir)) {
|
|
64
|
+
if (verbose)
|
|
65
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} The components directory ${componentDir} does not exist yet, it will be created\n`));
|
|
66
|
+
fs.mkdirSync(componentDir, { recursive: true });
|
|
67
|
+
}
|
|
68
|
+
if (!fs.statSync(componentDir).isDirectory()) {
|
|
69
|
+
process.stderr.write(chalk.redBright(`${chalk.bold(figures.cross)} The components path ${componentDir} exists, but is not a folder\n`));
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
63
72
|
return {
|
|
64
73
|
_config: {
|
|
65
74
|
base: cms_url,
|
|
@@ -143,26 +152,142 @@ function isNotNullOrUndefined(i) {
|
|
|
143
152
|
return i ? true : false;
|
|
144
153
|
}
|
|
145
154
|
|
|
155
|
+
function createCmsClient(args) {
|
|
156
|
+
const { _config: cfg } = parseArgs(args);
|
|
157
|
+
const client = createClient(cfg);
|
|
158
|
+
if (cfg.debug)
|
|
159
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Connecting to ${cfg.base.href} as ${cfg.actAs ?? cfg.clientId}\n`));
|
|
160
|
+
return client;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const contentTypesBuilder = yargs => {
|
|
164
|
+
yargs.option('excludeTypes', { alias: 'ect', description: "Exclude these content types", string: true, type: 'array', demandOption: false, default: [] });
|
|
165
|
+
yargs.option('excludeBaseTypes', { alias: 'ebt', description: "Exclude these base types", string: true, type: 'array', demandOption: false, default: ['folder', 'media', 'image', 'video'] });
|
|
166
|
+
yargs.option("baseTypes", { alias: 'b', description: "Select only these base types", string: true, type: 'array', demandOption: false, default: [] });
|
|
167
|
+
yargs.option("types", { alias: 't', description: "Select only these types", string: true, type: 'array', demandOption: false, default: [] });
|
|
168
|
+
return yargs;
|
|
169
|
+
};
|
|
170
|
+
async function getContentTypes(client, args, pageSize = 100, allowSystem = false) {
|
|
171
|
+
const { _config: cfg, excludeBaseTypes, excludeTypes, baseTypes, types } = parseArgs(args);
|
|
172
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Pulling Content Types from Optimizely CMS\n`));
|
|
173
|
+
if (cfg.debug)
|
|
174
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetching page 1 of ? (${pageSize} items per page)\n`));
|
|
175
|
+
let resultsPage = await client.contentTypes.contentTypesList(undefined, undefined, 0, pageSize);
|
|
176
|
+
const results = resultsPage.items ?? [];
|
|
177
|
+
let pagesRemaining = Math.ceil(resultsPage.totalItemCount / resultsPage.pageSize) - (resultsPage.pageIndex + 1);
|
|
178
|
+
while (pagesRemaining > 0 && results.length < resultsPage.totalItemCount) {
|
|
179
|
+
if (cfg.debug)
|
|
180
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetching page ${resultsPage.pageIndex + 2} of ${Math.ceil(resultsPage.totalItemCount / resultsPage.pageSize)} (${resultsPage.pageSize} items per page)\n`));
|
|
181
|
+
resultsPage = await client.contentTypes.contentTypesList(undefined, undefined, resultsPage.pageIndex + 1, resultsPage.pageSize);
|
|
182
|
+
results.push(...resultsPage.items);
|
|
183
|
+
pagesRemaining = Math.ceil(resultsPage.totalItemCount / resultsPage.pageSize) - (resultsPage.pageIndex + 1);
|
|
184
|
+
}
|
|
185
|
+
if (cfg.debug) {
|
|
186
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetched ${results.length} Content-Types from Optimizely CMS\n`));
|
|
187
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Filtering Content-Types based upon arguments\n`));
|
|
188
|
+
}
|
|
189
|
+
const contentTypes = results.filter(data => {
|
|
190
|
+
// Remove items based upon filters
|
|
191
|
+
const keepType = (!excludeBaseTypes.includes(data.baseType)) &&
|
|
192
|
+
(!excludeTypes.includes(data.key)) &&
|
|
193
|
+
(baseTypes.length == 0 || baseTypes.includes(data.baseType)) &&
|
|
194
|
+
(types.length == 0 || types.includes(data.key));
|
|
195
|
+
if (!keepType) {
|
|
196
|
+
if (cfg.debug)
|
|
197
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Content-Type ${data.key} due to applied filters\n`));
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
// Skip system types if desired
|
|
201
|
+
if (data.source == 'system' && !allowSystem) {
|
|
202
|
+
if (cfg.debug)
|
|
203
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Content-Type ${data.key} due to it being a system type\n`));
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
return true;
|
|
207
|
+
});
|
|
208
|
+
if (cfg.debug)
|
|
209
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Applied content type filters, reduced from ${results.length} to ${contentTypes.length} items\n`));
|
|
210
|
+
return {
|
|
211
|
+
all: results,
|
|
212
|
+
contentTypes
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const stylesBuilder = yargs => {
|
|
217
|
+
const newArgs = contentTypesBuilder(yargs);
|
|
218
|
+
newArgs.option('excludeNodeTypes', { alias: 'ent', description: "Exclude these node types", string: true, type: 'array', demandOption: false, default: [] });
|
|
219
|
+
newArgs.option('excludeTemplates', { alias: 'et', description: "Exclude these templates", string: true, type: 'array', demandOption: false, default: ['folder', 'media', 'image', 'video'] });
|
|
220
|
+
newArgs.option("nodes", { alias: 'n', description: "Select only these node types", string: true, type: 'array', demandOption: false, default: [] });
|
|
221
|
+
newArgs.option("templates", { alias: 'd', description: "Select only these templates", string: true, type: 'array', demandOption: false, default: [] });
|
|
222
|
+
newArgs.option("templateTypes", { alias: 'tt', description: "Select only these template types", choices: ['node', 'base', 'component'], type: 'array', demandOption: false, default: [] });
|
|
223
|
+
return newArgs;
|
|
224
|
+
};
|
|
225
|
+
async function getStyles(client, args, pageSize = 100) {
|
|
226
|
+
const { _config: cfg, excludeBaseTypes, excludeTypes, excludeNodeTypes, excludeTemplates, baseTypes, types, nodes, templates, templateTypes } = parseArgs(args);
|
|
227
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Pulling Style-Definitions from Optimizely CMS\n`));
|
|
228
|
+
if (cfg.debug)
|
|
229
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetching page 1 of ? (${pageSize} items per page)\n`));
|
|
230
|
+
let resultsPage = await client.displayTemplates.displayTemplatesList(0, pageSize);
|
|
231
|
+
const results = resultsPage.items ?? [];
|
|
232
|
+
let pagesRemaining = Math.ceil(resultsPage.totalItemCount / resultsPage.pageSize) - (resultsPage.pageIndex + 1);
|
|
233
|
+
while (pagesRemaining > 0 && results.length < resultsPage.totalItemCount) {
|
|
234
|
+
if (cfg.debug)
|
|
235
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetching page ${resultsPage.pageIndex + 2} of ${Math.ceil(resultsPage.totalItemCount / resultsPage.pageSize)} (${resultsPage.pageSize} items per page)\n`));
|
|
236
|
+
resultsPage = await client.displayTemplates.displayTemplatesList(resultsPage.pageIndex + 1, resultsPage.pageSize);
|
|
237
|
+
results.push(...resultsPage.items);
|
|
238
|
+
pagesRemaining = Math.ceil(resultsPage.totalItemCount / resultsPage.pageSize) - (resultsPage.pageIndex + 1);
|
|
239
|
+
}
|
|
240
|
+
if (cfg.debug) {
|
|
241
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetched ${results.length} Style-Definitions from Optimizely CMS\n`));
|
|
242
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Filtering Style-Definitions based upon arguments\n`));
|
|
243
|
+
}
|
|
244
|
+
const styles = results.filter(data => {
|
|
245
|
+
if (isExcluded(data.key, excludeTemplates, templates)) {
|
|
246
|
+
if (cfg.debug)
|
|
247
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${data.key} - Style defintion key filtering active\n`));
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
const templateType = data.baseType ? 'base' : data.nodeType ? 'node' : data.contentType ? 'component' : 'unknown';
|
|
251
|
+
if (isExcluded(templateType, [], templateTypes)) {
|
|
252
|
+
if (cfg.debug)
|
|
253
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${data.key} - Style type filtering is active\n`));
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
if (data.baseType && isExcluded(data.baseType, excludeBaseTypes, baseTypes)) {
|
|
257
|
+
if (cfg.debug)
|
|
258
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${data.key} - Style is defined at base type level and base type filtering is active\n`));
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
if (data.contentType && isExcluded(data.contentType, excludeTypes, types)) {
|
|
262
|
+
if (cfg.debug)
|
|
263
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${data.key} - Style is defined at component type level and component type filtering is active\n`));
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
if (data.nodeType && isExcluded(data.nodeType, excludeNodeTypes, nodes)) {
|
|
267
|
+
if (cfg.debug)
|
|
268
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Style-Defintion ${data.key} - Style is defined at node type level and node type filtering is active\n`));
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
return true;
|
|
272
|
+
});
|
|
273
|
+
if (cfg.debug)
|
|
274
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Applied content type filters, reduced from ${results.length} to ${styles.length} items\n`));
|
|
275
|
+
return {
|
|
276
|
+
all: results,
|
|
277
|
+
styles
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function isExcluded(value, exclusions, inclusions) {
|
|
281
|
+
if (value == undefined || value == null)
|
|
282
|
+
return false;
|
|
283
|
+
return exclusions.includes(value) || (inclusions.length > 0 && !inclusions.includes(value));
|
|
284
|
+
}
|
|
285
|
+
|
|
146
286
|
const StylesListCommand = {
|
|
147
287
|
command: "styles:list",
|
|
148
288
|
describe: "List Visual Builder style definitions from the CMS",
|
|
149
289
|
handler: async (args) => {
|
|
150
|
-
const {
|
|
151
|
-
const client = createClient(cfg);
|
|
152
|
-
const pageSize = 100;
|
|
153
|
-
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Reading DisplayStyles from Optimizely CMS\n`));
|
|
154
|
-
if (cfg.debug)
|
|
155
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetching page 1 of ? (${pageSize} items per page)\n`));
|
|
156
|
-
let templatesPage = await client.displayTemplates.displayTemplatesList(0, pageSize);
|
|
157
|
-
const results = templatesPage.items ?? [];
|
|
158
|
-
let pagesRemaining = Math.ceil(templatesPage.totalItemCount / templatesPage.pageSize) - (templatesPage.pageIndex + 1);
|
|
159
|
-
while (pagesRemaining > 0 && results.length < templatesPage.totalItemCount) {
|
|
160
|
-
if (cfg.debug)
|
|
161
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetching page ${templatesPage.pageIndex + 2} of ${Math.ceil(templatesPage.totalItemCount / templatesPage.pageSize)} (${templatesPage.pageSize} items per page)\n`));
|
|
162
|
-
templatesPage = await client.displayTemplates.displayTemplatesList(templatesPage.pageIndex + 1, templatesPage.pageSize);
|
|
163
|
-
results.push(...templatesPage.items);
|
|
164
|
-
pagesRemaining = Math.ceil(templatesPage.totalItemCount / templatesPage.pageSize) - (templatesPage.pageIndex + 1);
|
|
165
|
-
}
|
|
290
|
+
const { all: results } = await getStyles(createCmsClient(args), { ...args, excludeBaseTypes: [], excludeNodeTypes: [], excludeTemplates: [], excludeTypes: [], baseTypes: [], nodes: [], templates: [], types: [], templateTypes: [] });
|
|
166
291
|
const styles = new Table({
|
|
167
292
|
head: [
|
|
168
293
|
chalk.yellow(chalk.bold("Name")),
|
|
@@ -190,50 +315,15 @@ const StylesPullCommand = {
|
|
|
190
315
|
command: "styles:pull",
|
|
191
316
|
describe: "Create Visual Builder style definitions from the CMS",
|
|
192
317
|
builder: (yargs) => {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
return yargs;
|
|
318
|
+
const newYargs = stylesBuilder(yargs);
|
|
319
|
+
newYargs.option('force', { alias: 'f', description: "Overwrite existing files", boolean: true, type: 'boolean', demandOption: false, default: false });
|
|
320
|
+
newYargs.option("definitions", { alias: 'd', description: "Create/overwrite typescript definitions", boolean: true, type: 'boolean', demandOption: false, default: true });
|
|
321
|
+
return newYargs;
|
|
198
322
|
},
|
|
199
323
|
handler: async (args) => {
|
|
200
|
-
const { _config: cfg, components: basePath, force, definitions
|
|
201
|
-
const client =
|
|
202
|
-
const
|
|
203
|
-
//#region Load all templates from Optimizely CMS
|
|
204
|
-
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Reading DisplayStyles from Optimizely CMS\n`));
|
|
205
|
-
if (cfg.debug)
|
|
206
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetching page 1 of ? (${pageSize} items per page)\n`));
|
|
207
|
-
let templatesPage = await client.displayTemplates.displayTemplatesList(0, pageSize);
|
|
208
|
-
const results = templatesPage.items ?? [];
|
|
209
|
-
let pagesRemaining = Math.ceil(templatesPage.totalItemCount / templatesPage.pageSize) - (templatesPage.pageIndex + 1);
|
|
210
|
-
while (pagesRemaining > 0 && results.length < templatesPage.totalItemCount) {
|
|
211
|
-
if (cfg.debug)
|
|
212
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetching page ${templatesPage.pageIndex + 2} of ${Math.ceil(templatesPage.totalItemCount / templatesPage.pageSize)} (${templatesPage.pageSize} items per page)\n`));
|
|
213
|
-
templatesPage = await client.displayTemplates.displayTemplatesList(templatesPage.pageIndex + 1, templatesPage.pageSize);
|
|
214
|
-
results.push(...templatesPage.items);
|
|
215
|
-
pagesRemaining = Math.ceil(templatesPage.totalItemCount / templatesPage.pageSize) - (templatesPage.pageIndex + 1);
|
|
216
|
-
}
|
|
217
|
-
if (cfg.debug)
|
|
218
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetched ${results.length} Content-Types from Optimizely CMS\n`));
|
|
219
|
-
//#endregion
|
|
220
|
-
//#region Ensure target base path exists
|
|
221
|
-
if (!fs.existsSync(basePath))
|
|
222
|
-
fs.mkdirSync(basePath, { recursive: true });
|
|
223
|
-
if (!fs.statSync(basePath).isDirectory()) {
|
|
224
|
-
process.stderr.write(chalk.redBright(`${chalk.bold(figures.cross)} The components path ${basePath} is not a folder\n`));
|
|
225
|
-
process.exit(1);
|
|
226
|
-
}
|
|
227
|
-
//#endregion
|
|
228
|
-
//#region Apply template filters
|
|
229
|
-
const filteredResults = results.filter(result => {
|
|
230
|
-
if (excludeTemplates.includes(result.key))
|
|
231
|
-
return false;
|
|
232
|
-
if (templates.length > 0 && !templates.includes(result.key))
|
|
233
|
-
return false;
|
|
234
|
-
return true;
|
|
235
|
-
});
|
|
236
|
-
//#endregion
|
|
324
|
+
const { _config: cfg, components: basePath, force, definitions } = parseArgs(args);
|
|
325
|
+
const client = createCmsClient(args);
|
|
326
|
+
const { styles: filteredResults } = await getStyles(client, args);
|
|
237
327
|
//#region Create & Write opti-style.json files
|
|
238
328
|
const typeFiles = {};
|
|
239
329
|
const updatedTemplates = (await Promise.all(filteredResults.map(async (displayTemplate) => {
|
|
@@ -241,8 +331,8 @@ const StylesPullCommand = {
|
|
|
241
331
|
let targetType;
|
|
242
332
|
let typesPath;
|
|
243
333
|
if (displayTemplate.nodeType) {
|
|
244
|
-
itemPath = path.join(basePath, '
|
|
245
|
-
typesPath = path.join(basePath, '
|
|
334
|
+
itemPath = path.join(basePath, 'nodes', displayTemplate.nodeType, displayTemplate.key);
|
|
335
|
+
typesPath = path.join(basePath, 'nodes', displayTemplate.nodeType);
|
|
246
336
|
targetType = 'node/' + displayTemplate.nodeType;
|
|
247
337
|
}
|
|
248
338
|
else if (displayTemplate.baseType) {
|
|
@@ -260,6 +350,17 @@ const StylesPullCommand = {
|
|
|
260
350
|
fs.mkdirSync(itemPath, { recursive: true });
|
|
261
351
|
// Write Style JSON
|
|
262
352
|
const filePath = path.join(itemPath, `${displayTemplate.key}.opti-style.json`);
|
|
353
|
+
if (fs.existsSync(filePath)) {
|
|
354
|
+
if (!force) {
|
|
355
|
+
if (cfg.debug)
|
|
356
|
+
process.stdout.write(chalk.gray(`${figures.cross} Skipping style file for ${displayTemplate.key} - File already exists\n`));
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if (cfg.debug)
|
|
360
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Overwriting style file for ${displayTemplate.key}\n`));
|
|
361
|
+
}
|
|
362
|
+
else if (cfg.debug)
|
|
363
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating style file for ${displayTemplate.key}\n`));
|
|
263
364
|
fs.writeFileSync(filePath, JSON.stringify(displayTemplate, undefined, 2));
|
|
264
365
|
if (!typeFiles[targetType]) {
|
|
265
366
|
typeFiles[targetType] = {
|
|
@@ -269,17 +370,23 @@ const StylesPullCommand = {
|
|
|
269
370
|
}
|
|
270
371
|
typeFiles[targetType].templates.push({ file: filePath, data: displayTemplate });
|
|
271
372
|
return displayTemplate.key;
|
|
272
|
-
})));
|
|
373
|
+
}))).filter(x => x);
|
|
273
374
|
//#endregion
|
|
274
375
|
//#region Create needed definition files
|
|
275
376
|
if (definitions) {
|
|
276
377
|
for (const targetId of Object.getOwnPropertyNames(typeFiles)) {
|
|
277
378
|
const { filePath: typeFilePath, templates } = typeFiles[targetId];
|
|
278
|
-
if (fs.existsSync(typeFilePath)
|
|
379
|
+
if (fs.existsSync(typeFilePath)) {
|
|
380
|
+
if (!force) {
|
|
381
|
+
if (cfg.debug)
|
|
382
|
+
process.stdout.write(chalk.gray(`${figures.cross} Skipped writing definition file for ${targetId} - it already exists\n`));
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
279
385
|
if (cfg.debug)
|
|
280
|
-
process.stdout.write(chalk.gray(`${figures.
|
|
281
|
-
continue;
|
|
386
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Overwriting definition file for ${targetId}\n`));
|
|
282
387
|
}
|
|
388
|
+
else if (cfg.debug)
|
|
389
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating definition file for ${targetId}\n`));
|
|
283
390
|
// Write Style definition
|
|
284
391
|
const imports = ['import type { LayoutProps } from "@remkoj/optimizely-cms-react/components"', 'import type { ReactNode } from "react"'];
|
|
285
392
|
const typeContents = [];
|
|
@@ -314,8 +421,7 @@ export type ${typeId}Component<DT extends Record<string, any> = Record<string, a
|
|
|
314
421
|
}
|
|
315
422
|
}
|
|
316
423
|
//#endregion
|
|
317
|
-
process.stdout.write(chalk.
|
|
318
|
-
process.stdout.write(chalk.green(chalk.bold(figures.tick + " Done")) + "\n");
|
|
424
|
+
process.stdout.write(chalk.green(chalk.bold(figures.tick + ` Created/updated style definitions for ${updatedTemplates.join(', ')}`)) + "\n");
|
|
319
425
|
}
|
|
320
426
|
};
|
|
321
427
|
function ucFirst$1(input) {
|
|
@@ -328,59 +434,30 @@ const TypesPullCommand = {
|
|
|
328
434
|
command: "types:pull",
|
|
329
435
|
describe: "Pull content type definition files into the project",
|
|
330
436
|
builder: (yargs) => {
|
|
437
|
+
const newArgs = contentTypesBuilder(yargs);
|
|
331
438
|
yargs.option('force', { alias: 'f', description: "Overwrite existing files", boolean: true, type: 'boolean', demandOption: false, default: false });
|
|
332
|
-
|
|
333
|
-
yargs.option('excludeBaseTypes', { alias: 'ebt', description: "Exclude these base types", string: true, type: 'array', demandOption: false, default: ['folder', 'media', 'image', 'video'] });
|
|
334
|
-
yargs.option("baseTypes", { alias: 'b', description: "Select only these base types", string: true, type: 'array', demandOption: false, default: [] });
|
|
335
|
-
yargs.option("types", { alias: 't', description: "Select only these types", string: true, type: 'array', demandOption: false, default: [] });
|
|
336
|
-
return yargs;
|
|
439
|
+
return newArgs;
|
|
337
440
|
},
|
|
338
441
|
handler: async (args) => {
|
|
339
|
-
const { _config:
|
|
340
|
-
const client =
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
if (cfg.debug)
|
|
344
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetching page 1 of ? (${pageSize} items per page)\n`));
|
|
345
|
-
let templatesPage = await client.contentTypes.contentTypesList(undefined, undefined, 0, pageSize);
|
|
346
|
-
const results = templatesPage.items ?? [];
|
|
347
|
-
let pagesRemaining = Math.ceil(templatesPage.totalItemCount / templatesPage.pageSize) - (templatesPage.pageIndex + 1);
|
|
348
|
-
while (pagesRemaining > 0 && results.length < templatesPage.totalItemCount) {
|
|
349
|
-
if (cfg.debug)
|
|
350
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetching page ${templatesPage.pageIndex + 2} of ${Math.ceil(templatesPage.totalItemCount / templatesPage.pageSize)} (${templatesPage.pageSize} items per page)\n`));
|
|
351
|
-
templatesPage = await client.contentTypes.contentTypesList(undefined, undefined, templatesPage.pageIndex + 1, templatesPage.pageSize);
|
|
352
|
-
results.push(...templatesPage.items);
|
|
353
|
-
pagesRemaining = Math.ceil(templatesPage.totalItemCount / templatesPage.pageSize) - (templatesPage.pageIndex + 1);
|
|
354
|
-
}
|
|
355
|
-
if (cfg.debug)
|
|
356
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetched ${results.length} Content-Types from Optimizely CMS\n`));
|
|
357
|
-
if (!fs.existsSync(basePath))
|
|
358
|
-
fs.mkdirSync(basePath, { recursive: true });
|
|
359
|
-
if (!fs.statSync(basePath).isDirectory()) {
|
|
360
|
-
process.stderr.write(chalk.redBright(`${chalk.bold(figures.cross)} The components path ${basePath} is not a folder\n`));
|
|
361
|
-
process.exit(1);
|
|
362
|
-
}
|
|
363
|
-
const updatedTypes = results.filter(data => {
|
|
364
|
-
return (!excludeBaseTypes.includes(data.baseType)) &&
|
|
365
|
-
(!excludeTypes.includes(data.key)) &&
|
|
366
|
-
(!baseTypes || baseTypes.length == 0 || baseTypes.includes(data.baseType)) &&
|
|
367
|
-
(!types || types.length == 0 || types.includes(data.key));
|
|
368
|
-
}).map(contentType => {
|
|
442
|
+
const { _config: { debug }, components: basePath, force } = parseArgs(args);
|
|
443
|
+
const client = createCmsClient(args);
|
|
444
|
+
const { contentTypes } = await getContentTypes(client, args);
|
|
445
|
+
const updatedTypes = contentTypes.map(contentType => {
|
|
369
446
|
const typePath = path.join(basePath, contentType.baseType, contentType.key);
|
|
370
447
|
const typeFile = path.join(typePath, `${contentType.key}.opti-type.json`);
|
|
371
448
|
if (!fs.existsSync(typePath))
|
|
372
449
|
fs.mkdirSync(typePath, { recursive: true });
|
|
373
450
|
if (fs.existsSync(typeFile) && !force) {
|
|
374
|
-
|
|
451
|
+
if (debug)
|
|
452
|
+
process.stdout.write(chalk.yellow(`${figures.cross} Skipping type definition for ${contentType.displayName} (${contentType.key}) - File already exists\n`));
|
|
375
453
|
return contentType.key;
|
|
376
454
|
}
|
|
377
|
-
if (
|
|
455
|
+
if (debug)
|
|
378
456
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Writing type definition for ${contentType.displayName} (${contentType.key})\n`));
|
|
379
457
|
fs.writeFileSync(typeFile, JSON.stringify(contentType, undefined, 2));
|
|
380
458
|
return contentType.key;
|
|
381
459
|
}).filter(x => x);
|
|
382
|
-
process.stdout.write(chalk.
|
|
383
|
-
process.stdout.write(chalk.green(chalk.bold(figures.tick + " Done")) + "\n");
|
|
460
|
+
process.stdout.write(chalk.green(chalk.bold(`${figures.tick} Created/updated type definitions for ${updatedTypes.join(', ')}\n`)));
|
|
384
461
|
}
|
|
385
462
|
};
|
|
386
463
|
|
|
@@ -444,209 +521,81 @@ const TypesPushCommand = {
|
|
|
444
521
|
}
|
|
445
522
|
};
|
|
446
523
|
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
yargs.option('force', { alias: 'f', description: "Overwrite existing files", boolean: true, type: 'boolean', demandOption: false, default: false });
|
|
452
|
-
yargs.option('excludeTypes', { alias: 'ect', description: "Exclude these content types", string: true, type: 'array', demandOption: false, default: [] });
|
|
453
|
-
yargs.option('excludeBaseTypes', { alias: 'ebt', description: "Exclude these base types", string: true, type: 'array', demandOption: false, default: ['folder', 'media', 'image', 'video'] });
|
|
454
|
-
return yargs;
|
|
455
|
-
},
|
|
456
|
-
handler: async (args) => {
|
|
457
|
-
const { _config: cfg, components: basePath, excludeBaseTypes, excludeTypes, force } = parseArgs(args);
|
|
458
|
-
const client = createClient(cfg);
|
|
459
|
-
const pageSize = 100;
|
|
460
|
-
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Pulling Content Types from Optimizely CMS\n`));
|
|
461
|
-
if (cfg.debug)
|
|
462
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetching page 1 of ? (${pageSize} items per page)\n`));
|
|
463
|
-
let templatesPage = await client.contentTypes.contentTypesList(undefined, undefined, 0, pageSize);
|
|
464
|
-
const results = templatesPage.items ?? [];
|
|
465
|
-
let pagesRemaining = Math.ceil(templatesPage.totalItemCount / templatesPage.pageSize) - (templatesPage.pageIndex + 1);
|
|
466
|
-
while (pagesRemaining > 0 && results.length < templatesPage.totalItemCount) {
|
|
467
|
-
if (cfg.debug)
|
|
468
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetching page ${templatesPage.pageIndex + 2} of ${Math.ceil(templatesPage.totalItemCount / templatesPage.pageSize)} (${templatesPage.pageSize} items per page)\n`));
|
|
469
|
-
templatesPage = await client.contentTypes.contentTypesList(undefined, undefined, templatesPage.pageIndex + 1, templatesPage.pageSize);
|
|
470
|
-
results.push(...templatesPage.items);
|
|
471
|
-
pagesRemaining = Math.ceil(templatesPage.totalItemCount / templatesPage.pageSize) - (templatesPage.pageIndex + 1);
|
|
472
|
-
}
|
|
473
|
-
if (cfg.debug)
|
|
474
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetched ${results.length} Content-Types from Optimizely CMS\n`));
|
|
475
|
-
if (!fs.existsSync(basePath))
|
|
476
|
-
fs.mkdirSync(basePath, { recursive: true });
|
|
477
|
-
if (!fs.statSync(basePath).isDirectory()) {
|
|
478
|
-
process.stderr.write(chalk.redBright(`${chalk.bold(figures.cross)} The components path ${basePath} is not a folder\n`));
|
|
479
|
-
process.exit(1);
|
|
480
|
-
}
|
|
481
|
-
// Apply content type filters
|
|
482
|
-
if (cfg.debug)
|
|
483
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Applying content type filters\n`));
|
|
484
|
-
const contentTypes = results.filter(contentType => {
|
|
485
|
-
if (contentType.source == 'system') {
|
|
486
|
-
if (cfg.debug)
|
|
487
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.displayName} (${contentType.key}) - Internal CMS type\n`));
|
|
488
|
-
return false;
|
|
489
|
-
}
|
|
490
|
-
const baseType = contentType.baseType ?? 'default';
|
|
491
|
-
if (excludeBaseTypes.includes(baseType)) {
|
|
492
|
-
if (cfg.debug)
|
|
493
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.displayName} (${contentType.key}) - Base type excluded\n`));
|
|
494
|
-
return false;
|
|
495
|
-
}
|
|
496
|
-
if (excludeTypes.includes(contentType.key)) {
|
|
497
|
-
if (cfg.debug)
|
|
498
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.displayName} (${contentType.key}) - Content type excluded\n`));
|
|
499
|
-
return false;
|
|
500
|
-
}
|
|
501
|
-
return true;
|
|
502
|
-
});
|
|
503
|
-
if (cfg.debug)
|
|
504
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Applied content type filters, reduced from ${results.length} to ${contentTypes.length} items\n`));
|
|
505
|
-
// Create the type specific
|
|
506
|
-
const updatedTypes = contentTypes.map(contentType => {
|
|
507
|
-
const baseType = contentType.baseType ?? 'default';
|
|
508
|
-
// Create the type folder
|
|
509
|
-
const typePath = path.join(basePath, baseType, contentType.key);
|
|
510
|
-
if (!fs.existsSync(typePath))
|
|
511
|
-
fs.mkdirSync(typePath, { recursive: true });
|
|
512
|
-
// Create fragments
|
|
513
|
-
createGraphFragments(contentType, typePath, basePath, force, cfg, contentTypes);
|
|
514
|
-
// Create component
|
|
515
|
-
createComponent(contentType, typePath, force, cfg);
|
|
516
|
-
return contentType.key;
|
|
517
|
-
}).filter(x => x);
|
|
518
|
-
createFactory(contentTypes, basePath, force, cfg);
|
|
519
|
-
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Created/updated type definitions for ${updatedTypes.join(', ')}\n`));
|
|
520
|
-
process.stdout.write(chalk.green(chalk.bold(figures.tick + " Done")) + "\n");
|
|
521
|
-
}
|
|
524
|
+
const builder = yargs => {
|
|
525
|
+
const updatedArgs = contentTypesBuilder(yargs);
|
|
526
|
+
updatedArgs.option('force', { alias: 'f', description: "Overwrite existing files", boolean: true, type: 'boolean', demandOption: false, default: false });
|
|
527
|
+
return updatedArgs;
|
|
522
528
|
};
|
|
523
|
-
function
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Factory for ${ucFirst(baseType)} will contain these components: ${inheritingTypes.map(x => x.displayName ?? x.key).join(", ")}\n`));
|
|
533
|
-
// Check the file presence and if we should overwrite
|
|
534
|
-
const baseTypeFactory = path.join(basePath, baseType, 'index.ts');
|
|
535
|
-
if (fs.existsSync(baseTypeFactory)) {
|
|
536
|
-
if (cfg.debug)
|
|
537
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Overwriting ${ucFirst(baseType)} factory\n`));
|
|
529
|
+
function createTypeFolders(contentTypes, basePath, debug = false) {
|
|
530
|
+
const folders = contentTypes.map(contentType => {
|
|
531
|
+
const baseType = contentType.baseType ?? 'default';
|
|
532
|
+
// Create the type folder
|
|
533
|
+
const typePath = path.join(basePath, baseType, contentType.key);
|
|
534
|
+
if (!fs.existsSync(typePath)) {
|
|
535
|
+
fs.mkdirSync(typePath, { recursive: true });
|
|
536
|
+
if (debug)
|
|
537
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Created folder ${typePath} for Content-Type ${contentType.key}\n`));
|
|
538
538
|
}
|
|
539
|
-
//
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
});
|
|
547
|
-
lines.push('');
|
|
548
|
-
lines.push(`export const ${baseType}Dictionary : ComponentTypeDictionary = [`);
|
|
549
|
-
inheritingTypes.forEach(type => {
|
|
550
|
-
lines.push(' {', ` type: '${type.key}',`, ` component: ${type.key}`, ' },');
|
|
551
|
-
});
|
|
552
|
-
lines.push(']', '', `export default ${baseType}Dictionary`);
|
|
553
|
-
fs.writeFileSync(baseTypeFactory, lines.join("\n"));
|
|
554
|
-
return baseType;
|
|
555
|
-
}).filter(x => x);
|
|
556
|
-
const cmsFactoryFile = path.join(basePath, 'index.ts');
|
|
557
|
-
if (fs.existsSync(cmsFactoryFile)) {
|
|
558
|
-
if (cfg.debug)
|
|
559
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Overwriting CMS Components factory\n`));
|
|
560
|
-
}
|
|
561
|
-
const lines = [
|
|
562
|
-
'// Auto generated dictionary',
|
|
563
|
-
'import { ComponentTypeDictionary } from "@remkoj/optimizely-cms-react";'
|
|
564
|
-
];
|
|
565
|
-
baseTypeFactories.forEach(type => {
|
|
566
|
-
lines.push(`import ${type}Components from "./${type}";`);
|
|
567
|
-
});
|
|
568
|
-
lines.push('');
|
|
569
|
-
baseTypeFactories.forEach(type => {
|
|
570
|
-
lines.push(`prefixDictionaryEntries(${type}Components, '${ucFirst(type)}');`);
|
|
571
|
-
if (type == "experience")
|
|
572
|
-
lines.push(`prefixDictionaryEntries(${type}Components, 'Page'); // Experiences are a subtype of Page`);
|
|
573
|
-
if (type == "element")
|
|
574
|
-
lines.push(`prefixDictionaryEntries(${type}Components, 'Component'); // Elements are a subtype of Component`);
|
|
575
|
-
});
|
|
576
|
-
lines.push('');
|
|
577
|
-
lines.push('export const cmsComponentDictionary : ComponentTypeDictionary = [');
|
|
578
|
-
baseTypeFactories.forEach(type => {
|
|
579
|
-
lines.push(` ...${type}Components,`);
|
|
539
|
+
// Check folders
|
|
540
|
+
if (!fs.statSync(typePath).isDirectory())
|
|
541
|
+
throw new Error(`The folder ${typePath} for Content-Type ${contentType.key} exists, but is not a directory!`);
|
|
542
|
+
return {
|
|
543
|
+
type: contentType.key,
|
|
544
|
+
path: typePath,
|
|
545
|
+
};
|
|
580
546
|
});
|
|
581
|
-
|
|
582
|
-
{
|
|
583
|
-
list.forEach((component, idx, dictionary) => {
|
|
584
|
-
dictionary[idx].type = typeof component.type == 'string' ? prefix + "/" + component.type : [ prefix, ...component.type ]
|
|
585
|
-
})
|
|
586
|
-
return list
|
|
587
|
-
}`);
|
|
588
|
-
fs.writeFileSync(cmsFactoryFile, lines.join("\n"));
|
|
547
|
+
return folders;
|
|
589
548
|
}
|
|
590
|
-
function
|
|
591
|
-
|
|
592
|
-
if (fs.existsSync(componentFile)) {
|
|
593
|
-
if (force) {
|
|
594
|
-
if (cfg.debug)
|
|
595
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Overwriting ${contentType.displayName} (${contentType.key}) component\n`));
|
|
596
|
-
}
|
|
597
|
-
else {
|
|
598
|
-
if (cfg.debug)
|
|
599
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.displayName} (${contentType.key}) component - file already exists\n`));
|
|
600
|
-
return undefined;
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
const isPage = contentType.baseType == 'page' || contentType.baseType == 'experience';
|
|
604
|
-
const varName = `${contentType.key}${ucFirst(contentType.baseType ?? 'part')}`;
|
|
605
|
-
const component = `import ${isPage ? '{ OptimizelyNextPage as CmsComponent } from "@remkoj/optimizely-cms-nextjs"' : '{ CmsComponent } from "@remkoj/optimizely-cms-react"'};
|
|
606
|
-
import { ${contentType.key}DataFragmentDoc, type ${contentType.key}DataFragment } from "@/gql/graphql";
|
|
607
|
-
|
|
608
|
-
/**
|
|
609
|
-
* ${contentType.displayName}
|
|
610
|
-
* ${contentType.description}
|
|
611
|
-
*/
|
|
612
|
-
export const ${varName} : CmsComponent<${contentType.key}DataFragment> = ({ data }) => {
|
|
613
|
-
const componentName = '${contentType.displayName}'
|
|
614
|
-
const componentInfo = '${contentType.description ?? ''}'
|
|
615
|
-
return <div className="mx-auto px-2 container">
|
|
616
|
-
<div>{ componentName }</div>
|
|
617
|
-
<div>{ componentInfo }</div>
|
|
618
|
-
<pre className="w-full overflow-x-hidden font-mono text-sm">{ JSON.stringify(data, undefined, 4) }</pre>
|
|
619
|
-
</div>
|
|
620
|
-
}
|
|
621
|
-
${varName}.displayName = "${contentType.displayName} (${ucFirst(contentType.baseType)}/${contentType.key})"
|
|
622
|
-
${varName}.getDataFragment = () => ['${contentType.key}Data', ${contentType.key}DataFragmentDoc]
|
|
623
|
-
${isPage && `${varName}.getMetaData = async (contentLink) => {
|
|
624
|
-
// Add your metadata logic here
|
|
625
|
-
return {}
|
|
626
|
-
}`.trim()}
|
|
627
|
-
|
|
628
|
-
export default ${varName}`;
|
|
629
|
-
fs.writeFileSync(componentFile, component);
|
|
630
|
-
}
|
|
631
|
-
function ucFirst(current) {
|
|
632
|
-
return current[0]?.toUpperCase() + current.substring(1);
|
|
549
|
+
function getTypeFolder(list, type) {
|
|
550
|
+
return list.filter(x => x.type == type).at(0)?.path;
|
|
633
551
|
}
|
|
634
|
-
|
|
552
|
+
|
|
553
|
+
const NextJsQueriesCommand = {
|
|
554
|
+
command: "nextjs:fragments",
|
|
555
|
+
describe: "Create the GrapQL Fragments for a Next.JS / Optimizely Graph structure",
|
|
556
|
+
builder,
|
|
557
|
+
handler: async (args, opts) => {
|
|
558
|
+
// Prepare
|
|
559
|
+
const { loadedContentTypes, createdTypeFolders } = opts || {};
|
|
560
|
+
const { components: basePath, _config: { debug }, force } = parseArgs(args);
|
|
561
|
+
const { contentTypes, all: allContentTypes } = loadedContentTypes ?? await getContentTypes(createCmsClient(args), args);
|
|
562
|
+
// Start process
|
|
563
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Generating GraphQL fragments for ${contentTypes.map(x => x.key).join(', ')}\n`));
|
|
564
|
+
const typeFolders = createdTypeFolders ?? createTypeFolders(contentTypes, basePath, debug);
|
|
565
|
+
const updatedTypes = contentTypes.map(contentType => {
|
|
566
|
+
const typePath = getTypeFolder(typeFolders, contentType.key);
|
|
567
|
+
return createGraphFragments(contentType, typePath, basePath, force, debug, allContentTypes);
|
|
568
|
+
}).filter(x => x).flat();
|
|
569
|
+
// Report outcome
|
|
570
|
+
if (updatedTypes.length > 0)
|
|
571
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Created/updated GraphQL fragments for ${updatedTypes.join(', ')}\n`));
|
|
572
|
+
else
|
|
573
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} No GraphQL fragments created/updated\n`));
|
|
574
|
+
if (!opts)
|
|
575
|
+
process.stdout.write(chalk.green(chalk.bold(figures.tick + " Done")) + "\n");
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
function createGraphFragments(contentType, typePath, basePath, force, debug, contentTypes) {
|
|
579
|
+
const returnValue = [];
|
|
635
580
|
const baseType = contentType.baseType ?? 'default';
|
|
636
581
|
const baseQueryFile = path.join(typePath, `${contentType.key}.${baseType}.graphql`);
|
|
637
582
|
if (fs.existsSync(baseQueryFile)) {
|
|
638
583
|
if (force) {
|
|
639
|
-
if (
|
|
584
|
+
if (debug)
|
|
640
585
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Overwriting ${contentType.displayName} (${contentType.key}) base fragment\n`));
|
|
641
586
|
}
|
|
642
587
|
else {
|
|
643
|
-
if (
|
|
588
|
+
if (debug)
|
|
644
589
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.displayName} (${contentType.key}) base fragment - file already exists\n`));
|
|
645
590
|
return undefined;
|
|
646
591
|
}
|
|
647
592
|
}
|
|
593
|
+
else if (debug) {
|
|
594
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating ${contentType.displayName} (${contentType.key}) base fragment\n`));
|
|
595
|
+
}
|
|
648
596
|
const { fragment, propertyTypes } = createInitialFragment(contentType);
|
|
649
597
|
fs.writeFileSync(baseQueryFile, fragment);
|
|
598
|
+
returnValue.push(contentType.key);
|
|
650
599
|
let dependencies = Array.isArray(propertyTypes) ? [...propertyTypes] : [];
|
|
651
600
|
while (Array.isArray(dependencies) && dependencies.length > 0) {
|
|
652
601
|
let newDependencies = [];
|
|
@@ -661,15 +610,18 @@ function createGraphFragments(contentType, typePath, basePath, force, cfg, conte
|
|
|
661
610
|
if (!fs.existsSync(propertyFragmentDir))
|
|
662
611
|
fs.mkdirSync(propertyFragmentDir, { recursive: true });
|
|
663
612
|
if (!fs.existsSync(propertyFragmentFile) || force) {
|
|
664
|
-
|
|
613
|
+
if (debug)
|
|
614
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Writing ${propContentType.displayName} (${propContentType.key}) property fragment\n`));
|
|
665
615
|
const propContentTypeInfo = createInitialFragment(propContentType, true);
|
|
666
616
|
fs.writeFileSync(propertyFragmentFile, propContentTypeInfo.fragment);
|
|
617
|
+
returnValue.push(propContentType.key);
|
|
667
618
|
if (Array.isArray(propContentTypeInfo.propertyTypes))
|
|
668
619
|
newDependencies.push(...propContentTypeInfo.propertyTypes);
|
|
669
620
|
}
|
|
670
621
|
});
|
|
671
622
|
dependencies = newDependencies;
|
|
672
623
|
}
|
|
624
|
+
return returnValue.length > 0 ? returnValue : undefined;
|
|
673
625
|
}
|
|
674
626
|
function createInitialFragment(contentType, forProperty = false) {
|
|
675
627
|
const propertyTypes = [];
|
|
@@ -727,6 +679,8 @@ function createInitialFragment(contentType, forProperty = false) {
|
|
|
727
679
|
break;
|
|
728
680
|
}
|
|
729
681
|
});
|
|
682
|
+
if (contentType.baseType == "experience")
|
|
683
|
+
fragmentFields.push('...ExperienceData');
|
|
730
684
|
if (fragmentFields.length == 0)
|
|
731
685
|
fragmentFields.push('_metadata { key }');
|
|
732
686
|
const tpl = `fragment ${contentType.key}${forProperty ? 'Property' : ''}Data on ${contentType.key}${forProperty ? 'Property' : ''} {
|
|
@@ -738,6 +692,499 @@ function createInitialFragment(contentType, forProperty = false) {
|
|
|
738
692
|
};
|
|
739
693
|
}
|
|
740
694
|
|
|
695
|
+
function ucFirst(current) {
|
|
696
|
+
if (typeof current != 'string')
|
|
697
|
+
throw new Error("Only strings can be transformed");
|
|
698
|
+
if (current == "")
|
|
699
|
+
return current;
|
|
700
|
+
return current[0]?.toUpperCase() + current.substring(1);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
const NextJsComponentsCommand = {
|
|
704
|
+
command: "nextjs:components",
|
|
705
|
+
describe: "Create the React Components for a Next.JS / Optimizely Graph structure",
|
|
706
|
+
builder,
|
|
707
|
+
handler: async (args, opts) => {
|
|
708
|
+
// Prepare
|
|
709
|
+
const { loadedContentTypes, createdTypeFolders } = opts || {};
|
|
710
|
+
const { components: basePath, _config: { debug }, force } = parseArgs(args);
|
|
711
|
+
const { contentTypes } = loadedContentTypes ?? await getContentTypes(createCmsClient(args), args);
|
|
712
|
+
// Start process
|
|
713
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Generating React Components for ${contentTypes.map(x => x.key).join(', ')}\n`));
|
|
714
|
+
const typeFolders = createdTypeFolders ?? createTypeFolders(contentTypes, basePath, debug);
|
|
715
|
+
const updatedTypes = contentTypes.map(contentType => {
|
|
716
|
+
const typePath = getTypeFolder(typeFolders, contentType.key);
|
|
717
|
+
return createComponent(contentType, typePath, force, debug);
|
|
718
|
+
}).filter(x => x);
|
|
719
|
+
// Report outcome
|
|
720
|
+
if (updatedTypes.length > 0)
|
|
721
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Created/updated React Components for ${updatedTypes.join(', ')}\n`));
|
|
722
|
+
else
|
|
723
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} No React Components created/updated\n`));
|
|
724
|
+
if (!opts)
|
|
725
|
+
process.stdout.write(chalk.green(chalk.bold(figures.tick + " Done")) + "\n");
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
function createComponent(contentType, typePath, force, debug = false) {
|
|
729
|
+
const componentFile = path.join(typePath, 'index.tsx');
|
|
730
|
+
if (fs.existsSync(componentFile)) {
|
|
731
|
+
if (!force) {
|
|
732
|
+
if (debug)
|
|
733
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.displayName} (${contentType.key}) component - file already exists\n`));
|
|
734
|
+
return undefined;
|
|
735
|
+
}
|
|
736
|
+
if (debug)
|
|
737
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Overwriting ${contentType.displayName} (${contentType.key}) component\n`));
|
|
738
|
+
}
|
|
739
|
+
else if (debug)
|
|
740
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating ${contentType.displayName} (${contentType.key}) component\n`));
|
|
741
|
+
// Get type information & short-hands
|
|
742
|
+
const displayTemplate = getDisplayTemplateInfo$1(contentType, typePath);
|
|
743
|
+
const baseDisplayTemplate = getBaseTypeTemplateInfo(contentType, typePath);
|
|
744
|
+
const varName = `${contentType.key}${ucFirst(contentType.baseType ?? 'part')}`;
|
|
745
|
+
const tplFn = Templates[contentType.baseType] ?? Templates['default'];
|
|
746
|
+
if (!tplFn) {
|
|
747
|
+
if (debug)
|
|
748
|
+
process.stdout.write(chalk.redBright(`${figures.cross} Skipping ${contentType.displayName} (${contentType.key}) component - no template for ${contentType.baseType} found\n`));
|
|
749
|
+
return undefined;
|
|
750
|
+
}
|
|
751
|
+
// Apply template
|
|
752
|
+
fs.writeFileSync(componentFile, tplFn(contentType, varName, displayTemplate, baseDisplayTemplate));
|
|
753
|
+
return contentType.key;
|
|
754
|
+
}
|
|
755
|
+
function getBaseTypeTemplateInfo(contentType, typePath) {
|
|
756
|
+
const displayTemplatesFile = path.join(typePath, '..', 'styles', 'displayTemplates.ts');
|
|
757
|
+
if (fs.existsSync(displayTemplatesFile)) {
|
|
758
|
+
const baseTypeVarName = contentType.baseType ? contentType.baseType[0].toUpperCase() + contentType.baseType.substring(1) : undefined;
|
|
759
|
+
if (!baseTypeVarName)
|
|
760
|
+
return undefined;
|
|
761
|
+
const displayTemplateContent = fs.readFileSync(displayTemplatesFile).toString();
|
|
762
|
+
const displayTemplateName = baseTypeVarName + 'LayoutProps';
|
|
763
|
+
if (displayTemplateContent.includes(`export type ${displayTemplateName} =`)) {
|
|
764
|
+
return displayTemplateName;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
return undefined;
|
|
768
|
+
}
|
|
769
|
+
function getDisplayTemplateInfo$1(contentType, typePath) {
|
|
770
|
+
const displayTemplatesFile = path.join(typePath, 'displayTemplates.ts');
|
|
771
|
+
if (fs.existsSync(displayTemplatesFile)) {
|
|
772
|
+
const displayTemplateContent = fs.readFileSync(displayTemplatesFile).toString();
|
|
773
|
+
const displayTemplateName = contentType.key + 'LayoutProps';
|
|
774
|
+
if (displayTemplateContent.includes(`export type ${displayTemplateName} =`)) {
|
|
775
|
+
return displayTemplateName;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
return undefined;
|
|
779
|
+
}
|
|
780
|
+
const Templates = {
|
|
781
|
+
// Default Template for all components without specifics
|
|
782
|
+
default: (contentType, varName, displayTemplate, baseDisplayTemplate) => `import { CmsComponent } from "@remkoj/optimizely-cms-react";
|
|
783
|
+
import { ${contentType.key}DataFragmentDoc, type ${contentType.key}DataFragment } from "@/gql/graphql";${displayTemplate ? `
|
|
784
|
+
import { ${displayTemplate} } from "./displayTemplates";` : ''}${baseDisplayTemplate ? `
|
|
785
|
+
import { ${baseDisplayTemplate} } from "../styles/displayTemplates";` : ''}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* ${contentType.displayName}
|
|
789
|
+
* ${contentType.description}
|
|
790
|
+
*/
|
|
791
|
+
export const ${varName} : CmsComponent<${contentType.key}DataFragment${(displayTemplate || baseDisplayTemplate) ? ', ' + [displayTemplate, baseDisplayTemplate].filter(x => x).join(" | ") : ''}> = ({ data${(displayTemplate || baseDisplayTemplate) ? ', layoutProps' : ''}, children }) => {
|
|
792
|
+
const componentName = '${contentType.displayName}'
|
|
793
|
+
const componentInfo = '${contentType.description ?? ''}'
|
|
794
|
+
return <div className="w-full border-y border-y-solid border-y-slate-900 py-2 mb-4">
|
|
795
|
+
<div className="font-bold italic">{ componentName }</div>
|
|
796
|
+
<div>{ componentInfo }</div>
|
|
797
|
+
{ 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> }
|
|
798
|
+
{ children && <div className="mt-4 mx-4 flex flex-col">{ children }</div>}
|
|
799
|
+
</div>
|
|
800
|
+
}
|
|
801
|
+
${varName}.displayName = "${contentType.displayName} (${ucFirst(contentType.baseType)}/${contentType.key})"
|
|
802
|
+
${varName}.getDataFragment = () => ['${contentType.key}Data', ${contentType.key}DataFragmentDoc]
|
|
803
|
+
|
|
804
|
+
export default ${varName}`,
|
|
805
|
+
// Template for all page component types
|
|
806
|
+
page: (contentType, varName, displayTemplate) => `import { OptimizelyNextPage as CmsComponent } from "@remkoj/optimizely-cms-nextjs";
|
|
807
|
+
import { ${contentType.key}DataFragmentDoc, type ${contentType.key}DataFragment } from "@/gql/graphql";${displayTemplate ? `
|
|
808
|
+
import { ${displayTemplate} } from "./displayTemplates";` : ''}
|
|
809
|
+
import { getSdk } from "@/gql"
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* ${contentType.displayName}
|
|
813
|
+
* ${contentType.description}
|
|
814
|
+
*/
|
|
815
|
+
export const ${varName} : CmsComponent<${contentType.key}DataFragment${displayTemplate ? ', ' + displayTemplate : ''}> = ({ data${displayTemplate ? ', layoutProps' : ''}, children }) => {
|
|
816
|
+
const componentName = '${contentType.displayName}'
|
|
817
|
+
const componentInfo = '${contentType.description ?? ''}'
|
|
818
|
+
return <div className="mx-auto px-2 container">
|
|
819
|
+
<div className="font-bold italic">{ componentName }</div>
|
|
820
|
+
<div>{ componentInfo }</div>
|
|
821
|
+
{ 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> }
|
|
822
|
+
{ children && <div className="flex flex-col mt-4 mx-4">{ children }</div>}
|
|
823
|
+
</div>
|
|
824
|
+
}
|
|
825
|
+
${varName}.displayName = "${contentType.displayName} (${ucFirst(contentType.baseType)}/${contentType.key})"
|
|
826
|
+
${varName}.getDataFragment = () => ['${contentType.key}Data', ${contentType.key}DataFragmentDoc]
|
|
827
|
+
${varName}.getMetaData = async (contentLink, locale, client) => {
|
|
828
|
+
const sdk = getSdk(client);
|
|
829
|
+
// Add your metadata logic here
|
|
830
|
+
return {}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
export default ${varName}`,
|
|
834
|
+
// Template for all experience component types
|
|
835
|
+
experience: (contentType, varName, displayTemplate) => `import { OptimizelyNextPage as CmsComponent } from "@remkoj/optimizely-cms-nextjs";
|
|
836
|
+
import { ${contentType.key}DataFragmentDoc, type ${contentType.key}DataFragment } from "@/gql/graphql";
|
|
837
|
+
import { type Maybe, type ICompositionNode, type ExperienceDataFragment } from "@/gql/graphql";
|
|
838
|
+
import { OptimizelyComposition, isNode, CmsEditable } from "@remkoj/optimizely-cms-react/rsc";${displayTemplate ? `
|
|
839
|
+
import { ${displayTemplate} } from "./displayTemplates";` : ''}
|
|
840
|
+
import { getSdk } from "@/gql"
|
|
841
|
+
|
|
842
|
+
/**
|
|
843
|
+
* ${contentType.displayName}
|
|
844
|
+
* ${contentType.description}
|
|
845
|
+
*/
|
|
846
|
+
export const ${varName} : CmsComponent<${contentType.key}DataFragment${displayTemplate ? ', ' + displayTemplate : ''}> = ({ data${displayTemplate ? ', layoutProps' : ''} }) => {
|
|
847
|
+
const composition = (data as ExperienceDataFragment).composition as Maybe<ICompositionNode>
|
|
848
|
+
return <CmsEditable as="div" className="mx-auto px-2 container" cmsFieldName="unstructuredData">
|
|
849
|
+
{ composition && isNode(composition) && <OptimizelyComposition node={composition} /> }
|
|
850
|
+
</CmsEditable>
|
|
851
|
+
}
|
|
852
|
+
${varName}.displayName = "${contentType.displayName} (${ucFirst(contentType.baseType)}/${contentType.key})"
|
|
853
|
+
${varName}.getDataFragment = () => ['${contentType.key}Data', ${contentType.key}DataFragmentDoc]
|
|
854
|
+
${varName}.getMetaData = async (contentLink, locale, client) => {
|
|
855
|
+
const sdk = getSdk(client);
|
|
856
|
+
// Add your metadata logic here
|
|
857
|
+
return {}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
export default ${varName}`
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
const NextJsVisualBuilderCommand = {
|
|
864
|
+
command: "nextjs:visualbuilder",
|
|
865
|
+
describe: "Create the React Components for Visual Builder in a Next.JS / Optimizely Graph structure",
|
|
866
|
+
builder,
|
|
867
|
+
handler: async (args) => {
|
|
868
|
+
// Prepare
|
|
869
|
+
const { components: basePath, _config: { debug }, force } = parseArgs(args);
|
|
870
|
+
// Create the fall-back node and style defintions
|
|
871
|
+
await StylesPullCommand.handler({ ...args, excludeNodeTypes: [], excludeTemplates: [], nodes: [], templates: [], templateTypes: ['node'], definitions: true });
|
|
872
|
+
createGenericNode(basePath, force, debug);
|
|
873
|
+
// Get all styles
|
|
874
|
+
const { styles } = await getStyles(createCmsClient(args), { ...args, excludeNodeTypes: [], excludeTemplates: [], nodes: [], templates: [], templateTypes: ['node', 'base'] });
|
|
875
|
+
// Process node styles
|
|
876
|
+
styles.filter(x => typeof (x.nodeType) == 'string' && x.nodeType.length > 0).map(styleDefintion => {
|
|
877
|
+
const templatePath = path.join(basePath, 'nodes', styleDefintion.nodeType, styleDefintion.key);
|
|
878
|
+
createSpecificNode(styleDefintion, templatePath, force, debug);
|
|
879
|
+
});
|
|
880
|
+
// Process base styles
|
|
881
|
+
styles.filter(x => typeof (x.baseType) == 'string' && x.baseType.length > 0).map(styleDefinition => {
|
|
882
|
+
const templatePath = path.join(basePath, styleDefinition.baseType, 'styles', styleDefinition.key);
|
|
883
|
+
createSpecificNode(styleDefinition, templatePath, force, debug);
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
function createSpecificNode(template, templatePath, force = false, debug = false) {
|
|
888
|
+
const nodeFile = path.join(templatePath, 'index.tsx');
|
|
889
|
+
if (fs.existsSync(nodeFile)) {
|
|
890
|
+
if (!force) {
|
|
891
|
+
if (debug)
|
|
892
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${template.displayName} (${template.key}) component - file already exists\n`));
|
|
893
|
+
return undefined;
|
|
894
|
+
}
|
|
895
|
+
if (debug)
|
|
896
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Overwriting ${template.displayName} (${template.key}) component\n`));
|
|
897
|
+
}
|
|
898
|
+
else if (debug)
|
|
899
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating ${template.displayName} (${template.key}) component\n`));
|
|
900
|
+
if (!fs.existsSync(templatePath))
|
|
901
|
+
fs.mkdirSync(templatePath, { recursive: true });
|
|
902
|
+
const displayTemplateName = getDisplayTemplateInfo(template, templatePath);
|
|
903
|
+
const component = `import { type CmsLayoutComponent } from "@remkoj/optimizely-cms-react";
|
|
904
|
+
import { extractSettings } from "@remkoj/optimizely-cms-react/components";${displayTemplateName ? `
|
|
905
|
+
import { ${displayTemplateName} } from "../displayTemplates";` : ''}
|
|
906
|
+
|
|
907
|
+
export const ${template.key} : CmsLayoutComponent<${displayTemplateName ?? '{}'}> = ({ contentLink, layoutProps, children }) => {
|
|
908
|
+
const layout = extractSettings(layoutProps);
|
|
909
|
+
return (<div className="vb:${template.nodeType} vb:${template.nodeType}:${template.key}">{ children }</div>);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
export default ${template.key};`;
|
|
913
|
+
fs.writeFileSync(nodeFile, component);
|
|
914
|
+
}
|
|
915
|
+
function createGenericNode(basePath, force, debug) {
|
|
916
|
+
const nodeItem = path.join(basePath, 'node.tsx');
|
|
917
|
+
if (fs.existsSync(nodeItem)) {
|
|
918
|
+
if (!force) {
|
|
919
|
+
if (debug)
|
|
920
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping generic node component - file already exists\n`));
|
|
921
|
+
return undefined;
|
|
922
|
+
}
|
|
923
|
+
if (debug)
|
|
924
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Overwriting generic node component\n`));
|
|
925
|
+
}
|
|
926
|
+
else if (debug)
|
|
927
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating generic node component\n`));
|
|
928
|
+
const nodeContent = `import { type CmsLayoutComponent } from "@remkoj/optimizely-cms-react"
|
|
929
|
+
import { CmsEditable } from '@remkoj/optimizely-cms-react/rsc'
|
|
930
|
+
|
|
931
|
+
export const VisualBuilderNode : CmsLayoutComponent = ({ contentLink, layoutProps, children }) =>
|
|
932
|
+
{
|
|
933
|
+
let className = \`vb:\${layoutProps?.layoutType}\`
|
|
934
|
+
if (layoutProps && layoutProps.layoutType == "section")
|
|
935
|
+
return <CmsEditable as="div" className={ className } cmsId={ contentLink.key }>{ children }</CmsEditable>
|
|
936
|
+
return <div className={ className }>{ children }</div>
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
export default VisualBuilderNode`;
|
|
940
|
+
fs.writeFileSync(nodeItem, nodeContent);
|
|
941
|
+
}
|
|
942
|
+
function getDisplayTemplateInfo(template, typePath) {
|
|
943
|
+
const displayTemplatesFile = path.join(typePath, '..', 'displayTemplates.ts');
|
|
944
|
+
if (fs.existsSync(displayTemplatesFile)) {
|
|
945
|
+
const displayTemplateContent = fs.readFileSync(displayTemplatesFile).toString();
|
|
946
|
+
const displayTemplateName = template.key + 'Props';
|
|
947
|
+
if (displayTemplateContent.includes(`export type ${displayTemplateName} =`)) {
|
|
948
|
+
return displayTemplateName;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
return undefined;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
const NextJsFactoryCommand = {
|
|
955
|
+
command: "nextjs:factory",
|
|
956
|
+
describe: "Create the ComponentFactory for a Next.JS / Optimizely Graph structure",
|
|
957
|
+
builder,
|
|
958
|
+
handler: async (args) => {
|
|
959
|
+
const { components: basePath, force, _config: { debug } } = parseArgs(args);
|
|
960
|
+
if (debug)
|
|
961
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Start generating component factories\n`));
|
|
962
|
+
const baseDirEntries = fs.readdirSync(basePath);
|
|
963
|
+
const entries = baseDirEntries
|
|
964
|
+
.filter(entry => fs.statSync(path.join(basePath, entry)).isDirectory())
|
|
965
|
+
.map(entry => entry == "nodes" ? processSubGroupFolder(entry, path.join(basePath, entry), force, debug) : processTypeListFolder(entry, path.join(basePath, entry), force, debug))
|
|
966
|
+
.filter(x => x);
|
|
967
|
+
// Post process the entries
|
|
968
|
+
if (debug)
|
|
969
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Post processing resolved top-level factory entries\n`));
|
|
970
|
+
entries.forEach((e, i) => {
|
|
971
|
+
if (isImportInfoList(e) && typeof e.prefix == 'string')
|
|
972
|
+
switch (e.prefix) {
|
|
973
|
+
case "Experience":
|
|
974
|
+
entries[i].prefix = [e.prefix, "Page"];
|
|
975
|
+
break;
|
|
976
|
+
case "Element":
|
|
977
|
+
entries[i].prefix = [e.prefix, "Component"];
|
|
978
|
+
break;
|
|
979
|
+
}
|
|
980
|
+
});
|
|
981
|
+
// Add any global components
|
|
982
|
+
if (debug)
|
|
983
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Adding global components\n`));
|
|
984
|
+
baseDirEntries.filter(x => x.endsWith('.tsx') && fs.statSync(path.join(basePath, x)).isFile()).forEach(entry => {
|
|
985
|
+
const entryName = entry.substring(0, entry.length - 4).replaceAll('.', '').split('-').map(p => ucFirst(p)).join('');
|
|
986
|
+
const importPath = './' + entry.substring(0, entry.length - 4);
|
|
987
|
+
entries.push({
|
|
988
|
+
isList: false,
|
|
989
|
+
component: (entryName[0].toLowerCase() + entryName.substring(1)) + "Component",
|
|
990
|
+
path: importPath,
|
|
991
|
+
type: entryName
|
|
992
|
+
});
|
|
993
|
+
});
|
|
994
|
+
// We're always overwriting the main factory
|
|
995
|
+
if (debug)
|
|
996
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating/overwriting main factory\n`));
|
|
997
|
+
const mainFactoryFile = path.join(basePath, "index.ts");
|
|
998
|
+
fs.writeFileSync(mainFactoryFile, generateFactory(entries, "cms"));
|
|
999
|
+
process.stdout.write(chalk.yellow(`${figures.arrowRight} Generated main factory in: ${mainFactoryFile}.\n`));
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
function processSubGroupFolder(groupName, groupTypePath, force = false, debug = false) {
|
|
1003
|
+
const subgroupInfo = fs
|
|
1004
|
+
.readdirSync(groupTypePath)
|
|
1005
|
+
.filter(entry => {
|
|
1006
|
+
return fs.statSync(path.join(groupTypePath, entry)).isDirectory();
|
|
1007
|
+
}).map(entry => processTypeListFolder(entry, path.join(groupTypePath, entry), force, debug))
|
|
1008
|
+
.filter(x => x);
|
|
1009
|
+
if (subgroupInfo.length == 0)
|
|
1010
|
+
return null;
|
|
1011
|
+
const baseTypeIndex = path.join(groupTypePath, "index.ts");
|
|
1012
|
+
/* Check file existence and determine if we can continue */
|
|
1013
|
+
if (debug)
|
|
1014
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Identified base type ${groupName}\n`));
|
|
1015
|
+
if (fs.existsSync(baseTypeIndex)) {
|
|
1016
|
+
if (!force) {
|
|
1017
|
+
if (debug)
|
|
1018
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping factory for ${groupName} - file already exists\n`));
|
|
1019
|
+
return {
|
|
1020
|
+
isList: true,
|
|
1021
|
+
component: groupName + 'Dictionary',
|
|
1022
|
+
prefix: groupName[0].toUpperCase() + groupName.substring(1),
|
|
1023
|
+
path: './' + groupName
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
if (debug)
|
|
1027
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Overwriting factory for ${groupName}\n`));
|
|
1028
|
+
}
|
|
1029
|
+
else if (debug)
|
|
1030
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Generating new factory for ${groupName}\n`));
|
|
1031
|
+
fs.writeFileSync(baseTypeIndex, generateFactory(subgroupInfo, groupName));
|
|
1032
|
+
process.stdout.write(chalk.yellow(`${figures.arrowRight} Generated partial factory in: ${baseTypeIndex}.\n`));
|
|
1033
|
+
return {
|
|
1034
|
+
isList: true,
|
|
1035
|
+
component: groupName + 'Dictionary',
|
|
1036
|
+
prefix: groupName[0].toUpperCase() + groupName.substring(1),
|
|
1037
|
+
path: './' + groupName
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
function isImportInfoList(toTest) {
|
|
1041
|
+
return toTest.isList == true;
|
|
1042
|
+
}
|
|
1043
|
+
function isPrefixedImportInfoList(toTest) {
|
|
1044
|
+
return isImportInfoList(toTest) && ((typeof toTest.prefix == 'string' && toTest.prefix.length > 0) || (Array.isArray(toTest.prefix) && toTest.prefix.length > 0 && toTest.prefix.every(x => typeof x == 'string' && x.length > 0)));
|
|
1045
|
+
}
|
|
1046
|
+
function processTypeListFolder(baseType, baseTypePath, force = false, debug = false) {
|
|
1047
|
+
const baseTypeIndex = path.join(baseTypePath, "index.ts");
|
|
1048
|
+
/* Check file existence and determine if we can continue */
|
|
1049
|
+
if (debug)
|
|
1050
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Identified base type ${baseType}\n`));
|
|
1051
|
+
if (fs.existsSync(baseTypeIndex)) {
|
|
1052
|
+
if (!force) {
|
|
1053
|
+
if (debug)
|
|
1054
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping factory for ${baseType} - file already exists\n`));
|
|
1055
|
+
return {
|
|
1056
|
+
isList: true,
|
|
1057
|
+
component: baseType + 'Dictionary',
|
|
1058
|
+
prefix: baseType[0].toUpperCase() + baseType.substring(1),
|
|
1059
|
+
path: './' + baseType
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
if (debug)
|
|
1063
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Overwriting factory for ${baseType}\n`));
|
|
1064
|
+
}
|
|
1065
|
+
else if (debug)
|
|
1066
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Generating new factory for ${baseType}\n`));
|
|
1067
|
+
// Get the components to import
|
|
1068
|
+
let hasStyles = false;
|
|
1069
|
+
const components = fs
|
|
1070
|
+
.readdirSync(baseTypePath)
|
|
1071
|
+
.filter(entry => {
|
|
1072
|
+
if (entry == 'styles') {
|
|
1073
|
+
hasStyles = true;
|
|
1074
|
+
return false;
|
|
1075
|
+
}
|
|
1076
|
+
return fs.statSync(path.join(baseTypePath, entry)).isDirectory() &&
|
|
1077
|
+
fs.existsSync(path.join(baseTypePath, entry, "index.tsx"));
|
|
1078
|
+
})
|
|
1079
|
+
.map(entry => {
|
|
1080
|
+
return {
|
|
1081
|
+
component: entry + 'Component',
|
|
1082
|
+
path: './' + entry,
|
|
1083
|
+
type: entry
|
|
1084
|
+
};
|
|
1085
|
+
});
|
|
1086
|
+
// Handle styles folder
|
|
1087
|
+
if (hasStyles) {
|
|
1088
|
+
const styles = processTypeListFolder("styles", path.join(baseTypePath, 'styles'), force, debug);
|
|
1089
|
+
if (styles) {
|
|
1090
|
+
components.push({
|
|
1091
|
+
path: "./styles",
|
|
1092
|
+
component: "styleDictionary",
|
|
1093
|
+
isList: true,
|
|
1094
|
+
prefix: "Styles"
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
components.sort((a, b) => {
|
|
1099
|
+
if (isImportInfoList(a) && !isImportInfoList(b))
|
|
1100
|
+
return -1;
|
|
1101
|
+
if (!isImportInfoList(a) && isImportInfoList(b))
|
|
1102
|
+
return 1;
|
|
1103
|
+
return 0;
|
|
1104
|
+
});
|
|
1105
|
+
if (components.length == 0) {
|
|
1106
|
+
if (debug)
|
|
1107
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} The base type ${baseType} has no components.\n`));
|
|
1108
|
+
return null;
|
|
1109
|
+
}
|
|
1110
|
+
if (debug)
|
|
1111
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Generating factory for ${baseType} with the components: ${components.map(x => x.isList == true ? x.component : x.type).join(", ")}.\n`));
|
|
1112
|
+
// Create the file
|
|
1113
|
+
const factoryContent = generateFactory(components, baseType);
|
|
1114
|
+
fs.writeFileSync(baseTypeIndex, factoryContent);
|
|
1115
|
+
process.stdout.write(chalk.yellow(`${figures.arrowRight} Generated partial factory in: ${baseTypeIndex}.\n`));
|
|
1116
|
+
// Build the result
|
|
1117
|
+
return {
|
|
1118
|
+
isList: true,
|
|
1119
|
+
component: baseType + 'Dictionary',
|
|
1120
|
+
prefix: baseType[0].toUpperCase() + baseType.substring(1),
|
|
1121
|
+
path: './' + baseType
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
function generateFactory(components, typeName) {
|
|
1125
|
+
const needsPrefixFunction = components.some(isPrefixedImportInfoList);
|
|
1126
|
+
const factoryContent = `// Auto generated dictionary
|
|
1127
|
+
import { ComponentTypeDictionary } from "@remkoj/optimizely-cms-react";
|
|
1128
|
+
${components.map(x => `import ${x.component} from "${x.path}";`).join("\n")}
|
|
1129
|
+
|
|
1130
|
+
${needsPrefixFunction ? `// Prefix entries - if needed
|
|
1131
|
+
${components.filter(isPrefixedImportInfoList).map(x => Array.isArray(x.prefix) ?
|
|
1132
|
+
x.prefix.map(z => `prefixDictionaryEntries(${x.component}, "${z}");`).join("\n") :
|
|
1133
|
+
`prefixDictionaryEntries(${x.component}, "${x.prefix}");`).join("\n")}
|
|
1134
|
+
|
|
1135
|
+
` : ''}// Build dictionary
|
|
1136
|
+
export const ${typeName}Dictionary : ComponentTypeDictionary = [
|
|
1137
|
+
${components.map(x => x.isList == true ? `...${x.component}` : `{
|
|
1138
|
+
type: "${x.type}",
|
|
1139
|
+
component: ${x.component}
|
|
1140
|
+
}`).join(",\n ")}
|
|
1141
|
+
];
|
|
1142
|
+
|
|
1143
|
+
// Export dictionary
|
|
1144
|
+
export default ${typeName}Dictionary;${needsPrefixFunction ? `
|
|
1145
|
+
|
|
1146
|
+
// Helper functions
|
|
1147
|
+
function prefixDictionaryEntries(list: ComponentTypeDictionary, prefix: string) : ComponentTypeDictionary
|
|
1148
|
+
{
|
|
1149
|
+
list.forEach((component, idx, dictionary) => {
|
|
1150
|
+
dictionary[idx].type = typeof component.type == 'string' ? prefix + "/" + component.type : [ prefix, ...component.type ]
|
|
1151
|
+
});
|
|
1152
|
+
return list;
|
|
1153
|
+
}` : ''}
|
|
1154
|
+
`;
|
|
1155
|
+
return factoryContent;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
const NextJsCreateCommand = {
|
|
1159
|
+
command: "nextjs:create",
|
|
1160
|
+
describe: "Scaffold a complete Next.JS / Optimizely Graph structure",
|
|
1161
|
+
builder,
|
|
1162
|
+
handler: async (args) => {
|
|
1163
|
+
const { _config: cfg, components: basePath } = parseArgs(args);
|
|
1164
|
+
const client = createCmsClient(args);
|
|
1165
|
+
const getContentTypesResult = await getContentTypes(client, args);
|
|
1166
|
+
const { contentTypes } = getContentTypesResult;
|
|
1167
|
+
const folders = createTypeFolders(contentTypes, basePath, cfg.debug);
|
|
1168
|
+
process.stdout.write(chalk.yellowBright(chalk.bold(figures.tick) + " Prepared context, to limit duplicate work") + "\n");
|
|
1169
|
+
// First create the base files in parallel
|
|
1170
|
+
await Promise.all([
|
|
1171
|
+
TypesPullCommand.handler(args),
|
|
1172
|
+
StylesPullCommand.handler({ ...args, excludeNodeTypes: [], excludeTemplates: [], nodes: [], templates: [], templateTypes: [], definitions: true }),
|
|
1173
|
+
NextJsQueriesCommand.handler(args, { loadedContentTypes: getContentTypesResult, createdTypeFolders: folders })
|
|
1174
|
+
]);
|
|
1175
|
+
process.stdout.write(chalk.yellowBright(chalk.bold(figures.tick) + " Prepared types, styles and fragments") + "\n");
|
|
1176
|
+
// Then create the components
|
|
1177
|
+
await NextJsComponentsCommand.handler(args, { loadedContentTypes: getContentTypesResult, createdTypeFolders: folders });
|
|
1178
|
+
await NextJsVisualBuilderCommand.handler(args);
|
|
1179
|
+
process.stdout.write(chalk.yellowBright(chalk.bold(figures.tick) + " Created components") + "\n");
|
|
1180
|
+
// Finally create the factories
|
|
1181
|
+
await NextJsFactoryCommand.handler(args);
|
|
1182
|
+
process.stdout.write(chalk.yellowBright(chalk.bold(figures.tick) + " Created component factory") + "\n");
|
|
1183
|
+
process.stdout.write("\n");
|
|
1184
|
+
process.stdout.write(chalk.green(chalk.bold(figures.tick + " Scaffolded a complete Next.JS / Optimizely Graph structure")) + "\n");
|
|
1185
|
+
}
|
|
1186
|
+
};
|
|
1187
|
+
|
|
741
1188
|
const CmsVersionCommand = {
|
|
742
1189
|
command: "cms:version",
|
|
743
1190
|
describe: "Get the CMS Version information",
|
|
@@ -772,10 +1219,14 @@ const commands = [
|
|
|
772
1219
|
TypesPullCommand,
|
|
773
1220
|
TypesPushCommand,
|
|
774
1221
|
NextJsCreateCommand,
|
|
1222
|
+
NextJsQueriesCommand,
|
|
1223
|
+
NextJsComponentsCommand,
|
|
1224
|
+
NextJsVisualBuilderCommand,
|
|
1225
|
+
NextJsFactoryCommand,
|
|
775
1226
|
CmsVersionCommand
|
|
776
1227
|
];
|
|
777
1228
|
|
|
778
|
-
var version = "2.0.
|
|
1229
|
+
var version = "2.0.1-pre1";
|
|
779
1230
|
var name = "opti-cms";
|
|
780
1231
|
var APP = {
|
|
781
1232
|
version: version,
|