@remkoj/optimizely-cms-cli 2.0.0-pre5 → 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/README.md +15 -1
- package/dist/index.js +861 -285
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
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,
|
|
@@ -77,30 +86,35 @@ function parseArgs({ client_id, client_secret, cms_url, user_id, verbose, path:
|
|
|
77
86
|
const StylesPushCommand = {
|
|
78
87
|
command: "styles:push",
|
|
79
88
|
describe: "Push Visual Builder style definitions into the CMS (create/replace)",
|
|
89
|
+
builder: (yargs) => {
|
|
90
|
+
yargs.option('excludeTemplates', { alias: 'e', description: "Exclude these templates", string: true, type: 'array', demandOption: false, default: [] });
|
|
91
|
+
yargs.option("templates", { alias: 't', description: "Select only these templates", string: true, type: 'array', demandOption: false, default: [] });
|
|
92
|
+
return yargs;
|
|
93
|
+
},
|
|
80
94
|
handler: async (args) => {
|
|
81
|
-
const { _config: cfg, ...opts } = parseArgs(args);
|
|
95
|
+
const { _config: cfg, excludeTemplates, templates, ...opts } = parseArgs(args);
|
|
82
96
|
const client = createClient(cfg);
|
|
83
|
-
/*const currentTemplates = await client.displayTemplates.displayTemplatesList()
|
|
84
|
-
currentTemplates.items?.map(tpl => {
|
|
85
|
-
console.log(JSON.stringify(tpl, undefined, 4))
|
|
86
|
-
})*/
|
|
87
97
|
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Pushing (create/replace) DisplayStyles into Optimizely CMS\n`));
|
|
88
98
|
const styleDefinitionFiles = await glob("./**/*.opti-style.json", {
|
|
89
99
|
cwd: opts.components
|
|
90
100
|
});
|
|
91
|
-
const results = await Promise.all(styleDefinitionFiles.map(async (styleDefinitionFile) => {
|
|
101
|
+
const results = (await Promise.all(styleDefinitionFiles.map(async (styleDefinitionFile) => {
|
|
92
102
|
const filePath = path.normalize(path.join(opts.components, styleDefinitionFile));
|
|
93
103
|
const styleDefinition = tryReadJsonFile(filePath, cfg.debug);
|
|
94
104
|
const styleKey = styleDefinition.key;
|
|
95
105
|
if (!styleKey) {
|
|
96
106
|
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`));
|
|
97
|
-
return;
|
|
107
|
+
return undefined;
|
|
98
108
|
}
|
|
109
|
+
if (excludeTemplates.includes(styleKey))
|
|
110
|
+
return undefined; // Skip excluded styles
|
|
111
|
+
if (templates.length > 0 && !templates.includes(styleKey))
|
|
112
|
+
return undefined; // Only include defined styles, if any
|
|
99
113
|
if (cfg.debug)
|
|
100
114
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Pushing: ${styleKey}\n`));
|
|
101
115
|
const newTemplate = await client.displayTemplates.displayTemplatesPut(styleKey, styleDefinition);
|
|
102
116
|
return newTemplate;
|
|
103
|
-
}));
|
|
117
|
+
}))).filter(isNotNullOrUndefined);
|
|
104
118
|
const styles = new Table({
|
|
105
119
|
head: [
|
|
106
120
|
chalk.yellow(chalk.bold("Name")),
|
|
@@ -134,27 +148,146 @@ function tryReadJsonFile(filePath, debug = false) {
|
|
|
134
148
|
}
|
|
135
149
|
return undefined;
|
|
136
150
|
}
|
|
151
|
+
function isNotNullOrUndefined(i) {
|
|
152
|
+
return i ? true : false;
|
|
153
|
+
}
|
|
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
|
+
}
|
|
137
285
|
|
|
138
286
|
const StylesListCommand = {
|
|
139
287
|
command: "styles:list",
|
|
140
288
|
describe: "List Visual Builder style definitions from the CMS",
|
|
141
289
|
handler: async (args) => {
|
|
142
|
-
const {
|
|
143
|
-
const client = createClient(cfg);
|
|
144
|
-
const pageSize = 100;
|
|
145
|
-
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Reading DisplayStyles from Optimizely CMS\n`));
|
|
146
|
-
if (cfg.debug)
|
|
147
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetching page 1 of ? (${pageSize} items per page)\n`));
|
|
148
|
-
let templatesPage = await client.displayTemplates.displayTemplatesList(0, pageSize);
|
|
149
|
-
const results = templatesPage.items ?? [];
|
|
150
|
-
let pagesRemaining = Math.ceil(templatesPage.totalItemCount / templatesPage.pageSize) - (templatesPage.pageIndex + 1);
|
|
151
|
-
while (pagesRemaining > 0 && results.length < templatesPage.totalItemCount) {
|
|
152
|
-
if (cfg.debug)
|
|
153
|
-
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`));
|
|
154
|
-
templatesPage = await client.displayTemplates.displayTemplatesList(templatesPage.pageIndex + 1, templatesPage.pageSize);
|
|
155
|
-
results.push(...templatesPage.items);
|
|
156
|
-
pagesRemaining = Math.ceil(templatesPage.totalItemCount / templatesPage.pageSize) - (templatesPage.pageIndex + 1);
|
|
157
|
-
}
|
|
290
|
+
const { all: results } = await getStyles(createCmsClient(args), { ...args, excludeBaseTypes: [], excludeNodeTypes: [], excludeTemplates: [], excludeTypes: [], baseTypes: [], nodes: [], templates: [], types: [], templateTypes: [] });
|
|
158
291
|
const styles = new Table({
|
|
159
292
|
head: [
|
|
160
293
|
chalk.yellow(chalk.bold("Name")),
|
|
@@ -181,39 +314,25 @@ const StylesListCommand = {
|
|
|
181
314
|
const StylesPullCommand = {
|
|
182
315
|
command: "styles:pull",
|
|
183
316
|
describe: "Create Visual Builder style definitions from the CMS",
|
|
317
|
+
builder: (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;
|
|
322
|
+
},
|
|
184
323
|
handler: async (args) => {
|
|
185
|
-
const { _config: cfg, components: basePath } = parseArgs(args);
|
|
186
|
-
const client =
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
if (cfg.debug)
|
|
190
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetching page 1 of ? (${pageSize} items per page)\n`));
|
|
191
|
-
let templatesPage = await client.displayTemplates.displayTemplatesList(0, pageSize);
|
|
192
|
-
const results = templatesPage.items ?? [];
|
|
193
|
-
let pagesRemaining = Math.ceil(templatesPage.totalItemCount / templatesPage.pageSize) - (templatesPage.pageIndex + 1);
|
|
194
|
-
while (pagesRemaining > 0 && results.length < templatesPage.totalItemCount) {
|
|
195
|
-
if (cfg.debug)
|
|
196
|
-
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`));
|
|
197
|
-
templatesPage = await client.displayTemplates.displayTemplatesList(templatesPage.pageIndex + 1, templatesPage.pageSize);
|
|
198
|
-
results.push(...templatesPage.items);
|
|
199
|
-
pagesRemaining = Math.ceil(templatesPage.totalItemCount / templatesPage.pageSize) - (templatesPage.pageIndex + 1);
|
|
200
|
-
}
|
|
201
|
-
if (cfg.debug)
|
|
202
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetched ${results.length} Content-Types from Optimizely CMS\n`));
|
|
203
|
-
if (!fs.existsSync(basePath))
|
|
204
|
-
fs.mkdirSync(basePath, { recursive: true });
|
|
205
|
-
if (!fs.statSync(basePath).isDirectory()) {
|
|
206
|
-
process.stderr.write(chalk.redBright(`${chalk.bold(figures.cross)} The components path ${basePath} is not a folder\n`));
|
|
207
|
-
process.exit(1);
|
|
208
|
-
}
|
|
324
|
+
const { _config: cfg, components: basePath, force, definitions } = parseArgs(args);
|
|
325
|
+
const client = createCmsClient(args);
|
|
326
|
+
const { styles: filteredResults } = await getStyles(client, args);
|
|
327
|
+
//#region Create & Write opti-style.json files
|
|
209
328
|
const typeFiles = {};
|
|
210
|
-
const updatedTemplates = await Promise.all(
|
|
329
|
+
const updatedTemplates = (await Promise.all(filteredResults.map(async (displayTemplate) => {
|
|
211
330
|
let itemPath = undefined;
|
|
212
331
|
let targetType;
|
|
213
332
|
let typesPath;
|
|
214
333
|
if (displayTemplate.nodeType) {
|
|
215
|
-
itemPath = path.join(basePath, '
|
|
216
|
-
typesPath = path.join(basePath, '
|
|
334
|
+
itemPath = path.join(basePath, 'nodes', displayTemplate.nodeType, displayTemplate.key);
|
|
335
|
+
typesPath = path.join(basePath, 'nodes', displayTemplate.nodeType);
|
|
217
336
|
targetType = 'node/' + displayTemplate.nodeType;
|
|
218
337
|
}
|
|
219
338
|
else if (displayTemplate.baseType) {
|
|
@@ -231,6 +350,17 @@ const StylesPullCommand = {
|
|
|
231
350
|
fs.mkdirSync(itemPath, { recursive: true });
|
|
232
351
|
// Write Style JSON
|
|
233
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`));
|
|
234
364
|
fs.writeFileSync(filePath, JSON.stringify(displayTemplate, undefined, 2));
|
|
235
365
|
if (!typeFiles[targetType]) {
|
|
236
366
|
typeFiles[targetType] = {
|
|
@@ -238,292 +368,234 @@ const StylesPullCommand = {
|
|
|
238
368
|
templates: []
|
|
239
369
|
};
|
|
240
370
|
}
|
|
241
|
-
typeFiles[targetType].templates.push(displayTemplate);
|
|
371
|
+
typeFiles[targetType].templates.push({ file: filePath, data: displayTemplate });
|
|
242
372
|
return displayTemplate.key;
|
|
243
|
-
}));
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const settingOptions = '"' + Object.getOwnPropertyNames(displayTemplate.settings[settingName].choices).join('" | "') + '"';
|
|
255
|
-
typeSettings.push(` { key: "${settingName}", value: ${settingOptions}}`);
|
|
373
|
+
}))).filter(x => x);
|
|
374
|
+
//#endregion
|
|
375
|
+
//#region Create needed definition files
|
|
376
|
+
if (definitions) {
|
|
377
|
+
for (const targetId of Object.getOwnPropertyNames(typeFiles)) {
|
|
378
|
+
const { filePath: typeFilePath, templates } = typeFiles[targetId];
|
|
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;
|
|
256
384
|
}
|
|
257
|
-
|
|
258
|
-
|
|
385
|
+
if (cfg.debug)
|
|
386
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Overwriting definition file for ${targetId}\n`));
|
|
259
387
|
}
|
|
260
|
-
else
|
|
261
|
-
|
|
388
|
+
else if (cfg.debug)
|
|
389
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating definition file for ${targetId}\n`));
|
|
390
|
+
// Write Style definition
|
|
391
|
+
const imports = ['import type { LayoutProps } from "@remkoj/optimizely-cms-react/components"', 'import type { ReactNode } from "react"'];
|
|
392
|
+
const typeContents = [];
|
|
393
|
+
const props = [];
|
|
394
|
+
let typeId = targetId.split('/', 2)[1];
|
|
395
|
+
templates.forEach(({ file: displayTemplateFile, data: displayTemplate }) => {
|
|
396
|
+
const importPath = path.relative(path.dirname(typeFilePath), displayTemplateFile).replaceAll('\\', '/');
|
|
397
|
+
imports.push(`import type ${displayTemplate.key}Styles from "./${importPath}"`);
|
|
398
|
+
typeContents.push(`export type ${displayTemplate.key}Props = LayoutProps<typeof ${displayTemplate.key}Styles>`);
|
|
399
|
+
typeContents.push(`export type ${displayTemplate.key}ComponentProps<DT extends Record<string, any> = Record<string, any>> = {
|
|
400
|
+
data: DT
|
|
401
|
+
layoutProps: ${displayTemplate.key}Props | undefined
|
|
402
|
+
} & JSX.IntrinsicElements['div']`);
|
|
403
|
+
typeContents.push(`export type ${displayTemplate.key}Component<DT extends Record<string, any> = Record<string, any>> = (props: ${displayTemplate.key}ComponentProps<DT>) => ReactNode`);
|
|
404
|
+
typeContents.push('');
|
|
405
|
+
props.push(`${displayTemplate.key}Props`);
|
|
406
|
+
if (!typeId)
|
|
407
|
+
typeId = displayTemplate.nodeType ?? displayTemplate.baseType ?? displayTemplate.contentType;
|
|
408
|
+
});
|
|
409
|
+
if (typeId) {
|
|
410
|
+
typeId = ucFirst$1(typeId);
|
|
411
|
+
typeContents.push('');
|
|
412
|
+
typeContents.push(`export type ${typeId}LayoutProps = ${props.join(' | ')}
|
|
413
|
+
export type ${typeId}ComponentProps<DT extends Record<string, any> = Record<string, any>, LP extends ${typeId}LayoutProps = ${typeId}LayoutProps> = {
|
|
414
|
+
data: DT
|
|
415
|
+
layoutProps: LP | undefined
|
|
416
|
+
} & JSX.IntrinsicElements['div']
|
|
417
|
+
|
|
418
|
+
export type ${typeId}Component<DT extends Record<string, any> = Record<string, any>, LP extends ${typeId}LayoutProps = ${typeId}LayoutProps> = (props: ${typeId}ComponentProps<DT,LP>) => ReactNode`);
|
|
262
419
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
typeContents.push(` settings: ${displayTemplate.key}Settings`);
|
|
266
|
-
typeContents.push(`}`);
|
|
267
|
-
typeId = displayTemplate.contentType ?? displayTemplate.baseType ?? displayTemplate.nodeType;
|
|
268
|
-
typeId = typeId[0]?.toUpperCase() + typeId.substring(1) + "LayoutProps";
|
|
269
|
-
});
|
|
270
|
-
typeContents.push(`export type ${typeId} = ${templates.map(t => t.key + "LayoutProps").join(' | ')}`);
|
|
271
|
-
typeContents.push(`export default ${typeId}`);
|
|
272
|
-
fs.writeFileSync(typeFilePath, typeContents.join("\n"));
|
|
420
|
+
fs.writeFileSync(typeFilePath, imports.join("\n") + "\n\n" + typeContents.join("\n"));
|
|
421
|
+
}
|
|
273
422
|
}
|
|
274
|
-
|
|
275
|
-
process.stdout.write(chalk.green(chalk.bold(figures.tick +
|
|
423
|
+
//#endregion
|
|
424
|
+
process.stdout.write(chalk.green(chalk.bold(figures.tick + ` Created/updated style definitions for ${updatedTemplates.join(', ')}`)) + "\n");
|
|
276
425
|
}
|
|
277
426
|
};
|
|
427
|
+
function ucFirst$1(input) {
|
|
428
|
+
if (typeof (input) != 'string' || input.length < 1)
|
|
429
|
+
return input;
|
|
430
|
+
return input[0].toUpperCase() + input.substring(1);
|
|
431
|
+
}
|
|
278
432
|
|
|
279
433
|
const TypesPullCommand = {
|
|
280
434
|
command: "types:pull",
|
|
281
435
|
describe: "Pull content type definition files into the project",
|
|
282
436
|
builder: (yargs) => {
|
|
437
|
+
const newArgs = contentTypesBuilder(yargs);
|
|
283
438
|
yargs.option('force', { alias: 'f', description: "Overwrite existing files", boolean: true, type: 'boolean', demandOption: false, default: false });
|
|
284
|
-
|
|
285
|
-
yargs.option('excludeBaseTypes', { alias: 'ebt', description: "Exclude these base types", string: true, type: 'array', demandOption: false, default: ['folder', 'media', 'image', 'video'] });
|
|
286
|
-
return yargs;
|
|
439
|
+
return newArgs;
|
|
287
440
|
},
|
|
288
441
|
handler: async (args) => {
|
|
289
|
-
const { _config:
|
|
290
|
-
const client =
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
if (cfg.debug)
|
|
294
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetching page 1 of ? (${pageSize} items per page)\n`));
|
|
295
|
-
let templatesPage = await client.contentTypes.contentTypesList(undefined, undefined, 0, pageSize);
|
|
296
|
-
const results = templatesPage.items ?? [];
|
|
297
|
-
let pagesRemaining = Math.ceil(templatesPage.totalItemCount / templatesPage.pageSize) - (templatesPage.pageIndex + 1);
|
|
298
|
-
while (pagesRemaining > 0 && results.length < templatesPage.totalItemCount) {
|
|
299
|
-
if (cfg.debug)
|
|
300
|
-
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`));
|
|
301
|
-
templatesPage = await client.contentTypes.contentTypesList(undefined, undefined, templatesPage.pageIndex + 1, templatesPage.pageSize);
|
|
302
|
-
results.push(...templatesPage.items);
|
|
303
|
-
pagesRemaining = Math.ceil(templatesPage.totalItemCount / templatesPage.pageSize) - (templatesPage.pageIndex + 1);
|
|
304
|
-
}
|
|
305
|
-
if (cfg.debug)
|
|
306
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetched ${results.length} Content-Types from Optimizely CMS\n`));
|
|
307
|
-
if (!fs.existsSync(basePath))
|
|
308
|
-
fs.mkdirSync(basePath, { recursive: true });
|
|
309
|
-
if (!fs.statSync(basePath).isDirectory()) {
|
|
310
|
-
process.stderr.write(chalk.redBright(`${chalk.bold(figures.cross)} The components path ${basePath} is not a folder\n`));
|
|
311
|
-
process.exit(1);
|
|
312
|
-
}
|
|
313
|
-
const updatedTypes = results.map(contentType => {
|
|
314
|
-
if (excludeBaseTypes.includes(contentType.baseType)) {
|
|
315
|
-
if (cfg.debug)
|
|
316
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.displayName} (${contentType.key}) - Base type excluded\n`));
|
|
317
|
-
return undefined;
|
|
318
|
-
}
|
|
319
|
-
if (excludeTypes.includes(contentType.key)) {
|
|
320
|
-
if (cfg.debug)
|
|
321
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.displayName} (${contentType.key}) - Content type excluded\n`));
|
|
322
|
-
return undefined;
|
|
323
|
-
}
|
|
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 => {
|
|
324
446
|
const typePath = path.join(basePath, contentType.baseType, contentType.key);
|
|
325
447
|
const typeFile = path.join(typePath, `${contentType.key}.opti-type.json`);
|
|
326
448
|
if (!fs.existsSync(typePath))
|
|
327
449
|
fs.mkdirSync(typePath, { recursive: true });
|
|
328
|
-
if (
|
|
450
|
+
if (fs.existsSync(typeFile) && !force) {
|
|
451
|
+
if (debug)
|
|
452
|
+
process.stdout.write(chalk.yellow(`${figures.cross} Skipping type definition for ${contentType.displayName} (${contentType.key}) - File already exists\n`));
|
|
453
|
+
return contentType.key;
|
|
454
|
+
}
|
|
455
|
+
if (debug)
|
|
329
456
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Writing type definition for ${contentType.displayName} (${contentType.key})\n`));
|
|
330
457
|
fs.writeFileSync(typeFile, JSON.stringify(contentType, undefined, 2));
|
|
331
458
|
return contentType.key;
|
|
332
459
|
}).filter(x => x);
|
|
333
|
-
process.stdout.write(chalk.
|
|
334
|
-
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`)));
|
|
335
461
|
}
|
|
336
462
|
};
|
|
337
463
|
|
|
338
|
-
const
|
|
339
|
-
command: "
|
|
340
|
-
describe: "
|
|
464
|
+
const TypesPushCommand = {
|
|
465
|
+
command: "types:push",
|
|
466
|
+
describe: "Push content type definition into Optimizely CMS (create / replace)",
|
|
341
467
|
builder: (yargs) => {
|
|
342
468
|
yargs.option('force', { alias: 'f', description: "Overwrite existing files", boolean: true, type: 'boolean', demandOption: false, default: false });
|
|
343
469
|
yargs.option('excludeTypes', { alias: 'ect', description: "Exclude these content types", string: true, type: 'array', demandOption: false, default: [] });
|
|
344
470
|
yargs.option('excludeBaseTypes', { alias: 'ebt', description: "Exclude these base types", string: true, type: 'array', demandOption: false, default: ['folder', 'media', 'image', 'video'] });
|
|
471
|
+
yargs.option("baseTypes", { alias: 'b', description: "Select only these base types", string: true, type: 'array', demandOption: false, default: [] });
|
|
472
|
+
yargs.option("types", { alias: 't', description: "Select only these types", string: true, type: 'array', demandOption: false, default: [] });
|
|
345
473
|
return yargs;
|
|
346
474
|
},
|
|
347
475
|
handler: async (args) => {
|
|
348
|
-
const { _config: cfg, components: basePath, excludeBaseTypes, excludeTypes, force } = parseArgs(args);
|
|
476
|
+
const { _config: cfg, components: basePath, excludeBaseTypes, excludeTypes, baseTypes, types, force } = parseArgs(args);
|
|
349
477
|
const client = createClient(cfg);
|
|
350
|
-
|
|
351
|
-
process.stdout.write(chalk.yellowBright(`${figures.arrowRight}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
if (cfg.debug)
|
|
365
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Fetched ${results.length} Content-Types from Optimizely CMS\n`));
|
|
366
|
-
if (!fs.existsSync(basePath))
|
|
367
|
-
fs.mkdirSync(basePath, { recursive: true });
|
|
368
|
-
if (!fs.statSync(basePath).isDirectory()) {
|
|
369
|
-
process.stderr.write(chalk.redBright(`${chalk.bold(figures.cross)} The components path ${basePath} is not a folder\n`));
|
|
370
|
-
process.exit(1);
|
|
371
|
-
}
|
|
372
|
-
// Apply content type filters
|
|
373
|
-
if (cfg.debug)
|
|
374
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Applying content type filters\n`));
|
|
375
|
-
const contentTypes = results.filter(contentType => {
|
|
376
|
-
if (contentType.source == 'system') {
|
|
377
|
-
if (cfg.debug)
|
|
378
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.displayName} (${contentType.key}) - Internal CMS type\n`));
|
|
379
|
-
return false;
|
|
380
|
-
}
|
|
381
|
-
const baseType = contentType.baseType ?? 'default';
|
|
382
|
-
if (excludeBaseTypes.includes(baseType)) {
|
|
383
|
-
if (cfg.debug)
|
|
384
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.displayName} (${contentType.key}) - Base type excluded\n`));
|
|
385
|
-
return false;
|
|
386
|
-
}
|
|
387
|
-
if (excludeTypes.includes(contentType.key)) {
|
|
388
|
-
if (cfg.debug)
|
|
389
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.displayName} (${contentType.key}) - Content type excluded\n`));
|
|
390
|
-
return false;
|
|
391
|
-
}
|
|
392
|
-
return true;
|
|
478
|
+
// Find all type files
|
|
479
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Pushing (create/replace) Content Types into Optimizely CMS\n`));
|
|
480
|
+
const typeDefinitionFiles = await glob("./**/*.opti-type.json", { cwd: basePath });
|
|
481
|
+
// Read & filter all identified type files
|
|
482
|
+
const typeDefinitions = typeDefinitionFiles.map(file => {
|
|
483
|
+
return {
|
|
484
|
+
file: file,
|
|
485
|
+
definition: JSON.parse(fs.readFileSync(path.join(basePath, file), { encoding: 'utf-8' }).toString())
|
|
486
|
+
};
|
|
487
|
+
}).filter(data => {
|
|
488
|
+
return (!excludeBaseTypes.includes(data.definition.baseType)) &&
|
|
489
|
+
(!excludeTypes.includes(data.definition.key)) &&
|
|
490
|
+
(!baseTypes || baseTypes.length == 0 || baseTypes.includes(data.definition.baseType)) &&
|
|
491
|
+
(!types || types.length == 0 || types.includes(data.definition.key));
|
|
393
492
|
});
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
493
|
+
// Output selected types
|
|
494
|
+
const results = await Promise.all(typeDefinitions.map(type => {
|
|
495
|
+
process.stdout.write(chalk.yellowBright(` ${figures.arrowRight} Pushing ${type.definition.displayName} from ${type.file}\n`));
|
|
496
|
+
return client.contentTypes.contentTypesPut(type.definition.key, type.definition, force)
|
|
497
|
+
.then(ct => { return { key: type.definition.key, type: ct, file: type.file }; })
|
|
498
|
+
.catch(e => { return { key: type.definition.key, type: type.definition, file: type.file, error: e }; });
|
|
499
|
+
}));
|
|
500
|
+
const overview = new Table({
|
|
501
|
+
head: [
|
|
502
|
+
chalk.yellow(chalk.bold("Name")),
|
|
503
|
+
chalk.yellow(chalk.bold("Key")),
|
|
504
|
+
chalk.yellow(chalk.bold("Status")),
|
|
505
|
+
chalk.yellow(chalk.bold("Message"))
|
|
506
|
+
],
|
|
507
|
+
colWidths: [31, 20, 9, 20],
|
|
508
|
+
colAligns: ["left", "left", "center", "left"]
|
|
509
|
+
});
|
|
510
|
+
results.forEach(item => {
|
|
511
|
+
overview.push([
|
|
512
|
+
item.type.displayName,
|
|
513
|
+
item.key,
|
|
514
|
+
item.error ? chalk.redBright(chalk.bold(figures.cross)) : chalk.green(chalk.bold(figures.tick)),
|
|
515
|
+
item.error ?? ''
|
|
516
|
+
]);
|
|
517
|
+
});
|
|
518
|
+
process.stdout.write(overview.toString() + "\n");
|
|
519
|
+
// Mark us as done
|
|
411
520
|
process.stdout.write(chalk.green(chalk.bold(figures.tick + " Done")) + "\n");
|
|
412
521
|
}
|
|
413
522
|
};
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
if (
|
|
428
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight}
|
|
523
|
+
|
|
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;
|
|
528
|
+
};
|
|
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`));
|
|
429
538
|
}
|
|
430
|
-
//
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
});
|
|
438
|
-
lines.push('');
|
|
439
|
-
lines.push(`export const ${baseType}Dictionary : ComponentTypeDictionary = [`);
|
|
440
|
-
inheritingTypes.forEach(type => {
|
|
441
|
-
lines.push(' {', ` type: '${type.key}',`, ` component: ${type.key}`, ' },');
|
|
442
|
-
});
|
|
443
|
-
lines.push(']', '', `export default ${baseType}Dictionary`);
|
|
444
|
-
fs.writeFileSync(baseTypeFactory, lines.join("\n"));
|
|
445
|
-
return baseType;
|
|
446
|
-
}).filter(x => x);
|
|
447
|
-
const cmsFactoryFile = path.join(basePath, 'index.ts');
|
|
448
|
-
if (fs.existsSync(cmsFactoryFile)) {
|
|
449
|
-
if (cfg.debug)
|
|
450
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Overwriting CMS Components factory\n`));
|
|
451
|
-
}
|
|
452
|
-
const lines = [
|
|
453
|
-
'// Auto generated dictionary',
|
|
454
|
-
'import { ComponentTypeDictionary } from "@remkoj/optimizely-cms-react";'
|
|
455
|
-
];
|
|
456
|
-
baseTypeFactories.forEach(type => {
|
|
457
|
-
lines.push(`import ${type}Components from "./${type}";`);
|
|
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
|
+
};
|
|
458
546
|
});
|
|
459
|
-
|
|
460
|
-
baseTypeFactories.forEach(type => {
|
|
461
|
-
lines.push(`prefixDictionaryEntries(${type}Components, '${ucFirst(type)}');`);
|
|
462
|
-
});
|
|
463
|
-
lines.push('');
|
|
464
|
-
lines.push('export const cmsComponentDictionary : ComponentTypeDictionary = [');
|
|
465
|
-
baseTypeFactories.forEach(type => {
|
|
466
|
-
lines.push(` ...${type}Components,`);
|
|
467
|
-
});
|
|
468
|
-
lines.push(']', '', `export default cmsComponentDictionary`, `function prefixDictionaryEntries(list: ComponentTypeDictionary, prefix: string) : ComponentTypeDictionary
|
|
469
|
-
{
|
|
470
|
-
list.forEach((component, idx, dictionary) => {
|
|
471
|
-
dictionary[idx].type = typeof component.type == 'string' ? prefix + "/" + component.type : [ prefix, ...component.type ]
|
|
472
|
-
})
|
|
473
|
-
return list
|
|
474
|
-
}`);
|
|
475
|
-
fs.writeFileSync(cmsFactoryFile, lines.join("\n"));
|
|
547
|
+
return folders;
|
|
476
548
|
}
|
|
477
|
-
function
|
|
478
|
-
|
|
479
|
-
if (fs.existsSync(componentFile)) {
|
|
480
|
-
if (force) {
|
|
481
|
-
if (cfg.debug)
|
|
482
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Overwriting ${contentType.displayName} (${contentType.key}) component\n`));
|
|
483
|
-
}
|
|
484
|
-
else {
|
|
485
|
-
if (cfg.debug)
|
|
486
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.displayName} (${contentType.key}) component - file already exists\n`));
|
|
487
|
-
return undefined;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
const varName = `${contentType.key}${ucFirst(contentType.baseType ?? 'part')}`;
|
|
491
|
-
const component = `import { CmsComponent } from "@remkoj/optimizely-cms-react";
|
|
492
|
-
import { ${contentType.key}DataFragmentDoc, type ${contentType.key}DataFragment } from "@/gql/graphql";
|
|
493
|
-
|
|
494
|
-
export const ${varName} : CmsComponent<${contentType.key}DataFragment> = ({ data }) => {
|
|
495
|
-
const componentName = '${contentType.displayName}'
|
|
496
|
-
const componentInfo = '${contentType.description ?? ''}'
|
|
497
|
-
return <div className="mx-auto px-2 container">
|
|
498
|
-
<div>{ componentName }</div>
|
|
499
|
-
<div>{ componentInfo }</div>
|
|
500
|
-
<pre className="w-full overflow-x-hidden font-mono text-sm">{ JSON.stringify(data, undefined, 4) }</pre>
|
|
501
|
-
</div>
|
|
502
|
-
}
|
|
503
|
-
${varName}.getDataFragment = () => ['${contentType.key}Data', ${contentType.key}DataFragmentDoc]
|
|
504
|
-
|
|
505
|
-
export default ${varName}`;
|
|
506
|
-
fs.writeFileSync(componentFile, component);
|
|
549
|
+
function getTypeFolder(list, type) {
|
|
550
|
+
return list.filter(x => x.type == type).at(0)?.path;
|
|
507
551
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
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 = [];
|
|
512
580
|
const baseType = contentType.baseType ?? 'default';
|
|
513
581
|
const baseQueryFile = path.join(typePath, `${contentType.key}.${baseType}.graphql`);
|
|
514
582
|
if (fs.existsSync(baseQueryFile)) {
|
|
515
583
|
if (force) {
|
|
516
|
-
if (
|
|
584
|
+
if (debug)
|
|
517
585
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Overwriting ${contentType.displayName} (${contentType.key}) base fragment\n`));
|
|
518
586
|
}
|
|
519
587
|
else {
|
|
520
|
-
if (
|
|
588
|
+
if (debug)
|
|
521
589
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping ${contentType.displayName} (${contentType.key}) base fragment - file already exists\n`));
|
|
522
590
|
return undefined;
|
|
523
591
|
}
|
|
524
592
|
}
|
|
593
|
+
else if (debug) {
|
|
594
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating ${contentType.displayName} (${contentType.key}) base fragment\n`));
|
|
595
|
+
}
|
|
525
596
|
const { fragment, propertyTypes } = createInitialFragment(contentType);
|
|
526
597
|
fs.writeFileSync(baseQueryFile, fragment);
|
|
598
|
+
returnValue.push(contentType.key);
|
|
527
599
|
let dependencies = Array.isArray(propertyTypes) ? [...propertyTypes] : [];
|
|
528
600
|
while (Array.isArray(dependencies) && dependencies.length > 0) {
|
|
529
601
|
let newDependencies = [];
|
|
@@ -538,15 +610,18 @@ function createGraphFragments(contentType, typePath, basePath, force, cfg, conte
|
|
|
538
610
|
if (!fs.existsSync(propertyFragmentDir))
|
|
539
611
|
fs.mkdirSync(propertyFragmentDir, { recursive: true });
|
|
540
612
|
if (!fs.existsSync(propertyFragmentFile) || force) {
|
|
541
|
-
|
|
613
|
+
if (debug)
|
|
614
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Writing ${propContentType.displayName} (${propContentType.key}) property fragment\n`));
|
|
542
615
|
const propContentTypeInfo = createInitialFragment(propContentType, true);
|
|
543
616
|
fs.writeFileSync(propertyFragmentFile, propContentTypeInfo.fragment);
|
|
617
|
+
returnValue.push(propContentType.key);
|
|
544
618
|
if (Array.isArray(propContentTypeInfo.propertyTypes))
|
|
545
619
|
newDependencies.push(...propContentTypeInfo.propertyTypes);
|
|
546
620
|
}
|
|
547
621
|
});
|
|
548
622
|
dependencies = newDependencies;
|
|
549
623
|
}
|
|
624
|
+
return returnValue.length > 0 ? returnValue : undefined;
|
|
550
625
|
}
|
|
551
626
|
function createInitialFragment(contentType, forProperty = false) {
|
|
552
627
|
const propertyTypes = [];
|
|
@@ -604,6 +679,8 @@ function createInitialFragment(contentType, forProperty = false) {
|
|
|
604
679
|
break;
|
|
605
680
|
}
|
|
606
681
|
});
|
|
682
|
+
if (contentType.baseType == "experience")
|
|
683
|
+
fragmentFields.push('...ExperienceData');
|
|
607
684
|
if (fragmentFields.length == 0)
|
|
608
685
|
fragmentFields.push('_metadata { key }');
|
|
609
686
|
const tpl = `fragment ${contentType.key}${forProperty ? 'Property' : ''}Data on ${contentType.key}${forProperty ? 'Property' : ''} {
|
|
@@ -615,6 +692,499 @@ function createInitialFragment(contentType, forProperty = false) {
|
|
|
615
692
|
};
|
|
616
693
|
}
|
|
617
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
|
+
|
|
618
1188
|
const CmsVersionCommand = {
|
|
619
1189
|
command: "cms:version",
|
|
620
1190
|
describe: "Get the CMS Version information",
|
|
@@ -641,16 +1211,22 @@ const CmsVersionCommand = {
|
|
|
641
1211
|
}
|
|
642
1212
|
};
|
|
643
1213
|
|
|
1214
|
+
// Style processing
|
|
644
1215
|
const commands = [
|
|
645
1216
|
StylesPushCommand,
|
|
646
1217
|
StylesListCommand,
|
|
647
1218
|
StylesPullCommand,
|
|
648
1219
|
TypesPullCommand,
|
|
1220
|
+
TypesPushCommand,
|
|
649
1221
|
NextJsCreateCommand,
|
|
1222
|
+
NextJsQueriesCommand,
|
|
1223
|
+
NextJsComponentsCommand,
|
|
1224
|
+
NextJsVisualBuilderCommand,
|
|
1225
|
+
NextJsFactoryCommand,
|
|
650
1226
|
CmsVersionCommand
|
|
651
1227
|
];
|
|
652
1228
|
|
|
653
|
-
var version = "2.0.
|
|
1229
|
+
var version = "2.0.1-pre1";
|
|
654
1230
|
var name = "opti-cms";
|
|
655
1231
|
var APP = {
|
|
656
1232
|
version: version,
|