@remkoj/optimizely-cms-cli 5.1.6 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/index.js +236 -182
- package/dist/index.js.map +1 -1
- package/package.json +15 -15
package/dist/index.js
CHANGED
|
@@ -1,30 +1,51 @@
|
|
|
1
1
|
import { globSync, glob } from 'glob';
|
|
2
2
|
import dotenv from 'dotenv';
|
|
3
3
|
import { expand } from 'dotenv-expand';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import fs from 'node:fs';
|
|
4
6
|
import { getCmsIntegrationApiConfigFromEnvironment, createClient, OptiCmsVersion, IntegrationApi, ContentRoots } from '@remkoj/optimizely-cms-api';
|
|
5
7
|
import yargs from 'yargs';
|
|
6
8
|
import chalk from 'chalk';
|
|
7
|
-
import path from 'node:path';
|
|
8
|
-
import fs from 'node:fs';
|
|
9
9
|
import figures from 'figures';
|
|
10
10
|
import Table from 'cli-table3';
|
|
11
11
|
import fsAsync from 'node:fs/promises';
|
|
12
12
|
import { input, select, confirm } from '@inquirer/prompts';
|
|
13
13
|
|
|
14
|
+
function getProjectDir() {
|
|
15
|
+
let testPath = process.cwd();
|
|
16
|
+
let hasPackageJson = false;
|
|
17
|
+
do {
|
|
18
|
+
hasPackageJson = fs.statSync(path.join(testPath, 'package.json'), { throwIfNoEntry: false })?.isFile() ?? false;
|
|
19
|
+
if (!hasPackageJson)
|
|
20
|
+
testPath = path.normalize(path.join(testPath, '..'));
|
|
21
|
+
} while (!hasPackageJson && testPath.length > 2);
|
|
22
|
+
if (!hasPackageJson) {
|
|
23
|
+
throw new Error('No package.json found!');
|
|
24
|
+
}
|
|
25
|
+
return testPath;
|
|
26
|
+
}
|
|
14
27
|
/**
|
|
15
28
|
* Prepare the application context, by parsing the .env files in the main
|
|
16
29
|
* application directory.
|
|
17
30
|
*
|
|
18
31
|
* @returns A string array with the files processed
|
|
19
32
|
*/
|
|
20
|
-
function prepare() {
|
|
21
|
-
const
|
|
22
|
-
|
|
33
|
+
function prepare(pDir) {
|
|
34
|
+
const projectDir = pDir ?? getProjectDir();
|
|
35
|
+
const envFiles = globSync(".env*", { cwd: projectDir })
|
|
36
|
+
.sort((a, b) => b.length - a.length)
|
|
37
|
+
.filter(n => n == ".env" || n == ".env.local" || (process.env.NODE_ENV && n == `.env.${process.env.NODE_ENV}.local`));
|
|
38
|
+
expand(dotenv.config({
|
|
39
|
+
path: envFiles.map(envFile => path.join(projectDir, envFile)),
|
|
40
|
+
debug: false,
|
|
41
|
+
quiet: true
|
|
42
|
+
}));
|
|
23
43
|
return envFiles;
|
|
24
44
|
}
|
|
25
45
|
|
|
26
|
-
function createOptiCmsApp(scriptName, version, epilogue, envFiles) {
|
|
46
|
+
function createOptiCmsApp(scriptName, version, epilogue, envFiles, projectDir) {
|
|
27
47
|
let config;
|
|
48
|
+
const defaultPath = projectDir ?? process.cwd();
|
|
28
49
|
try {
|
|
29
50
|
config = getCmsIntegrationApiConfigFromEnvironment();
|
|
30
51
|
}
|
|
@@ -38,7 +59,7 @@ function createOptiCmsApp(scriptName, version, epilogue, envFiles) {
|
|
|
38
59
|
.scriptName(scriptName)
|
|
39
60
|
.version(version)
|
|
40
61
|
.usage('$0 <cmd> [args]')
|
|
41
|
-
.option("path", { alias: "p", description: "Application root folder", string: true, type: "string", demandOption: false, default:
|
|
62
|
+
.option("path", { alias: "p", description: "Application root folder", string: true, type: "string", demandOption: false, default: defaultPath })
|
|
42
63
|
.option("components", { alias: "c", description: "Path to components folder", string: true, type: "string", demandOption: false, default: "./src/components/cms" })
|
|
43
64
|
.option("cms_url", { alias: "cu", description: "Optimizely CMS URL", string: true, type: "string", demandOption: isDemanded(config.base), default: config.base, coerce: (val) => new URL(val) })
|
|
44
65
|
.option("client_id", { alias: "ci", description: "API Client ID", string: true, type: "string", demandOption: isDemanded(config.clientId), default: config.clientId })
|
|
@@ -230,6 +251,8 @@ async function getContentTypes(client, args, pageSize = 100, allowSystem = false
|
|
|
230
251
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Filtering Content-Types based upon arguments\n`));
|
|
231
252
|
}
|
|
232
253
|
const validBaseTypes = getEnumOptions(IntegrationApi.ContentBaseType).map(x => x.toLowerCase());
|
|
254
|
+
if (cfg.debug)
|
|
255
|
+
process.stdout.write(`Allowing base types: ${validBaseTypes.join(', ')}\n`);
|
|
233
256
|
const allContentTypes = all ?
|
|
234
257
|
// If we're returning all content types, including non-supported base types, make sure the base type is always set
|
|
235
258
|
results.map(contentType => {
|
|
@@ -238,15 +261,21 @@ async function getContentTypes(client, args, pageSize = 100, allowSystem = false
|
|
|
238
261
|
baseType: contentType.baseType ?? 'default'
|
|
239
262
|
};
|
|
240
263
|
}) :
|
|
241
|
-
// Otherwise filter out any non supported content base type
|
|
264
|
+
// Otherwise filter out any non supported content base type and types that include a ':'
|
|
242
265
|
results.filter(contentType => {
|
|
243
266
|
const baseType = (contentType.baseType ?? 'default').toLowerCase();
|
|
244
|
-
const isValid = validBaseTypes.includes(baseType);
|
|
267
|
+
const isValid = validBaseTypes.includes(baseType) && !contentType.key.includes(':');
|
|
245
268
|
if (!isValid && cfg.debug)
|
|
246
269
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Removing ${contentType.key} as it has an unsupported base type: ${baseType}\n`));
|
|
247
270
|
return isValid;
|
|
248
271
|
});
|
|
249
272
|
const contentTypes = allContentTypes.filter(data => {
|
|
273
|
+
// Remove all intermediary/supporting types
|
|
274
|
+
if (!all && data.key.includes(':')) {
|
|
275
|
+
if (cfg.debug)
|
|
276
|
+
process.stdout.write(chalk.gray(`${figures.arrowRight} Skipping Content-Type ${data.key} due to it containing a ':'\n`));
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
250
279
|
// Remove items based upon filters
|
|
251
280
|
const keepType = (!excludeBaseTypes.includes(data.baseType)) &&
|
|
252
281
|
(!excludeTypes.includes(data.key)) &&
|
|
@@ -519,8 +548,8 @@ async function createTemplateMetadata(client, displayTemplate, basePath, createP
|
|
|
519
548
|
break;
|
|
520
549
|
case 'content':
|
|
521
550
|
const contentType = await client.contentTypes.contentTypesGet(displayTemplate.contentType ?? '-');
|
|
522
|
-
itemPath = path.join(basePath, contentType.baseType, contentType.key);
|
|
523
|
-
typesPath = path.join(basePath, contentType.baseType, contentType.key);
|
|
551
|
+
itemPath = path.join(basePath, contentType.baseType, contentType.key.split(':').pop());
|
|
552
|
+
typesPath = path.join(basePath, contentType.baseType, contentType.key.split(':').pop());
|
|
524
553
|
targetType = targetPrefix + '/' + displayTemplate.contentType;
|
|
525
554
|
break;
|
|
526
555
|
default:
|
|
@@ -802,8 +831,8 @@ const TypesPullCommand = {
|
|
|
802
831
|
const client = createCmsClient(args);
|
|
803
832
|
const { contentTypes } = await getContentTypes(client, args);
|
|
804
833
|
const updatedTypes = contentTypes.map(contentType => {
|
|
805
|
-
const typePath = path.join(basePath, contentType.baseType, contentType.key);
|
|
806
|
-
const typeFile = path.join(typePath, `${contentType.key}.opti-type.json`);
|
|
834
|
+
const typePath = path.join(basePath, contentType.baseType, contentType.key.split(':').pop());
|
|
835
|
+
const typeFile = path.join(typePath, `${contentType.key.split(':').pop()}.opti-type.json`);
|
|
807
836
|
if (!fs.existsSync(typePath))
|
|
808
837
|
fs.mkdirSync(typePath, { recursive: true });
|
|
809
838
|
if (fs.existsSync(typeFile) && !force) {
|
|
@@ -915,7 +944,7 @@ function createTypeFolders(contentTypes, basePath, debug = false) {
|
|
|
915
944
|
const folders = contentTypes.map(contentType => {
|
|
916
945
|
const baseType = contentType.baseType ?? 'default';
|
|
917
946
|
// Create the type folder
|
|
918
|
-
const typePath = path.join(basePath, baseType, contentType.key);
|
|
947
|
+
const typePath = path.join(basePath, baseType, contentType.key.split(':').pop());
|
|
919
948
|
if (!fs.existsSync(typePath)) {
|
|
920
949
|
fs.mkdirSync(typePath, { recursive: true });
|
|
921
950
|
if (debug)
|
|
@@ -934,43 +963,59 @@ function createTypeFolders(contentTypes, basePath, debug = false) {
|
|
|
934
963
|
function getTypeFolder(list, type) {
|
|
935
964
|
return list.filter(x => x.type == type).at(0)?.path;
|
|
936
965
|
}
|
|
966
|
+
const PROPS_LOCK_FILE = '.opti-props.lock';
|
|
967
|
+
function getGeneratedProps(appPath) {
|
|
968
|
+
const lockPath = path.join(appPath, PROPS_LOCK_FILE);
|
|
969
|
+
try {
|
|
970
|
+
const lockData = JSON.parse(fs.readFileSync(lockPath).toString());
|
|
971
|
+
const generatedProps = Array.isArray(lockData) ? lockData.map(x => { return { propName: x.propertyName, propType: x.propertyType }; }) : [];
|
|
972
|
+
return generatedProps;
|
|
973
|
+
}
|
|
974
|
+
catch (e) {
|
|
975
|
+
return [];
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
function writeGeneratedProps(appPath, generatedProps) {
|
|
979
|
+
const lockPath = path.join(appPath, PROPS_LOCK_FILE);
|
|
980
|
+
fs.writeFileSync(lockPath, JSON.stringify(generatedProps.map(x => { return { propertyName: x.propName, propertyType: x.propType }; }), undefined, 2));
|
|
981
|
+
}
|
|
937
982
|
|
|
938
|
-
/**
|
|
939
|
-
* Keep track of all generated properties
|
|
940
|
-
*/
|
|
941
|
-
let generatedProps = [];
|
|
942
983
|
const NextJsFragmentsCommand = {
|
|
943
984
|
command: "nextjs:fragments",
|
|
944
985
|
describe: "Create the GrapQL Fragments for a Next.JS / Optimizely Graph structure",
|
|
945
986
|
builder,
|
|
946
987
|
handler: async (args, opts) => {
|
|
947
|
-
generatedProps = [];
|
|
948
988
|
// Prepare
|
|
949
989
|
const { loadedContentTypes, createdTypeFolders } = opts || {};
|
|
950
|
-
const { components: basePath, _config: { debug }, force } = parseArgs(args);
|
|
990
|
+
const { components: basePath, _config: { debug }, force, path: appPath } = parseArgs(args);
|
|
951
991
|
const client = createCmsClient(args);
|
|
992
|
+
// Get locked property names
|
|
993
|
+
const generatedProps = getGeneratedProps(appPath);
|
|
994
|
+
// Get content types
|
|
952
995
|
const { contentTypes, all: allContentTypes } = loadedContentTypes ?? await getContentTypes(client, args);
|
|
953
996
|
// Start process
|
|
954
997
|
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Generating GraphQL fragments for ${contentTypes.map(x => x.key).join(', ')}\n`));
|
|
955
998
|
const typeFolders = createdTypeFolders ?? createTypeFolders(contentTypes, basePath, debug);
|
|
956
999
|
const updatedTypes = contentTypes.map(contentType => {
|
|
957
1000
|
const typePath = getTypeFolder(typeFolders, contentType.key);
|
|
958
|
-
return createGraphFragments(contentType, typePath, basePath, force, debug, allContentTypes, client.runtimeCmsVersion == OptiCmsVersion.CMS12);
|
|
1001
|
+
return createGraphFragments(contentType, typePath, basePath, force, debug, allContentTypes, generatedProps, client.runtimeCmsVersion == OptiCmsVersion.CMS12);
|
|
959
1002
|
}).filter(x => x).flat();
|
|
960
1003
|
// Report outcome
|
|
961
1004
|
if (updatedTypes.length > 0)
|
|
962
1005
|
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Created/updated GraphQL fragments for ${updatedTypes.join(', ')}\n`));
|
|
963
1006
|
else
|
|
964
1007
|
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} No GraphQL fragments created/updated\n`));
|
|
1008
|
+
// Write updated lock
|
|
1009
|
+
writeGeneratedProps(appPath, generatedProps);
|
|
965
1010
|
if (!opts)
|
|
966
1011
|
process.stdout.write(chalk.green(chalk.bold(figures.tick + " Done")) + "\n");
|
|
967
|
-
generatedProps = [];
|
|
968
1012
|
}
|
|
969
1013
|
};
|
|
970
|
-
function createGraphFragments(contentType, typePath, basePath, force, debug, contentTypes, forCms12 = false) {
|
|
1014
|
+
function createGraphFragments(contentType, typePath, basePath, force, debug, contentTypes, generatedProps = [], forCms12 = false) {
|
|
971
1015
|
const returnValue = [];
|
|
972
1016
|
const baseType = contentType.baseType ?? 'default';
|
|
973
|
-
const baseQueryFile = path.join(typePath, `${contentType.key}.${baseType}.graphql`);
|
|
1017
|
+
const baseQueryFile = path.join(typePath, `${contentType.key.split(':').pop()}.${baseType}.graphql`);
|
|
1018
|
+
//console.log('Mapping', contentType.key, baseQueryFile);
|
|
974
1019
|
if (fs.existsSync(baseQueryFile)) {
|
|
975
1020
|
if (force) {
|
|
976
1021
|
if (debug)
|
|
@@ -985,7 +1030,7 @@ function createGraphFragments(contentType, typePath, basePath, force, debug, con
|
|
|
985
1030
|
else if (debug) {
|
|
986
1031
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating ${contentType.displayName} (${contentType.key}) base fragment\n`));
|
|
987
1032
|
}
|
|
988
|
-
const { fragment, propertyTypes } = createInitialFragment(contentType, false, undefined, forCms12);
|
|
1033
|
+
const { fragment, propertyTypes } = createInitialFragment(contentType, false, undefined, generatedProps, forCms12);
|
|
989
1034
|
fs.writeFileSync(baseQueryFile, fragment);
|
|
990
1035
|
returnValue.push(contentType.key);
|
|
991
1036
|
let dependencies = Array.isArray(propertyTypes) ? [...propertyTypes] : [];
|
|
@@ -998,14 +1043,14 @@ function createGraphFragments(contentType, typePath, basePath, force, debug, con
|
|
|
998
1043
|
return;
|
|
999
1044
|
}
|
|
1000
1045
|
const fullTypeName = forCms12 ? contentType.key + propContentType.key : propContentType.key;
|
|
1001
|
-
const propertyFragmentFile = path.join(basePath, propContentType.baseType, propContentType.key, `${fullTypeName}.property.graphql`);
|
|
1046
|
+
const propertyFragmentFile = path.join(basePath, propContentType.baseType, propContentType.key.split(':').pop(), `${fullTypeName.split(':').pop()}.property.graphql`);
|
|
1002
1047
|
const propertyFragmentDir = path.dirname(propertyFragmentFile);
|
|
1003
1048
|
if (!fs.existsSync(propertyFragmentDir))
|
|
1004
1049
|
fs.mkdirSync(propertyFragmentDir, { recursive: true });
|
|
1005
1050
|
if (!fs.existsSync(propertyFragmentFile) || force) {
|
|
1006
1051
|
if (debug)
|
|
1007
1052
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Writing ${propContentType.displayName} (${propContentType.key}) property fragment\n`));
|
|
1008
|
-
const propContentTypeInfo = createInitialFragment(propContentType, true, contentType, forCms12);
|
|
1053
|
+
const propContentTypeInfo = createInitialFragment(propContentType, true, contentType, generatedProps, forCms12);
|
|
1009
1054
|
fs.writeFileSync(propertyFragmentFile, propContentTypeInfo.fragment);
|
|
1010
1055
|
returnValue.push(propContentType.key);
|
|
1011
1056
|
if (Array.isArray(propContentTypeInfo.propertyTypes))
|
|
@@ -1016,9 +1061,10 @@ function createGraphFragments(contentType, typePath, basePath, force, debug, con
|
|
|
1016
1061
|
}
|
|
1017
1062
|
return returnValue.length > 0 ? returnValue : undefined;
|
|
1018
1063
|
}
|
|
1019
|
-
function createInitialFragment(contentType, forProperty = false, forBaseType, forCms12 = false) {
|
|
1020
|
-
const { fragmentFields, propertyTypes } = renderProperties(contentType, forCms12);
|
|
1021
|
-
const
|
|
1064
|
+
function createInitialFragment(contentType, forProperty = false, forBaseType, generatedProps = [], forCms12 = false) {
|
|
1065
|
+
const { fragmentFields, propertyTypes } = renderProperties(contentType, generatedProps, forCms12);
|
|
1066
|
+
const contentTypeKey = contentType.key.split(':').pop();
|
|
1067
|
+
const fragmentTarget = forProperty ? (forCms12 ? (forBaseType?.key ?? '') + contentTypeKey : contentTypeKey + 'Property') : contentTypeKey;
|
|
1022
1068
|
const tpl = `fragment ${fragmentTarget}Data on ${fragmentTarget} {
|
|
1023
1069
|
${fragmentFields.join("\n ")}
|
|
1024
1070
|
}`;
|
|
@@ -1027,7 +1073,7 @@ function createInitialFragment(contentType, forProperty = false, forBaseType, fo
|
|
|
1027
1073
|
propertyTypes: propertyTypes.length == 0 ? null : propertyTypes
|
|
1028
1074
|
};
|
|
1029
1075
|
}
|
|
1030
|
-
function renderProperties(contentType, forCms12 = false) {
|
|
1076
|
+
function renderProperties(contentType, generatedProps = [], forCms12 = false) {
|
|
1031
1077
|
const propertyTypes = [];
|
|
1032
1078
|
const fragmentFields = [];
|
|
1033
1079
|
const typeProps = contentType.properties ?? {};
|
|
@@ -1039,7 +1085,8 @@ function renderProperties(contentType, forCms12 = false) {
|
|
|
1039
1085
|
if (forCms12 && ['Categories'].includes(propKey))
|
|
1040
1086
|
return;
|
|
1041
1087
|
const propType = typeProps[propKey].type;
|
|
1042
|
-
const
|
|
1088
|
+
const propDataType = getPropDataType(typeProps[propKey]);
|
|
1089
|
+
const isConflict = generatedProps.some(x => x.propName == propKey && x.propType != propDataType);
|
|
1043
1090
|
const propName = isConflict ? `${contentType.key}${propKey}: ${propKey}` : propKey;
|
|
1044
1091
|
// Write the property
|
|
1045
1092
|
switch (propType) {
|
|
@@ -1048,8 +1095,10 @@ function renderProperties(contentType, forCms12 = false) {
|
|
|
1048
1095
|
const typeData = typeProps[propKey];
|
|
1049
1096
|
switch (typeData.items.type) {
|
|
1050
1097
|
case IntegrationApi.PropertyDataType.INTEGER:
|
|
1051
|
-
if (typeData.format == 'categorization')
|
|
1052
|
-
fragmentFields.push(`${propName} { Id, Name, Description }`)
|
|
1098
|
+
if (typeData.format == 'categorization') {
|
|
1099
|
+
//fragmentFields.push(`${propName} { Id, Name, Description }`)
|
|
1100
|
+
console.warn(chalk.redBright(`❗ Property ${propName} is a 'categorization', this is not supported. If you need to use this property, add it manually into the generated files`));
|
|
1101
|
+
}
|
|
1053
1102
|
else
|
|
1054
1103
|
fragmentFields.push(propName);
|
|
1055
1104
|
break;
|
|
@@ -1063,7 +1112,7 @@ function renderProperties(contentType, forCms12 = false) {
|
|
|
1063
1112
|
fragmentFields.push(`${propName} { ...IContentListItem }`);
|
|
1064
1113
|
break;
|
|
1065
1114
|
case IntegrationApi.PropertyDataType.COMPONENT:
|
|
1066
|
-
const componentType = typeData.items.contentType;
|
|
1115
|
+
const componentType = typeData.items.contentType.split(':').pop();
|
|
1067
1116
|
switch (componentType) {
|
|
1068
1117
|
case 'link':
|
|
1069
1118
|
fragmentFields.push(`${propName} { ...LinkItemData }`);
|
|
@@ -1111,8 +1160,8 @@ function renderProperties(contentType, forCms12 = false) {
|
|
|
1111
1160
|
fragmentFields.push(`${propName} { ...ReferenceData }`);
|
|
1112
1161
|
break;
|
|
1113
1162
|
case IntegrationApi.PropertyDataType.COMPONENT: {
|
|
1114
|
-
const componentType = typeProps[propKey].contentType;
|
|
1115
|
-
if (
|
|
1163
|
+
const componentType = typeProps[propKey].contentType.split(':').pop();
|
|
1164
|
+
if (componentType == "link") {
|
|
1116
1165
|
fragmentFields.push(`${propName} { ...LinkItemData }`);
|
|
1117
1166
|
}
|
|
1118
1167
|
else {
|
|
@@ -1123,16 +1172,20 @@ function renderProperties(contentType, forCms12 = false) {
|
|
|
1123
1172
|
break;
|
|
1124
1173
|
}
|
|
1125
1174
|
case IntegrationApi.PropertyDataType.BINARY:
|
|
1126
|
-
fragmentFields.push(
|
|
1175
|
+
fragmentFields.push(propName);
|
|
1176
|
+
break;
|
|
1177
|
+
case IntegrationApi.PropertyDataType.CONTENT:
|
|
1178
|
+
fragmentFields.push(`${propName} { ...${forCms12 ? 'PageIContentListItem' : 'BlockData'} }`);
|
|
1127
1179
|
break;
|
|
1128
1180
|
default:
|
|
1129
1181
|
fragmentFields.push(propName);
|
|
1130
1182
|
break;
|
|
1131
1183
|
}
|
|
1132
|
-
generatedProps.
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1184
|
+
if (propName === propKey && !generatedProps.some(x => x.propName === propKey))
|
|
1185
|
+
generatedProps.push({
|
|
1186
|
+
propType: propDataType,
|
|
1187
|
+
propName: propKey
|
|
1188
|
+
});
|
|
1136
1189
|
});
|
|
1137
1190
|
if (contentType.baseType == "experience")
|
|
1138
1191
|
fragmentFields.push('...ExperienceData');
|
|
@@ -1144,6 +1197,18 @@ function renderProperties(contentType, forCms12 = false) {
|
|
|
1144
1197
|
}
|
|
1145
1198
|
return { fragmentFields, propertyTypes };
|
|
1146
1199
|
}
|
|
1200
|
+
function getPropDataType(baseInfo) {
|
|
1201
|
+
const baseType = baseInfo.type;
|
|
1202
|
+
if (!baseType)
|
|
1203
|
+
throw new Error("Invalid property type definition");
|
|
1204
|
+
const propInfo = baseInfo.type === "array" ? baseInfo.items : baseInfo;
|
|
1205
|
+
switch (propInfo.type) {
|
|
1206
|
+
case "string":
|
|
1207
|
+
return propInfo.format === 'html' ? 'richtext' : propInfo.type;
|
|
1208
|
+
default:
|
|
1209
|
+
return propInfo.type;
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1147
1212
|
|
|
1148
1213
|
function ucFirst(current) {
|
|
1149
1214
|
if (typeof current != 'string')
|
|
@@ -1194,7 +1259,7 @@ function createComponent(contentType, typePath, force, debug = false) {
|
|
|
1194
1259
|
// Get type information & short-hands
|
|
1195
1260
|
const displayTemplate = getDisplayTemplateInfo$1(contentType, typePath);
|
|
1196
1261
|
const baseDisplayTemplate = getBaseTypeTemplateInfo(contentType, typePath);
|
|
1197
|
-
const varName = `${contentType.key}${ucFirst(contentType.baseType ?? 'part')}`;
|
|
1262
|
+
const varName = `${contentType.key.split(':').pop()}${ucFirst(contentType.baseType ?? 'part')}`;
|
|
1198
1263
|
const tplFn = Templates[contentType.baseType] ?? Templates['default'];
|
|
1199
1264
|
if (!tplFn) {
|
|
1200
1265
|
if (debug)
|
|
@@ -1233,7 +1298,7 @@ function getDisplayTemplateInfo$1(contentType, typePath) {
|
|
|
1233
1298
|
const Templates = {
|
|
1234
1299
|
// Default Template for all components without specifics
|
|
1235
1300
|
default: (contentType, varName, displayTemplate, baseDisplayTemplate) => `import { CmsEditable, type CmsComponent } from "@remkoj/optimizely-cms-react/rsc";
|
|
1236
|
-
import { ${contentType.key}DataFragmentDoc, type ${contentType.key}DataFragment } from "@/gql/graphql";${displayTemplate ? `
|
|
1301
|
+
import { ${contentType.key.split(':').pop()}DataFragmentDoc, type ${contentType.key.split(':').pop()}DataFragment } from "@/gql/graphql";${displayTemplate ? `
|
|
1237
1302
|
import { ${displayTemplate} } from "./displayTemplates";` : ''}${baseDisplayTemplate ? `
|
|
1238
1303
|
import { ${baseDisplayTemplate} } from "../styles/displayTemplates";` : ''}
|
|
1239
1304
|
|
|
@@ -1241,22 +1306,23 @@ import { ${baseDisplayTemplate} } from "../styles/displayTemplates";` : ''}
|
|
|
1241
1306
|
* ${contentType.displayName}
|
|
1242
1307
|
* ${contentType.description}
|
|
1243
1308
|
*/
|
|
1244
|
-
export const ${varName} : CmsComponent<${contentType.key}DataFragment${(displayTemplate || baseDisplayTemplate) ? ', ' + [displayTemplate, baseDisplayTemplate].filter(x => x).join(" | ") : ''}> = ({ data${(displayTemplate || baseDisplayTemplate) ? ', layoutProps' : ''}, editProps }) => {
|
|
1309
|
+
export const ${varName} : CmsComponent<${contentType.key.split(':').pop()}DataFragment${(displayTemplate || baseDisplayTemplate) ? ', ' + [displayTemplate, baseDisplayTemplate].filter(x => x).join(" | ") : ''}> = ({ data${(displayTemplate || baseDisplayTemplate) ? ', layoutProps' : ''}, editProps${contentType.baseType == 'section' ? ', children' : ''} }) => {
|
|
1245
1310
|
const componentName = '${contentType.displayName}'
|
|
1246
1311
|
const componentInfo = '${contentType.description?.replaceAll("'", "\\'") ?? ''}'
|
|
1247
1312
|
return <CmsEditable className="w-full border-y border-y-solid border-y-slate-900 py-2 mb-4" {...editProps}>
|
|
1248
1313
|
<div className="font-bold italic">{ componentName }</div>
|
|
1249
1314
|
<div>{ componentInfo }</div>
|
|
1250
|
-
{ 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> }
|
|
1315
|
+
{ 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> }${contentType.baseType == 'section' ? `
|
|
1316
|
+
{ children }` : ''}
|
|
1251
1317
|
</CmsEditable>
|
|
1252
1318
|
}
|
|
1253
1319
|
${varName}.displayName = "${contentType.displayName} (${ucFirst(contentType.baseType)}/${contentType.key})"
|
|
1254
|
-
${varName}.getDataFragment = () => ['${contentType.key}Data', ${contentType.key}DataFragmentDoc]
|
|
1320
|
+
${varName}.getDataFragment = () => ['${contentType.key.split(':').pop()}Data', ${contentType.key.split(':').pop()}DataFragmentDoc]
|
|
1255
1321
|
|
|
1256
1322
|
export default ${varName}`,
|
|
1257
1323
|
// Template for all page component types
|
|
1258
1324
|
page: (contentType, varName, displayTemplate) => `import { type OptimizelyNextPage as CmsComponent } from "@remkoj/optimizely-cms-nextjs";
|
|
1259
|
-
import { ${contentType.key}DataFragmentDoc, type ${contentType.key}DataFragment } from "@/gql/graphql";${displayTemplate ? `
|
|
1325
|
+
import { ${contentType.key.split(':').pop()}DataFragmentDoc, type ${contentType.key.split(':').pop()}DataFragment } from "@/gql/graphql";${displayTemplate ? `
|
|
1260
1326
|
import { ${displayTemplate} } from "./displayTemplates";` : ''}
|
|
1261
1327
|
import { getSdk } from "@/gql"
|
|
1262
1328
|
|
|
@@ -1264,7 +1330,7 @@ import { getSdk } from "@/gql"
|
|
|
1264
1330
|
* ${contentType.displayName}
|
|
1265
1331
|
* ${contentType.description}
|
|
1266
1332
|
*/
|
|
1267
|
-
export const ${varName} : CmsComponent<${contentType.key}DataFragment${displayTemplate ? ', ' + displayTemplate : ''}> = ({ data${displayTemplate ? ', layoutProps' : ''} }) => {
|
|
1333
|
+
export const ${varName} : CmsComponent<${contentType.key.split(':').pop()}DataFragment${displayTemplate ? ', ' + displayTemplate : ''}> = ({ data${displayTemplate ? ', layoutProps' : ''} }) => {
|
|
1268
1334
|
const componentName = '${contentType.displayName}'
|
|
1269
1335
|
const componentInfo = '${contentType.description?.replaceAll("'", "\\'") ?? ''}'
|
|
1270
1336
|
return <div className="mx-auto px-2 container">
|
|
@@ -1274,7 +1340,7 @@ export const ${varName} : CmsComponent<${contentType.key}DataFragment${displayTe
|
|
|
1274
1340
|
</div>
|
|
1275
1341
|
}
|
|
1276
1342
|
${varName}.displayName = "${contentType.displayName} (${ucFirst(contentType.baseType)}/${contentType.key})"
|
|
1277
|
-
${varName}.getDataFragment = () => ['${contentType.key}Data', ${contentType.key}DataFragmentDoc]
|
|
1343
|
+
${varName}.getDataFragment = () => ['${contentType.key.split(':').pop()}Data', ${contentType.key.split(':').pop()}DataFragmentDoc]
|
|
1278
1344
|
${varName}.getMetaData = async (contentLink, locale, client) => {
|
|
1279
1345
|
const sdk = getSdk(client);
|
|
1280
1346
|
// Add your metadata logic here
|
|
@@ -1285,7 +1351,7 @@ export default ${varName}`,
|
|
|
1285
1351
|
// Template for all experience component types
|
|
1286
1352
|
experience: (contentType, varName, displayTemplate) => `import { type OptimizelyNextPage as CmsComponent } from "@remkoj/optimizely-cms-nextjs";
|
|
1287
1353
|
import { getFragmentData } from "@/gql/fragment-masking";
|
|
1288
|
-
import { ExperienceDataFragmentDoc,
|
|
1354
|
+
import { ExperienceDataFragmentDoc, ${contentType.key.split(':').pop()}DataFragmentDoc, type ${contentType.key.split(':').pop()}DataFragment } from "@/gql/graphql";
|
|
1289
1355
|
import { OptimizelyComposition, isNode, CmsEditable } from "@remkoj/optimizely-cms-react/rsc";${displayTemplate ? `
|
|
1290
1356
|
import { ${displayTemplate} } from "./displayTemplates";` : ''}
|
|
1291
1357
|
import { getSdk } from "@/gql"
|
|
@@ -1294,14 +1360,14 @@ import { getSdk } from "@/gql"
|
|
|
1294
1360
|
* ${contentType.displayName}
|
|
1295
1361
|
* ${contentType.description}
|
|
1296
1362
|
*/
|
|
1297
|
-
export const ${varName} : CmsComponent<${contentType.key}DataFragment${displayTemplate ? ', ' + displayTemplate : ''}> = ({ data${displayTemplate ? ', layoutProps' : ''}, ctx }) => {
|
|
1298
|
-
const composition = getFragmentData(
|
|
1363
|
+
export const ${varName} : CmsComponent<${contentType.key.split(':').pop()}DataFragment${displayTemplate ? ', ' + displayTemplate : ''}> = ({ data${displayTemplate ? ', layoutProps' : ''}, ctx }) => {
|
|
1364
|
+
const composition = getFragmentData(ExperienceDataFragmentDoc, data)?.composition
|
|
1299
1365
|
return <CmsEditable as="div" className="mx-auto px-2 container" cmsFieldName="unstructuredData" ctx={ctx}>
|
|
1300
|
-
{ composition && isNode(composition) && <OptimizelyComposition node={composition} /> }
|
|
1366
|
+
{ composition && isNode(composition) && <OptimizelyComposition node={composition} ctx={ctx} /> }
|
|
1301
1367
|
</CmsEditable>
|
|
1302
1368
|
}
|
|
1303
1369
|
${varName}.displayName = "${contentType.displayName} (${ucFirst(contentType.baseType)}/${contentType.key})"
|
|
1304
|
-
${varName}.getDataFragment = () => ['${contentType.key}Data', ${contentType.key}DataFragmentDoc]
|
|
1370
|
+
${varName}.getDataFragment = () => ['${contentType.key.split(':').pop()}Data', ${contentType.key}DataFragmentDoc]
|
|
1305
1371
|
${varName}.getMetaData = async (contentLink, locale, client) => {
|
|
1306
1372
|
const sdk = getSdk(client);
|
|
1307
1373
|
// Add your metadata logic here
|
|
@@ -1414,6 +1480,7 @@ function getDisplayTemplateInfo(template, typePath) {
|
|
|
1414
1480
|
|
|
1415
1481
|
const ROOT_FACTORY_KEY = ".";
|
|
1416
1482
|
const FACTORY_FILE_NAME = "index.ts";
|
|
1483
|
+
const reservedNames = ["loading.tsx", "loading.jsx", "suspense.tsx", "suspense.jsx"];
|
|
1417
1484
|
const NextJsFactoryCommand = {
|
|
1418
1485
|
command: "nextjs:factory",
|
|
1419
1486
|
describe: "Create the ComponentFactory for a Next.JS / Optimizely Graph structure",
|
|
@@ -1422,17 +1489,22 @@ const NextJsFactoryCommand = {
|
|
|
1422
1489
|
const { components: basePath, force, _config: { debug } } = parseArgs(args);
|
|
1423
1490
|
if (debug)
|
|
1424
1491
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Start generating component factories\n`));
|
|
1492
|
+
// Get & filter all component files
|
|
1425
1493
|
const components = globSync(["./**/*.jsx", "./**/*.tsx"], {
|
|
1426
1494
|
cwd: basePath
|
|
1427
1495
|
}).map(p => p.split(path.sep)).filter(p => {
|
|
1496
|
+
// Get the filename
|
|
1497
|
+
const fileName = p.at(p.length - 1);
|
|
1498
|
+
if (!fileName)
|
|
1499
|
+
return false;
|
|
1428
1500
|
// Consider components in a file starting with "_" as a partial
|
|
1429
|
-
if (
|
|
1501
|
+
if (fileName.startsWith('_') == true)
|
|
1430
1502
|
return false;
|
|
1431
1503
|
// Consider components in a folder named "partials" as a partial
|
|
1432
|
-
if (p.
|
|
1504
|
+
if (p.some(folder => folder === 'partials'))
|
|
1433
1505
|
return false;
|
|
1434
1506
|
// Skip the special loader component
|
|
1435
|
-
if (
|
|
1507
|
+
if (reservedNames.includes(fileName))
|
|
1436
1508
|
return false;
|
|
1437
1509
|
// Check if the file has a default export
|
|
1438
1510
|
const fileBuffer = fs.readFileSync(path.join(basePath, p.join(path.sep)));
|
|
@@ -1443,87 +1515,70 @@ const NextJsFactoryCommand = {
|
|
|
1443
1515
|
}
|
|
1444
1516
|
return true;
|
|
1445
1517
|
});
|
|
1518
|
+
// Report what we have found
|
|
1446
1519
|
if (debug)
|
|
1447
1520
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Identified ${components.length} components in ${basePath}\n`));
|
|
1521
|
+
// Build factory / component structure
|
|
1448
1522
|
const componentFactoryDefintions = new Map();
|
|
1449
1523
|
components.forEach(component => {
|
|
1524
|
+
// Determine component target
|
|
1525
|
+
const componentKey = processName(component.length == 1 ? ucFirst(path.basename(component[0], path.extname(component[0]))) : component.at(component.length - 2));
|
|
1526
|
+
let componentVariant = (component.length > 1 ? component.at(component.length - 1) ?? 'default' : 'default').replace('index', 'default');
|
|
1527
|
+
componentVariant = path.basename(componentVariant, path.extname(componentVariant));
|
|
1528
|
+
const componentDir = path.dirname(path.join(...component));
|
|
1529
|
+
// Get factory information
|
|
1450
1530
|
const factorySegments = component.length > 2 ? component.slice(0, -2) : [ROOT_FACTORY_KEY];
|
|
1451
|
-
const factoryKey =
|
|
1531
|
+
const factoryKey = path.posix.join(...factorySegments);
|
|
1452
1532
|
const factoryFile = path.join(factoryKey, FACTORY_FILE_NAME);
|
|
1453
|
-
//
|
|
1533
|
+
// Check dynamic & suspense
|
|
1534
|
+
const useDynamic = [
|
|
1535
|
+
path.join(basePath, componentDir, 'loading.tsx'),
|
|
1536
|
+
path.join(basePath, componentDir, 'loading.jsx')
|
|
1537
|
+
].some(fs.existsSync);
|
|
1538
|
+
const useSuspense = [
|
|
1539
|
+
path.join(basePath, componentDir, 'suspense.tsx'),
|
|
1540
|
+
path.join(basePath, componentDir, 'suspense.jsx')
|
|
1541
|
+
].some(fs.existsSync);
|
|
1542
|
+
// Prepare data
|
|
1543
|
+
const componentImport = component.length == 1 ?
|
|
1544
|
+
`.${path.posix.sep}${path.basename(component[0], path.extname(component[0]))}` :
|
|
1545
|
+
`.${path.posix.sep}${path.posix.relative(factoryKey, componentDir)}${componentVariant !== 'default' ? path.posix.sep + componentVariant : ''}`;
|
|
1546
|
+
const loaderImport = useDynamic ? componentImport + path.posix.sep + 'loading' : undefined;
|
|
1547
|
+
const suspenseImport = useSuspense ? componentImport + path.posix.sep + 'suspense' : undefined;
|
|
1548
|
+
const componentVariablesBase = componentKey + (componentVariant !== 'default' ? processName(componentVariant) : '');
|
|
1549
|
+
// Add to the factory
|
|
1454
1550
|
const factory = componentFactoryDefintions.get(factoryKey) || { file: factoryFile, entries: [], subfactories: [] };
|
|
1455
|
-
const useSuspense = fs.existsSync(path.join(basePath, component.slice(0, -1).join(path.sep), 'loading.tsx')) || fs.existsSync(path.join(basePath, component.slice(0, -1).join(path.sep), 'loading.jsx'));
|
|
1456
|
-
if (useSuspense && debug)
|
|
1457
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Components in ${component.slice(0, -1).join(path.sep)} will use suspense\n`));
|
|
1458
|
-
const componentSegments = component.slice(-2).map(p => {
|
|
1459
|
-
const entry = p.substring(0, p.length - path.extname(p).length);
|
|
1460
|
-
return entry.toLowerCase() == "index" ? null : entry;
|
|
1461
|
-
}).filter(x => x);
|
|
1462
|
-
const componentImport = "./" + componentSegments.join('/');
|
|
1463
|
-
const loaderImport = useSuspense ? "./" + component.slice(-2).map((p, i, a) => {
|
|
1464
|
-
const entry = p.substring(0, p.length - path.extname(p).length);
|
|
1465
|
-
if (i == a.length - 1)
|
|
1466
|
-
return 'loading';
|
|
1467
|
-
return entry.toLowerCase() == "index" ? null : entry;
|
|
1468
|
-
}).join('/') : undefined;
|
|
1469
1551
|
factory.entries.push({
|
|
1552
|
+
key: componentKey,
|
|
1553
|
+
variant: componentVariant,
|
|
1470
1554
|
import: componentImport,
|
|
1471
|
-
variable:
|
|
1472
|
-
key: componentSegments.join('/') == "node" ? "Node" : componentSegments.join('/'),
|
|
1555
|
+
variable: componentVariablesBase + 'Component',
|
|
1473
1556
|
loaderImport,
|
|
1474
|
-
loaderVariable:
|
|
1557
|
+
loaderVariable: useDynamic ? componentVariablesBase + 'Loader' : undefined,
|
|
1558
|
+
suspenseImport,
|
|
1559
|
+
suspenseVariable: useSuspense ? componentVariablesBase + 'Placeholder' : undefined
|
|
1475
1560
|
});
|
|
1476
1561
|
componentFactoryDefintions.set(factoryKey, factory);
|
|
1477
|
-
//
|
|
1562
|
+
// Add/update parent factories
|
|
1478
1563
|
const parentSegements = factoryKey == ROOT_FACTORY_KEY ? factorySegments.slice(0, -1) : [ROOT_FACTORY_KEY, ...factorySegments.slice(0, -1)];
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
prefix: processName(factorySegments.slice(-1).at(0)),
|
|
1483
|
-
variable: factorySegments.map(processName).join("") + "Factory"
|
|
1484
|
-
};
|
|
1485
|
-
for (let i = parentSegements.length; i > 0; i--) {
|
|
1486
|
-
const parentFactoryKey = parentSegements.slice(0, i).filter(x => x != ROOT_FACTORY_KEY).join(path.sep) || ROOT_FACTORY_KEY;
|
|
1564
|
+
parentSegements.forEach((_, idx, data) => {
|
|
1565
|
+
const parentFactoryKey = path.posix.join(...data.slice(0, idx + 1)) || ROOT_FACTORY_KEY;
|
|
1566
|
+
const childFactoryKey = factorySegments.slice(idx, idx + 1).at(0);
|
|
1487
1567
|
const parentFactory = componentFactoryDefintions.get(parentFactoryKey) ?? { file: path.join(parentFactoryKey, FACTORY_FILE_NAME), entries: [], subfactories: [] };
|
|
1488
|
-
if (!parentFactory.subfactories.some(x => x.key
|
|
1489
|
-
parentFactory.subfactories.push(
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
prefix: processName(parentFactoryKey.split(path.sep).slice(-1).at(0)),
|
|
1496
|
-
variable: parentFactoryKey.split(path.sep).map(processName).join("") + "Factory"
|
|
1497
|
-
};
|
|
1568
|
+
if (!parentFactory.subfactories.some(x => x.key === childFactoryKey)) {
|
|
1569
|
+
parentFactory.subfactories.push({
|
|
1570
|
+
key: childFactoryKey,
|
|
1571
|
+
import: './' + childFactoryKey,
|
|
1572
|
+
variable: processName(childFactoryKey) + 'Factory'
|
|
1573
|
+
});
|
|
1574
|
+
componentFactoryDefintions.set(parentFactoryKey, parentFactory);
|
|
1498
1575
|
}
|
|
1499
|
-
}
|
|
1500
|
-
});
|
|
1501
|
-
const mainFactory = componentFactoryDefintions.get(ROOT_FACTORY_KEY);
|
|
1502
|
-
if (mainFactory) {
|
|
1503
|
-
if (debug)
|
|
1504
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Updating prefixes within RootFactory\n`));
|
|
1505
|
-
mainFactory.subfactories = mainFactory.subfactories.map(subFactory => {
|
|
1506
|
-
if (typeof (subFactory.prefix == 'string'))
|
|
1507
|
-
switch (subFactory.prefix) {
|
|
1508
|
-
case "Video":
|
|
1509
|
-
case "Image":
|
|
1510
|
-
subFactory.prefix = ["Media", subFactory.prefix, "Component"];
|
|
1511
|
-
break;
|
|
1512
|
-
case "Experience":
|
|
1513
|
-
subFactory.prefix = [subFactory.prefix, "Page"];
|
|
1514
|
-
break;
|
|
1515
|
-
case "Element":
|
|
1516
|
-
subFactory.prefix = ["Component"];
|
|
1517
|
-
break;
|
|
1518
|
-
case "Media":
|
|
1519
|
-
subFactory.prefix = [subFactory.prefix, "Component"];
|
|
1520
|
-
break;
|
|
1521
|
-
}
|
|
1522
|
-
return subFactory;
|
|
1523
1576
|
});
|
|
1524
|
-
}
|
|
1577
|
+
});
|
|
1578
|
+
// Report factory file count
|
|
1525
1579
|
if (debug)
|
|
1526
1580
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Finished preparing ${componentFactoryDefintions.size} factories, start writing\n`));
|
|
1581
|
+
// Iterate over the factories and create them
|
|
1527
1582
|
let updateCounter = 0;
|
|
1528
1583
|
for (const key of componentFactoryDefintions.keys()) {
|
|
1529
1584
|
const factory = componentFactoryDefintions.get(key);
|
|
@@ -1572,59 +1627,55 @@ function processName(input) {
|
|
|
1572
1627
|
return nameSegements.map(ucFirst).join('');
|
|
1573
1628
|
}
|
|
1574
1629
|
function generateFactory(factoryInfo, factoryKey) {
|
|
1575
|
-
|
|
1630
|
+
// Get the factory name
|
|
1576
1631
|
const factoryName = factoryKey.split(path.sep).map(processName).join("") + "Factory";
|
|
1577
|
-
|
|
1578
|
-
const
|
|
1579
|
-
const
|
|
1632
|
+
// Get the components and sub-factories, sorted by key to minimize changes between runs
|
|
1633
|
+
const components = [...factoryInfo.entries].sort((a, b) => { return a.key < b.key ? -1 : a.key > b.key ? 1 : 0; });
|
|
1634
|
+
const subFactories = [...factoryInfo.subfactories].sort((a, b) => { return a.key < b.key ? -1 : a.key > b.key ? 1 : 0; });
|
|
1635
|
+
// Check if there's at least one component that uses next/dynamic
|
|
1636
|
+
const hasDynamic = factoryInfo.entries.some(x => x.loaderImport);
|
|
1637
|
+
// The intro for the factory
|
|
1638
|
+
const factoryIntro = `// Auto generated dictionary
|
|
1580
1639
|
// @not-modified => When this line is removed, the "force" parameter of the CLI tool is required to overwrite this file
|
|
1581
|
-
import { type ComponentTypeDictionary } from
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
${
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
}
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
}
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
//
|
|
1616
|
-
|
|
1617
|
-
${
|
|
1618
|
-
//
|
|
1619
|
-
|
|
1620
|
-
{
|
|
1621
|
-
list.forEach((component, idx, dictionary) => {
|
|
1622
|
-
dictionary[idx].type = typeof component.type == 'string' ? prefix + "/" + component.type : [ prefix, ...component.type ]
|
|
1623
|
-
});
|
|
1624
|
-
return list;
|
|
1625
|
-
}
|
|
1626
|
-
` : ''}`;
|
|
1627
|
-
return factoryContent;
|
|
1640
|
+
import { type ComponentTypeDictionary } from '@remkoj/optimizely-cms-react';${hasDynamic ? `
|
|
1641
|
+
import dynamic from 'next/dynamic';` : ''}`;
|
|
1642
|
+
// The outry for the factory
|
|
1643
|
+
const factoryOutro = `// Export dictionary
|
|
1644
|
+
export default ${factoryName};`;
|
|
1645
|
+
// The imports of the factory
|
|
1646
|
+
const factoryImports = [...components, ...subFactories].map(x => {
|
|
1647
|
+
let imports = [x.loaderImport ?
|
|
1648
|
+
`import ${x.loaderVariable} from '${x.loaderImport}';` :
|
|
1649
|
+
`import ${x.variable} from '${x.import}';`];
|
|
1650
|
+
if (x.suspenseImport)
|
|
1651
|
+
imports.push(`import ${x.suspenseVariable} from '${x.suspenseImport}';`);
|
|
1652
|
+
return imports.join('\n');
|
|
1653
|
+
}).join('\n');
|
|
1654
|
+
// The dynamic imports of the factory
|
|
1655
|
+
const dynamicImports = hasDynamic ? `// Lazy load components that have a loading file, this only affects client components
|
|
1656
|
+
// See https://nextjs.org/docs/app/guides/lazy-loading#importing-server-components
|
|
1657
|
+
// for more details on how this affects server components in Next.js
|
|
1658
|
+
` + components.filter(x => x.loaderImport).map(x => {
|
|
1659
|
+
return `const ${x.variable} = dynamic(() => import('${x.import}'), {
|
|
1660
|
+
ssr: true,
|
|
1661
|
+
loading: ${x.loaderVariable}
|
|
1662
|
+
});`;
|
|
1663
|
+
}).join('\n') : undefined;
|
|
1664
|
+
// The actual entries for the factory
|
|
1665
|
+
const factoryEntries = [...components.map(x => {
|
|
1666
|
+
return ` {
|
|
1667
|
+
type: '${x.key}',${x.variant && x.variant !== 'default' ? `
|
|
1668
|
+
variant: '${x.variant}',` : ''}
|
|
1669
|
+
component: ${x.variable}${x.suspenseVariable ? `,
|
|
1670
|
+
useSuspense: true,
|
|
1671
|
+
loader: ${x.suspenseVariable}` : ''}
|
|
1672
|
+
}`;
|
|
1673
|
+
}), ...subFactories.map(x => ` ...${x.variable}`)];
|
|
1674
|
+
// The body of the factory
|
|
1675
|
+
const factoryBody = `// Build dictionary
|
|
1676
|
+
export const ${factoryName} : ComponentTypeDictionary = [${factoryEntries.length > 0 ? '\n' + factoryEntries.join(',\n') + '\n' : ''}];`;
|
|
1677
|
+
// Combine everything into one string
|
|
1678
|
+
return [factoryIntro, factoryImports, dynamicImports, factoryBody, factoryOutro].filter(x => (x?.length || 0) > 0).join('\n\n') + '\n';
|
|
1628
1679
|
}
|
|
1629
1680
|
|
|
1630
1681
|
const NextJsCreateCommand = {
|
|
@@ -1668,15 +1719,16 @@ const NextJsQueriesCommand = {
|
|
|
1668
1719
|
handler: async (args, opts) => {
|
|
1669
1720
|
// Prepare
|
|
1670
1721
|
const { loadedContentTypes, createdTypeFolders } = opts || {};
|
|
1671
|
-
const { components: basePath, _config: { debug }, force } = parseArgs(args);
|
|
1722
|
+
const { components: basePath, _config: { debug }, force, path: appPath } = parseArgs(args);
|
|
1672
1723
|
const client = createCmsClient(args);
|
|
1673
1724
|
const { contentTypes, all: allContentTypes } = loadedContentTypes ?? await getContentTypes(client, args);
|
|
1725
|
+
const generatedProps = getGeneratedProps(appPath);
|
|
1674
1726
|
// Start process
|
|
1675
1727
|
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Generating GraphQL fragments for ${contentTypes.map(x => x.key).join(', ')}\n`));
|
|
1676
1728
|
const typeFolders = createdTypeFolders ?? createTypeFolders(contentTypes, basePath, debug);
|
|
1677
1729
|
const updatedTypes = contentTypes.map(contentType => {
|
|
1678
1730
|
const typePath = getTypeFolder(typeFolders, contentType.key);
|
|
1679
|
-
return createGraphQueries(contentType, typePath, basePath, force, debug, allContentTypes, client.runtimeCmsVersion == OptiCmsVersion.CMS12);
|
|
1731
|
+
return createGraphQueries(contentType, typePath, basePath, force, debug, allContentTypes, generatedProps, client.runtimeCmsVersion == OptiCmsVersion.CMS12);
|
|
1680
1732
|
}).filter(x => x).flat();
|
|
1681
1733
|
// Report outcome
|
|
1682
1734
|
if (updatedTypes.length > 0)
|
|
@@ -1685,9 +1737,10 @@ const NextJsQueriesCommand = {
|
|
|
1685
1737
|
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} No GraphQL fragments created/updated\n`));
|
|
1686
1738
|
if (!opts)
|
|
1687
1739
|
process.stdout.write(chalk.green(chalk.bold(figures.tick + " Done")) + "\n");
|
|
1740
|
+
writeGeneratedProps(appPath, generatedProps);
|
|
1688
1741
|
}
|
|
1689
1742
|
};
|
|
1690
|
-
function createGraphQueries(contentType, typePath, basePath, force, debug, contentTypes, forCms12 = false) {
|
|
1743
|
+
function createGraphQueries(contentType, typePath, basePath, force, debug, contentTypes, generatedProps = [], forCms12 = false) {
|
|
1691
1744
|
const returnValue = [];
|
|
1692
1745
|
const baseQueryFile = path.join(typePath, `${contentType.key}.query.graphql`);
|
|
1693
1746
|
if (fs.existsSync(baseQueryFile)) {
|
|
@@ -1704,7 +1757,7 @@ function createGraphQueries(contentType, typePath, basePath, force, debug, conte
|
|
|
1704
1757
|
else if (debug) {
|
|
1705
1758
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating ${contentType.displayName} (${contentType.key}) base fragment\n`));
|
|
1706
1759
|
}
|
|
1707
|
-
const { fragment, propertyTypes } = createInitialQuery(contentType, false, undefined, forCms12);
|
|
1760
|
+
const { fragment, propertyTypes } = createInitialQuery(contentType, false, undefined, generatedProps, forCms12);
|
|
1708
1761
|
fs.writeFileSync(baseQueryFile, fragment);
|
|
1709
1762
|
returnValue.push(contentType.key);
|
|
1710
1763
|
let dependencies = Array.isArray(propertyTypes) ? [...propertyTypes] : [];
|
|
@@ -1724,7 +1777,7 @@ function createGraphQueries(contentType, typePath, basePath, force, debug, conte
|
|
|
1724
1777
|
if (!fs.existsSync(propertyFragmentFile) || force) {
|
|
1725
1778
|
if (debug)
|
|
1726
1779
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Writing ${propContentType.displayName} (${propContentType.key}) property fragment\n`));
|
|
1727
|
-
const propContentTypeInfo = createInitialFragment(propContentType, true, contentType, forCms12);
|
|
1780
|
+
const propContentTypeInfo = createInitialFragment(propContentType, true, contentType, generatedProps, forCms12);
|
|
1728
1781
|
fs.writeFileSync(propertyFragmentFile, propContentTypeInfo.fragment);
|
|
1729
1782
|
returnValue.push(propContentType.key);
|
|
1730
1783
|
if (Array.isArray(propContentTypeInfo.propertyTypes))
|
|
@@ -1735,8 +1788,8 @@ function createGraphQueries(contentType, typePath, basePath, force, debug, conte
|
|
|
1735
1788
|
}
|
|
1736
1789
|
return returnValue.length > 0 ? returnValue : undefined;
|
|
1737
1790
|
}
|
|
1738
|
-
function createInitialQuery(contentType, forProperty = false, forBaseType, forCms12 = false) {
|
|
1739
|
-
const { fragmentFields, propertyTypes } = renderProperties(contentType, forCms12);
|
|
1791
|
+
function createInitialQuery(contentType, forProperty = false, forBaseType, generatedProps = [], forCms12 = false) {
|
|
1792
|
+
const { fragmentFields, propertyTypes } = renderProperties(contentType, generatedProps, forCms12);
|
|
1740
1793
|
const fragmentTarget = forProperty ? (forCms12 ? ('') + contentType.key : contentType.key + 'Property') : contentType.key;
|
|
1741
1794
|
const tpl = `query get${fragmentTarget}Data($key: String!, $locale: [Locales], $version: String, $changeset: String, $variation: String) {
|
|
1742
1795
|
data: ${fragmentTarget} (
|
|
@@ -2116,7 +2169,7 @@ const commands = [
|
|
|
2116
2169
|
CmsVersionCommand
|
|
2117
2170
|
];
|
|
2118
2171
|
|
|
2119
|
-
var version = "5.
|
|
2172
|
+
var version = "5.2.0";
|
|
2120
2173
|
var name = "opti-cms";
|
|
2121
2174
|
var APP = {
|
|
2122
2175
|
version: version,
|
|
@@ -2124,8 +2177,9 @@ var APP = {
|
|
|
2124
2177
|
};
|
|
2125
2178
|
|
|
2126
2179
|
async function main() {
|
|
2127
|
-
const
|
|
2128
|
-
const
|
|
2180
|
+
const projectDir = getProjectDir();
|
|
2181
|
+
const envFiles = prepare(projectDir);
|
|
2182
|
+
const app = createOptiCmsApp(APP.name, APP.version, undefined, envFiles, projectDir);
|
|
2129
2183
|
app.command(commands);
|
|
2130
2184
|
try {
|
|
2131
2185
|
await app.parse(process.argv.slice(2));
|