contensis-cli 1.0.0-beta.9 → 1.0.0-beta.91

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.
Files changed (125) hide show
  1. package/README.md +1146 -78
  2. package/cli.js +3 -0
  3. package/dist/commands/connect.js +3 -3
  4. package/dist/commands/connect.js.map +2 -2
  5. package/dist/commands/create.js +45 -10
  6. package/dist/commands/create.js.map +2 -2
  7. package/dist/commands/dev.js +75 -0
  8. package/dist/commands/dev.js.map +7 -0
  9. package/dist/commands/diff.js +57 -0
  10. package/dist/commands/diff.js.map +7 -0
  11. package/dist/commands/execute.js +103 -0
  12. package/dist/commands/execute.js.map +7 -0
  13. package/dist/commands/get.js +169 -32
  14. package/dist/commands/get.js.map +3 -3
  15. package/dist/commands/globalOptions.js +37 -12
  16. package/dist/commands/globalOptions.js.map +2 -2
  17. package/dist/commands/import.js +47 -12
  18. package/dist/commands/import.js.map +2 -2
  19. package/dist/commands/index.js +22 -2
  20. package/dist/commands/index.js.map +2 -2
  21. package/dist/commands/list.js +53 -10
  22. package/dist/commands/list.js.map +2 -2
  23. package/dist/commands/login.js +2 -2
  24. package/dist/commands/login.js.map +2 -2
  25. package/dist/commands/push.js +17 -13
  26. package/dist/commands/push.js.map +2 -2
  27. package/dist/commands/remove.js +51 -8
  28. package/dist/commands/remove.js.map +2 -2
  29. package/dist/commands/set.js +139 -12
  30. package/dist/commands/set.js.map +2 -2
  31. package/dist/index.js +1 -1
  32. package/dist/index.js.map +2 -2
  33. package/dist/localisation/en-GB.js +259 -49
  34. package/dist/localisation/en-GB.js.map +2 -2
  35. package/dist/mappers/ContensisCliService-to-RequestHanderSiteConfigYaml.js +56 -0
  36. package/dist/mappers/ContensisCliService-to-RequestHanderSiteConfigYaml.js.map +7 -0
  37. package/dist/mappers/DevInit-to-CIWorkflow.js +127 -0
  38. package/dist/mappers/DevInit-to-CIWorkflow.js.map +7 -0
  39. package/dist/mappers/DevInit-to-RolePermissions.js +54 -0
  40. package/dist/mappers/DevInit-to-RolePermissions.js.map +7 -0
  41. package/dist/mappers/DevRequests-to-RequestHanderSiteConfigYaml.js +56 -0
  42. package/dist/mappers/DevRequests-to-RequestHanderSiteConfigYaml.js.map +7 -0
  43. package/dist/models/CliService.d.js +17 -0
  44. package/dist/models/CliService.d.js.map +7 -0
  45. package/dist/models/DevService.d.js +17 -0
  46. package/dist/models/DevService.d.js.map +7 -0
  47. package/dist/providers/CredentialProvider.js +46 -14
  48. package/dist/providers/CredentialProvider.js.map +3 -3
  49. package/dist/providers/SessionCacheProvider.js +21 -1
  50. package/dist/providers/SessionCacheProvider.js.map +2 -2
  51. package/dist/providers/file-provider.js +12 -6
  52. package/dist/providers/file-provider.js.map +3 -3
  53. package/dist/services/ContensisCliService.js +1148 -421
  54. package/dist/services/ContensisCliService.js.map +3 -3
  55. package/dist/services/ContensisDevService.js +309 -0
  56. package/dist/services/ContensisDevService.js.map +7 -0
  57. package/dist/services/ContensisRoleService.js +87 -0
  58. package/dist/services/ContensisRoleService.js.map +7 -0
  59. package/dist/shell.js +58 -18
  60. package/dist/shell.js.map +3 -3
  61. package/dist/util/console.printer.js +171 -55
  62. package/dist/util/console.printer.js.map +2 -2
  63. package/dist/util/diff.js +102 -0
  64. package/dist/util/diff.js.map +7 -0
  65. package/dist/util/dotenv.js +57 -0
  66. package/dist/util/dotenv.js.map +7 -0
  67. package/dist/util/find.js +31 -0
  68. package/dist/util/find.js.map +7 -0
  69. package/dist/util/git.js +126 -0
  70. package/dist/util/git.js.map +7 -0
  71. package/dist/util/index.js +8 -2
  72. package/dist/util/index.js.map +3 -3
  73. package/dist/util/logger.js +90 -29
  74. package/dist/util/logger.js.map +3 -3
  75. package/dist/util/os.js +39 -0
  76. package/dist/util/os.js.map +7 -0
  77. package/dist/util/timers.js +49 -0
  78. package/dist/util/timers.js.map +7 -0
  79. package/dist/util/yaml.js +45 -0
  80. package/dist/util/yaml.js.map +7 -0
  81. package/dist/version.js +1 -1
  82. package/dist/version.js.map +1 -1
  83. package/esbuild.config.js +3 -1
  84. package/package.json +12 -3
  85. package/src/commands/connect.ts +3 -2
  86. package/src/commands/create.ts +61 -8
  87. package/src/commands/dev.ts +69 -0
  88. package/src/commands/diff.ts +41 -0
  89. package/src/commands/execute.ts +117 -0
  90. package/src/commands/get.ts +242 -28
  91. package/src/commands/globalOptions.ts +42 -12
  92. package/src/commands/import.ts +58 -8
  93. package/src/commands/index.ts +22 -1
  94. package/src/commands/list.ts +85 -11
  95. package/src/commands/login.ts +2 -1
  96. package/src/commands/push.ts +18 -11
  97. package/src/commands/remove.ts +66 -4
  98. package/src/commands/set.ts +189 -9
  99. package/src/index.ts +1 -4
  100. package/src/localisation/en-GB.ts +374 -66
  101. package/src/mappers/ContensisCliService-to-RequestHanderSiteConfigYaml.ts +44 -0
  102. package/src/mappers/DevInit-to-CIWorkflow.ts +150 -0
  103. package/src/mappers/DevInit-to-RolePermissions.ts +33 -0
  104. package/src/mappers/DevRequests-to-RequestHanderSiteConfigYaml.ts +44 -0
  105. package/src/models/CliService.d.ts +36 -0
  106. package/src/models/DevService.d.ts +5 -0
  107. package/src/models/JsModules.d.ts +1 -0
  108. package/src/providers/CredentialProvider.ts +51 -18
  109. package/src/providers/SessionCacheProvider.ts +29 -2
  110. package/src/providers/file-provider.ts +17 -6
  111. package/src/services/ContensisCliService.ts +1458 -518
  112. package/src/services/ContensisDevService.ts +365 -0
  113. package/src/services/ContensisRoleService.ts +76 -0
  114. package/src/shell.ts +68 -18
  115. package/src/util/console.printer.ts +240 -78
  116. package/src/util/diff.ts +113 -0
  117. package/src/util/dotenv.ts +37 -0
  118. package/src/util/find.ts +8 -0
  119. package/src/util/git.ts +130 -0
  120. package/src/util/index.ts +16 -7
  121. package/src/util/logger.ts +145 -31
  122. package/src/util/os.ts +7 -0
  123. package/src/util/timers.ts +24 -0
  124. package/src/util/yaml.ts +13 -0
  125. package/src/version.ts +1 -1
@@ -1,72 +1,77 @@
1
+ import to from 'await-to-js';
2
+ import chalk from 'chalk';
1
3
  import fs from 'fs';
2
- import path from 'path';
3
- import fetch from 'node-fetch';
4
4
  import inquirer from 'inquirer';
5
- import to from 'await-to-js';
6
- import { Component, ContentType } from 'contensis-core-api';
7
- import { isPassword, isSharedSecret, isUuid, url } from '~/util';
8
- import SessionCacheProvider from '../providers/SessionCacheProvider';
9
- import ContensisAuthService from './ContensisAuthService';
10
- import CredentialProvider from '~/providers/CredentialProvider';
11
- import { logError, Logger } from '~/util/logger';
12
- import { LogMessages } from '~/localisation/en-GB';
5
+ import fetch from 'node-fetch';
6
+ import path from 'path';
7
+
8
+ import { Component, ContentType, Project } from 'contensis-core-api';
9
+ import { Node } from 'contensis-delivery-api/lib/models';
10
+ import { Entry, Role } from 'contensis-management-api/lib/models';
13
11
  import {
14
12
  ContensisMigrationService,
15
13
  MigrateRequest,
16
14
  PushBlockParams,
17
15
  SourceCms,
18
16
  logEntriesTable,
17
+ ContentTypesResult,
18
+ Model,
19
+ MigrateModelsResult,
20
+ BlockActionType,
19
21
  } from 'migratortron';
20
- import { Entry } from 'contensis-management-api/lib/models';
21
22
 
22
- import { csvFormatter } from '~/util/csv.formatter';
23
- import { xmlFormatter } from '~/util/xml.formatter';
24
- import { jsonFormatter } from '~/util/json.formatter';
25
- import { printBlockVersion, printMigrateResult } from '~/util/console.printer';
26
- import { readJsonFile } from '~/providers/file-provider';
27
-
28
- type OutputFormat = 'json' | 'csv' | 'xml';
23
+ import ContensisAuthService from './ContensisAuthService';
29
24
 
30
- type OutputOptions = {
31
- format?: OutputFormat;
32
- output?: string;
33
- };
25
+ import { LogMessages } from '~/localisation/en-GB';
26
+ import { OutputFormat, OutputOptionsConstructorArg } from '~/models/CliService';
34
27
 
35
- interface IConnectOptions extends IAuthOptions {
36
- alias?: string;
37
- projectId?: string;
38
- }
28
+ import { readJsonFile } from '~/providers/file-provider';
29
+ import SessionCacheProvider from '../providers/SessionCacheProvider';
30
+ import CredentialProvider from '~/providers/CredentialProvider';
39
31
 
40
- interface IAuthOptions {
41
- user?: string;
42
- password?: string;
43
- clientId?: string;
44
- sharedSecret?: string;
45
- }
32
+ import {
33
+ isPassword,
34
+ isSharedSecret,
35
+ isUuid,
36
+ tryParse,
37
+ tryStringify,
38
+ url,
39
+ } from '~/util';
40
+ import {
41
+ printBlockVersion,
42
+ printMigrateResult,
43
+ printModelMigrationAnalysis,
44
+ printModelMigrationResult,
45
+ } from '~/util/console.printer';
46
+ import { csvFormatter } from '~/util/csv.formatter';
47
+ import { xmlFormatter } from '~/util/xml.formatter';
48
+ import { jsonFormatter } from '~/util/json.formatter';
49
+ import { diffLogStrings } from '~/util/diff';
50
+ import { logError, Logger } from '~/util/logger';
51
+ import { promiseDelay } from '~/util/timers';
52
+ import { findByIdOrName } from '~/util/find';
46
53
 
47
- interface IImportOptions {
48
- sourceEnv?: string;
49
- sourceProjectId?: string;
50
- }
54
+ let insecurePasswordWarningShown = false;
51
55
 
