contensis-cli 1.0.0-beta.10 → 1.0.0-beta.100

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