contensis-cli 1.2.2-beta.0 → 1.2.2-beta.10

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.
@@ -1,3 +1,13 @@
1
+ export type CliUrls =
2
+ | {
3
+ api: string;
4
+ cms: string;
5
+ liveWeb: string;
6
+ previewWeb: string;
7
+ iisWeb: string;
8
+ iisPreviewWeb: string;
9
+ }
10
+ | undefined;
1
11
 
2
12
  export type OutputFormat = 'json' | 'csv' | 'xml';
3
13
 
@@ -4,6 +4,7 @@ import fs from 'fs';
4
4
  import inquirer from 'inquirer';
5
5
  import fetch from 'node-fetch';
6
6
  import path from 'path';
7
+ import clone from 'rfdc';
7
8
 
8
9
  import { Component, ContentType, Project } from 'contensis-core-api';
9
10
  import { Role } from 'contensis-management-api/lib/models';
@@ -22,7 +23,11 @@ import {
22
23
  import ContensisAuthService from './ContensisAuthService';
23
24
 
24
25
  import { LogMessages } from '~/localisation/en-GB';
25
- import { OutputFormat, OutputOptionsConstructorArg } from '~/models/CliService';
26
+ import {
27
+ CliUrls,
28
+ OutputFormat,
29
+ OutputOptionsConstructorArg,
30
+ } from '~/models/CliService';
26
31
 
27
32
  import { readFileAsJSON } from '~/providers/file-provider';
28
33
  import SessionCacheProvider from '../providers/SessionCacheProvider';
@@ -78,16 +83,7 @@ class ContensisCli {
78
83
 
79
84
  sourceAlias?: string;
80
85
  targetEnv?: string;
81
- urls:
82
- | {
83
- api: string;
84
- cms: string;
85
- liveWeb: string;
86
- previewWeb: string;
87
- iisWeb: string;
88
- iisPreviewWeb: string;
89
- }
90
- | undefined;
86
+ urls: CliUrls;
91
87
  log = Logger;
92
88
  messages = LogMessages;
93
89
 
@@ -1183,7 +1179,7 @@ class ContensisCli {
1183
1179
 
1184
1180
  PrintContentModels = async (
1185
1181
  modelIds: string[] = [],
1186
- printRequiredBy?: boolean
1182
+ opts: { export?: boolean; printRequiredBy?: boolean }
1187
1183
  ) => {
1188
1184
  const { currentProject, log, messages } = this;
1189
1185
  const contensis = await this.ConnectContensis();
@@ -1199,62 +1195,72 @@ class ContensisCli {
1199
1195
  modelIds.some(id => id.toLowerCase() === m.id.toLowerCase())
1200
1196
  )
1201
1197
  : undefined;
1198
+ const exportResources: (ContentType | Component)[] = [];
1199
+
1200
+ if (opts.export) {
1201
+ // Generate a list of contentTypeIds and componentIds from all models
1202
+ // and dependencies
1203
+ const contentTypeIds = Array.from(
1204
+ new Set([
1205
+ ...(returnModels || models || []).map(m => m.id),
1206
+ ...(returnModels || models || [])
1207
+ .map(m => m.dependencies?.contentTypes?.map(c => c[0]) || [])
1208
+ .flat(),
1209
+ ])
1210
+ );
1211
+ const componentIds = Array.from(
1212
+ new Set(
1213
+ (returnModels || models || [])
1214
+ .map(m => m.dependencies?.components?.map(c => c[0]) || [])
1215
+ .flat()
1216
+ )
1217
+ );
1202
1218
 
1203
- // Generate a list of contentTypeIds and componentIds from all models
1204
- // and dependencies
1205
- const contentTypeIds = Array.from(
1206
- new Set([
1207
- ...(returnModels || models || []).map(m => m.id),
1208
- ...(returnModels || models || [])
1209
- .map(m => m.dependencies?.contentTypes?.map(c => c[0]) || [])
1210
- .flat(),
1211
- ])
1212
- );
1213
- const componentIds = Array.from(
1214
- new Set(
1215
- (returnModels || models || [])
1216
- .map(m => m.dependencies?.components?.map(c => c[0]) || [])
1217
- .flat()
1218
- )
1219
- );
1220
-
1221
- // Create an array of all the content types and component definitions
1222
- // we will use this when outputting to a file
1223
- const contentModelBackup = [
1224
- ...contentTypes.filter(c =>
1225
- contentTypeIds.map(i => i.toLowerCase()).includes(c.id.toLowerCase())
1226
- ),
1227
- ...components.filter(c =>
1228
- componentIds.map(i => i.toLowerCase()).includes(c.id.toLowerCase())
1229
- ),
1230
- ];
1219
+ // Create an array of all the content types and component definitions
1220
+ // we will use this when outputting to a file
1221
+ exportResources.push(
1222
+ ...contentTypes.filter(c =>
1223
+ contentTypeIds
1224
+ .map(i => i.toLowerCase())
1225
+ .includes(c.id.toLowerCase())
1226
+ ),
1227
+ ...components.filter(c =>
1228
+ componentIds.map(i => i.toLowerCase()).includes(c.id.toLowerCase())
1229
+ )
1230
+ );
1231
+ }
1231
1232
 
1232
1233
  if (Array.isArray(returnModels)) {
1233
1234
  log.success(messages.models.list(currentProject));
1234
- await this.HandleFormattingAndOutput(contentModelBackup, () => {
1235
- // print the content models to console
1236
- for (const model of returnModels) {
1237
- if (!printRequiredBy) {
1238
- // truncate parts of the output
1239
- delete model.dependencyOf;
1240
- if (model.dependencies?.contentTypes)
1241
- model.dependencies.contentTypes.forEach(id => (id[1] = []));
1242
- if (model.dependencies?.components)
1243
- model.dependencies.components.forEach(id => (id[1] = []));
1244
- }
1235
+ await this.HandleFormattingAndOutput(
1236
+ opts.export ? exportResources : returnModels,
1237
+ () => {
1238
+ // print the content models to console
1239
+ for (const model of returnModels) {
1240
+ // create a clone of each model to safely modify
1241
+ const draft = clone()(model);
1242
+ if (!opts.printRequiredBy) {
1243
+ // truncate parts of the output
1244
+ delete draft.dependencyOf;
1245
+ if (draft.dependencies?.contentTypes)
1246
+ draft.dependencies.contentTypes.forEach(id => id.pop());
1247
+ if (draft.dependencies?.components)
1248
+ draft.dependencies.components.forEach(id => id.pop());
1249
+ }
1245
1250
 
1251
+ log.raw('');
1252
+ log.object(draft);
1253
+ }
1246
1254
  log.raw('');
1247
- log.object(model);
1248
1255
  }
1249
- log.raw('');
1250
- });
1256
+ );
1251
1257
  } else {
1252
1258
  log.success(
1253
1259
  messages.models.get(currentProject, models?.length.toString() || '0')
1254
1260
  );
1255
1261
  log.raw('');
1256
1262
  if (models?.length) {
1257
- await this.HandleFormattingAndOutput(contentModelBackup, () => {
1263
+ await this.HandleFormattingAndOutput(exportResources, () => {
1258
1264
  // print the content models to console
1259
1265
  for (const model of models) {
1260
1266
  const components = model.components?.length || 0;
@@ -1784,10 +1790,11 @@ class ContensisCli {
1784
1790
 
1785
1791
  if (err) logError(err);
1786
1792
  else {
1793
+ const { migrateEntries, nodes } =
1794
+ contensis.content.targets[currentProject];
1795
+
1787
1796
  const output = saveEntries
1788
- ? contensis.content.targets[currentProject].migrateEntries?.map(
1789
- me => me.finalEntry
1790
- )
1797
+ ? migrateEntries?.map(me => me.finalEntry)
1791
1798
  : result;
1792
1799
  await this.HandleFormattingAndOutput(output, () => {
1793
1800
  // print the migrateResult to console
@@ -1796,6 +1803,16 @@ class ContensisCli {
1796
1803
  showDiff: logOutput === 'all' || logOutput === 'changes',
1797
1804
  showChanged: logOutput === 'changes',
1798
1805
  });
1806
+ if (['all', 'changes'].includes(logOutput))
1807
+ printNodeTreeOutput(
1808
+ this,
1809
+ {
1810
+ ...nodes.rootAncestor,
1811
+ status: 'no change',
1812
+ children: nodes.migrateNodes as any,
1813
+ },
1814
+ logOutput
1815
+ );
1799
1816
  });
1800
1817
  }
1801
1818
  if (
@@ -1812,7 +1829,12 @@ class ContensisCli {
1812
1829
  commit
1813
1830
  ? (result.migrateResult?.created || 0) +
1814
1831
  (result.migrateResult?.updated || 0)
1815
- : result.entriesToMigrate[currentProject].totalCount
1832
+ : result.entriesToMigrate[currentProject].totalCount,
1833
+ commit
1834
+ ? (result.nodesResult?.created || 0) +
1835
+ (result.nodesResult?.updated || 0)
1836
+ : (result.nodesToMigrate?.[currentProject]
1837
+ .totalCount as number) || 0
1816
1838
  )
1817
1839
  );
1818
1840
  if (!commit) {
@@ -1965,13 +1987,14 @@ class ContensisCli {
1965
1987
  }
1966
1988
 
1967
1989
  const [err, result] = await contensis.MigrateNodes();
1968
- const migrateTree =
1969
- contensis.nodes.targetRepos[currentProject].nodes.migrateNodesTreeView;
1970
1990
 
1971
1991
  if (err) log.raw(``);
1972
1992
  else
1973
1993
  await this.HandleFormattingAndOutput(result, () => {
1974
1994
  // print the migrateResult to console
1995
+ const migrateTree =
1996
+ contensis.nodes.targetRepos[currentProject].nodes
1997
+ .migrateNodesTreeView;
1975
1998
  printNodeTreeOutput(this, migrateTree, logOutput, logLimit);
1976
1999
  printNodesMigrateResult(this, result, {
1977
2000
  showAll: logOutput === 'all',
@@ -5,6 +5,7 @@ import { Logger, addNewLines } from './logger';
5
5
  import {
6
6
  BlockVersion,
7
7
  CopyFieldResult,
8
+ EntriesMigrationResult,
8
9
  EntriesResult,
9
10
  MigrateModelsResult,
10
11
  MigrateNodesTree,
@@ -116,8 +117,8 @@ export const printBlockVersion = (
116
117
  };
117
118
 
118
119
  export const printEntriesMigrateResult = (
119
- { log, messages, currentProject }: ContensisCli,
120
- migrateResult: EntriesResult | CopyFieldResult,
120
+ service: ContensisCli,
121
+ migrateResult: EntriesMigrationResult | EntriesResult | CopyFieldResult,
121
122
  {
122
123
  action = 'import',
123
124
  showDiff = false,
@@ -131,7 +132,7 @@ export const printEntriesMigrateResult = (
131
132
  } = {}
132
133
  ) => {
133
134
  console.log(``);
134
-
135
+ const { log, messages, currentProject } = service;
135
136
  for (const [contentTypeId, entryRes] of Object.entries(
136
137
  migrateResult.entriesToMigrate.entryIds
137
138
  ) as [string, any]) {
@@ -229,6 +230,14 @@ export const printEntriesMigrateResult = (
229
230
  ? log.successText
230
231
  : log.warningText;
231
232
 
233
+ if (isTotalCountRow && 'nodes' in migrateResult) {
234
+ printNodesMigrateResult(service, migrateResult, {
235
+ showAll,
236
+ showDiff,
237
+ showChanged,
238
+ isEntriesMigration: true,
239
+ });
240
+ }
232
241
  console.log(
233
242
  ` - ${
234
243
  isTotalCountRow
@@ -267,22 +276,24 @@ export const printEntriesMigrateResult = (
267
276
 
268
277
  export const printNodesMigrateResult = (
269
278
  { log, currentProject }: ContensisCli,
270
- migrateResult: NodesResult,
279
+ migrateResult: EntriesMigrationResult | NodesResult,
271
280
  {
272
281
  action = 'import',
273
282
  logLimit = 50,
274
283
  showDiff = false,
275
284
  showAll = false,
276
285
  showChanged = false,
286
+ isEntriesMigration = false,
277
287
  }: {
278
288
  action?: 'import' | 'delete';
279
289
  logLimit?: number;
280
290
  showDiff?: boolean;
281
291
  showAll?: boolean;
282
292
  showChanged?: boolean;
293
+ isEntriesMigration?: boolean;
283
294
  } = {}
284
295
  ) => {
285
- log.raw(``);
296
+ if (!isEntriesMigration) log.raw(``);
286
297
  for (const [projectId, counts] of Object.entries(migrateResult.nodes || {})) {
287
298
  const importTitle =
288
299
  action === 'delete'
@@ -292,14 +303,13 @@ export const printNodesMigrateResult = (
292
303
  ? `from project ${log.highlightText(projectId)} `
293
304
  : ''
294
305
  }to ${log.boldText(log.warningText(currentProject))}`;
295
- log.help(importTitle);
306
+ if (!isEntriesMigration) log.help(importTitle);
296
307
 
297
308
  const migrateStatusAndCount = migrateResult.nodesToMigrate[
298
309
  currentProject
299
310
  ] as ProjectNodesToMigrate;
300
311
 
301
- const existingCount =
302
- migrateResult.existing?.[currentProject]?.totalCount || 0;
312
+ const existingCount = migrateResult.nodes?.[projectId]?.totalCount || 0;
303
313
 
304
314
  const totalCount = Object.keys(migrateResult.nodesToMigrate.nodeIds).length;
305
315
  const existingPercent = counts.totalCount
@@ -320,25 +330,28 @@ export const printNodesMigrateResult = (
320
330
  const changedColor =
321
331
  changedPercentage === '100' ? log.successText : log.warningText;
322
332
 
323
- console.log(
324
- ` - ${log.highlightText(
325
- `totalCount: ${migrateStatusAndCount.totalCount}`
326
- )}${
327
- changedPercentage === '100'
328
- ? ''
329
- : existingColor(` [existing: ${`${existingPercent}%`}]`)
330
- }${
331
- existingPercent === '0'
332
- ? ''
333
- : changedColor(
334
- ` ${
335
- changedPercentage === '100'
336
- ? 'up to date'
337
- : `[needs update: ${100 - Number(changedPercentage)}%]`
338
- }`
339
- )
340
- }`
341
- );
333
+ if (!isEntriesMigration || migrateStatusAndCount.totalCount > 0)
334
+ console.log(
335
+ ` - ${log.highlightText(
336
+ `${isEntriesMigration ? ' + nodes' : 'totalCount'}: ${
337
+ migrateStatusAndCount.totalCount
338
+ }`
339
+ )}${
340
+ changedPercentage === '100'
341
+ ? ''
342
+ : existingColor(` [existing: ${`${existingPercent}%`}]`)
343
+ }${
344
+ existingPercent === '0'
345
+ ? ''
346
+ : changedColor(
347
+ ` ${
348
+ changedPercentage === '100'
349
+ ? 'up to date'
350
+ : `[needs update: ${100 - Number(changedPercentage)}%]`
351
+ }`
352
+ )
353
+ }`
354
+ );
342
355
  }
343
356
  if (migrateResult.errors?.length) {
344
357
  console.log(
@@ -468,7 +481,7 @@ export const printModelMigrationResult = (
468
481
 
469
482
  export const printNodeTreeOutput = (
470
483
  { log, messages }: ContensisCli,
471
- root: Node | MigrateNodesTree | undefined,
484
+ root: Node | Partial<MigrateNodesTree> | undefined,
472
485
  logDetail = 'errors',
473
486
  logLimit = 1000
474
487
  ) => {
@@ -496,7 +509,7 @@ export const printNodeTreeOutput = (
496
509
  log.line();
497
510
 
498
511
  const outputNode = (
499
- node: Node | MigrateNodesTree,
512
+ node: Partial<Node | MigrateNodesTree>,
500
513
  spaces: string,
501
514
  isRoot = false
502
515
  ) => {
@@ -506,6 +519,7 @@ export const printNodeTreeOutput = (
506
519
  const changesOutput =
507
520
  logDetail === 'changes' &&
508
521
  'status' in node &&
522
+ node.status &&
509
523
  ['create', 'update'].includes(node.status);
510
524
 
511
525
  const diffOutput =
@@ -514,7 +528,7 @@ export const printNodeTreeOutput = (
514
528
  node.diff?.replaceAll('\n', '');
515
529
 
516
530
  return `${
517
- 'status' in node
531
+ 'status' in node && node.status
518
532
  ? `${statusColour(node.status)(
519
533
  node.status.substring(0, 1).toUpperCase()
520
534
  )} `
@@ -531,8 +545,10 @@ export const printNodeTreeOutput = (
531
545
  : 'standardText'
532
546
  ](
533
547
  'isCanonical' in node && node.isCanonical
534
- ? log.boldText(fullOutput || isRoot ? node.path : `/${node.slug}`)
535
- : fullOutput || isRoot
548
+ ? log.boldText(
549
+ fullOutput || isRoot || !node.slug ? node.path : `/${node.slug}`
550
+ )
551
+ : fullOutput || isRoot || !node.slug
536
552
  ? node.path
537
553
  : `/${node.slug}`
538
554
  )}${node.entry ? ` ${log.helpText(node.entry.sys.contentTypeId)}` : ''}${
@@ -540,7 +556,7 @@ export const printNodeTreeOutput = (
540
556
  } ${'displayName' in node ? log.infoText(node.displayName) : ''}${
541
557
  fullOutput || (changesOutput && node.id !== node.originalId)
542
558
  ? `~n ${log.infoText(`id:`)} ${
543
- node.id === node.originalId
559
+ !('originalId' in node) || node.id === node.originalId
544
560
  ? node.id
545
561
  : `${node.id} ${log.infoText(`<= ${node.originalId}`)}`
546
562
  }`
@@ -551,6 +567,7 @@ export const printNodeTreeOutput = (
551
567
  node.parentId
552
568
  ? `~n ${log.infoText(
553
569
  `parentId: ${
570
+ !('originalParentId' in node) ||
554
571
  node.parentId === node.originalParentId
555
572
  ? node.parentId
556
573
  : `${node.parentId} <= ${node.originalParentId}`
@@ -575,7 +592,10 @@ export const printNodeTreeOutput = (
575
592
  }`;
576
593
  };
577
594
 
578
- const outputChildren = (node: Node | undefined, depth = 2) => {
595
+ const outputChildren = (
596
+ node: Partial<Node | MigrateNodesTree> | undefined,
597
+ depth = 2
598
+ ) => {
579
599
  let str = '';
580
600
  for (const child of ((node as any)?.children || []) as Node[]) {
581
601
  str += `${outputNode(child, Array(depth + 1).join(' '))}\n`;
@@ -13,26 +13,42 @@ export const csvFormatter = async <T>(entries: T | T[]) => {
13
13
 
14
14
  // Parse the flattened object to csv
15
15
  // const csv = stringify(flatEntries, { header: true });
16
+ // Create an exhaustive list of columns from the entries array
17
+ const columns = new Set<string>(flatEntries.map(e => Object.keys(e)).flat());
16
18
  const csv = await new Promise<string>((resolve, reject) => {
17
- stringify(flatEntries, { header: true }, (err, data) => {
18
- if (err) reject(err);
19
- resolve(data);
20
- });
19
+ stringify(
20
+ flatEntries,
21
+ {
22
+ header: true,
23
+ cast: { boolean: (value, context) => `${value}` },
24
+ columns: [...columns],
25
+ },
26
+ (err, data) => {
27
+ if (err) reject(err);
28
+ resolve(data);
29
+ }
30
+ );
21
31
  });
22
32
  return csv;
23
33
  };
24
34
 
25
35
  export const csvToJson = async <T = any>(data: string): Promise<T[]> => {
26
- // return parse(data, {
27
- // columns: true,
28
- // skip_empty_lines: true,
29
- // });
30
36
  return new Promise((resolve, reject) => {
31
37
  parse(
32
38
  data,
33
39
  {
34
40
  columns: true,
35
41
  skip_empty_lines: true,
42
+ cast: (value, context) => {
43
+ if (context.header || context.column === 'sys.version.versionNo')
44
+ return value;
45
+ if (value === '') return undefined;
46
+ try {
47
+ return JSON.parse(value);
48
+ } catch (e) {
49
+ return value;
50
+ }
51
+ },
36
52
  },
37
53
  (err, records) => {
38
54
  if (err) reject(err);
@@ -7,11 +7,13 @@ export const jsonFormatter = <T>(obj: T, fields?: string[]) =>
7
7
 
8
8
  // Flatten a JSON object such as an entry so there are no
9
9
  // nested object and the keys are presented like "sys.version.versionNo": "1.0"
10
- export const flattenObject = (obj: any) => flatten(cleaner(obj, ['workflow']));
10
+ export const flattenObject = (obj: any) =>
11
+ flatten(cleaner(obj, ['workflow']), { maxDepth: 99 });
11
12
 
12
13
  // Unflatten a JSON object such as an entry so the arrays and
13
14
  // nested objects are reconstructed - the opposite of flattenObject
14
- export const unflattenObject = (obj: any) => unflatten(obj);
15
+ export const unflattenObject = (obj: any) =>
16
+ unflatten(obj, { overwrite: true });
15
17
 
16
18
  // Will limit and sort an object's keys by an array of supplied fields
17
19
  export const limitFields = (obj: any, fields?: string[]): any => {
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const LIB_VERSION = "1.2.2-beta.0";
1
+ export const LIB_VERSION = "1.2.2-beta.10";