52
56
  class ContensisCli {
53
57
  static quit = (error?: Error) => {
54
58
  process.removeAllListeners('exit');
55
59
  const exitCode = error ? 1 : 0;
56
60
 
57
- console.info(`\nExiting contensis-cli with exit code: ${exitCode}\n`);
61
+ // console.info(`\nExiting contensis-cli with exit code: ${exitCode}\n`);
58
62
  process.exit(exitCode);
59
63
  };
60
64
 
61
- cache: SessionCache;
65
+ private command: CliCommand;
66
+ private format?: OutputFormat;
67
+ private output?: string;
68
+ private session: SessionCacheProvider;
69
+
62
70
  contensis?: ContensisMigrationService;
63
71
  contensisOpts: Partial<MigrateRequest>;
64
- contentTypes?: ContentType[];
65
- components?: Component[];
66
- currentEnv: string;
67
72
  currentProject: string;
68
- env: EnvironmentCache;
69
- sourceEnv?: string;
73
+
74
+ sourceAlias?: string;
70
75
  targetEnv?: string;
71
76
  urls:
72
77
  | {
@@ -78,20 +83,50 @@ class ContensisCli {
78
83
  iisPreviewWeb: string;
79
84
  }
80
85
  | undefined;
81
- private command: CliCommand;
82
- private format?: OutputFormat;
83
- private output?: string;
84
86
  log = Logger;
85
87
  messages = LogMessages;
86
- private session: SessionCacheProvider;
87
88
 
88
89
  verb: string;
89
90
  noun: string;
90
91
  thirdArg: string;
91
92
 
93
+ get cache() {
94
+ return this.session.Get();
95
+ }
96
+
97
+ get currentEnv() {
98
+ return this.cache.currentEnvironment || '';
99
+ }
100
+
101
+ set currentEnv(currentEnvironment: string) {
102
+ this.session.Update({ currentEnvironment });
103
+ }
104
+
105
+ get env() {
106
+ const currentEnvironment = this.currentEnv;
107
+ const environments = this.cache.environments || {};
108
+
109
+ if (!currentEnvironment) return {} as EnvironmentCache;
110
+ else if (!!environments[currentEnvironment])
111
+ return environments[currentEnvironment];
112
+ else {
113
+ return {
114
+ history: [],
115
+ lastUserId: '',
116
+ projects: [],
117
+ versionStatus: 'latest',
118
+ } as EnvironmentCache;
119
+ }
120
+ }
121
+
122
+ constructor(
123
+ args: string[],
124
+ outputOpts?: OutputOptionsConstructorArg,
125
+ contensisOpts?: Partial<MigrateRequest>
126
+ );
92
127
  constructor(
93
128
  args: string[],
94
- outputOpts?: OutputOptions & IConnectOptions & IImportOptions,
129
+ outputOpts?: OutputOptionsConstructorArg,
95
130
  contensisOpts: Partial<MigrateRequest> = {}
96
131
  ) {
97
132
  // console.log('args: ', JSON.stringify(args, null, 2));
@@ -106,40 +141,28 @@ class ContensisCli {
106
141
  }`.trim();
107
142
 
108
143
  this.session = new SessionCacheProvider();
109
- this.cache = this.session.Get();
144
+
110
145
  this.contensisOpts = contensisOpts;
111
146
  this.format = outputOpts?.format;
112
147
  this.output =
113
148
  outputOpts?.output && path.join(process.cwd(), outputOpts.output);
114
149
 
115
- const currentEnvironment =
116
- outputOpts?.alias || this.cache.currentEnvironment || '';
150
+ const currentEnvironment = outputOpts?.alias || this.currentEnv;
117
151
  const environments = this.cache.environments || {};
118
-
119
- if (!currentEnvironment) this.env = {} as EnvironmentCache;
120
- else if (!!environments[currentEnvironment])
121
- this.env = environments[currentEnvironment];
122
- else {
123
- this.env = {
124
- history: [],
125
- lastUserId: '',
126
- projects: [],
127
- versionStatus: 'latest',
128
- };
129
- }
152
+ this.currentEnv = currentEnvironment;
130
153
 
131
154
  const env = this.env;
132
155
 
133
156
  if (outputOpts?.projectId) env.currentProject = outputOpts.projectId;
134
157
  if (outputOpts?.user) env.lastUserId = outputOpts.user;
158
+ // setting this in env means passwordFallback is written to environments.json
135
159
  if (outputOpts?.password) env.passwordFallback = outputOpts.password;
136
160
  if (outputOpts?.clientId) env.lastUserId = outputOpts.clientId;
137
161
  if (outputOpts?.sharedSecret)
138
162
  env.passwordFallback = outputOpts.sharedSecret;
139
163
 
140
- this.currentEnv = currentEnvironment;
141
164
  this.currentProject = env?.currentProject || 'null';
142
- this.sourceEnv = outputOpts?.sourceEnv || currentEnvironment;
165
+ this.sourceAlias = outputOpts?.sourceAlias || currentEnvironment;
143
166
 
144
167
  if (currentEnvironment) {
145
168
  this.urls = url(currentEnvironment, env?.currentProject || 'website');
@@ -154,7 +177,7 @@ class ContensisCli {
154
177
  if (currentEnvironment) {
155
178
  env.history = [this.command];
156
179
  if (commandText) {
157
- environments[currentEnvironment] = this.env;
180
+ environments[currentEnvironment] = env;
158
181
  this.session.Update({
159
182
  currentEnvironment,
160
183
  environments,
@@ -181,45 +204,24 @@ class ContensisCli {
181
204
  };
182
205
 
183
206
  Connect = async (environment: string) => {
184
- const { cache, log, messages, session } = this;
207
+ const { log, messages, session } = this;
185
208
 
186
209
  if (environment) {
187
- const envCache = cache.environments[environment];
188
- if (!envCache)
189
- cache.environments[environment] = {
190
- versionStatus: 'published',
191
- history: [],
192
- lastUserId: '',
193
- projects: [],
194
- ...(!this.currentEnv ? this.env : {}),
195
- };
196
-
197
- this.env = cache.environments[environment];
198
210
  this.currentEnv = environment;
199
211
  this.urls = url(environment, 'website');
200
212
 
201
213
  const [fetchErr, response] = await to(fetch(this.urls.cms));
202
214
  if (response && response?.status < 400) {
203
215
  log.success(messages.connect.connected(environment));
216
+ session.UpdateEnv(this.env, environment);
204
217
 
205
218
  if (this.env?.lastUserId) {
206
- await this.ConnectContensis();
219
+ // await this.ConnectContensis();
207
220
  await this.PrintProjects();
208
221
  } else {
209
222
  log.warning(messages.projects.noList());
210
223
  log.help(messages.connect.tip());
211
- // cache.environments[environment] = {
212
- // versionStatus: 'published',
213
- // history: [],
214
- // lastUserId: '',
215
- // projects: [],
216
- // };
217
224
  }
218
-
219
- session.Update({
220
- currentEnvironment: environment,
221
- environments: cache.environments,
222
- });
223
225
  } else {
224
226
  // Cannot reach environment - status X
225
227
  log.error(
@@ -233,68 +235,75 @@ class ContensisCli {
233
235
  };
234
236
 
235
237
  ConnectContensis = async ({ commit = false } = {}) => {
236
- const { contensisOpts, currentEnv, env, log, messages } = this;
237
- const userId = env?.lastUserId;
238
- const isGuidId = userId && isUuid(userId);
238
+ if (!this.contensis) {
239
+ const { contensisOpts, currentEnv, env, log, messages } = this;
240
+ const userId = env?.lastUserId;
241
+ const isGuidId = userId && isUuid(userId);
239
242
 
240
- if (currentEnv && userId) {
241
- const [credentialError, credentials] = await new CredentialProvider(
242
- {
243
+ if (currentEnv && userId) {
244
+ const credentials = await this.GetCredentials(
243
245
  userId,
244
- alias: currentEnv,
245
- },
246
- env.passwordFallback
247
- ).Init();
246
+ env.passwordFallback
247
+ );
248
248
 
249
- if (credentialError && !credentials.current) {
250
- // Log problem with Credential Provider
251
- log.error(credentialError as any);
252
- return;
253
- }
254
- const cachedPassword = credentials?.current?.password;
255
-
256
- if (cachedPassword) {
257
- this.contensis = new ContensisMigrationService(
258
- {
259
- ...contensisOpts,
260
- source: {
261
- url: this.urls?.cms || '',
262
- username: !isGuidId ? userId : undefined,
263
- password: !isGuidId ? cachedPassword : undefined,
264
- clientId: isGuidId ? userId : undefined,
265
- sharedSecret: isGuidId ? cachedPassword : undefined,
266
- project: env?.currentProject || '',
267
- assetHostname: this.urls?.previewWeb,
249
+ const cachedPassword = credentials?.current?.password;
250
+
251
+ if (cachedPassword) {
252
+ this.contensis = new ContensisMigrationService(
253
+ {
254
+ ...contensisOpts,
255
+ source: {
256
+ url: this.urls?.cms || '',
257
+ username: !isGuidId ? userId : undefined,
258
+ password: !isGuidId ? cachedPassword : undefined,
259
+ clientId: isGuidId ? userId : undefined,
260
+ sharedSecret: isGuidId ? cachedPassword : undefined,
261
+ project: env?.currentProject || '',
262
+ assetHostname: this.urls?.previewWeb,
263
+ },
264
+ concurrency:
265
+ typeof contensisOpts.concurrency !== 'undefined'
266
+ ? contensisOpts.concurrency
267
+ : 3,
268
+ outputProgress: true,
268
269
  },
269
- concurrency:
270
- typeof contensisOpts.concurrency !== 'undefined'
271
- ? contensisOpts.concurrency
272
- : 3,
273
- outputProgress: true,
274
- },
275
- !commit
276
- );
270
+ !commit
271
+ );
272
+ }
273
+ } else {
274
+ if (!currentEnv) log.help(messages.connect.help());
275
+ if (!userId) log.help(messages.connect.tip());
277
276
  }
278
- } else {
279
- if (!currentEnv) log.help(messages.connect.help());
280
- if (!userId) log.help(messages.connect.tip());
281
277
  }
278
+ return this.contensis;
282
279
  };
283
280
 
284
281
  ConnectContensisImport = async ({
285
- commit,
286
- source,
287
- fileData,
288
- fileDataType,
282
+ commit = false,
283
+ fromFile,
284
+ importDataType,
289
285
  }: {
290
- commit: boolean;
291
- source: 'contensis' | 'file' | 'input';
292
- fileData?: any[] | string;
293
- fileDataType?: 'entries' | 'contentTypes' | 'components';
286
+ commit?: boolean;
287
+ fromFile?: string;
288
+ importDataType?:
289
+ | 'entries'
290
+ | 'contentTypes'
291
+ | 'components'
292
+ | 'models'
293
+ | 'user-input';
294
294
  }) => {
295
- const { contensisOpts, currentEnv, env, log, messages, sourceEnv } = this;
295
+ const source: 'contensis' | 'file' = fromFile ? 'file' : 'contensis';
296
+
297
+ const fileData = fromFile
298
+ ? readJsonFile<(Entry | ContentType | Component)[]>(fromFile) || []
299
+ : [];
300
+
301
+ if (typeof fileData === 'string')
302
+ throw new Error(`Import file format must be of type JSON`);
303
+
304
+ const { contensisOpts, currentEnv, env, log, messages, sourceAlias } = this;
296
305
  const environments = this.cache.environments || {};
297
- const sourceEnvironment = environments[sourceEnv || ''] || {};
306
+ const sourceEnvironment = environments[sourceAlias || ''] || {};
298
307
  const sourceCms =
299
308
  ('source' in contensisOpts && contensisOpts.source) ||
300
309
  ({} as Partial<SourceCms>);
@@ -303,7 +312,7 @@ class ContensisCli {
303
312
  const sourceProjectId =
304
313
  sourceCms.project || sourceEnvironment.currentProject || 'website';
305
314
  const isSourceGuidId = sourceUserId && isUuid(sourceUserId);
306
- const sourceUrls = url(sourceEnv || '', sourceProjectId);
315
+ const sourceUrls = url(sourceAlias || '', sourceProjectId);
307
316
 
308
317
  const sourcePassword =
309
318
  sourceCms.sharedSecret ||
@@ -314,40 +323,24 @@ class ContensisCli {
314
323
  const isTargetGuidId = targetUserId && isUuid(targetUserId);
315
324
 
316
325
  if (sourceUserId && currentEnv && targetUserId) {
317
- const [sourceCredentialError, sourceCredentials] =
318
- await new CredentialProvider(
319
- {
320
- userId: sourceUserId,
321
- alias: currentEnv,
322
- },
323
- sourcePassword
324
- ).Init();
325
-
326
- if (sourceCredentialError && !sourceCredentials.current) {
327
- // Log problem with Credential Provider
328
- logError(sourceCredentialError);
329
- return;
330
- }
326
+ const sourceCredentials = await this.GetCredentials(
327
+ sourceUserId,
328
+ sourcePassword,
329
+ sourceAlias,
330
+ false
331
+ );
332
+
331
333
  const cachedSourcePassword = sourceCredentials?.current?.password;
332
334
 
333
- const [targetCredentialError, targetCredentials] =
334
- await new CredentialProvider(
335
- {
336
- userId: targetUserId,
337
- alias: currentEnv,
338
- },
339
- env.passwordFallback
340
- ).Init();
335
+ const targetCredentials = await this.GetCredentials(
336
+ targetUserId,
337
+ env.passwordFallback
338
+ );
341
339
 
342
- if (targetCredentialError && !targetCredentials.current) {
343
- // Log problem with Credential Provider
344
- log.error(targetCredentialError as any);
345
- return;
346
- }
347
340
  const cachedTargetPassword = targetCredentials?.current?.password;
348
341
 
349
342
  if (cachedSourcePassword && cachedTargetPassword) {
350
- if (source === 'file' || source === 'input') {
343
+ if (source === 'file' || importDataType === 'user-input') {
351
344
  this.contensis = new ContensisMigrationService(
352
345
  {
353
346
  concurrency: 3,
@@ -362,12 +355,11 @@ class ContensisCli {
362
355
  targetProjects: [env.currentProject || ''],
363
356
  assetHostname: this.urls?.previewWeb,
364
357
  },
365
- ...(fileDataType ? { [fileDataType]: fileData } : {}),
358
+ ...(importDataType ? { [importDataType]: fileData } : {}),
366
359
  },
367
360
  !commit
368
361
  );
369
- }
370
- if (source === 'contensis') {
362
+ } else if (source === 'contensis') {
371
363
  this.contensis = new ContensisMigrationService(
372
364
  {
373
365
  concurrency: 3,
@@ -400,6 +392,40 @@ class ContensisCli {
400
392
  if (!currentEnv) log.help(messages.connect.help());
401
393
  if (!targetUserId) log.help(messages.connect.tip());
402
394
  }
395
+ return this.contensis;
396
+ };
397
+
398
+ GetCredentials = async (
399
+ userId: string,
400
+ password?: string,
401
+ currentEnv = this.currentEnv,
402
+ saveCurrentEnv = true
403
+ ): Promise<CredentialProvider | undefined> => {
404
+ const { log, messages } = this;
405
+ if (userId) {
406
+ const [credentialError, credentials] = await new CredentialProvider(
407
+ { userId, alias: currentEnv },
408
+ password
409
+ ).Init();
410
+
411
+ if (credentialError && !credentials.current) {
412
+ // Log problem with Credential Provider
413
+ log.error(credentialError as any);
414
+ return;
415
+ }
416
+
417
+ if (credentials.remarks.secure !== true) {
418
+ if (!insecurePasswordWarningShown) {
419
+ log.warning(messages.login.insecurePassword());
420
+ insecurePasswordWarningShown = true;
421
+ }
422
+ } else {
423
+ const env = this.cache.environments[currentEnv];
424
+ env.passwordFallback = undefined;
425
+ this.session.UpdateEnv(env, currentEnv, saveCurrentEnv);
426
+ }
427
+ return credentials;
428
+ }
403
429
  };
404
430
 
405
431
  Login = async (
@@ -409,111 +435,102 @@ class ContensisCli {
409
435
  promptPassword = true,
410
436
  sharedSecret = isSharedSecret(this.env.passwordFallback),
411
437
  silent = false,
438
+ attempt = 1,
412
439
  }: {
413
440
  password?: string;
414
441
  promptPassword?: boolean;
415
442
  sharedSecret?: string;
416
443
  silent?: boolean;
417
- }
444
+ attempt?: number;
445
+ } = {}
418
446
  ): Promise<string | undefined> => {
419
- let inputPassword = password;
447
+ let inputPassword = password || sharedSecret;
420
448
  const { log, messages } = this;
421
449
 
422
450
  if (userId) {
423
- const { cache, currentEnv, env } = this;
451
+ const { currentEnv, env } = this;
424
452
 
425
453
  if (currentEnv) {
426
- const [credentialError, credentials] = await new CredentialProvider(
427
- { userId, alias: currentEnv },
428
- inputPassword || sharedSecret
429
- ).Init();
430
-
431
- if (credentialError && !credentials.current) {
432
- // Log problem with Credential Provider
433
- log.error(credentialError as any);
434
- return;
435
- }
436
-
437
- if (credentials.remarks.secure !== true)
438
- log.warning(messages.login.insecurePassword());
439
-
440
- const cachedPassword = isPassword(credentials?.current?.password);
441
- const cachedSecret = isSharedSecret(credentials?.current?.password);
442
-
443
- if (
444
- !sharedSecret &&
445
- !inputPassword &&
446
- !cachedPassword &&
447
- !cachedSecret &&
448
- promptPassword
449
- ) {
450
- // Password prompt
451
- ({ inputPassword } = await inquirer.prompt([
452
- {
453
- type: 'password',
454
- message: messages.login.passwordPrompt(currentEnv, userId),
455
- name: 'inputPassword',
456
- mask: '*',
457
- prefix: undefined,
458
- },
459
- ]));
460
- }
454
+ const credentials = await this.GetCredentials(userId, inputPassword);
455
+
456
+ if (credentials) {
457
+ const cachedPassword = isPassword(credentials.current?.password);
458
+ const cachedSecret = isSharedSecret(credentials.current?.password);
459
+
460
+ if (!cachedPassword && !cachedSecret && promptPassword) {
461
+ // Password prompt
462
+ ({ inputPassword } = await inquirer.prompt([
463
+ {
464
+ type: 'password',
465
+ message: messages.login.passwordPrompt(currentEnv, userId),
466
+ name: 'inputPassword',
467
+ mask: '*',
468
+ prefix: undefined,
469
+ },
470
+ ]));
471
+ }
461
472
 
462
- if (sharedSecret || cachedSecret || inputPassword || cachedPassword) {
463
- const authService = new ContensisAuthService({
464
- username: userId,
465
- password: inputPassword || cachedPassword,
466
- projectId: env?.currentProject || 'website',
467
- rootUrl: this.urls?.cms || '',
468
- clientId: userId,
469
- clientSecret: sharedSecret || cachedSecret,
470
- });
473
+ if (inputPassword || cachedPassword || cachedSecret) {
474
+ const authService = new ContensisAuthService({
475
+ username: userId,
476
+ password: inputPassword || cachedPassword,
477
+ projectId: env?.currentProject || 'website',
478
+ rootUrl: this.urls?.cms || '',
479
+ clientId: userId,
480
+ clientSecret: sharedSecret || cachedSecret,
481
+ });
482
+
483
+ const [authError, bearerToken] = await to(
484
+ authService.BearerToken()
485
+ );
471
486
 
472
- const [authError, bearerToken] = await to(authService.BearerToken());
473
-
474
- // Login successful
475
- if (bearerToken) {
476
- // Set env vars
477
- env.authToken = bearerToken;
478
- env.lastUserId = userId;
479
- env.passwordFallback =
480
- credentials.remarks.secure !== true
481
- ? credentials.current?.password
482
- : undefined;
483
-
484
- if (!silent) {
485
- Logger.success(messages.login.success(currentEnv, userId));
486
- await this.PrintProjects();
487
+ // Login successful
488
+ if (bearerToken) {
489
+ // Set env vars
490
+ env.authToken = bearerToken;
491
+ env.lastUserId = userId;
492
+ env.passwordFallback =
493
+ credentials.remarks.secure !== true
494
+ ? credentials.current?.password
495
+ : undefined;
496
+
497
+ // Persist env before finding projects or doing anything else
498
+ this.session.UpdateEnv(env);
499
+ if (inputPassword) await credentials.Save(inputPassword);
500
+ if (sharedSecret) await credentials.Save(sharedSecret);
501
+
502
+ if (!silent) {
503
+ Logger.success(messages.login.success(currentEnv, userId));
504
+ await this.PrintProjects();
505
+ }
506
+ } else if (authError) {
507
+ Logger.error(authError.toString());
508
+ // Clear env vars
509
+ env.authToken = '';
510
+ env.lastUserId = '';
511
+ env.passwordFallback = undefined;
512
+ // Persist env to remove cleared values
513
+ this.session.UpdateEnv(env);
514
+
515
+ // If the auth error was raised using a cached password
516
+ if (
517
+ (cachedPassword || cachedSecret) &&
518
+ credentials.remarks.secure
519
+ ) {
520
+ // Remove any bad stored credential and trigger login prompt again
521
+ await credentials.Delete();
522
+ return await this.Login(userId, { password, sharedSecret });
523
+ } else {
524
+ throw new Error(messages.login.failed(currentEnv, userId));
525
+ }
487
526
  }
488
- if (inputPassword) await credentials.Save(inputPassword);
489
- if (sharedSecret) await credentials.Save(sharedSecret);
490
- } else if (authError) {
491
- Logger.error(authError.toString());
492
- // Clear env vars
493
- env.authToken = '';
494
- env.lastUserId = '';
495
- env.passwordFallback = undefined;
496
-
497
- // If the auth error was raised using a cached password
498
- if (
499
- (cachedPassword || cachedSecret) &&
500
- credentials.remarks.secure
501
- ) {
502
- // Remove any bad stored credential and trigger login prompt again
503
- await credentials.Delete();
504
- return await this.Login(userId, { password, sharedSecret });
505
- } else {
506
- throw new Error(messages.login.failed(currentEnv, userId));
507
- }
508
- }
509
527
 
510
- // Persist env
511
- this.session.Update({
512
- environments: cache.environments,
513
- });
514
- return env.authToken;
515
- } else {
516
- Logger.error(messages.login.passwordPrompt(currentEnv, userId));
528
+ return env.authToken;
529
+ } else {
530
+ Logger.error(messages.login.passwordPrompt());
531
+ if (attempt < 2)
532
+ return await this.Login(userId, { attempt: attempt + 1 });
533
+ }
517
534
  }
518
535
  } else {
519
536
  // No environment set, use `contensis connect {alias}` first
@@ -525,19 +542,65 @@ class ContensisCli {
525
542
  }
526
543
  };
527
544
 
545
+ PrintContensisVersion = async () => {
546
+ const { log, messages } = this;
547
+ const contensis = await this.ConnectContensis();
548
+
549
+ if (contensis) {
550
+ // Retrieve projects list for env
551
+ const [projectsErr, projects] = await to(
552
+ contensis.projects.GetSourceProjects()
553
+ );
554
+
555
+ if (Array.isArray(projects)) {
556
+ // Print contensis version to console
557
+ this.HandleFormattingAndOutput(contensis.contensisVersion, () =>
558
+ log.raw(log.highlightText(contensis.contensisVersion))
559
+ );
560
+ }
561
+
562
+ if (projectsErr) {
563
+ log.error(messages.projects.noList());
564
+ log.error(projectsErr.message);
565
+ }
566
+ }
567
+ };
568
+
569
+ PrintBearerToken = async () => {
570
+ const { log, messages } = this;
571
+ const contensis = await this.ConnectContensis();
572
+
573
+ if (contensis) {
574
+ // Retrieve token for env
575
+ const [error, token] = await to(
576
+ contensis.content.sourceRepo.repo.BearerToken()
577
+ );
578
+ if (token) {
579
+ // Print bearer token to console
580
+ this.HandleFormattingAndOutput(token, () =>
581
+ log.raw(log.highlightText(token))
582
+ );
583
+ }
584
+
585
+ if (error) {
586
+ log.error(messages.projects.noList());
587
+ log.error(error.message);
588
+ }
589
+ }
590
+ };
591
+
528
592
  PrintProjects = async () => {
529
- const { cache, currentEnv, currentProject, log, messages, session } = this;
530
- if (!this.contensis) await this.ConnectContensis();
593
+ const { currentProject, log, messages, session } = this;
594
+ const contensis = await this.ConnectContensis();
531
595
 
532
- if (this.contensis) {
596
+ if (contensis) {
533
597
  // Retrieve projects list for env
534
598
  const [projectsErr, projects] = await to(
535
- this.contensis.projects.GetSourceProjects()
599
+ contensis.projects.GetSourceProjects()
536
600
  );
537
601
 
538
602
  if (Array.isArray(projects)) {
539
603
  // save these projects in cache
540
- const currentVals = cache.environments[currentEnv] || {};
541
604
  const nextCurrentProject =
542
605
  currentProject && currentProject !== 'null'
543
606
  ? currentProject
@@ -545,46 +608,80 @@ class ContensisCli {
545
608
  ? 'website'
546
609
  : undefined;
547
610
 
548
- cache.environments[currentEnv] = {
549
- ...currentVals,
611
+ session.UpdateEnv({
550
612
  projects: projects.map(p => p.id),
551
613
  currentProject: nextCurrentProject,
552
- };
614
+ });
553
615
 
554
616
  log.success(messages.projects.list());
617
+ log.raw('');
618
+
555
619
  this.HandleFormattingAndOutput(projects, () => {
556
620
  // print the projects to console
557
- for (const project of projects) {
621
+ for (const project of projects.sort((a, b) =>
622
+ a.id.localeCompare(b.id)
623
+ )) {
624
+ let color;
625
+ try {
626
+ color = chalk.keyword((project as any).color);
627
+ } catch (ex) {
628
+ color = chalk.white;
629
+ }
558
630
  console.log(
559
- ` - ${nextCurrentProject === project.id ? '* ' : ''}[${
560
- project.primaryLanguage
561
- }] ${project.id}`
631
+ `${
632
+ nextCurrentProject === project.id
633
+ ? `>> ${log.boldText(color(project.id))}`
634
+ : ` ${color(project.id)}`
635
+ } ${log.infoText(
636
+ `[${project.supportedLanguages
637
+ .map(l =>
638
+ l === project.primaryLanguage ? `*${log.boldText(l)}` : l
639
+ )
640
+ .join(' ')}]`
641
+ )}`
562
642
  );
563
643
  }
564
644
  });
565
645
 
566
- session.Update({
567
- environments: cache.environments,
568
- });
646
+ if (!this.SetProject(nextCurrentProject))
647
+ log.warning(messages.projects.tip());
648
+ }
569
649
 
570
- if (nextCurrentProject) {
571
- this.env = cache.environments[currentEnv];
572
- this.SetProject(nextCurrentProject);
573
- }
650
+ if (projectsErr) {
651
+ log.error(messages.projects.noList());
652
+ log.error(projectsErr.message);
653
+ }
654
+ }
655
+ };
656
+
657
+ PrintProject = async (projectId = this.currentProject) => {
658
+ const { log, messages, session } = this;
659
+ const contensis = await this.ConnectContensis();
660
+
661
+ if (contensis) {
662
+ // Retrieve projects list for env
663
+ const [projectsErr, projects] = await to(
664
+ contensis.projects.GetSourceProjects()
665
+ );
666
+
667
+ const foundProject = projects?.find(
668
+ p => p.id.toLowerCase() === projectId.toLowerCase()
669
+ );
670
+
671
+ if (foundProject) {
672
+ log.raw('');
673
+ this.HandleFormattingAndOutput(foundProject, log.object);
574
674
  }
575
675
 
576
676
  if (projectsErr) {
577
677
  log.error(messages.projects.noList());
578
678
  log.error(projectsErr.message);
579
679
  }
580
- // } else {
581
- // log.warning(messages.projects.noList());
582
- // log.help(messages.connect.tip());
583
680
  }
584
681
  };
585
682
 
586
- SetProject = async (projectId = '') => {
587
- const { cache, env, log, messages, session } = this;
683
+ SetProject = (projectId = 'website') => {
684
+ const { env, log, messages, session } = this;
588
685
  let nextProjectId: string | undefined;
589
686
  if (env?.projects.length > 0 && env?.lastUserId) {
590
687
  nextProjectId = env.projects.find(
@@ -592,10 +689,9 @@ class ContensisCli {
592
689
  );
593
690
  if (nextProjectId) {
594
691
  env.currentProject = nextProjectId;
595
- session.Update({
596
- environments: cache.environments,
597
- });
692
+ session.UpdateEnv(env);
598
693
  log.success(messages.projects.set(projectId));
694
+ log.raw('');
599
695
  } else {
600
696
  log.error(messages.projects.failedSet(projectId));
601
697
  }
@@ -607,8 +703,8 @@ class ContensisCli {
607
703
  return nextProjectId;
608
704
  };
609
705
 
610
- SetVersion = async (versionStatus: 'latest' | 'published') => {
611
- const { cache, env, log, messages, session } = this;
706
+ SetVersion = (versionStatus: 'latest' | 'published') => {
707
+ const { env, log, messages, session } = this;
612
708
  if (!['latest', 'published'].includes(versionStatus)) {
613
709
  log.error(messages.version.invalid(versionStatus));
614
710
  return false;
@@ -618,10 +714,7 @@ class ContensisCli {
618
714
  return false;
619
715
  }
620
716
  if (env?.projects.length > 0 && env?.lastUserId) {
621
- env.versionStatus = versionStatus;
622
- session.Update({
623
- environments: cache.environments,
624
- });
717
+ session.UpdateEnv({ versionStatus });
625
718
  log.success(messages.version.set(this.currentEnv, versionStatus));
626
719
  return true;
627
720
  } else {
@@ -632,30 +725,13 @@ class ContensisCli {
632
725
  }
633
726
  };
634
727
 
635
- HydrateContensis = async () => {
636
- const { log } = this;
637
- if (!this.contensis) await this.ConnectContensis();
638
-
639
- if (this.contensis) {
640
- // Retrieve content types list for env
641
- const [contensisErr, models] = await to(
642
- this.contensis.models.HydrateContensisRepositories()
643
- );
644
-
645
- if (contensisErr) {
646
- log.error(contensisErr.message);
647
- return contensisErr;
648
- }
649
- }
650
- };
651
-
652
728
  PrintApiKeys = async () => {
653
729
  const { currentEnv, log, messages } = this;
654
- if (!this.contensis) await this.ConnectContensis();
730
+ const contensis = await this.ConnectContensis();
655
731
 
656
- if (this.contensis) {
732
+ if (contensis) {
657
733
  // Retrieve keys list for env
658
- const [keysErr, apiKeys] = await this.contensis.apiKeys.GetKeys();
734
+ const [keysErr, apiKeys] = await contensis.apiKeys.GetKeys();
659
735
 
660
736
  if (Array.isArray(apiKeys)) {
661
737
  log.success(messages.keys.list(currentEnv));
@@ -689,27 +765,29 @@ class ContensisCli {
689
765
 
690
766
  CreateApiKey = async (name: string, description?: string) => {
691
767
  const { currentEnv, log, messages } = this;
692
- if (!this.contensis) await this.ConnectContensis();
768
+ const contensis = await this.ConnectContensis();
693
769
 
694
- if (this.contensis) {
695
- const [err, key] = await this.contensis.apiKeys.CreateKey(
696
- name,
697
- description
698
- );
770
+ if (contensis) {
771
+ const [err, key] = await contensis.apiKeys.CreateKey(name, description);
699
772
 
700
773
  if (key) {
701
774
  log.success(messages.keys.created(currentEnv, name));
702
775
 
703
776
  // print the key details to console
704
777
  console.log(
705
- ` - ${key.name}${
706
- key.description ? ` (${key.description})` : ''
707
- } [${key.dateModified.toString().substring(0, 10)} ${key.modifiedBy}]`
778
+ ` - ${chalk.bold(key.name)} [${key.dateModified
779
+ .toString()
780
+ .substring(0, 10)} ${key.modifiedBy}]`
708
781
  );
709
- console.log(` - id: ${key.id}`);
710
- console.log(` - sharedSecret: ${key.sharedSecret}`);
782
+ if (key.description)
783
+ console.log(` ${log.infoText(key.description)}`);
784
+ console.log(` ${chalk.bold.grey`id`}: ${key.id}`);
785
+ console.log(
786
+ ` ${chalk.bold.grey`sharedSecret`}: ${key.sharedSecret}`
787
+ );
788
+ console.log('');
789
+ log.help(messages.keys.tip());
711
790
  }
712
- console.log('');
713
791
 
714
792
  if (err) {
715
793
  log.error(messages.keys.failedCreate(currentEnv, name), err);
@@ -717,42 +795,447 @@ class ContensisCli {
717
795
  }
718
796
  };
719
797
 
720
- RemoveApiKey = async (id: string) => {
721
- const { currentEnv, log, messages } = this;
722
- if (!this.contensis) await this.ConnectContensis({ commit: true });
798
+ RemoveApiKey = async (id: string) => {
799
+ const { currentEnv, log, messages } = this;
800
+ const contensis = await this.ConnectContensis({ commit: true });
801
+
802
+ if (contensis) {
803
+ const [err, key] = await contensis.apiKeys.RemoveKey(id);
804
+
805
+ if (!err) {
806
+ log.success(messages.keys.removed(currentEnv, id));
807
+ console.log('');
808
+ } else {
809
+ log.error(messages.keys.failedRemove(currentEnv, id), err);
810
+ }
811
+ }
812
+ };
813
+
814
+ PrintRoles = async () => {
815
+ const { currentEnv, log, messages } = this;
816
+ const contensis = await this.ConnectContensis();
817
+
818
+ if (contensis) {
819
+ // Retrieve roles list for env
820
+ const [rolesErr, roles] = await to(contensis.roles.GetRoles());
821
+
822
+ if (Array.isArray(roles)) {
823
+ log.success(messages.roles.list(currentEnv));
824
+
825
+ if (!roles.length) log.help(messages.roles.noneExist());
826
+
827
+ this.HandleFormattingAndOutput(roles, () => {
828
+ // print the roles to console
829
+ for (const {
830
+ id,
831
+ name,
832
+ description,
833
+ enabled,
834
+ assignments,
835
+ permissions,
836
+ } of roles) {
837
+ const color = enabled ? (s: string) => s : log.infoText;
838
+
839
+ console.log(color(` - ${chalk.bold(name)} ${log.infoText(id)}`));
840
+ if (description) console.log(log.infoText(` ${description}`));
841
+ if (enabled === false)
842
+ console.log(` ${chalk.bold.grey('enabled')}: false`);
843
+ if (assignments.groups?.length)
844
+ console.log(
845
+ ` ${chalk.bold.grey('groups')}: ${assignments.groups.join(
846
+ ', '
847
+ )}`
848
+ );
849
+ if (assignments.users?.length)
850
+ console.log(
851
+ ` ${chalk.bold.grey('users')}: ${assignments.users.join(
852
+ ', '
853
+ )}`
854
+ );
855
+ if (assignments.apiKeys?.length)
856
+ console.log(
857
+ ` ${chalk.bold.grey('keys')}: ${assignments.apiKeys.join(
858
+ ', '
859
+ )}`
860
+ );
861
+
862
+ if (permissions.entries?.length) {
863
+ console.log(` ${chalk.bold.grey('entries')}:`);
864
+ for (const p of permissions.entries)
865
+ console.log(
866
+ ` ${p.id}: ${log.infoText(
867
+ p.actions.length > 2
868
+ ? p.actions.length
869
+ : p.actions.join(', ')
870
+ )}`
871
+ );
872
+ }
873
+ if (permissions.contentTypes?.length)
874
+ console.log(
875
+ ` ${chalk.bold.grey(
876
+ 'contentTypes'
877
+ )}: ${permissions.contentTypes
878
+ .map(
879
+ p =>
880
+ `${p.id} [${p.actions.join(',')}] ${p.languages.join(
881
+ ' '
882
+ )}`
883
+ )
884
+ .join(', ')}`
885
+ );
886
+ }
887
+ });
888
+ }
889
+
890
+ if (rolesErr) {
891
+ log.error(messages.roles.noList(currentEnv));
892
+ log.error(jsonFormatter(rolesErr));
893
+ }
894
+ }
895
+ };
896
+
897
+ PrintRole = async (roleNameOrId: string) => {
898
+ const { currentEnv, log, messages } = this;
899
+ const contensis = await this.ConnectContensis();
900
+
901
+ if (contensis) {
902
+ // Retrieve roles list for env
903
+ const [rolesErr, roles] = await to(contensis.roles.GetRoles());
904
+
905
+ if (Array.isArray(roles)) {
906
+ log.success(messages.roles.list(currentEnv));
907
+
908
+ const role = findByIdOrName(roles, roleNameOrId);
909
+
910
+ if (role) this.HandleFormattingAndOutput(role, log.object);
911
+ else log.error(messages.roles.failedGet(currentEnv, roleNameOrId));
912
+ }
913
+
914
+ if (rolesErr) {
915
+ log.error(messages.roles.noList(currentEnv));
916
+ log.error(jsonFormatter(rolesErr));
917
+ }
918
+ }
919
+ };
920
+
921
+ CreateRole = async (role: Partial<Role>) => {
922
+ const { currentEnv, log, messages } = this;
923
+ const contensis = await this.ConnectContensis();
924
+
925
+ if (contensis) {
926
+ const [err, created] = await contensis.roles.CreateRole(role as Role);
927
+
928
+ if (created) {
929
+ log.success(
930
+ messages.roles.created(currentEnv, role.id || role.name || '')
931
+ );
932
+
933
+ this.HandleFormattingAndOutput(created, log.object);
934
+
935
+ log.help(messages.roles.tip());
936
+ return role.id;
937
+ }
938
+
939
+ if (err) {
940
+ log.error(
941
+ messages.roles.failedCreate(currentEnv, role.id || role.name || ''),
942
+ err
943
+ );
944
+ }
945
+ }
946
+ };
947
+
948
+ UpdateRole = async (roleNameOrId: string, role: Partial<Role>) => {
949
+ const { currentEnv, log, messages } = this;
950
+ const contensis = await this.ConnectContensis();
951
+
952
+ if (contensis) {
953
+ // Retrieve roles list for env
954
+ const [rolesErr, roles] = await to(contensis.roles.GetRoles());
955
+
956
+ if (Array.isArray(roles)) {
957
+ log.success(messages.roles.list(currentEnv));
958
+
959
+ const existingRole = findByIdOrName(roles, roleNameOrId, true);
960
+ if (existingRole) {
961
+ log.info(messages.roles.setPayload());
962
+ log.object(role);
963
+ log.raw(``);
964
+ const [updateErr, updated] = await contensis.roles.UpdateRole(
965
+ existingRole.id,
966
+ role
967
+ );
968
+ if (updateErr)
969
+ log.error(messages.roles.failedSet(currentEnv, roleNameOrId));
970
+ else {
971
+ log.success(messages.roles.set());
972
+
973
+ this.HandleFormattingAndOutput(updated, log.object);
974
+ }
975
+ } else {
976
+ // Role does not exist
977
+ log.error(messages.roles.failedGet(currentEnv, roleNameOrId));
978
+ }
979
+ }
980
+
981
+ if (rolesErr) {
982
+ log.error(messages.roles.noList(currentEnv));
983
+ log.error(jsonFormatter(rolesErr));
984
+ }
985
+ }
986
+ };
987
+
988
+ RemoveRole = async (roleNameOrId: string) => {
989
+ const { currentEnv, log, messages } = this;
990
+ const contensis = await this.ConnectContensis();
991
+
992
+ if (contensis) {
993
+ // Retrieve roles list for env
994
+ const [rolesErr, roles] = await to(contensis.roles.GetRoles());
995
+
996
+ if (Array.isArray(roles)) {
997
+ log.success(messages.roles.list(currentEnv));
998
+
999
+ const existingRole = findByIdOrName(roles, roleNameOrId, true);
1000
+
1001
+ if (existingRole) {
1002
+ const [deleteErr] = await contensis.roles.RemoveRole(existingRole.id);
1003
+
1004
+ if (deleteErr)
1005
+ log.error(messages.roles.failedRemove(currentEnv, roleNameOrId));
1006
+ else log.success(messages.roles.removed(currentEnv, roleNameOrId));
1007
+ } else {
1008
+ // Role does not exist
1009
+ log.error(messages.roles.failedGet(currentEnv, roleNameOrId));
1010
+ }
1011
+ }
1012
+
1013
+ if (rolesErr) {
1014
+ log.error(messages.roles.noList(currentEnv));
1015
+ log.error(jsonFormatter(rolesErr));
1016
+ }
1017
+ }
1018
+ };
1019
+
1020
+ CreateProject = async (project: Project) => {
1021
+ const { currentEnv, log, messages } = this;
1022
+ const contensis = await this.ConnectContensis();
1023
+
1024
+ if (contensis) {
1025
+ const [err, created] = await contensis.projects.CreateProject(project);
1026
+
1027
+ if (created) {
1028
+ log.success(messages.projects.created(currentEnv, project.id));
1029
+
1030
+ this.HandleFormattingAndOutput(created, () => {
1031
+ // set the CLI project to the newly created project
1032
+ this.SetProject(project.id);
1033
+ // print all the projects to console
1034
+ this.PrintProjects();
1035
+ });
1036
+ return project.id;
1037
+ }
1038
+
1039
+ if (err) {
1040
+ log.error(messages.projects.failedCreate(currentEnv, project.id), err);
1041
+ }
1042
+ }
1043
+ };
1044
+
1045
+ UpdateProject = async (project: Partial<Project>) => {
1046
+ const { currentEnv, currentProject, log, messages } = this;
1047
+ const contensis = await this.ConnectContensis();
1048
+
1049
+ if (contensis) {
1050
+ const [err, updated] = await contensis.projects.UpdateProject({
1051
+ id: currentProject,
1052
+ ...project,
1053
+ });
1054
+
1055
+ if (updated) {
1056
+ log.success(messages.projects.updated(currentEnv, currentProject));
1057
+
1058
+ this.HandleFormattingAndOutput(updated, log.object);
1059
+ return updated.id;
1060
+ }
1061
+
1062
+ if (err) {
1063
+ log.error(
1064
+ messages.projects.failedUpdate(currentEnv, currentProject),
1065
+ err
1066
+ );
1067
+ }
1068
+ }
1069
+ };
1070
+
1071
+ PrintContentModels = async (modelIds: string[] = []) => {
1072
+ const { currentProject, log, messages } = this;
1073
+ const contensis = await this.ConnectContensis();
1074
+ if (contensis) {
1075
+ // Retrieve models list for env
1076
+ const models = await contensis.models.contentModels();
1077
+ const contentTypes = await contensis.models.contentTypes();
1078
+ const components = await contensis.models.components();
1079
+
1080
+ // Models to output to console
1081
+ const returnModels = modelIds?.length
1082
+ ? models?.filter((m: Model) =>
1083
+ modelIds.some(id => id.toLowerCase() === m.id.toLowerCase())
1084
+ )
1085
+ : undefined;
1086
+
1087
+ // Generate a list of contentTypeIds and componentIds from all models
1088
+ // and dependencies
1089
+ const contentTypeIds = Array.from(
1090
+ new Set([
1091
+ ...(returnModels || models || []).map(m => m.id),
1092
+ ...(returnModels || models || [])
1093
+ .map(m => m.dependencies?.contentTypes?.map(c => c[0]) || [])
1094
+ .flat(),
1095
+ ])
1096
+ );
1097
+ const componentIds = Array.from(
1098
+ new Set(
1099
+ (returnModels || models || [])
1100
+ .map(m => m.dependencies?.components?.map(c => c[0]) || [])
1101
+ .flat()
1102
+ )
1103
+ );
1104
+
1105
+ // Create an array of all the content types and component definitions
1106
+ // we will use this when outputting to a file
1107
+ const contentModelBackup = [
1108
+ ...contentTypes.filter(c =>
1109
+ contentTypeIds.map(i => i.toLowerCase()).includes(c.id.toLowerCase())
1110
+ ),
1111
+ ...components.filter(c =>
1112
+ componentIds.map(i => i.toLowerCase()).includes(c.id.toLowerCase())
1113
+ ),
1114
+ ];
1115
+
1116
+ if (Array.isArray(returnModels)) {
1117
+ log.success(messages.models.list(currentProject));
1118
+ this.HandleFormattingAndOutput(contentModelBackup, () => {
1119
+ // print the content models to console
1120
+ for (const model of returnModels) {
1121
+ log.raw('');
1122
+ log.object(model);
1123
+ }
1124
+ log.raw('');
1125
+ });
1126
+ } else {
1127
+ log.success(
1128
+ messages.models.get(currentProject, models?.length.toString() || '0')
1129
+ );
1130
+ log.raw('');
1131
+ if (models?.length) {
1132
+ this.HandleFormattingAndOutput(contentModelBackup, () => {
1133
+ // print the content models to console
1134
+ for (const model of models) {
1135
+ const components = model.components?.length || 0;
1136
+ const contentTypes = model.contentTypes?.length || 0;
1137
+ const dependencies =
1138
+ (model.dependencies?.components?.length || 0) +
1139
+ (model.dependencies?.contentTypes?.length || 0);
1140
+ const dependencyOf =
1141
+ (model.dependencyOf?.components?.length || 0) +
1142
+ (model.dependencyOf?.contentTypes?.length || 0);
1143
+
1144
+ const hasAny =
1145
+ components + contentTypes + dependencies + dependencyOf;
1146
+ log.raw(
1147
+ ` - ${log.highlightText(log.boldText(model.id))} ${
1148
+ hasAny
1149
+ ? log.infoText(
1150
+ `{ ${components ? `components: ${components}, ` : ''}${
1151
+ contentTypes ? `contentTypes: ${contentTypes}, ` : ''
1152
+ }${
1153
+ dependencies ? `references: ${dependencies}, ` : ''
1154
+ }${
1155
+ dependencyOf ? `required by: ${dependencyOf}` : ''
1156
+ } }`
1157
+ )
1158
+ : ''
1159
+ }`
1160
+ );
1161
+ }
1162
+ log.raw('');
1163
+ });
1164
+ }
1165
+ }
1166
+ }
1167
+ };
1168
+
1169
+ ImportContentModels = async ({
1170
+ commit,
1171
+ fromFile,
1172
+ }: {
1173
+ commit: boolean;
1174
+ fromFile: string;
1175
+ }) => {
1176
+ const { currentProject, log, messages } = this;
723
1177
 
724
- if (this.contensis) {
725
- const [err, key] = await this.contensis.apiKeys.RemoveKey(id);
1178
+ const fileData = fromFile
1179
+ ? readJsonFile<(ContentType | Component)[]>(fromFile) || []
1180
+ : [];
1181
+ if (typeof fileData === 'string')
1182
+ throw new Error(`Import file format must be of type JSON`);
726
1183
 
727
- if (!err) {
728
- log.success(messages.keys.removed(currentEnv, id));
729
- console.log('');
1184
+ const contensis = await this.ConnectContensisImport({
1185
+ commit,
1186
+ fromFile,
1187
+ importDataType: 'models',
1188
+ });
1189
+
1190
+ if (contensis) {
1191
+ log.line();
1192
+ if (contensis.isPreview) {
1193
+ console.log(log.successText(` -- IMPORT PREVIEW -- `));
730
1194
  } else {
731
- log.error(messages.keys.failedRemove(currentEnv, id), err);
1195
+ console.log(log.warningText(` *** COMMITTING IMPORT *** `));
732
1196
  }
733
- }
734
- };
735
1197
 
736
- GetContentTypes = async () => {
737
- const { currentProject, log, messages } = this;
738
- let err;
739
- if (!this.contensis) err = await this.HydrateContensis();
1198
+ const [migrateErr, result] = await contensis.MigrateContentModels();
1199
+
1200
+ if (migrateErr) logError(migrateErr);
1201
+ else
1202
+ this.HandleFormattingAndOutput(result, () => {
1203
+ // print the results to console
1204
+ if (!commit) {
1205
+ log.raw(log.boldText(`\nContent types:`));
1206
+ if (!result.contentTypes) log.info(`- None returned\n`);
1207
+ else printModelMigrationAnalysis(this, result.contentTypes);
1208
+
1209
+ log.raw(log.boldText(`\nComponents:`));
1210
+ if (!result.components) log.info(`- None returned\n`);
1211
+ else printModelMigrationAnalysis(this, result.components);
1212
+ } else {
1213
+ const migrateResult = result as MigrateModelsResult;
1214
+ log.raw(log.boldText(`\nContent types:`));
1215
+ printModelMigrationResult(
1216
+ this,
1217
+ migrateResult[currentProject].contentTypes
1218
+ );
740
1219
 
741
- if (err) log.error(messages.contenttypes.noList(currentProject));
742
- if (this.contensis) {
743
- this.contentTypes = this.contensis.models.contentTypes();
744
- this.components = this.contensis.models.components();
1220
+ log.raw(log.boldText(`\nComponents:`));
1221
+ printModelMigrationResult(
1222
+ this,
1223
+ migrateResult[currentProject].components
1224
+ );
1225
+ }
1226
+ });
745
1227
  } else {
746
- log.warning(messages.contenttypes.noList(currentProject));
1228
+ log.warning(messages.models.noList(currentProject));
1229
+ log.help(messages.connect.tip());
747
1230
  }
748
1231
  };
749
1232
 
750
1233
  PrintContentTypes = async () => {
751
1234
  const { currentProject, log, messages } = this;
752
- await this.GetContentTypes();
753
- if (this.contensis) {
1235
+ const contensis = await this.ConnectContensis();
1236
+ if (contensis) {
754
1237
  // Retrieve content types list for env
755
- const { contentTypes } = this;
1238
+ const contentTypes = await contensis.models.contentTypes();
756
1239
 
757
1240
  if (Array.isArray(contentTypes)) {
758
1241
  log.success(messages.contenttypes.list(currentProject));
@@ -773,10 +1256,10 @@ class ContensisCli {
773
1256
 
774
1257
  PrintContentType = async (contentTypeId: string) => {
775
1258
  const { currentProject, log, messages } = this;
776
- await this.GetContentTypes();
777
- if (this.contensis) {
1259
+ const contensis = await this.ConnectContensis();
1260
+ if (contensis) {
778
1261
  // Retrieve content types list for env
779
- const { contentTypes } = this;
1262
+ const contentTypes = await contensis.models.contentTypes();
780
1263
 
781
1264
  if (Array.isArray(contentTypes)) {
782
1265
  const contentType = contentTypes.find(
@@ -799,15 +1282,12 @@ class ContensisCli {
799
1282
 
800
1283
  RemoveContentTypes = async (contentTypeIds: string[], commit = false) => {
801
1284
  const { currentProject, log, messages } = this;
802
- if (!this.contensis)
803
- await this.ConnectContensisImport({
804
- source: 'input',
805
- commit,
806
- });
807
- if (this.contensis) {
808
- const [err, result] = await this.contensis.DeleteContentTypes(
809
- contentTypeIds
810
- );
1285
+ const contensis = await this.ConnectContensisImport({
1286
+ commit,
1287
+ importDataType: 'user-input',
1288
+ });
1289
+ if (contensis) {
1290
+ const [err, result] = await contensis.DeleteContentTypes(contentTypeIds);
811
1291
 
812
1292
  if (err) {
813
1293
  log.error(
@@ -822,7 +1302,7 @@ class ContensisCli {
822
1302
  messages.contenttypes.removed(
823
1303
  currentProject,
824
1304
  contentTypeIds.join('", "'),
825
- !this.contensis.isPreview
1305
+ !contensis.isPreview
826
1306
  )
827
1307
  );
828
1308
  // print the results to console
@@ -851,22 +1331,21 @@ class ContensisCli {
851
1331
 
852
1332
  if (!Array.isArray(fileData)) fileData = [fileData];
853
1333
 
854
- await this.ConnectContensisImport({
1334
+ const contensis = await this.ConnectContensisImport({
855
1335
  commit,
856
- source: fromFile ? 'file' : 'contensis',
1336
+ importDataType: fromFile ? 'user-input' : undefined,
857
1337
  });
858
1338
 
859
- if (this.contensis) {
1339
+ if (contensis) {
860
1340
  // Pass each content type to the target repo
861
1341
  for (const contentType of fileData) {
862
1342
  // Fix invalid data
863
1343
  contentType.projectId = currentProject;
864
1344
  delete contentType.uuid;
865
1345
 
866
- const [err, created, createStatus] =
867
- await this.contensis.models.targetRepos[
868
- currentProject
869
- ].repo.UpsertContentType(false, contentType);
1346
+ const [err, created, createStatus] = await contensis.models.targetRepos[
1347
+ currentProject
1348
+ ].repo.UpsertContentType(false, contentType);
870
1349
 
871
1350
  if (err) log.error(err.message, err);
872
1351
  if (createStatus) {
@@ -884,12 +1363,59 @@ class ContensisCli {
884
1363
  }
885
1364
  };
886
1365
 
1366
+ DiffModels = async (
1367
+ {
1368
+ fromFile,
1369
+ }: {
1370
+ fromFile: string;
1371
+ },
1372
+ modelIds: string[] = []
1373
+ ) => {
1374
+ const { log } = this;
1375
+
1376
+ let fileData = fromFile ? readJsonFile<ContentType[]>(fromFile) || [] : [];
1377
+ if (typeof fileData === 'string')
1378
+ throw new Error(`Import file format must be of type JSON`);
1379
+
1380
+ if (!Array.isArray(fileData)) fileData = [fileData];
1381
+
1382
+ const contensis = await this.ConnectContensisImport({
1383
+ fromFile,
1384
+ importDataType: 'models',
1385
+ });
1386
+
1387
+ if (contensis) {
1388
+ const [err, result] = (await to(
1389
+ contensis.models.Diff(fileData.length ? fileData : modelIds)
1390
+ )) as [Error | null, ContentTypesResult | undefined];
1391
+
1392
+ if (err) log.error(err.message, err);
1393
+ if (result)
1394
+ // print the content type to console
1395
+ this.HandleFormattingAndOutput(result, () => {
1396
+ log.success(
1397
+ `Queried models ${log.infoText(
1398
+ `"${result.query.modelIds?.join(', ')}"`
1399
+ )}\n`
1400
+ );
1401
+
1402
+ log.raw(log.boldText(`Content types:`));
1403
+ if (!result.contentTypes) log.info(`- None returned\n`);
1404
+ else printModelMigrationAnalysis(this, result.contentTypes);
1405
+
1406
+ log.raw(log.boldText(`Components:`));
1407
+ if (!result.components) log.info(`- None returned\n`);
1408
+ else printModelMigrationAnalysis(this, result.components);
1409
+ });
1410
+ }
1411
+ };
1412
+
887
1413
  PrintComponents = async () => {
888
1414
  const { currentProject, log, messages } = this;
889
- await this.GetContentTypes();
890
- if (this.contensis) {
1415
+ const contensis = await this.ConnectContensis();
1416
+ if (contensis) {
891
1417
  // Retrieve components list for env
892
- const { components } = this;
1418
+ const components = await contensis.models.components();
893
1419
 
894
1420
  if (Array.isArray(components)) {
895
1421
  log.success(messages.components.list(currentProject));
@@ -911,10 +1437,10 @@ class ContensisCli {
911
1437
 
912
1438
  PrintComponent = async (componentId: string) => {
913
1439
  const { currentProject, log, messages } = this;
914
- await this.GetContentTypes();
915
- if (this.contensis) {
1440
+ const contensis = await this.ConnectContensis();
1441
+ if (contensis) {
916
1442
  // Retrieve content types list for env
917
- const { components } = this;
1443
+ const components = await contensis.models.components();
918
1444
 
919
1445
  if (Array.isArray(components)) {
920
1446
  const component = components.find(
@@ -933,13 +1459,12 @@ class ContensisCli {
933
1459
 
934
1460
  RemoveComponents = async (componentIds: string[], commit = false) => {
935
1461
  const { currentProject, log, messages } = this;
936
- if (!this.contensis)
937
- await this.ConnectContensisImport({
938
- source: 'input',
939
- commit,
940
- });
941
- if (this.contensis) {
942
- const [err, result] = await this.contensis.DeleteContentTypes(
1462
+ const contensis = await this.ConnectContensisImport({
1463
+ commit,
1464
+ importDataType: 'user-input',
1465
+ });
1466
+ if (contensis) {
1467
+ const [err, result] = await contensis.DeleteContentTypes(
943
1468
  undefined,
944
1469
  componentIds
945
1470
  );
@@ -957,7 +1482,7 @@ class ContensisCli {
957
1482
  messages.components.removed(
958
1483
  currentProject,
959
1484
  componentIds.join('", "'),
960
- !this.contensis.isPreview
1485
+ !contensis.isPreview
961
1486
  )
962
1487
  );
963
1488
  // print the results to console
@@ -986,22 +1511,21 @@ class ContensisCli {
986
1511
 
987
1512
  if (!Array.isArray(fileData)) fileData = [fileData];
988
1513
 
989
- await this.ConnectContensisImport({
1514
+ const contensis = await this.ConnectContensisImport({
990
1515
  commit,
991
- source: fromFile ? 'file' : 'contensis',
1516
+ importDataType: fromFile ? 'user-input' : undefined,
992
1517
  });
993
1518
 
994
- if (this.contensis) {
1519
+ if (contensis) {
995
1520
  // Pass each component to the target repo
996
1521
  for (const component of fileData) {
997
1522
  // Fix invalid data
998
1523
  component.projectId = currentProject;
999
1524
  delete component.uuid;
1000
1525
 
1001
- const [err, created, createStatus] =
1002
- await this.contensis.models.targetRepos[
1003
- currentProject
1004
- ].repo.UpsertComponent(false, component);
1526
+ const [err, created, createStatus] = await contensis.models.targetRepos[
1527
+ currentProject
1528
+ ].repo.UpsertComponent(false, component);
1005
1529
 
1006
1530
  if (err) log.error(err.message, err);
1007
1531
  if (createStatus) {
@@ -1019,38 +1543,42 @@ class ContensisCli {
1019
1543
  }
1020
1544
  };
1021
1545
 
1022
- RemoveEntry = async (id: string, commit = false) => {
1023
- const { currentEnv, log, messages } = this;
1024
- if (!this.contensis)
1025
- await this.ConnectContensisImport({
1026
- source: 'input',
1027
- commit,
1028
- });
1546
+ RemoveEntries = async (commit = false) => {
1547
+ const { currentEnv, currentProject, log, messages } = this;
1548
+ const contensis = await this.ConnectContensisImport({
1549
+ commit,
1550
+ importDataType: 'user-input',
1551
+ });
1029
1552
 
1030
- if (this.contensis) {
1031
- if (this.contensis.isPreview) {
1553
+ if (contensis) {
1554
+ if (contensis.isPreview) {
1032
1555
  console.log(log.successText(` -- PREVIEW -- `));
1033
1556
  } else {
1034
1557
  console.log(log.warningText(` *** COMMITTING DELETE *** `));
1035
1558
  }
1036
- const [err, result] = await this.contensis.DeleteEntries();
1559
+ const [err, result] = await contensis.DeleteEntries();
1037
1560
  if (result)
1038
1561
  this.HandleFormattingAndOutput(result, () => {
1039
1562
  // print the migrateResult to console
1040
- printMigrateResult(this, result, { action: 'delete' });
1563
+ printMigrateResult(this, result, {
1564
+ action: 'delete',
1565
+ showAllEntries: true,
1566
+ });
1041
1567
  });
1042
1568
  if (
1043
1569
  !err &&
1044
- ((!commit &&
1045
- Object.values(result.entriesToMigrate)?.[0].totalCount > 0) ||
1570
+ ((!commit && result.entriesToMigrate[currentProject].totalCount) ||
1046
1571
  (commit && result.migrateResult?.deleted))
1047
1572
  ) {
1048
- log.success(messages.entries.removed(currentEnv, id, commit));
1049
- if (!commit) log.help(messages.entries.commitTip());
1573
+ log.success(messages.entries.removed(currentEnv, commit));
1574
+ if (!commit) {
1575
+ log.raw(``);
1576
+ log.help(messages.entries.commitTip());
1577
+ }
1050
1578
  } else {
1051
- log.error(messages.entries.failedRemove(currentEnv, id), err);
1052
- if (!Object.values(result.entriesToMigrate)?.[0].totalCount)
1053
- log.help(messages.entries.notFound(id));
1579
+ log.error(messages.entries.failedRemove(currentEnv), err);
1580
+ if (!result.entriesToMigrate[currentProject].totalCount)
1581
+ log.help(messages.entries.notFound(currentEnv));
1054
1582
  }
1055
1583
  }
1056
1584
  };
@@ -1061,21 +1589,21 @@ class ContensisCli {
1061
1589
  withDependents?: boolean;
1062
1590
  }) => {
1063
1591
  const { currentProject, log, messages } = this;
1064
- await this.ConnectContensis();
1592
+ const contensis = await this.ConnectContensis();
1065
1593
 
1066
- if (this.contensis) {
1594
+ if (contensis) {
1067
1595
  log.line();
1068
- const entries = await this.contensis.GetEntries({ withDependents });
1596
+ const entries = await contensis.GetEntries({ withDependents });
1069
1597
  this.HandleFormattingAndOutput(entries, () =>
1070
1598
  // print the entries to console
1071
1599
  logEntriesTable(
1072
1600
  entries,
1073
1601
  currentProject,
1074
- this.contensis?.payload.query?.fields
1602
+ contensis.payload.query?.fields
1075
1603
  )
1076
1604
  );
1077
1605
  } else {
1078
- log.warning(messages.contenttypes.noList(currentProject));
1606
+ log.warning(messages.models.noList(currentProject));
1079
1607
  log.help(messages.connect.tip());
1080
1608
  }
1081
1609
  };
@@ -1083,89 +1611,216 @@ class ContensisCli {
1083
1611
  ImportEntries = async ({
1084
1612
  commit,
1085
1613
  fromFile,
1614
+ logOutput,
1086
1615
  }: {
1087
1616
  commit: boolean;
1088
1617
  fromFile: string;
1618
+ logOutput: string;
1089
1619
  }) => {
1090
- const { currentProject, log, messages } = this;
1091
-
1092
- const fileData = fromFile ? readJsonFile<Entry[]>(fromFile) || [] : [];
1093
- if (typeof fileData === 'string')
1094
- throw new Error(`Import file format must be of type JSON`);
1620
+ const { currentEnv, currentProject, log, messages } = this;
1095
1621
 
1096
- await this.ConnectContensisImport({
1622
+ const contensis = await this.ConnectContensisImport({
1097
1623
  commit,
1098
- source: fromFile ? 'file' : 'contensis',
1099
- fileData,
1100
- fileDataType: 'entries',
1624
+ fromFile,
1625
+ importDataType: 'entries',
1101
1626
  });
1102
1627
 
1103
- if (this.contensis) {
1628
+ if (contensis) {
1104
1629
  log.line();
1105
- if (this.contensis.isPreview) {
1630
+ if (contensis.isPreview) {
1106
1631
  console.log(log.successText(` -- IMPORT PREVIEW -- `));
1107
1632
  } else {
1108
1633
  console.log(log.warningText(` *** COMMITTING IMPORT *** `));
1109
1634
  }
1110
1635
 
1111
- const [migrateErr, migrateResult] = await this.contensis.MigrateEntries();
1636
+ const [err, result] = await contensis.MigrateEntries();
1112
1637
 
1113
- if (migrateErr) logError(migrateErr);
1638
+ if (err) logError(err);
1114
1639
  else
1115
- this.HandleFormattingAndOutput(migrateResult, () => {
1640
+ this.HandleFormattingAndOutput(result, () => {
1116
1641
  // print the migrateResult to console
1117
- printMigrateResult(this, migrateResult);
1642
+ printMigrateResult(this, result, {
1643
+ showAllEntries: logOutput === 'all',
1644
+ showChangedEntries: logOutput === 'changes',
1645
+ });
1118
1646
  });
1647
+
1648
+ if (
1649
+ !err &&
1650
+ !result.errors?.length &&
1651
+ ((!commit && result.entriesToMigrate[currentProject].totalCount) ||
1652
+ (commit &&
1653
+ (result.migrateResult?.created || result.migrateResult?.updated)))
1654
+ ) {
1655
+ log.success(
1656
+ messages.entries.imported(
1657
+ currentEnv,
1658
+ commit,
1659
+ commit
1660
+ ? (result.migrateResult?.created || 0) +
1661
+ (result.migrateResult?.updated || 0)
1662
+ : result.entriesToMigrate[currentProject].totalCount
1663
+ )
1664
+ );
1665
+ if (!commit) {
1666
+ log.raw(``);
1667
+ log.help(messages.entries.commitTip());
1668
+ }
1669
+ } else {
1670
+ log.error(messages.entries.failedImport(currentEnv), err);
1671
+ if (!result.entriesToMigrate[currentProject].totalCount)
1672
+ log.help(messages.entries.notFound(currentEnv));
1673
+ }
1119
1674
  } else {
1120
- log.warning(messages.contenttypes.noList(currentProject));
1675
+ log.warning(messages.models.noList(currentProject));
1121
1676
  log.help(messages.connect.tip());
1122
1677
  }
1123
1678
  };
1124
1679
 
1125
- PrintWebhookSubscriptions = async (
1126
- subscriptionIds?: string[],
1127
- name?: string
1128
- ) => {
1680
+ GetNodes = async (rootPath: string, depth = 0) => {
1681
+ const { currentProject, log, messages } = this;
1682
+ const contensis = await this.ConnectContensis();
1683
+
1684
+ if (contensis) {
1685
+ log.line();
1686
+ const [err] = await to(
1687
+ contensis.content.sourceRepo.nodes.GetNodes(rootPath, depth)
1688
+ );
1689
+ if (err) {
1690
+ log.error(messages.nodes.failedGet(currentProject), err);
1691
+ return;
1692
+ }
1693
+ const root = contensis.content.sourceRepo.nodes.tree;
1694
+
1695
+ log.success(messages.nodes.get(currentProject, rootPath, depth));
1696
+
1697
+ const outputNode = (node: Node | any, spaces: string) =>
1698
+ `${node.entry ? log.highlightText('e') : log.infoText('-')}${
1699
+ node.isCanonical ? log.highlightText('c') : log.infoText('-')
1700
+ }${
1701
+ node.includeInMenu ? log.highlightText('m') : log.infoText('-')
1702
+ }${spaces}${
1703
+ node.isCanonical ? log.boldText(`/${node.slug}`) : `/${node.slug}`
1704
+ }${node.entry ? ` ${log.helpText(node.entry.sys.contentTypeId)}` : ''}${
1705
+ node.childCount ? ` +${node.childCount}` : ``
1706
+ } ${log.infoText(node.displayName)}`;
1707
+
1708
+ this.HandleFormattingAndOutput(root, () => {
1709
+ // print the nodes to console
1710
+ log.object({ ...root, children: undefined });
1711
+ log.raw('');
1712
+ log.info(
1713
+ `${log.highlightText('e')} = has entry; ${log.highlightText(
1714
+ 'c'
1715
+ )} = canonical; ${log.highlightText('m')} = include in menu`
1716
+ );
1717
+ log.line();
1718
+ const outputChildren = (root: Node | undefined, depth = 2) => {
1719
+ let str = '';
1720
+ for (const node of (root as any)?.children as Node[]) {
1721
+ str += `${outputNode(node, Array(depth + 1).join(' '))}\n`;
1722
+ if ('children' in node) str += outputChildren(node, depth + 1);
1723
+ }
1724
+ return str;
1725
+ };
1726
+
1727
+ const children = outputChildren(root);
1728
+ log.limits(
1729
+ `${outputNode(root, ' ')}${children ? `\n${children}` : ''}`,
1730
+ 100
1731
+ );
1732
+ });
1733
+ } else {
1734
+ log.warning(messages.models.noList(currentProject));
1735
+ log.help(messages.connect.tip());
1736
+ }
1737
+ };
1738
+
1739
+ PrintWebhookSubscriptions = async (subscriptionIdsOrNames?: string[]) => {
1129
1740
  const { currentEnv, log, messages } = this;
1130
- if (!this.contensis) await this.ConnectContensis();
1131
- if (this.contensis) {
1741
+ const contensis = await this.ConnectContensis();
1742
+ if (contensis) {
1132
1743
  // Retrieve webhooks list for env
1133
1744
  const [webhooksErr, webhooks] =
1134
- await this.contensis.subscriptions.webhooks.GetSubscriptions();
1135
-
1136
- const filteredResults =
1137
- typeof name === 'string'
1138
- ? webhooks.filter(w =>
1139
- w.name?.toLowerCase().includes(name.toLowerCase())
1140
- )
1141
- : Array.isArray(subscriptionIds)
1142
- ? webhooks.filter(w => subscriptionIds?.some(id => id === w.id))
1143
- : webhooks;
1745
+ await contensis.subscriptions.webhooks.GetSubscriptions();
1746
+
1747
+ const filteredResults = subscriptionIdsOrNames?.length
1748
+ ? webhooks?.filter(
1749
+ w =>
1750
+ subscriptionIdsOrNames?.some(idname =>
1751
+ w.name?.toLowerCase().includes(idname.toLowerCase())
1752
+ ) ||
1753
+ subscriptionIdsOrNames?.some(
1754
+ id => id.toLowerCase() === w.id.toLowerCase()
1755
+ )
1756
+ )
1757
+ : webhooks;
1144
1758
 
1145
1759
  if (Array.isArray(filteredResults)) {
1146
- this.HandleFormattingAndOutput(filteredResults, () => {
1147
- // print the keys to console
1148
- log.success(messages.webhooks.list(currentEnv));
1149
- for (const {
1150
- id,
1151
- description,
1152
- method,
1153
- name,
1154
- version,
1155
- url,
1156
- } of filteredResults) {
1157
- console.log(
1158
- ` - ${name}${
1159
- description ? ` (${description})` : ''
1160
- } [${version.modified.toString().substring(0, 10)} ${
1161
- version.modifiedBy
1162
- }]`
1163
- );
1164
- console.log(` ${id}`);
1165
- console.log(` [${method}] ${url}`);
1166
- }
1167
- console.log('');
1168
- });
1760
+ log.success(messages.webhooks.list(currentEnv));
1761
+ if (!webhooks?.length) log.warning(messages.webhooks.noneExist());
1762
+ else {
1763
+ this.HandleFormattingAndOutput(filteredResults, () => {
1764
+ // print the keys to console
1765
+ for (const {
1766
+ id,
1767
+ description,
1768
+ method,
1769
+ name,
1770
+ version,
1771
+ url,
1772
+ enabled,
1773
+ topics,
1774
+ templates,
1775
+ headers,
1776
+ } of filteredResults) {
1777
+ console.log(
1778
+ log.infoText(
1779
+ ` ${chalk.bold.white`- ${name}`} ${id} [${(
1780
+ version.modified || version.created
1781
+ )
1782
+ .toString()
1783
+ .substring(0, 10)} ${
1784
+ version.modifiedBy || version.createdBy
1785
+ }]`
1786
+ )
1787
+ );
1788
+ if (description) console.log(log.infoText` ${description}`);
1789
+ console.log(` ${log.infoText`[${method}]`} ${url}`);
1790
+ if (headers && Object.keys(headers).length) {
1791
+ console.log(` ${log.infoText`headers`}:`);
1792
+
1793
+ for (const [key, { value, secret }] of Object.entries(headers))
1794
+ console.log(
1795
+ ` ${chalk.bold.gray(key)}: ${secret ? '🤐' : value}`
1796
+ );
1797
+ }
1798
+ if (topics?.length)
1799
+ if (topics?.length === 1)
1800
+ console.log(
1801
+ ` ${log.infoText`topics`}: ${topics
1802
+ .map(t => JSON.stringify(t))
1803
+ .join(' ')
1804
+ .replaceAll('"', '')
1805
+ .replaceAll(',', ' ')
1806
+ .replaceAll('{', '')
1807
+ .replaceAll('}', '')}`
1808
+ );
1809
+ else {
1810
+ console.log(` ${log.infoText`topics`}:`);
1811
+ log.objectRecurse(topics, 1, ' ');
1812
+ }
1813
+ if (templates && Object.keys(templates).length)
1814
+ console.log(
1815
+ ` ${log.infoText`templates`}: ${Object.keys(
1816
+ templates
1817
+ ).join(' ')}`
1818
+ );
1819
+ if (enabled === false)
1820
+ console.log(` ${log.infoText`enabled`}: ${enabled}`);
1821
+ }
1822
+ });
1823
+ }
1169
1824
  }
1170
1825
 
1171
1826
  if (webhooksErr) {
@@ -1176,16 +1831,16 @@ class ContensisCli {
1176
1831
  };
1177
1832
 
1178
1833
  PrintBlocks = async () => {
1179
- const { currentEnv, log, messages } = this;
1180
- if (!this.contensis) await this.ConnectContensis();
1181
- if (this.contensis) {
1834
+ const { currentEnv, env, log, messages } = this;
1835
+ const contensis = await this.ConnectContensis();
1836
+ if (contensis) {
1182
1837
  // Retrieve blocks list for env
1183
- const [err, blocks] = await this.contensis.blocks.GetBlocks();
1838
+ const [err, blocks] = await contensis.blocks.GetBlocks();
1184
1839
 
1185
1840
  if (Array.isArray(blocks)) {
1186
1841
  this.HandleFormattingAndOutput(blocks, () => {
1187
1842
  // print the blocks to console
1188
- log.success(messages.blocks.list(currentEnv));
1843
+ log.success(messages.blocks.list(currentEnv, env.currentProject));
1189
1844
  for (const {
1190
1845
  id,
1191
1846
  description,
@@ -1211,6 +1866,8 @@ class ContensisCli {
1211
1866
  );
1212
1867
  }
1213
1868
  });
1869
+
1870
+ return blocks;
1214
1871
  }
1215
1872
 
1216
1873
  if (err) {
@@ -1226,10 +1883,10 @@ class ContensisCli {
1226
1883
  version: string
1227
1884
  ) => {
1228
1885
  const { currentEnv, env, log, messages } = this;
1229
- if (!this.contensis) await this.ConnectContensis();
1230
- if (this.contensis) {
1886
+ const contensis = await this.ConnectContensis();
1887
+ if (contensis) {
1231
1888
  // Retrieve block version
1232
- const [err, blocks] = await this.contensis.blocks.GetBlockVersions(
1889
+ const [err, blocks] = await contensis.blocks.GetBlockVersions(
1233
1890
  blockId,
1234
1891
  branch,
1235
1892
  version
@@ -1239,7 +1896,7 @@ class ContensisCli {
1239
1896
  this.HandleFormattingAndOutput(blocks, () => {
1240
1897
  // print the version detail to console
1241
1898
  log.success(
1242
- messages.blocks.get(`${currentEnv}:${env.currentProject}`)
1899
+ messages.blocks.get(blockId, currentEnv, env.currentProject)
1243
1900
  );
1244
1901
  for (const block of blocks)
1245
1902
  printBlockVersion(
@@ -1255,6 +1912,8 @@ class ContensisCli {
1255
1912
  : undefined
1256
1913
  );
1257
1914
  });
1915
+
1916
+ return blocks;
1258
1917
  }
1259
1918
 
1260
1919
  if (err) {
@@ -1268,18 +1927,20 @@ class ContensisCli {
1268
1927
  const { currentEnv, env, log, messages } = this;
1269
1928
 
1270
1929
  // Output request to console
1271
- messages.blocks.tryPush(
1272
- block.id,
1273
- block.source.branch,
1274
- currentEnv,
1275
- env.currentProject
1930
+ log.info(
1931
+ messages.blocks.tryPush(
1932
+ block.id,
1933
+ block.source.branch,
1934
+ currentEnv,
1935
+ env.currentProject
1936
+ )
1276
1937
  );
1277
1938
  console.log(jsonFormatter(block));
1278
1939
 
1279
- if (!this.contensis) await this.ConnectContensis();
1280
- if (this.contensis) {
1940
+ const contensis = await this.ConnectContensis();
1941
+ if (contensis) {
1281
1942
  // Push new block version
1282
- const [err, blockVersion] = await this.contensis.blocks.PushBlockVersion(
1943
+ const [err, blockVersion] = await contensis.blocks.PushBlockVersion(
1283
1944
  block
1284
1945
  );
1285
1946
  if (!err) {
@@ -1291,7 +1952,6 @@ class ContensisCli {
1291
1952
  env.currentProject
1292
1953
  )
1293
1954
  );
1294
- console.log(jsonFormatter(blockVersion));
1295
1955
  }
1296
1956
  if (blockVersion) {
1297
1957
  this.HandleFormattingAndOutput(blockVersion, () => {
@@ -1303,6 +1963,113 @@ class ContensisCli {
1303
1963
  throw new Error(
1304
1964
  messages.blocks.failedPush(block.id, currentEnv, env.currentProject)
1305
1965
  );
1966
+ } else {
1967
+ throw new Error(
1968
+ messages.blocks.failedPush(block.id, currentEnv, env.currentProject)
1969
+ );
1970
+ }
1971
+ };
1972
+
1973
+ GetLatestBlockVersion = async (
1974
+ blockId: string,
1975
+ branch = 'default'
1976
+ ): Promise<[AppError | null, string | undefined]> => {
1977
+ const { contensis, log, messages } = this;
1978
+
1979
+ // Look for block versions pushed to "default" branch
1980
+ const [getErr, blockVersions] =
1981
+ (await contensis?.blocks.GetBlockVersions(blockId, branch)) || [];
1982
+
1983
+ if (getErr) {
1984
+ return [getErr, undefined];
1985
+ }
1986
+
1987
+ // Parse versionNo from response
1988
+ let blockVersionNo = 'latest';
1989
+ // The first blockVersion should be the latest one
1990
+ try {
1991
+ blockVersionNo = `${blockVersions?.[0]?.version.versionNo}`;
1992
+
1993
+ if (!Number.isNaN(blockVersionNo) && Number(blockVersionNo) > 0)
1994
+ // Is a valid versionNo
1995
+ return [null, blockVersionNo];
1996
+ else throw new Error(`'${blockVersionNo}' is not a valid version number`);
1997
+ } catch (parseVersionEx: any) {
1998
+ // Catch parsing errors in case of an unexpected response
1999
+ log.info(
2000
+ `Request for blockId: ${blockId}, branch: ${branch}, version: latest`
2001
+ );
2002
+ log.info(
2003
+ `Get block versions response was: ${tryStringify(blockVersions)}`
2004
+ );
2005
+ log.error(messages.blocks.failedParsingVersion());
2006
+ return [parseVersionEx, undefined];
2007
+ }
2008
+ };
2009
+
2010
+ ExecuteBlockAction = async (
2011
+ action: BlockActionType,
2012
+ blockId: string,
2013
+ version = 'latest'
2014
+ ) => {
2015
+ const { currentEnv, env, log, messages } = this;
2016
+ const contensis = await this.ConnectContensis();
2017
+ if (contensis) {
2018
+ let actionOnBlockVersion = version;
2019
+
2020
+ // If action is release and version is latest, find the latest version number
2021
+ if (action === 'release' && version === 'latest') {
2022
+ const [getErr, blockVersion] = await this.GetLatestBlockVersion(
2023
+ blockId
2024
+ );
2025
+
2026
+ if (getErr) {
2027
+ // Log error getting latest block version no
2028
+ // and throw the error message so the process can exit with a failure
2029
+ throw new Error(
2030
+ `${messages.blocks.noList(
2031
+ currentEnv,
2032
+ env.currentProject
2033
+ )} (${getErr})`
2034
+ );
2035
+ } else if (blockVersion) {
2036
+ actionOnBlockVersion = blockVersion;
2037
+ }
2038
+ }
2039
+
2040
+ // Execute block action
2041
+ const [err, blockVersion] = await contensis.blocks.BlockAction(
2042
+ blockId,
2043
+ action,
2044
+ actionOnBlockVersion
2045
+ );
2046
+
2047
+ if (blockVersion) {
2048
+ this.HandleFormattingAndOutput(blockVersion, () => {
2049
+ // print the version detail to console
2050
+ log.success(
2051
+ messages.blocks.actionComplete(
2052
+ action,
2053
+ blockId,
2054
+ currentEnv,
2055
+ env.currentProject
2056
+ )
2057
+ );
2058
+ printBlockVersion(this, blockVersion);
2059
+ });
2060
+ }
2061
+
2062
+ if (err) {
2063
+ log.error(jsonFormatter(err));
2064
+ throw new Error(
2065
+ messages.blocks.actionFailed(
2066
+ action,
2067
+ blockId,
2068
+ currentEnv,
2069
+ env.currentProject
2070
+ )
2071
+ );
2072
+ }
1306
2073
  }
1307
2074
  };
1308
2075
 
@@ -1310,79 +2077,252 @@ class ContensisCli {
1310
2077
  blockId: string,
1311
2078
  branch: string,
1312
2079
  version: string,
1313
- dataCenter: 'hq' | 'manchester' | 'london'
2080
+ dataCenter: 'hq' | 'manchester' | 'london' | undefined,
2081
+ follow = false
1314
2082
  ) => {
1315
- const { currentEnv, log, messages } = this;
1316
- if (!this.contensis) await this.ConnectContensis();
1317
- if (this.contensis) {
2083
+ const { currentEnv, env, log, messages } = this;
2084
+ const contensis = await this.ConnectContensis();
2085
+ if (contensis) {
1318
2086
  // Retrieve block logs
1319
- const [err, blockLogs] = await this.contensis.blocks.GetBlockLogs({
2087
+ log.success(
2088
+ messages.blocks.getLogs(blockId, branch, currentEnv, env.currentProject)
2089
+ );
2090
+
2091
+ const [err, blockLogs] = await contensis.blocks.GetBlockLogs({
1320
2092
  blockId,
1321
2093
  branchId: branch,
1322
2094
  version,
1323
2095
  dataCenter,
1324
2096
  });
1325
2097
 
1326
- if (blockLogs) {
1327
- this.HandleFormattingAndOutput(blockLogs, () => {
1328
- // print the logs to console
1329
- log.success(messages.blocks.list(currentEnv));
2098
+ if (err) {
2099
+ log.error(
2100
+ messages.blocks.failedGetLogs(blockId, currentEnv, env.currentProject)
2101
+ );
2102
+ log.error(jsonFormatter(err));
2103
+ } else if (blockLogs) {
2104
+ const removeTrailingNewline = (logs: string) =>
2105
+ logs.endsWith('\n') ? logs.slice(0, logs.length - 1) : logs;
2106
+ const renderLogs = removeTrailingNewline(blockLogs);
2107
+
2108
+ this.HandleFormattingAndOutput(renderLogs, () => {
2109
+ // print the logs to console
1330
2110
  console.log(
1331
2111
  ` - ${blockId} ${branch} ${
1332
2112
  Number(version) ? `v${version}` : version
1333
- } [${dataCenter}]`
2113
+ } ${dataCenter ? `[${dataCenter}]` : ''}`
1334
2114
  );
1335
2115
  log.line();
1336
- console.log(log.infoText(blockLogs));
1337
- log.line();
2116
+ console.log(log.infoText(renderLogs));
2117
+ });
2118
+
2119
+ // Code for the `--follow` options
2120
+ let following = follow;
2121
+ let alreadyShown = blockLogs;
2122
+ let needsNewLine = false;
2123
+ let counter = 0;
2124
+
2125
+ // remove existing listeners and add them back afterwards
2126
+ const listeners = process.listeners('SIGINT');
2127
+
2128
+ process.removeAllListeners('SIGINT');
2129
+ // add listener to update following to false and break out
2130
+ process.on('SIGINT', () => {
2131
+ Logger.warning(
2132
+ messages.blocks.stopFollow(blockId, currentEnv, env.currentProject)
2133
+ );
2134
+ stopFollowing();
2135
+ });
2136
+
2137
+ let delay = promiseDelay(5 * 1000, null);
2138
+ const stopFollowing = () => {
2139
+ following = false;
2140
+ delay.cancel();
2141
+
2142
+ // Add back the listeners we removed previously
2143
+ process.removeAllListeners('SIGINT');
2144
+ for (const listener of listeners)
2145
+ process.addListener('SIGINT', listener);
2146
+ };
2147
+
2148
+ while (following) {
2149
+ if (counter++ > 300) {
2150
+ Logger.warning(
2151
+ messages.blocks.timeoutFollow(
2152
+ blockId,
2153
+ currentEnv,
2154
+ env.currentProject
2155
+ )
2156
+ );
2157
+ stopFollowing();
2158
+ }
2159
+
2160
+ // wait n. seconds then poll for logs again
2161
+ await delay.wait();
2162
+
2163
+ const [lastErr, lastLogs] = following
2164
+ ? await contensis.blocks.GetBlockLogs({
2165
+ blockId,
2166
+ branchId: branch,
2167
+ version,
2168
+ dataCenter,
2169
+ })
2170
+ : [null, null];
2171
+
2172
+ if (lastLogs) {
2173
+ // Find the difference and output it next
2174
+ const difference = diffLogStrings(lastLogs, alreadyShown);
2175
+ if (difference) {
2176
+ if (needsNewLine) {
2177
+ console.log('');
2178
+ }
2179
+ // Take the trailing newline off of the logged output to
2180
+ // avoid blank lines inbetween logs fetched sequentially
2181
+ const render = removeTrailingNewline(difference);
2182
+ console.log(log.infoText(render));
2183
+
2184
+ // Add what we've just rendered to already shown "cache"
2185
+ alreadyShown += `${render}\n`;
2186
+ needsNewLine = false;
2187
+ } else {
2188
+ // If no difference output a dot
2189
+ process.stdout.write('.');
2190
+ needsNewLine = true;
2191
+ }
2192
+ } else if (lastErr) {
2193
+ // If error output an x
2194
+ process.stdout.write('x');
2195
+ needsNewLine = true;
2196
+ }
2197
+ }
2198
+ }
2199
+ }
2200
+ };
2201
+
2202
+ PrintProxies = async (proxyId?: string) => {
2203
+ const { currentEnv, env, log, messages } = this;
2204
+ const contensis = await this.ConnectContensis();
2205
+ if (contensis) {
2206
+ // Retrieve proxies list for env
2207
+ const [err, proxies] = await (contensis.proxies.GetProxies as any)(
2208
+ proxyId
2209
+ ); // TODO: resolve any cast;
2210
+
2211
+ if (Array.isArray(proxies)) {
2212
+ this.HandleFormattingAndOutput(proxies, () => {
2213
+ // print the proxies to console
2214
+ log.success(messages.proxies.list(currentEnv, env.currentProject));
2215
+ for (const { id, name, description, endpoints, version } of proxies) {
2216
+ console.log(
2217
+ ` - ${name} [${
2218
+ version.versionNo
2219
+ }] ${id} ${log.infoText`${description}`}`
2220
+ );
2221
+ for (const [language, endpoint] of Object.entries(
2222
+ endpoints as { [k: string]: any }
2223
+ )) // TODO: resolve any cast
2224
+ console.log(
2225
+ ` - ${log.infoText`language: ${language}
2226
+ server: ${endpoint.server}
2227
+ headers.host: ${endpoint.headers.host}
2228
+ ssl: ${endpoint.ssl}`}`
2229
+ );
2230
+ }
1338
2231
  });
1339
2232
  }
1340
2233
 
1341
2234
  if (err) {
1342
- log.error(messages.blocks.noList(currentEnv));
2235
+ log.error(messages.proxies.noList(currentEnv, env.currentProject));
1343
2236
  log.error(jsonFormatter(err));
1344
2237
  }
1345
2238
  }
1346
2239
  };
