@remkoj/optimizely-cms-cli 6.0.0-pre12 → 6.0.0-pre14
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 +262 -146
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
package/README.md
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
# Optimizely CMS Command Line Toolkit <!-- omit in toc -->
|
|
2
|
+
|
|
3
|
+
> [!WARNING]
|
|
4
|
+
> There'll be an update of Optimizely SaaS CMS that is incompatible with all SDK versions prior to 5.1.6. If you don't upgrade, you will see empty pages (main website) and "Component not found" messages (preview).
|
|
5
|
+
|
|
2
6
|
A collection of Command Line tools used to increase productivity when working with the Optimizely CMS from a TypeScript / JavaScript based frontend.
|
|
3
7
|
|
|
4
8
|
The defaults and methods are based upon using a Next.JS application with the conventions introduced by the [Create Next App template](https://github.com/remkoj/optimizely-saas-starter)
|
|
@@ -123,4 +127,4 @@ yarn opti-cms nextjs:factory -f
|
|
|
123
127
|
| --baseTypes | -b | Select only content types with one of these base types. Add multiple times to build a list | [] |
|
|
124
128
|
| --types | -t | Select content types with this key. Add multiple times to build a list | [] |
|
|
125
129
|
| --all | -a | Include non-supported base types, non supported base types are skipped by default | |
|
|
126
|
-
| --force | -f | By default, this method is none-destructive. Set this parameter to overwrite existing files. | |
|
|
130
|
+
| --force | -f | By default, this method is none-destructive. Set this parameter to overwrite existing files. | |
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { globSync, glob, globIterate } from 'glob';
|
|
2
2
|
import { config } from 'dotenv';
|
|
3
3
|
import { expand } from 'dotenv-expand';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import fs from 'node:fs';
|
|
4
6
|
import { readPartialEnvConfig, createClient, OptiCmsVersion, ContentRoots } from '@remkoj/optimizely-cms-api';
|
|
5
7
|
import yargs from 'yargs';
|
|
6
8
|
import chalk from 'chalk';
|
|
7
|
-
import path from 'node:path';
|
|
8
|
-
import fs from 'node:fs';
|
|
9
9
|
import figures from 'figures';
|
|
10
10
|
import Table from 'cli-table3';
|
|
11
11
|
import fsAsync from 'node:fs/promises';
|
|
@@ -13,23 +13,38 @@ import { input, select, confirm } from '@inquirer/prompts';
|
|
|
13
13
|
import createDeepMerge from '@fastify/deepmerge';
|
|
14
14
|
import { Ajv } from 'ajv';
|
|
15
15
|
import addFormats from 'ajv-formats';
|
|
16
|
-
import
|
|
16
|
+
import { DocumentGenerator, PropertyCollisionTracker } from '@remkoj/optimizely-graph-functions/generate';
|
|
17
17
|
import deepEqual from 'fast-deep-equal';
|
|
18
18
|
|
|
19
|
+
function getProjectDir() {
|
|
20
|
+
let testPath = process.cwd();
|
|
21
|
+
let hasPackageJson = false;
|
|
22
|
+
do {
|
|
23
|
+
hasPackageJson = fs.statSync(path.join(testPath, 'package.json'), { throwIfNoEntry: false })?.isFile() ?? false;
|
|
24
|
+
if (!hasPackageJson)
|
|
25
|
+
testPath = path.normalize(path.join(testPath, '..'));
|
|
26
|
+
} while (!hasPackageJson && testPath.length > 2);
|
|
27
|
+
if (!hasPackageJson) {
|
|
28
|
+
throw new Error('No package.json found!');
|
|
29
|
+
}
|
|
30
|
+
return testPath;
|
|
31
|
+
}
|
|
19
32
|
/**
|
|
20
33
|
* Prepare the application context, by parsing the .env files in the main
|
|
21
34
|
* application directory.
|
|
22
35
|
*
|
|
23
36
|
* @returns A string array with the files processed
|
|
24
37
|
*/
|
|
25
|
-
function prepare() {
|
|
26
|
-
const
|
|
27
|
-
const envFiles =
|
|
38
|
+
function prepare(pDir) {
|
|
39
|
+
const projectDir = pDir ?? getProjectDir();
|
|
40
|
+
const envFiles = globSync(".env*", { cwd: projectDir })
|
|
41
|
+
.sort((a, b) => b.length - a.length)
|
|
42
|
+
.filter(n => n == ".env" || n == ".env.local" || (process.env.NODE_ENV && n == `.env.${process.env.NODE_ENV}.local`));
|
|
28
43
|
expand(config({ path: envFiles, debug: false, quiet: true }));
|
|
29
44
|
return envFiles;
|
|
30
45
|
}
|
|
31
46
|
|
|
32
|
-
function createOptiCmsApp(scriptName, version, epilogue, envFiles) {
|
|
47
|
+
function createOptiCmsApp(scriptName, version, epilogue, envFiles, projectDir) {
|
|
33
48
|
if (envFiles) {
|
|
34
49
|
process.stdout.write(chalk.bold(`✅ Loaded environment files:`) + "\n");
|
|
35
50
|
envFiles.forEach(envFile => {
|
|
@@ -45,11 +60,12 @@ function createOptiCmsApp(scriptName, version, epilogue, envFiles) {
|
|
|
45
60
|
console.error(chalk.redBright(`${chalk.bold("[ERROR]:")} Error processing environment variables: ${e.message}`));
|
|
46
61
|
process.exit(1);
|
|
47
62
|
}
|
|
63
|
+
const defaultPath = projectDir ?? process.cwd();
|
|
48
64
|
return yargs(process.argv)
|
|
49
65
|
.scriptName(scriptName)
|
|
50
66
|
.version(version)
|
|
51
67
|
.usage('$0 <cmd> [args]')
|
|
52
|
-
.option("path", { alias: "p", description: "Application root folder", string: true, type: "string", demandOption: false, default:
|
|
68
|
+
.option("path", { alias: "p", description: "Application root folder", string: true, type: "string", demandOption: false, default: defaultPath })
|
|
53
69
|
.option("components", { alias: "c", description: "Path to components folder", string: true, type: "string", demandOption: false, default: "./src/components/cms" })
|
|
54
70
|
.option("cms_url", { alias: "cu", description: "Optimizely CMS URL", string: true, type: "string", demandOption: isDemanded(config.base), default: config.base, coerce: (val) => val ? new URL(val) : undefined })
|
|
55
71
|
.option("client_id", { alias: "ci", description: "API Client ID", string: true, type: "string", demandOption: isDemanded(config.clientId), default: config.clientId })
|
|
@@ -588,6 +604,20 @@ function ucFirst$1(input) {
|
|
|
588
604
|
return input;
|
|
589
605
|
return input[0].toUpperCase() + input.substring(1);
|
|
590
606
|
}
|
|
607
|
+
function toTypeFilesList(client, templates, basePath, createPaths = true) {
|
|
608
|
+
return templates.reduce(async (prevList, displayTemplate) => {
|
|
609
|
+
const typeFiles = await prevList;
|
|
610
|
+
const { targetType, styleFilePath: filePath, helperFilePath: helperPath } = await createTemplateMetadata(client, displayTemplate, basePath, createPaths);
|
|
611
|
+
if (!typeFiles[targetType]) {
|
|
612
|
+
typeFiles[targetType] = {
|
|
613
|
+
filePath: helperPath,
|
|
614
|
+
templates: []
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
typeFiles[targetType].templates.push({ key: displayTemplate.key, file: filePath, data: displayTemplate });
|
|
618
|
+
return typeFiles;
|
|
619
|
+
}, Promise.resolve({}));
|
|
620
|
+
}
|
|
591
621
|
async function createTemplateMetadata(client, displayTemplate, basePath, createPaths = true) {
|
|
592
622
|
let itemPath = undefined;
|
|
593
623
|
let targetType;
|
|
@@ -811,6 +841,91 @@ const StylesCreateCommand = {
|
|
|
811
841
|
}
|
|
812
842
|
};
|
|
813
843
|
|
|
844
|
+
const StylesDeleteCommand = {
|
|
845
|
+
command: "styles:delete",
|
|
846
|
+
describe: "Remove Visual Builder style definitions from the CMS",
|
|
847
|
+
builder: (yargs) => {
|
|
848
|
+
const newYargs = stylesBuilder(yargs);
|
|
849
|
+
newYargs.option('force', { alias: 'f', description: "Overwrite existing files", boolean: true, type: 'boolean', demandOption: false, default: false });
|
|
850
|
+
newYargs.option('withStyleFile', { alias: 'w', description: "Delete the .opti-style.json file as weill", boolean: true, type: 'boolean', demandOption: false, default: true });
|
|
851
|
+
newYargs.option("definitions", { alias: 'u', description: "Update typescript definitions", boolean: true, type: 'boolean', demandOption: false, default: true });
|
|
852
|
+
return newYargs;
|
|
853
|
+
},
|
|
854
|
+
handler: async (args) => {
|
|
855
|
+
const { components: basePath } = parseArgs(args);
|
|
856
|
+
const client = createCmsClient(args);
|
|
857
|
+
const { styles, all: allStyles } = await getStyles(client, args, 100);
|
|
858
|
+
if (styles.findIndex(x => x.isDefault) >= 0)
|
|
859
|
+
process.stdout.write(chalk.redBright(chalk.bold((`\n${figures.warning} You are deleting a default Display Template this may lead to unpredicted behavior.\n\n`))));
|
|
860
|
+
if (!args.force) {
|
|
861
|
+
process.stdout.write(`This will remove the following display templates\n`);
|
|
862
|
+
for (const style of styles) {
|
|
863
|
+
process.stdout.write(` ${figures.arrowRight} ${style.displayName || style.key} [Key: ${style.key}; Default: ${style.isDefault ? 'Yes' : 'No'}]\n`);
|
|
864
|
+
}
|
|
865
|
+
process.stdout.write(`\nRun with -f to actually preform removal\n`);
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
const keysToDelete = styles.map(displayTemplate => displayTemplate.key);
|
|
869
|
+
const styleHelpers = await toTypeFilesList(client, allStyles, basePath, false);
|
|
870
|
+
for (const displayTemplate of styles) {
|
|
871
|
+
const { styleFilePath, targetType, itemPath, typesPath } = await createTemplateMetadata(client, displayTemplate, basePath, false);
|
|
872
|
+
// Remove style file
|
|
873
|
+
if (args.withStyleFile) {
|
|
874
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Removing *.opti-style.json file for ${displayTemplate.displayName} [${displayTemplate.key}]\n`));
|
|
875
|
+
await fsAsync.rm(styleFilePath, { force: false, recursive: false, maxRetries: 1 }).catch((e) => {
|
|
876
|
+
if (e?.code === 'ENOENT') // The file can't be deleted as it does not exist - that's the desired state
|
|
877
|
+
return;
|
|
878
|
+
throw e;
|
|
879
|
+
});
|
|
880
|
+
await fsAsync.rmdir(itemPath, { recursive: false, maxRetries: 1 }).catch((e) => {
|
|
881
|
+
if (e?.code === 'ENOTEMPTY') {
|
|
882
|
+
process.stdout.write(chalk.yellowBright(`${figures.warning} The folder is not empty, please remove ${itemPath} manually if needed\n`));
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
else if (e?.code === 'ENOENT')
|
|
886
|
+
return;
|
|
887
|
+
else
|
|
888
|
+
throw e;
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
// Remove/update typescript helper
|
|
892
|
+
if (args.definitions && styleHelpers[targetType]) {
|
|
893
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Removing/updating displayTemplates.ts file for ${displayTemplate.displayName} [${displayTemplate.key}]\n`));
|
|
894
|
+
const remainingTemplates = styleHelpers[targetType].templates.filter(x => !keysToDelete.includes(x.key));
|
|
895
|
+
if (remainingTemplates.length === 0) {
|
|
896
|
+
await fsAsync.rm(styleHelpers[targetType].filePath, { force: false, recursive: false, maxRetries: 1 }).catch((e) => {
|
|
897
|
+
if (e?.code === 'ENOENT') // The file can't be deleted as it does not exist - that's the desired state
|
|
898
|
+
return;
|
|
899
|
+
throw e;
|
|
900
|
+
});
|
|
901
|
+
await fsAsync.rmdir(typesPath, { recursive: false, maxRetries: 1 }).catch((e) => {
|
|
902
|
+
if (e?.code === 'ENOTEMPTY') {
|
|
903
|
+
process.stdout.write(chalk.yellowBright(`${figures.warning} The folder is not empty, please remove ${typesPath} manually if needed\n`));
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
else if (e?.code === 'ENOENT')
|
|
907
|
+
return;
|
|
908
|
+
else
|
|
909
|
+
throw e;
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
else {
|
|
913
|
+
const newEntry = {
|
|
914
|
+
filePath: styleHelpers[targetType].filePath,
|
|
915
|
+
templates: remainingTemplates
|
|
916
|
+
};
|
|
917
|
+
if (!await createDisplayTemplateHelper(newEntry, targetType, false, false))
|
|
918
|
+
process.stdout.write(chalk.yellowBright(`${figures.warning} The display template was not updated, please update ${newEntry.filePath} manually`));
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
// Actually remove from CMS
|
|
922
|
+
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Removing the display template ${displayTemplate.displayName} [${displayTemplate.key}] from Optimizely CMS\n`));
|
|
923
|
+
await client.displayTemplatesDelete({ path: { key: displayTemplate.key } });
|
|
924
|
+
}
|
|
925
|
+
process.stdout.write("\n" + chalk.green(chalk.bold(figures.tick + " Done")) + "\n");
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
|
|
814
929
|
const TypesPullCommand = {
|
|
815
930
|
command: "types:pull",
|
|
816
931
|
describe: "Pull content type definition files into the project",
|
|
@@ -1500,6 +1615,7 @@ function getDisplayTemplateInfo(template, typePath) {
|
|
|
1500
1615
|
|
|
1501
1616
|
const ROOT_FACTORY_KEY = ".";
|
|
1502
1617
|
const FACTORY_FILE_NAME = "index.ts";
|
|
1618
|
+
const reservedNames = ["loading.tsx", "loading.jsx", "suspense.tsx", "suspense.jsx"];
|
|
1503
1619
|
const NextJsFactoryCommand = {
|
|
1504
1620
|
command: "nextjs:factory",
|
|
1505
1621
|
describe: "Create the ComponentFactory for a Next.JS / Optimizely Graph structure",
|
|
@@ -1508,17 +1624,22 @@ const NextJsFactoryCommand = {
|
|
|
1508
1624
|
const { components: basePath, force, _config: { debug } } = parseArgs(args);
|
|
1509
1625
|
if (debug)
|
|
1510
1626
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Start generating component factories\n`));
|
|
1627
|
+
// Get & filter all component files
|
|
1511
1628
|
const components = globSync(["./**/*.jsx", "./**/*.tsx"], {
|
|
1512
1629
|
cwd: basePath
|
|
1513
1630
|
}).map(p => p.split(path.sep)).filter(p => {
|
|
1631
|
+
// Get the filename
|
|
1632
|
+
const fileName = p.at(p.length - 1);
|
|
1633
|
+
if (!fileName)
|
|
1634
|
+
return false;
|
|
1514
1635
|
// Consider components in a file starting with "_" as a partial
|
|
1515
|
-
if (
|
|
1636
|
+
if (fileName.startsWith('_') == true)
|
|
1516
1637
|
return false;
|
|
1517
1638
|
// Consider components in a folder named "partials" as a partial
|
|
1518
|
-
if (p.
|
|
1639
|
+
if (p.some(folder => folder === 'partials'))
|
|
1519
1640
|
return false;
|
|
1520
1641
|
// Skip the special loader component
|
|
1521
|
-
if (
|
|
1642
|
+
if (reservedNames.includes(fileName))
|
|
1522
1643
|
return false;
|
|
1523
1644
|
// Check if the file has a default export
|
|
1524
1645
|
const fileBuffer = fs.readFileSync(path.join(basePath, p.join(path.sep)));
|
|
@@ -1529,87 +1650,70 @@ const NextJsFactoryCommand = {
|
|
|
1529
1650
|
}
|
|
1530
1651
|
return true;
|
|
1531
1652
|
});
|
|
1653
|
+
// Report what we have found
|
|
1532
1654
|
if (debug)
|
|
1533
1655
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Identified ${components.length} components in ${basePath}\n`));
|
|
1656
|
+
// Build factory / component structure
|
|
1534
1657
|
const componentFactoryDefintions = new Map();
|
|
1535
1658
|
components.forEach(component => {
|
|
1659
|
+
// Determine component target
|
|
1660
|
+
const componentKey = processName(component.length == 1 ? ucFirst(path.basename(component[0], path.extname(component[0]))) : component.at(component.length - 2));
|
|
1661
|
+
let componentVariant = (component.length > 1 ? component.at(component.length - 1) ?? 'default' : 'default').replace('index', 'default');
|
|
1662
|
+
componentVariant = path.basename(componentVariant, path.extname(componentVariant));
|
|
1663
|
+
const componentDir = path.dirname(path.join(...component));
|
|
1664
|
+
// Get factory information
|
|
1536
1665
|
const factorySegments = component.length > 2 ? component.slice(0, -2) : [ROOT_FACTORY_KEY];
|
|
1537
|
-
const factoryKey =
|
|
1666
|
+
const factoryKey = path.posix.join(...factorySegments);
|
|
1538
1667
|
const factoryFile = path.join(factoryKey, FACTORY_FILE_NAME);
|
|
1539
|
-
//
|
|
1668
|
+
// Check dynamic & suspense
|
|
1669
|
+
const useDynamic = [
|
|
1670
|
+
path.join(basePath, componentDir, 'loading.tsx'),
|
|
1671
|
+
path.join(basePath, componentDir, 'loading.jsx')
|
|
1672
|
+
].some(fs.existsSync);
|
|
1673
|
+
const useSuspense = [
|
|
1674
|
+
path.join(basePath, componentDir, 'suspense.tsx'),
|
|
1675
|
+
path.join(basePath, componentDir, 'suspense.jsx')
|
|
1676
|
+
].some(fs.existsSync);
|
|
1677
|
+
// Prepare data
|
|
1678
|
+
const componentImport = component.length == 1 ?
|
|
1679
|
+
`.${path.posix.sep}${path.basename(component[0], path.extname(component[0]))}` :
|
|
1680
|
+
`.${path.posix.sep}${path.posix.relative(factoryKey, componentDir)}${componentVariant !== 'default' ? path.posix.sep + componentVariant : ''}`;
|
|
1681
|
+
const loaderImport = useDynamic ? componentImport + path.posix.sep + 'loading' : undefined;
|
|
1682
|
+
const suspenseImport = useSuspense ? componentImport + path.posix.sep + 'suspense' : undefined;
|
|
1683
|
+
const componentVariablesBase = componentKey + (componentVariant !== 'default' ? processName(componentVariant) : '');
|
|
1684
|
+
// Add to the factory
|
|
1540
1685
|
const factory = componentFactoryDefintions.get(factoryKey) || { file: factoryFile, entries: [], subfactories: [] };
|
|
1541
|
-
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'));
|
|
1542
|
-
if (useSuspense && debug)
|
|
1543
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Components in ${component.slice(0, -1).join(path.sep)} will use suspense\n`));
|
|
1544
|
-
const componentSegments = component.slice(-2).map(p => {
|
|
1545
|
-
const entry = p.substring(0, p.length - path.extname(p).length);
|
|
1546
|
-
return entry.toLowerCase() == "index" ? null : entry;
|
|
1547
|
-
}).filter(x => x);
|
|
1548
|
-
const componentImport = "./" + componentSegments.join('/');
|
|
1549
|
-
const loaderImport = useSuspense ? "./" + component.slice(-2).map((p, i, a) => {
|
|
1550
|
-
const entry = p.substring(0, p.length - path.extname(p).length);
|
|
1551
|
-
if (i == a.length - 1)
|
|
1552
|
-
return 'loading';
|
|
1553
|
-
return entry.toLowerCase() == "index" ? null : entry;
|
|
1554
|
-
}).join('/') : undefined;
|
|
1555
1686
|
factory.entries.push({
|
|
1687
|
+
key: componentKey,
|
|
1688
|
+
variant: componentVariant,
|
|
1556
1689
|
import: componentImport,
|
|
1557
|
-
variable:
|
|
1558
|
-
key: componentSegments.join('/') == "node" ? "Node" : componentSegments.join('/'),
|
|
1690
|
+
variable: componentVariablesBase + 'Component',
|
|
1559
1691
|
loaderImport,
|
|
1560
|
-
loaderVariable:
|
|
1692
|
+
loaderVariable: useDynamic ? componentVariablesBase + 'Loader' : undefined,
|
|
1693
|
+
suspenseImport,
|
|
1694
|
+
suspenseVariable: useSuspense ? componentVariablesBase + 'Placeholder' : undefined
|
|
1561
1695
|
});
|
|
1562
1696
|
componentFactoryDefintions.set(factoryKey, factory);
|
|
1563
|
-
//
|
|
1697
|
+
// Add/update parent factories
|
|
1564
1698
|
const parentSegements = factoryKey == ROOT_FACTORY_KEY ? factorySegments.slice(0, -1) : [ROOT_FACTORY_KEY, ...factorySegments.slice(0, -1)];
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
prefix: processName(factorySegments.slice(-1).at(0)),
|
|
1569
|
-
variable: factorySegments.map(processName).join("") + "Factory"
|
|
1570
|
-
};
|
|
1571
|
-
for (let i = parentSegements.length; i > 0; i--) {
|
|
1572
|
-
const parentFactoryKey = parentSegements.slice(0, i).filter(x => x != ROOT_FACTORY_KEY).join(path.sep) || ROOT_FACTORY_KEY;
|
|
1699
|
+
parentSegements.forEach((_, idx, data) => {
|
|
1700
|
+
const parentFactoryKey = path.posix.join(...data.slice(0, idx + 1)) || ROOT_FACTORY_KEY;
|
|
1701
|
+
const childFactoryKey = factorySegments.slice(idx, idx + 1).at(0);
|
|
1573
1702
|
const parentFactory = componentFactoryDefintions.get(parentFactoryKey) ?? { file: path.join(parentFactoryKey, FACTORY_FILE_NAME), entries: [], subfactories: [] };
|
|
1574
|
-
if (!parentFactory.subfactories.some(x => x.key
|
|
1575
|
-
parentFactory.subfactories.push(
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
prefix: processName(parentFactoryKey.split(path.sep).slice(-1).at(0)),
|
|
1582
|
-
variable: parentFactoryKey.split(path.sep).map(processName).join("") + "Factory"
|
|
1583
|
-
};
|
|
1703
|
+
if (!parentFactory.subfactories.some(x => x.key === childFactoryKey)) {
|
|
1704
|
+
parentFactory.subfactories.push({
|
|
1705
|
+
key: childFactoryKey,
|
|
1706
|
+
import: './' + childFactoryKey,
|
|
1707
|
+
variable: processName(childFactoryKey) + 'Factory'
|
|
1708
|
+
});
|
|
1709
|
+
componentFactoryDefintions.set(parentFactoryKey, parentFactory);
|
|
1584
1710
|
}
|
|
1585
|
-
}
|
|
1586
|
-
});
|
|
1587
|
-
const mainFactory = componentFactoryDefintions.get(ROOT_FACTORY_KEY);
|
|
1588
|
-
if (mainFactory) {
|
|
1589
|
-
if (debug)
|
|
1590
|
-
process.stdout.write(chalk.gray(`${figures.arrowRight} Updating prefixes within RootFactory\n`));
|
|
1591
|
-
mainFactory.subfactories = mainFactory.subfactories.map(subFactory => {
|
|
1592
|
-
if (typeof (subFactory.prefix == 'string'))
|
|
1593
|
-
switch (subFactory.prefix) {
|
|
1594
|
-
case "Video":
|
|
1595
|
-
case "Image":
|
|
1596
|
-
subFactory.prefix = ["Media", subFactory.prefix, "Component"];
|
|
1597
|
-
break;
|
|
1598
|
-
case "Experience":
|
|
1599
|
-
subFactory.prefix = [subFactory.prefix, "Page"];
|
|
1600
|
-
break;
|
|
1601
|
-
case "Element":
|
|
1602
|
-
subFactory.prefix = ["Component"];
|
|
1603
|
-
break;
|
|
1604
|
-
case "Media":
|
|
1605
|
-
subFactory.prefix = [subFactory.prefix, "Component"];
|
|
1606
|
-
break;
|
|
1607
|
-
}
|
|
1608
|
-
return subFactory;
|
|
1609
1711
|
});
|
|
1610
|
-
}
|
|
1712
|
+
});
|
|
1713
|
+
// Report factory file count
|
|
1611
1714
|
if (debug)
|
|
1612
1715
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Finished preparing ${componentFactoryDefintions.size} factories, start writing\n`));
|
|
1716
|
+
// Iterate over the factories and create them
|
|
1613
1717
|
let updateCounter = 0;
|
|
1614
1718
|
for (const key of componentFactoryDefintions.keys()) {
|
|
1615
1719
|
const factory = componentFactoryDefintions.get(key);
|
|
@@ -1658,59 +1762,55 @@ function processName(input) {
|
|
|
1658
1762
|
return nameSegements.map(ucFirst).join('');
|
|
1659
1763
|
}
|
|
1660
1764
|
function generateFactory(factoryInfo, factoryKey) {
|
|
1661
|
-
|
|
1765
|
+
// Get the factory name
|
|
1662
1766
|
const factoryName = factoryKey.split(path.sep).map(processName).join("") + "Factory";
|
|
1663
|
-
|
|
1664
|
-
const
|
|
1665
|
-
const
|
|
1767
|
+
// Get the components and sub-factories, sorted by key to minimize changes between runs
|
|
1768
|
+
const components = [...factoryInfo.entries].sort((a, b) => { return a.key < b.key ? -1 : a.key > b.key ? 1 : 0; });
|
|
1769
|
+
const subFactories = [...factoryInfo.subfactories].sort((a, b) => { return a.key < b.key ? -1 : a.key > b.key ? 1 : 0; });
|
|
1770
|
+
// Check if there's at least one component that uses next/dynamic
|
|
1771
|
+
const hasDynamic = factoryInfo.entries.some(x => x.loaderImport);
|
|
1772
|
+
// The intro for the factory
|
|
1773
|
+
const factoryIntro = `// Auto generated dictionary
|
|
1666
1774
|
// @not-modified => When this line is removed, the "force" parameter of the CLI tool is required to overwrite this file
|
|
1667
|
-
import { type ComponentTypeDictionary } from
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
${
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
}
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
}
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
//
|
|
1702
|
-
|
|
1703
|
-
${
|
|
1704
|
-
//
|
|
1705
|
-
|
|
1706
|
-
{
|
|
1707
|
-
list.forEach((component, idx, dictionary) => {
|
|
1708
|
-
dictionary[idx].type = typeof component.type == 'string' ? prefix + "/" + component.type : [ prefix, ...component.type ]
|
|
1709
|
-
});
|
|
1710
|
-
return list;
|
|
1711
|
-
}
|
|
1712
|
-
` : ''}`;
|
|
1713
|
-
return factoryContent;
|
|
1775
|
+
import { type ComponentTypeDictionary } from '@remkoj/optimizely-cms-react';${hasDynamic ? `
|
|
1776
|
+
import dynamic from 'next/dynamic';` : ''}`;
|
|
1777
|
+
// The outry for the factory
|
|
1778
|
+
const factoryOutro = `// Export dictionary
|
|
1779
|
+
export default ${factoryName};`;
|
|
1780
|
+
// The imports of the factory
|
|
1781
|
+
const factoryImports = [...components, ...subFactories].map(x => {
|
|
1782
|
+
let imports = [x.loaderImport ?
|
|
1783
|
+
`import ${x.loaderVariable} from '${x.loaderImport}';` :
|
|
1784
|
+
`import ${x.variable} from '${x.import}';`];
|
|
1785
|
+
if (x.suspenseImport)
|
|
1786
|
+
imports.push(`import ${x.suspenseVariable} from '${x.suspenseImport}';`);
|
|
1787
|
+
return imports.join('\n');
|
|
1788
|
+
}).join('\n');
|
|
1789
|
+
// The dynamic imports of the factory
|
|
1790
|
+
const dynamicImports = hasDynamic ? `// Lazy load components that have a loading file, this only affects client components
|
|
1791
|
+
// See https://nextjs.org/docs/app/guides/lazy-loading#importing-server-components
|
|
1792
|
+
// for more details on how this affects server components in Next.js
|
|
1793
|
+
` + components.filter(x => x.loaderImport).map(x => {
|
|
1794
|
+
return `const ${x.variable} = dynamic(() => import('${x.import}'), {
|
|
1795
|
+
ssr: true,
|
|
1796
|
+
loading: ${x.loaderVariable}
|
|
1797
|
+
});`;
|
|
1798
|
+
}).join('\n') : undefined;
|
|
1799
|
+
// The actual entries for the factory
|
|
1800
|
+
const factoryEntries = [...components.map(x => {
|
|
1801
|
+
return ` {
|
|
1802
|
+
type: '${x.key}',${x.variant && x.variant !== 'default' ? `
|
|
1803
|
+
variant: '${x.variant}',` : ''}
|
|
1804
|
+
component: ${x.variable}${x.suspenseVariable ? `,
|
|
1805
|
+
useSuspense: true,
|
|
1806
|
+
loader: ${x.suspenseVariable}` : ''}
|
|
1807
|
+
}`;
|
|
1808
|
+
}), ...subFactories.map(x => ` ...${x.variable}`)];
|
|
1809
|
+
// The body of the factory
|
|
1810
|
+
const factoryBody = `// Build dictionary
|
|
1811
|
+
export const ${factoryName} : ComponentTypeDictionary = [${factoryEntries.length > 0 ? '\n' + factoryEntries.join(',\n') + '\n' : ''}];`;
|
|
1812
|
+
// Combine everything into one string
|
|
1813
|
+
return [factoryIntro, factoryImports, dynamicImports, factoryBody, factoryOutro].filter(x => (x?.length || 0) > 0).join('\n\n') + '\n';
|
|
1714
1814
|
}
|
|
1715
1815
|
|
|
1716
1816
|
const NextJsCreateCommand = {
|
|
@@ -1750,16 +1850,23 @@ const NextJsFragmentsCommand = {
|
|
|
1750
1850
|
handler: async (args, opts) => {
|
|
1751
1851
|
// Prepare
|
|
1752
1852
|
const { loadedContentTypes, createdTypeFolders } = opts || {};
|
|
1753
|
-
const { components: basePath, _config: { debug }, force } = parseArgs(args);
|
|
1853
|
+
const { components: basePath, _config: { debug }, force, path: appPath } = parseArgs(args);
|
|
1754
1854
|
const client = createCmsClient(args);
|
|
1855
|
+
// Get content types
|
|
1755
1856
|
const { contentTypes, all: allContentTypes } = loadedContentTypes ?? await getContentTypes(client, args);
|
|
1857
|
+
const allContentTypesMap = new Map();
|
|
1858
|
+
allContentTypes.forEach(x => {
|
|
1859
|
+
if (x.key)
|
|
1860
|
+
allContentTypesMap.set(x.key, x);
|
|
1861
|
+
});
|
|
1862
|
+
const generator = new DocumentGenerator(allContentTypesMap);
|
|
1756
1863
|
// Start process
|
|
1757
1864
|
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Generating GraphQL fragments\n`));
|
|
1758
1865
|
const typeFolders = createdTypeFolders ?? createTypeFolders(contentTypes, basePath, debug);
|
|
1759
|
-
const tracker = new
|
|
1866
|
+
const tracker = new PropertyCollisionTracker(appPath);
|
|
1760
1867
|
const dependencies = [];
|
|
1761
1868
|
const updatedTypes = contentTypes.map(contentType => {
|
|
1762
|
-
const { written, propertyTypes } = createComponentFragments(contentType, getTypeFolder(typeFolders, contentType.key), force, debug, tracker);
|
|
1869
|
+
const { written, propertyTypes } = createComponentFragments(contentType, getTypeFolder(typeFolders, contentType.key), force, debug, tracker, generator);
|
|
1763
1870
|
dependencies.push(...propertyTypes);
|
|
1764
1871
|
return written ? contentType.key : undefined;
|
|
1765
1872
|
}).filter(x => x);
|
|
@@ -1779,7 +1886,7 @@ const NextJsFragmentsCommand = {
|
|
|
1779
1886
|
typeFolders.push(tf);
|
|
1780
1887
|
}
|
|
1781
1888
|
return tf;
|
|
1782
|
-
}, force, debug);
|
|
1889
|
+
}, force, debug, tracker, generator);
|
|
1783
1890
|
// Report outcome
|
|
1784
1891
|
if (generatedProps.length > 0)
|
|
1785
1892
|
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Created/updated GraphQL property fragments for ${generatedProps.join(', ')}\n`));
|
|
@@ -1789,7 +1896,7 @@ const NextJsFragmentsCommand = {
|
|
|
1789
1896
|
process.stdout.write(chalk.green(chalk.bold(figures.tick + " Done")) + "\n");
|
|
1790
1897
|
}
|
|
1791
1898
|
};
|
|
1792
|
-
function createComponentFragments(contentType, typePath, force, debug, propertyTracker = new Map()) {
|
|
1899
|
+
function createComponentFragments(contentType, typePath, force, debug, propertyTracker = new Map(), generator) {
|
|
1793
1900
|
const baseQueryFile = typePath.fragmentFile;
|
|
1794
1901
|
let writeFragment = true;
|
|
1795
1902
|
if (fs.existsSync(baseQueryFile)) {
|
|
@@ -1808,13 +1915,13 @@ function createComponentFragments(contentType, typePath, force, debug, propertyT
|
|
|
1808
1915
|
}
|
|
1809
1916
|
let written = false;
|
|
1810
1917
|
if (writeFragment) {
|
|
1811
|
-
const fragment =
|
|
1918
|
+
const fragment = generator.buildFragment(contentType, undefined, false, propertyTracker);
|
|
1812
1919
|
fs.writeFileSync(baseQueryFile, fragment);
|
|
1813
1920
|
written = true;
|
|
1814
1921
|
}
|
|
1815
|
-
return { written, propertyTypes:
|
|
1922
|
+
return { written, propertyTypes: DocumentGenerator.getReferencedPropertyComponents(contentType) };
|
|
1816
1923
|
}
|
|
1817
|
-
function createPropertyFragments(propertyTypesList, allContentTypes, selectTypeFolder, force = false, debug = false) {
|
|
1924
|
+
function createPropertyFragments(propertyTypesList, allContentTypes, selectTypeFolder, force = false, debug = false, propertyTracker = new Map(), generator) {
|
|
1818
1925
|
const returnValue = [];
|
|
1819
1926
|
for (const propertyContentTypeKey of propertyTypesList.filter((v, i, a) => a.indexOf(v, i + 1) <= i)) {
|
|
1820
1927
|
// Load the ContentType definition
|
|
@@ -1846,16 +1953,16 @@ function createPropertyFragments(propertyTypesList, allContentTypes, selectTypeF
|
|
|
1846
1953
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating ${contentType.displayName} (${contentType.key}) property fragment\n`));
|
|
1847
1954
|
}
|
|
1848
1955
|
if (mustWrite) {
|
|
1849
|
-
const fragment =
|
|
1956
|
+
const fragment = generator.buildFragment(contentType, undefined, true, propertyTracker);
|
|
1850
1957
|
fs.writeFileSync(propertyFragmentFile, fragment);
|
|
1851
1958
|
returnValue.push(contentType.key);
|
|
1852
1959
|
}
|
|
1853
1960
|
// Recurse down for properties that we're not yet rendering
|
|
1854
|
-
const referencedPropertyTypes =
|
|
1961
|
+
const referencedPropertyTypes = DocumentGenerator.getReferencedPropertyComponents(contentType).filter(x => !propertyTypesList.includes(x));
|
|
1855
1962
|
if (referencedPropertyTypes.length > 0) {
|
|
1856
1963
|
if (debug)
|
|
1857
1964
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Component property ${propertyContentTypeKey} uses components a property, recursing down\n`));
|
|
1858
|
-
const additionalProperties = createPropertyFragments(referencedPropertyTypes, allContentTypes, selectTypeFolder, force, debug);
|
|
1965
|
+
const additionalProperties = createPropertyFragments(referencedPropertyTypes, allContentTypes, selectTypeFolder, force, debug, propertyTracker, generator);
|
|
1859
1966
|
returnValue.push(...additionalProperties);
|
|
1860
1967
|
}
|
|
1861
1968
|
}
|
|
@@ -1873,16 +1980,23 @@ const NextJsQueriesCommand = {
|
|
|
1873
1980
|
handler: async (args, opts) => {
|
|
1874
1981
|
// Prepare
|
|
1875
1982
|
const { loadedContentTypes, createdTypeFolders } = opts || {};
|
|
1876
|
-
const { components: basePath, _config: { debug }, force } = parseArgs(args);
|
|
1983
|
+
const { components: basePath, _config: { debug }, force, path: appPath } = parseArgs(args);
|
|
1877
1984
|
const client = createCmsClient(args);
|
|
1878
1985
|
const { contentTypes, all: allContentTypes } = loadedContentTypes ?? await getContentTypes(client, args);
|
|
1986
|
+
const allContentTypesMap = new Map();
|
|
1987
|
+
allContentTypes.forEach(x => {
|
|
1988
|
+
if (x.key)
|
|
1989
|
+
allContentTypesMap.set(x.key, x);
|
|
1990
|
+
});
|
|
1991
|
+
const generator = new DocumentGenerator(allContentTypesMap);
|
|
1992
|
+
const propertyTracker = new PropertyCollisionTracker(appPath);
|
|
1879
1993
|
// Start process
|
|
1880
1994
|
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Generating GraphQL queries\n`));
|
|
1881
1995
|
const dependencies = [];
|
|
1882
1996
|
const typeFolders = createdTypeFolders ?? createTypeFolders(contentTypes, basePath, debug);
|
|
1883
1997
|
const updatedTypes = contentTypes.map(contentType => {
|
|
1884
1998
|
const typePath = getTypeFolder(typeFolders, contentType.key);
|
|
1885
|
-
const { written, propertyTypes } = createGraphQueries(contentType, typePath, force, debug);
|
|
1999
|
+
const { written, propertyTypes } = createGraphQueries(contentType, typePath, force, debug, propertyTracker, generator);
|
|
1886
2000
|
dependencies.push(...propertyTypes);
|
|
1887
2001
|
return written ? contentType.key : undefined;
|
|
1888
2002
|
}).filter(x => x).flat();
|
|
@@ -1901,7 +2015,7 @@ const NextJsQueriesCommand = {
|
|
|
1901
2015
|
typeFolders.push(tf);
|
|
1902
2016
|
}
|
|
1903
2017
|
return tf;
|
|
1904
|
-
}, force, debug);
|
|
2018
|
+
}, force, debug, propertyTracker, generator);
|
|
1905
2019
|
// Report outcome
|
|
1906
2020
|
if (generatedProps.length > 0)
|
|
1907
2021
|
process.stdout.write(chalk.yellowBright(`${figures.arrowRight} Created/updated GraphQL property fragments for ${generatedProps.join(', ')}\n`));
|
|
@@ -1911,7 +2025,7 @@ const NextJsQueriesCommand = {
|
|
|
1911
2025
|
process.stdout.write(chalk.green(chalk.bold(figures.tick + " Done")) + "\n");
|
|
1912
2026
|
}
|
|
1913
2027
|
};
|
|
1914
|
-
function createGraphQueries(contentType, typePath, force, debug) {
|
|
2028
|
+
function createGraphQueries(contentType, typePath, force, debug, tracker, generator) {
|
|
1915
2029
|
const baseQueryFile = typePath.queryFile;
|
|
1916
2030
|
let mustWrite = true;
|
|
1917
2031
|
if (fs.existsSync(baseQueryFile)) {
|
|
@@ -1929,12 +2043,12 @@ function createGraphQueries(contentType, typePath, force, debug) {
|
|
|
1929
2043
|
process.stdout.write(chalk.gray(`${figures.arrowRight} Creating ${contentType.displayName} (${contentType.key}) base fragment\n`));
|
|
1930
2044
|
}
|
|
1931
2045
|
if (mustWrite) {
|
|
1932
|
-
const fragment =
|
|
2046
|
+
const fragment = generator.buildGetQuery(contentType, undefined, tracker);
|
|
1933
2047
|
fs.writeFileSync(baseQueryFile, fragment);
|
|
1934
2048
|
}
|
|
1935
2049
|
return {
|
|
1936
2050
|
written: mustWrite,
|
|
1937
|
-
propertyTypes:
|
|
2051
|
+
propertyTypes: DocumentGenerator.getReferencedPropertyComponents(contentType)
|
|
1938
2052
|
};
|
|
1939
2053
|
}
|
|
1940
2054
|
|
|
@@ -2214,7 +2328,7 @@ function writeFileAsync(path, data) {
|
|
|
2214
2328
|
});
|
|
2215
2329
|
}
|
|
2216
2330
|
|
|
2217
|
-
var version = "6.0.0-
|
|
2331
|
+
var version = "6.0.0-pre14";
|
|
2218
2332
|
var name = "opti-cms";
|
|
2219
2333
|
var APP = {
|
|
2220
2334
|
version: version,
|
|
@@ -2582,20 +2696,22 @@ const commands = [
|
|
|
2582
2696
|
StylesListCommand,
|
|
2583
2697
|
StylesPullCommand,
|
|
2584
2698
|
StylesPushCommand,
|
|
2699
|
+
StylesDeleteCommand,
|
|
2585
2700
|
TypesPullCommand,
|
|
2586
2701
|
TypesPushCommand
|
|
2587
2702
|
];
|
|
2588
2703
|
|
|
2589
2704
|
async function main() {
|
|
2590
|
-
const
|
|
2591
|
-
const
|
|
2705
|
+
const projectDir = getProjectDir();
|
|
2706
|
+
const envFiles = prepare(projectDir);
|
|
2707
|
+
const app = createOptiCmsApp(APP.name, APP.version, undefined, envFiles, projectDir);
|
|
2592
2708
|
app.command(commands);
|
|
2593
2709
|
try {
|
|
2594
2710
|
await app.parse(process.argv.slice(2));
|
|
2595
2711
|
}
|
|
2596
2712
|
catch {
|
|
2597
2713
|
//We're ignoring error here, as yargs will already generate the "nice output" for it
|
|
2598
|
-
//console.log('Caught error')
|
|
2714
|
+
//console.log ('Caught error')
|
|
2599
2715
|
}
|
|
2600
2716
|
}
|
|
2601
2717
|
main();
|