@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/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 envFiles = globSync(".env*").sort((a, b) => b.length - a.length).filter(n => n == ".env" || n == ".env.local" || (process.env.NODE_ENV && n == `.env.${process.env.NODE_ENV}.local`));
22
- expand(dotenv.config({ path: envFiles, debug: false, quiet: true }));
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: process.cwd() })
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 fragmentTarget = forProperty ? (forCms12 ? (forBaseType?.key ?? '') + contentType.key : contentType.key + 'Property') : contentType.key;
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 isConflict = generatedProps.some(x => x.propName == propKey && x.propType != propType);
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 (forCms12 && componentType == "link") {
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(`${propName} { ...BinaryData }`);
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.push({
1133
- propType: typeProps[propKey].type,
1134
- propName: propKey
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, CompositionDataFragmentDoc, ${contentType.key}DataFragmentDoc, type ${contentType.key}DataFragment } from "@/gql/graphql";
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(CompositionDataFragmentDoc, getFragmentData(ExperienceDataFragmentDoc, data)?.composition)
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 (p.at(p.length - 1)?.startsWith('_') == true)
1501
+ if (fileName.startsWith('_') == true)
1430
1502
  return false;
1431
1503
  // Consider components in a folder named "partials" as a partial
1432
- if (p.at(p.length - 2)?.toLowerCase() == 'partials')
1504
+ if (p.some(folder => folder === 'partials'))
1433
1505
  return false;
1434
1506
  // Skip the special loader component
1435
- if (p.at(p.length - 1) == "loading.tsx" || p.at(p.length - 1) == "loading.jsx")
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 = factorySegments.join(path.sep);
1531
+ const factoryKey = path.posix.join(...factorySegments);
1452
1532
  const factoryFile = path.join(factoryKey, FACTORY_FILE_NAME);
1453
- // Add component to factory
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: [...componentSegments.map(processName), 'Component'].join(''),
1472
- key: componentSegments.join('/') == "node" ? "Node" : componentSegments.join('/'),
1555
+ variable: componentVariablesBase + 'Component',
1473
1556
  loaderImport,
1474
- loaderVariable: useSuspense ? [...componentSegments.map(processName), 'Loader'].join('') : undefined,
1557
+ loaderVariable: useDynamic ? componentVariablesBase + 'Loader' : undefined,
1558
+ suspenseImport,
1559
+ suspenseVariable: useSuspense ? componentVariablesBase + 'Placeholder' : undefined
1475
1560
  });
1476
1561
  componentFactoryDefintions.set(factoryKey, factory);
1477
- // Register with all appropriate parents
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
- let currentFactory = {
1480
- key: factoryKey,
1481
- import: './' + factorySegments.slice(-1).join('/'),
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 == currentFactory.key))
1489
- parentFactory.subfactories.push(currentFactory);
1490
- componentFactoryDefintions.set(parentFactoryKey, parentFactory);
1491
- if (i > 1) {
1492
- currentFactory = {
1493
- key: parentFactoryKey,
1494
- import: './' + parentFactoryKey.split(path.sep).slice(-1).at(0),
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
- const needsPrefixFunction = factoryInfo.subfactories.length > 0;
1630
+ // Get the factory name
1576
1631
  const factoryName = factoryKey.split(path.sep).map(processName).join("") + "Factory";
1577
- const components = factoryInfo.entries;
1578
- const subFactories = factoryInfo.subfactories;
1579
- const factoryContent = `// Auto generated dictionary
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 "@remkoj/optimizely-cms-react";
1582
- ${[...components, ...subFactories].map(x => {
1583
- let importLine = `import ${x.variable} from "${x.import}";`;
1584
- if (x.loaderImport && x.loaderVariable) {
1585
- importLine += `\nimport ${x.loaderVariable} from "${x.loaderImport}";`;
1586
- }
1587
- return importLine;
1588
- }).join("\n")}
1589
-
1590
- ${needsPrefixFunction ? `// Prefix entries - if needed
1591
- ${subFactories.map(subFactory => Array.isArray(subFactory.prefix) ?
1592
- subFactory.prefix.map(z => `prefixDictionaryEntries(${subFactory.variable}, "${z}");`).join("\n") :
1593
- `prefixDictionaryEntries(${subFactory.variable}, "${subFactory.prefix}");`).join("\n")}
1594
-
1595
- ` : ''}// Build dictionary
1596
- export const ${factoryName} : ComponentTypeDictionary = [
1597
- ${[...components.map(x => {
1598
- if (x.loaderVariable) {
1599
- return `{
1600
- type: "${x.key}",
1601
- component: ${x.variable},
1602
- useSuspense: true,
1603
- loader: ${x.loaderVariable}
1604
- }`;
1605
- }
1606
- else {
1607
- return `{
1608
- type: "${x.key}",
1609
- component: ${x.variable}
1610
- }`;
1611
- }
1612
- }), ...subFactories.map(x => `...${x.variable}`)].join(",\n ")}
1613
- ];
1614
-
1615
- // Export dictionary
1616
- export default ${factoryName};
1617
- ${needsPrefixFunction ? `
1618
- // Helper functions
1619
- function prefixDictionaryEntries(list: ComponentTypeDictionary, prefix: string) : ComponentTypeDictionary
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.1.6";
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 envFiles = prepare();
2128
- const app = createOptiCmsApp(APP.name, APP.version, undefined, envFiles);
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));