1347
2240
 
2241
+ PrintRenderers = async (rendererId?: string) => {
2242
+ const { currentEnv, env, log, messages } = this;
2243
+ const contensis = await this.ConnectContensis();
2244
+ if (contensis) {
2245
+ // Retrieve renderers list for env
2246
+ const [err, renderers] = await (contensis.renderers.GetRenderers as any)(
2247
+ rendererId
2248
+ ); // TODO: resolve any cast
2249
+
2250
+ if (Array.isArray(renderers)) {
2251
+ this.HandleFormattingAndOutput(renderers, () => {
2252
+ // print the renderers to console
2253
+ log.success(messages.renderers.list(currentEnv, env.currentProject));
2254
+ for (const {
2255
+ id,
2256
+ description,
2257
+ assignedContentTypes,
2258
+ rules,
2259
+ version,
2260
+ } of renderers) {
2261
+ console.log(
2262
+ ` - ${id} [${version.versionNo}] ${log.infoText`${description}`}`
2263
+ );
2264
+ if (assignedContentTypes?.length)
2265
+ console.log(
2266
+ log.infoText` assignedContentTypes: ${assignedContentTypes.join(
2267
+ ', '
2268
+ )}`
2269
+ );
2270
+ for (const rule of rules)
2271
+ if (rule.return)
2272
+ console.log(
2273
+ log.infoText` ${
2274
+ rule.return.endpointId ? 'endpointId' : 'blockId'
2275
+ }: ${rule.return.endpointId || rule.return.blockId}`
2276
+ );
2277
+ }
2278
+ });
2279
+ }
2280
+
2281
+ if (err) {
2282
+ log.error(messages.renderers.noList(currentEnv, env.currentProject));
2283
+ log.error(jsonFormatter(err));
2284
+ }
2285
+ }
2286
+ };
1348
2287
  HandleFormattingAndOutput = <T>(obj: T, logFn: (obj: T) => void) => {
1349
2288
  const { format, log, messages, output } = this;
2289
+ if (!format) {
2290
+ // print the object to console
2291
+ logFn(obj);
2292
+ } else if (format === 'csv') {
2293
+ log.raw('');
2294
+ log.raw(log.infoText(csvFormatter(obj)));
2295
+ } else if (format === 'xml') {
2296
+ log.raw('');
2297
+ log.raw(log.infoText(xmlFormatter(obj)));
2298
+ } else if (format === 'json') {
2299
+ log.raw('');
2300
+ log.raw(log.infoText(jsonFormatter(obj)));
2301
+ }
2302
+ log.raw('');
2303
+
1350
2304
  if (output) {
1351
2305
  let writeString = '';
2306
+ const isText = !tryParse(obj) && typeof obj === 'string';
1352
2307
  if (format === 'csv') {
1353
2308
  writeString = csvFormatter(obj as any);
1354
2309
  } else if (format === 'xml') {
1355
2310
  writeString = xmlFormatter(obj as any);
1356
- } else writeString = jsonFormatter(obj);
2311
+ } else writeString = isText ? (obj as string) : jsonFormatter(obj);
1357
2312
  // write output to file
1358
2313
  if (writeString) {
1359
2314
  fs.writeFileSync(output, writeString);
1360
- log.success(messages.app.fileOutput(format, output));
2315
+ log.success(messages.app.fileOutput(isText ? 'text' : format, output));
1361
2316
  } else {
1362
2317
  log.info(messages.app.noFileOutput());
1363
2318
  }
1364
- } else {
1365
- if (!format) {
1366
- // print the object to console
1367
- logFn(obj);
1368
- } else if (format === 'csv') {
1369
- log.raw('');
1370
- log.raw(log.infoText(csvFormatter(obj)));
1371
- } else if (format === 'xml') {
1372
- log.raw('');
1373
- log.raw(log.infoText(xmlFormatter(obj)));
1374
- } else if (format === 'json') {
1375
- log.raw('');
1376
- log.raw(log.infoText(jsonFormatter(obj)));
1377
- }
1378
- log.raw('');
1379
2319
  }
1380
2320
  };
1381
2321
  }
1382
2322
 
1383
2323
  export const cliCommand = (
1384
2324
  commandArgs: string[],
1385
- outputOpts: OutputOptions & IConnectOptions = {},
2325
+ outputOpts?: OutputOptionsConstructorArg,
1386
2326
  contensisOpts: Partial<MigrateRequest> = {}
1387
2327
  ) => {
1388
2328
  return new ContensisCli(['', '', ...commandArgs], outputOpts, contensisOpts);