contensis-cli 1.0.0-beta.6 → 1.0.0-beta.60

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 (71) hide show
  1. package/README.md +760 -75
  2. package/dist/commands/connect.js +3 -3
  3. package/dist/commands/connect.js.map +2 -2
  4. package/dist/commands/create.js +30 -10
  5. package/dist/commands/create.js.map +2 -2
  6. package/dist/commands/diff.js +57 -0
  7. package/dist/commands/diff.js.map +7 -0
  8. package/dist/commands/get.js +61 -12
  9. package/dist/commands/get.js.map +2 -2
  10. package/dist/commands/globalOptions.js +22 -17
  11. package/dist/commands/globalOptions.js.map +2 -2
  12. package/dist/commands/import.js +46 -11
  13. package/dist/commands/import.js.map +2 -2
  14. package/dist/commands/index.js +9 -1
  15. package/dist/commands/index.js.map +2 -2
  16. package/dist/commands/list.js +19 -8
  17. package/dist/commands/list.js.map +2 -2
  18. package/dist/commands/login.js +3 -3
  19. package/dist/commands/login.js.map +2 -2
  20. package/dist/commands/push.js +8 -4
  21. package/dist/commands/push.js.map +2 -2
  22. package/dist/commands/release.js +47 -0
  23. package/dist/commands/release.js.map +7 -0
  24. package/dist/commands/remove.js +40 -8
  25. package/dist/commands/remove.js.map +2 -2
  26. package/dist/commands/set.js +53 -12
  27. package/dist/commands/set.js.map +2 -2
  28. package/dist/localisation/en-GB.js +100 -48
  29. package/dist/localisation/en-GB.js.map +2 -2
  30. package/dist/providers/CredentialProvider.js +36 -7
  31. package/dist/providers/CredentialProvider.js.map +3 -3
  32. package/dist/providers/SessionCacheProvider.js +21 -1
  33. package/dist/providers/SessionCacheProvider.js.map +2 -2
  34. package/dist/providers/file-provider.js +8 -4
  35. package/dist/providers/file-provider.js.map +3 -3
  36. package/dist/services/ContensisCliService.js +640 -375
  37. package/dist/services/ContensisCliService.js.map +3 -3
  38. package/dist/shell.js +27 -10
  39. package/dist/shell.js.map +3 -3
  40. package/dist/util/console.printer.js +171 -55
  41. package/dist/util/console.printer.js.map +2 -2
  42. package/dist/util/index.js +5 -2
  43. package/dist/util/index.js.map +3 -3
  44. package/dist/util/logger.js +47 -16
  45. package/dist/util/logger.js.map +2 -2
  46. package/dist/version.js +1 -1
  47. package/dist/version.js.map +1 -1
  48. package/package.json +2 -2
  49. package/src/commands/connect.ts +3 -2
  50. package/src/commands/create.ts +37 -8
  51. package/src/commands/diff.ts +41 -0
  52. package/src/commands/get.ts +80 -5
  53. package/src/commands/globalOptions.ts +18 -17
  54. package/src/commands/import.ts +57 -7
  55. package/src/commands/index.ts +9 -1
  56. package/src/commands/list.ts +35 -9
  57. package/src/commands/login.ts +3 -2
  58. package/src/commands/push.ts +9 -2
  59. package/src/commands/release.ts +32 -0
  60. package/src/commands/remove.ts +50 -4
  61. package/src/commands/set.ts +65 -9
  62. package/src/localisation/en-GB.ts +146 -65
  63. package/src/providers/CredentialProvider.ts +39 -6
  64. package/src/providers/SessionCacheProvider.ts +29 -2
  65. package/src/providers/file-provider.ts +12 -4
  66. package/src/services/ContensisCliService.ts +789 -426
  67. package/src/shell.ts +31 -11
  68. package/src/util/console.printer.ts +240 -78
  69. package/src/util/index.ts +12 -6
  70. package/src/util/logger.ts +87 -18
  71. package/src/version.ts +1 -1
@@ -3,7 +3,8 @@ import path from 'path';
3
3
  import fetch from 'node-fetch';
4
4
  import inquirer from 'inquirer';
5
5
  import to from 'await-to-js';
6
- import { Component, ContentType } from 'contensis-core-api';
6
+ import chalk from 'chalk';
7
+ import { Component, ContentType, Project } from 'contensis-core-api';
7
8
  import { isPassword, isSharedSecret, isUuid, url } from '~/util';
8
9
  import SessionCacheProvider from '../providers/SessionCacheProvider';
9
10
  import ContensisAuthService from './ContensisAuthService';
@@ -16,13 +17,21 @@ import {
16
17
  PushBlockParams,
17
18
  SourceCms,
18
19
  logEntriesTable,
20
+ ContentTypesResult,
21
+ Model,
22
+ MigrateModelsResult,
19
23
  } from 'migratortron';
20
24
  import { Entry } from 'contensis-management-api/lib/models';
21
25
 
22
26
  import { csvFormatter } from '~/util/csv.formatter';
23
27
  import { xmlFormatter } from '~/util/xml.formatter';
24
28
  import { jsonFormatter } from '~/util/json.formatter';
25
- import { printBlockVersion, printMigrateResult } from '~/util/console.printer';
29
+ import {
30
+ printBlockVersion,
31
+ printMigrateResult,
32
+ printModelMigrationAnalysis,
33
+ printModelMigrationResult,
34
+ } from '~/util/console.printer';
26
35
  import { readJsonFile } from '~/providers/file-provider';
27
36
 
28
37
  type OutputFormat = 'json' | 'csv' | 'xml';
@@ -45,28 +54,31 @@ interface IAuthOptions {
45
54
  }
46
55
 
47
56
  interface IImportOptions {
48
- sourceEnv?: string;
57
+ sourceAlias?: string;
49
58
  sourceProjectId?: string;
50
59
  }
51
60
 
61
+ let insecurePasswordWarningShown = false;
62
+
52
63
  class ContensisCli {
53
64
  static quit = (error?: Error) => {
54
65
  process.removeAllListeners('exit');
55
66
  const exitCode = error ? 1 : 0;
56
67
 
57
- console.info(`\nExiting contensis-cli with exit code: ${exitCode}\n`);
68
+ // console.info(`\nExiting contensis-cli with exit code: ${exitCode}\n`);
58
69
  process.exit(exitCode);
59
70
  };
60
71
 
61
- cache: SessionCache;
72
+ private command: CliCommand;
73
+ private format?: OutputFormat;
74
+ private output?: string;
75
+ private session: SessionCacheProvider;
76
+
62
77
  contensis?: ContensisMigrationService;
63
78
  contensisOpts: Partial<MigrateRequest>;
64
- contentTypes?: ContentType[];
65
- components?: Component[];
66
- currentEnv: string;
67
79
  currentProject: string;
68
- env: EnvironmentCache;
69
- sourceEnv?: string;
80
+
81
+ sourceAlias?: string;
70
82
  targetEnv?: string;
71
83
  urls:
72
84
  | {
@@ -78,17 +90,42 @@ class ContensisCli {
78
90
  iisPreviewWeb: string;
79
91
  }
80
92
  | undefined;
81
- private command: CliCommand;
82
- private format?: OutputFormat;
83
- private output?: string;
84
93
  log = Logger;
85
94
  messages = LogMessages;
86
- private session: SessionCacheProvider;
87
95
 
88
96
  verb: string;
89
97
  noun: string;
90
98
  thirdArg: string;
91
99
 
100
+ get cache() {
101
+ return this.session.Get();
102
+ }
103
+
104
+ get currentEnv() {
105
+ return this.cache.currentEnvironment || '';
106
+ }
107
+
108
+ set currentEnv(currentEnvironment: string) {
109
+ this.session.Update({ currentEnvironment });
110
+ }
111
+
112
+ get env() {
113
+ const currentEnvironment = this.currentEnv;
114
+ const environments = this.cache.environments || {};
115
+
116
+ if (!currentEnvironment) return {} as EnvironmentCache;
117
+ else if (!!environments[currentEnvironment])
118
+ return environments[currentEnvironment];
119
+ else {
120
+ return {
121
+ history: [],
122
+ lastUserId: '',
123
+ projects: [],
124
+ versionStatus: 'latest',
125
+ } as EnvironmentCache;
126
+ }
127
+ }
128
+
92
129
  constructor(
93
130
  args: string[],
94
131
  outputOpts?: OutputOptions & IConnectOptions & IImportOptions,
@@ -106,40 +143,28 @@ class ContensisCli {
106
143
  }`.trim();
107
144
 
108
145
  this.session = new SessionCacheProvider();
109
- this.cache = this.session.Get();
146
+
110
147
  this.contensisOpts = contensisOpts;
111
148
  this.format = outputOpts?.format;
112
149
  this.output =
113
150
  outputOpts?.output && path.join(process.cwd(), outputOpts.output);
114
151
 
115
- const currentEnvironment =
116
- outputOpts?.alias || this.cache.currentEnvironment || '';
152
+ const currentEnvironment = outputOpts?.alias || this.currentEnv;
117
153
  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
- }
154
+ this.currentEnv = currentEnvironment;
130
155
 
131
156
  const env = this.env;
132
157
 
133
158
  if (outputOpts?.projectId) env.currentProject = outputOpts.projectId;
134
159
  if (outputOpts?.user) env.lastUserId = outputOpts.user;
160
+ // setting this in env means passwordFallback is written to environments.json
135
161
  if (outputOpts?.password) env.passwordFallback = outputOpts.password;
136
162
  if (outputOpts?.clientId) env.lastUserId = outputOpts.clientId;
137
163
  if (outputOpts?.sharedSecret)
138
164
  env.passwordFallback = outputOpts.sharedSecret;
139
165
 
140
- this.currentEnv = currentEnvironment;
141
166
  this.currentProject = env?.currentProject || 'null';
142
- this.sourceEnv = outputOpts?.sourceEnv || currentEnvironment;
167
+ this.sourceAlias = outputOpts?.sourceAlias || currentEnvironment;
143
168
 
144
169
  if (currentEnvironment) {
145
170
  this.urls = url(currentEnvironment, env?.currentProject || 'website');
@@ -154,7 +179,7 @@ class ContensisCli {
154
179
  if (currentEnvironment) {
155
180
  env.history = [this.command];
156
181
  if (commandText) {
157
- environments[currentEnvironment] = this.env;
182
+ environments[currentEnvironment] = env;
158
183
  this.session.Update({
159
184
  currentEnvironment,
160
185
  environments,
@@ -181,45 +206,24 @@ class ContensisCli {
181
206
  };
182
207
 
183
208
  Connect = async (environment: string) => {
184
- const { cache, log, messages, session } = this;
209
+ const { log, messages, session } = this;
185
210
 
186
211
  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
212
  this.currentEnv = environment;
199
213
  this.urls = url(environment, 'website');
200
214
 
201
215
  const [fetchErr, response] = await to(fetch(this.urls.cms));
202
216
  if (response && response?.status < 400) {
203
217
  log.success(messages.connect.connected(environment));
218
+ session.UpdateEnv(this.env, environment);
204
219
 
205
220
  if (this.env?.lastUserId) {
206
- await this.ConnectContensis();
221
+ // await this.ConnectContensis();
207
222
  await this.PrintProjects();
208
223
  } else {
209
224
  log.warning(messages.projects.noList());
210
225
  log.help(messages.connect.tip());
211
- // cache.environments[environment] = {
212
- // versionStatus: 'published',
213
- // history: [],
214
- // lastUserId: '',
215
- // projects: [],
216
- // };
217
226
  }
218
-
219
- session.Update({
220
- currentEnvironment: environment,
221
- environments: cache.environments,
222
- });
223
227
  } else {
224
228
  // Cannot reach environment - status X
225
229
  log.error(
@@ -233,68 +237,75 @@ class ContensisCli {
233
237
  };
234
238
 
235
239
  ConnectContensis = async ({ commit = false } = {}) => {
236
- const { contensisOpts, currentEnv, env, log, messages } = this;
237
- const userId = env?.lastUserId;
238
- const isGuidId = userId && isUuid(userId);
240
+ if (!this.contensis) {
241
+ const { contensisOpts, currentEnv, env, log, messages } = this;
242
+ const userId = env?.lastUserId;
243
+ const isGuidId = userId && isUuid(userId);
239
244
 
240
- if (currentEnv && userId) {
241
- const [credentialError, credentials] = await new CredentialProvider(
242
- {
245
+ if (currentEnv && userId) {
246
+ const credentials = await this.GetCredentials(
243
247
  userId,
244
- alias: currentEnv,
245
- },
246
- env.passwordFallback
247
- ).Init();
248
+ env.passwordFallback
249
+ );
248
250
 
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,
251
+ const cachedPassword = credentials?.current?.password;
252
+
253
+ if (cachedPassword) {
254
+ this.contensis = new ContensisMigrationService(
255
+ {
256
+ ...contensisOpts,
257
+ source: {
258
+ url: this.urls?.cms || '',
259
+ username: !isGuidId ? userId : undefined,
260
+ password: !isGuidId ? cachedPassword : undefined,
261
+ clientId: isGuidId ? userId : undefined,
262
+ sharedSecret: isGuidId ? cachedPassword : undefined,
263
+ project: env?.currentProject || '',
264
+ assetHostname: this.urls?.previewWeb,
265
+ },
266
+ concurrency:
267
+ typeof contensisOpts.concurrency !== 'undefined'
268
+ ? contensisOpts.concurrency
269
+ : 3,
270
+ outputProgress: true,
268
271
  },
269
- concurrency:
270
- typeof contensisOpts.concurrency !== 'undefined'
271
- ? contensisOpts.concurrency
272
- : 3,
273
- outputProgress: true,
274
- },
275
- !commit
276
- );
272
+ !commit
273
+ );
274
+ }
275
+ } else {
276
+ if (!currentEnv) log.help(messages.connect.help());
277
+ if (!userId) log.help(messages.connect.tip());
277
278
  }
278
- } else {
279
- if (!currentEnv) log.help(messages.connect.help());
280
- if (!userId) log.help(messages.connect.tip());
281
279
  }
280
+ return this.contensis;
282
281
  };
283
282
 
284
283
  ConnectContensisImport = async ({
285
- commit,
286
- source,
287
- fileData,
288
- fileDataType,
284
+ commit = false,
285
+ fromFile,
286
+ importDataType,
289
287
  }: {
290
- commit: boolean;
291
- source: 'contensis' | 'file' | 'input';
292
- fileData?: any[] | string;
293
- fileDataType?: 'entries' | 'contentTypes' | 'components';
288
+ commit?: boolean;
289
+ fromFile?: string;
290
+ importDataType?:
291
+ | 'entries'
292
+ | 'contentTypes'
293
+ | 'components'
294
+ | 'models'
295
+ | 'user-input';
294
296
  }) => {
295
- const { contensisOpts, currentEnv, env, log, messages, sourceEnv } = this;
297
+ const source: 'contensis' | 'file' = fromFile ? 'file' : 'contensis';
298
+
299
+ const fileData = fromFile
300
+ ? readJsonFile<(Entry | ContentType | Component)[]>(fromFile) || []
301
+ : [];
302
+
303
+ if (typeof fileData === 'string')
304
+ throw new Error(`Import file format must be of type JSON`);
305
+
306
+ const { contensisOpts, currentEnv, env, log, messages, sourceAlias } = this;
296
307
  const environments = this.cache.environments || {};
297
- const sourceEnvironment = environments[sourceEnv || ''] || {};
308
+ const sourceEnvironment = environments[sourceAlias || ''] || {};
298
309
  const sourceCms =
299
310
  ('source' in contensisOpts && contensisOpts.source) ||
300
311
  ({} as Partial<SourceCms>);
@@ -303,7 +314,7 @@ class ContensisCli {
303
314
  const sourceProjectId =
304
315
  sourceCms.project || sourceEnvironment.currentProject || 'website';
305
316
  const isSourceGuidId = sourceUserId && isUuid(sourceUserId);
306
- const sourceUrls = url(sourceEnv || '', sourceProjectId);
317
+ const sourceUrls = url(sourceAlias || '', sourceProjectId);
307
318
 
308
319
  const sourcePassword =
309
320
  sourceCms.sharedSecret ||
@@ -314,40 +325,24 @@ class ContensisCli {
314
325
  const isTargetGuidId = targetUserId && isUuid(targetUserId);
315
326
 
316
327
  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
- }
328
+ const sourceCredentials = await this.GetCredentials(
329
+ sourceUserId,
330
+ sourcePassword,
331
+ sourceAlias,
332
+ false
333
+ );
334
+
331
335
  const cachedSourcePassword = sourceCredentials?.current?.password;
332
336
 
333
- const [targetCredentialError, targetCredentials] =
334
- await new CredentialProvider(
335
- {
336
- userId: targetUserId,
337
- alias: currentEnv,
338
- },
339
- env.passwordFallback
340
- ).Init();
337
+ const targetCredentials = await this.GetCredentials(
338
+ targetUserId,
339
+ env.passwordFallback
340
+ );
341
341
 
342
- if (targetCredentialError && !targetCredentials.current) {
343
- // Log problem with Credential Provider
344
- log.error(targetCredentialError as any);
345
- return;
346
- }
347
342
  const cachedTargetPassword = targetCredentials?.current?.password;
348
343
 
349
344
  if (cachedSourcePassword && cachedTargetPassword) {
350
- if (source === 'file' || source === 'input') {
345
+ if (source === 'file' || importDataType === 'user-input') {
351
346
  this.contensis = new ContensisMigrationService(
352
347
  {
353
348
  concurrency: 3,
@@ -362,12 +357,11 @@ class ContensisCli {
362
357
  targetProjects: [env.currentProject || ''],
363
358
  assetHostname: this.urls?.previewWeb,
364
359
  },
365
- ...(fileDataType ? { [fileDataType]: fileData } : {}),
360
+ ...(importDataType ? { [importDataType]: fileData } : {}),
366
361
  },
367
362
  !commit
368
363
  );
369
- }
370
- if (source === 'contensis') {
364
+ } else if (source === 'contensis') {
371
365
  this.contensis = new ContensisMigrationService(
372
366
  {
373
367
  concurrency: 3,
@@ -400,6 +394,40 @@ class ContensisCli {
400
394
  if (!currentEnv) log.help(messages.connect.help());
401
395
  if (!targetUserId) log.help(messages.connect.tip());
402
396
  }
397
+ return this.contensis;
398
+ };
399
+
400
+ GetCredentials = async (
401
+ userId: string,
402
+ password?: string,
403
+ currentEnv = this.currentEnv,
404
+ saveCurrentEnv = true
405
+ ): Promise<CredentialProvider | undefined> => {
406
+ const { log, messages } = this;
407
+ if (userId) {
408
+ const [credentialError, credentials] = await new CredentialProvider(
409
+ { userId, alias: currentEnv },
410
+ password
411
+ ).Init();
412
+
413
+ if (credentialError && !credentials.current) {
414
+ // Log problem with Credential Provider
415
+ log.error(credentialError as any);
416
+ return;
417
+ }
418
+
419
+ if (credentials.remarks.secure !== true) {
420
+ if (!insecurePasswordWarningShown) {
421
+ log.warning(messages.login.insecurePassword());
422
+ insecurePasswordWarningShown = true;
423
+ }
424
+ } else {
425
+ const env = this.cache.environments[currentEnv];
426
+ env.passwordFallback = undefined;
427
+ this.session.UpdateEnv(env, currentEnv, saveCurrentEnv);
428
+ }
429
+ return credentials;
430
+ }
403
431
  };
404
432
 
405
433
  Login = async (
@@ -409,111 +437,102 @@ class ContensisCli {
409
437
  promptPassword = true,
410
438
  sharedSecret = isSharedSecret(this.env.passwordFallback),
411
439
  silent = false,
440
+ attempt = 1,
412
441
  }: {
413
442
  password?: string;
414
443
  promptPassword?: boolean;
415
444
  sharedSecret?: string;
416
445
  silent?: boolean;
417
- }
446
+ attempt?: number;
447
+ } = {}
418
448
  ): Promise<string | undefined> => {
419
- let inputPassword = password;
449
+ let inputPassword = password || sharedSecret;
420
450
  const { log, messages } = this;
421
451
 
422
452
  if (userId) {
423
- const { cache, currentEnv, env } = this;
453
+ const { currentEnv, env } = this;
424
454
 
425
455
  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
- }
456
+ const credentials = await this.GetCredentials(userId, inputPassword);
457
+
458
+ if (credentials) {
459
+ const cachedPassword = isPassword(credentials.current?.password);
460
+ const cachedSecret = isSharedSecret(credentials.current?.password);
461
+
462
+ if (!cachedPassword && !cachedSecret && promptPassword) {
463
+ // Password prompt
464
+ ({ inputPassword } = await inquirer.prompt([
465
+ {
466
+ type: 'password',
467
+ message: messages.login.passwordPrompt(currentEnv, userId),
468
+ name: 'inputPassword',
469
+ mask: '*',
470
+ prefix: undefined,
471
+ },
472
+ ]));
473
+ }
461
474
 
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
- });
475
+ if (inputPassword || cachedPassword || cachedSecret) {
476
+ const authService = new ContensisAuthService({
477
+ username: userId,
478
+ password: inputPassword || cachedPassword,
479
+ projectId: env?.currentProject || 'website',
480
+ rootUrl: this.urls?.cms || '',
481
+ clientId: userId,
482
+ clientSecret: sharedSecret || cachedSecret,
483
+ });
484
+
485
+ const [authError, bearerToken] = await to(
486
+ authService.BearerToken()
487
+ );
471
488
 
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();
489
+ // Login successful
490
+ if (bearerToken) {
491
+ // Set env vars
492
+ env.authToken = bearerToken;
493
+ env.lastUserId = userId;
494
+ env.passwordFallback =
495
+ credentials.remarks.secure !== true
496
+ ? credentials.current?.password
497
+ : undefined;
498
+
499
+ // Persist env before finding projects or doing anything else
500
+ this.session.UpdateEnv(env);
501
+ if (inputPassword) await credentials.Save(inputPassword);
502
+ if (sharedSecret) await credentials.Save(sharedSecret);
503
+
504
+ if (!silent) {
505
+ Logger.success(messages.login.success(currentEnv, userId));
506
+ await this.PrintProjects();
507
+ }
508
+ } else if (authError) {
509
+ Logger.error(authError.toString());
510
+ // Clear env vars
511
+ env.authToken = '';
512
+ env.lastUserId = '';
513
+ env.passwordFallback = undefined;
514
+ // Persist env to remove cleared values
515
+ this.session.UpdateEnv(env);
516
+
517
+ // If the auth error was raised using a cached password
518
+ if (
519
+ (cachedPassword || cachedSecret) &&
520
+ credentials.remarks.secure
521
+ ) {
522
+ // Remove any bad stored credential and trigger login prompt again
523
+ await credentials.Delete();
524
+ return await this.Login(userId, { password, sharedSecret });
525
+ } else {
526
+ throw new Error(messages.login.failed(currentEnv, userId));
527
+ }
487
528
  }
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
529
 
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));
530
+ return env.authToken;
531
+ } else {
532
+ Logger.error(messages.login.passwordPrompt());
533
+ if (attempt < 2)
534
+ return await this.Login(userId, { attempt: attempt + 1 });
535
+ }
517
536
  }
518
537
  } else {
519
538
  // No environment set, use `contensis connect {alias}` first
@@ -525,19 +544,42 @@ class ContensisCli {
525
544
  }
526
545
  };
527
546
 
547
+ PrintContensisVersion = async () => {
548
+ const { log, messages } = this;
549
+ const contensis = await this.ConnectContensis();
550
+
551
+ if (contensis) {
552
+ // Retrieve projects list for env
553
+ const [projectsErr, projects] = await to(
554
+ contensis.projects.GetSourceProjects()
555
+ );
556
+
557
+ if (Array.isArray(projects)) {
558
+ // Print contensis version to console
559
+ this.HandleFormattingAndOutput(contensis.contensisVersion, () =>
560
+ log.raw(log.highlightText(contensis.contensisVersion))
561
+ );
562
+ }
563
+
564
+ if (projectsErr) {
565
+ log.error(messages.projects.noList());
566
+ log.error(projectsErr.message);
567
+ }
568
+ }
569
+ };
570
+
528
571
  PrintProjects = async () => {
529
- const { cache, currentEnv, currentProject, log, messages, session } = this;
530
- if (!this.contensis) await this.ConnectContensis();
572
+ const { currentProject, log, messages, session } = this;
573
+ const contensis = await this.ConnectContensis();
531
574
 
532
- if (this.contensis) {
575
+ if (contensis) {
533
576
  // Retrieve projects list for env
534
577
  const [projectsErr, projects] = await to(
535
- this.contensis.projects.GetSourceProjects()
578
+ contensis.projects.GetSourceProjects()
536
579
  );
537
580
 
538
581
  if (Array.isArray(projects)) {
539
582
  // save these projects in cache
540
- const currentVals = cache.environments[currentEnv] || {};
541
583
  const nextCurrentProject =
542
584
  currentProject && currentProject !== 'null'
543
585
  ? currentProject
@@ -545,46 +587,80 @@ class ContensisCli {
545
587
  ? 'website'
546
588
  : undefined;
547
589
 
548
- cache.environments[currentEnv] = {
549
- ...currentVals,
590
+ session.UpdateEnv({
550
591
  projects: projects.map(p => p.id),
551
592
  currentProject: nextCurrentProject,
552
- };
593
+ });
553
594
 
554
595
  log.success(messages.projects.list());
596
+ log.raw('');
597
+
555
598
  this.HandleFormattingAndOutput(projects, () => {
556
599
  // print the projects to console
557
- for (const project of projects) {
600
+ for (const project of projects.sort((a, b) =>
601
+ a.id.localeCompare(b.id)
602
+ )) {
603
+ let color;
604
+ try {
605
+ color = chalk.keyword((project as any).color);
606
+ } catch (ex) {
607
+ color = chalk.white;
608
+ }
558
609
  console.log(
559
- ` - ${nextCurrentProject === project.id ? '* ' : ''}[${
560
- project.primaryLanguage
561
- }] ${project.id}`
610
+ `${
611
+ nextCurrentProject === project.id
612
+ ? `>> ${log.boldText(color(project.id))}`
613
+ : ` ${color(project.id)}`
614
+ } ${log.infoText(
615
+ `[${project.supportedLanguages
616
+ .map(l =>
617
+ l === project.primaryLanguage ? `*${log.boldText(l)}` : l
618
+ )
619
+ .join(' ')}]`
620
+ )}`
562
621
  );
563
622
  }
564
623
  });
565
624
 
566
- session.Update({
567
- environments: cache.environments,
568
- });
625
+ if (!this.SetProject(nextCurrentProject))
626
+ log.warning(messages.projects.tip());
627
+ }
569
628
 
570
- if (nextCurrentProject) {
571
- this.env = cache.environments[currentEnv];
572
- this.SetProject(nextCurrentProject);
573
- }
629
+ if (projectsErr) {
630
+ log.error(messages.projects.noList());
631
+ log.error(projectsErr.message);
632
+ }
633
+ }
634
+ };
635
+
636
+ PrintProject = async (projectId = this.currentProject) => {
637
+ const { log, messages, session } = this;
638
+ const contensis = await this.ConnectContensis();
639
+
640
+ if (contensis) {
641
+ // Retrieve projects list for env
642
+ const [projectsErr, projects] = await to(
643
+ contensis.projects.GetSourceProjects()
644
+ );
645
+
646
+ const foundProject = projects?.find(
647
+ p => p.id.toLowerCase() === projectId.toLowerCase()
648
+ );
649
+
650
+ if (foundProject) {
651
+ log.raw('');
652
+ this.HandleFormattingAndOutput(foundProject, log.object);
574
653
  }
575
654
 
576
655
  if (projectsErr) {
577
656
  log.error(messages.projects.noList());
578
657
  log.error(projectsErr.message);
579
658
  }
580
- // } else {
581
- // log.warning(messages.projects.noList());
582
- // log.help(messages.connect.tip());
583
659
  }
584
660
  };
585
661
 
586
- SetProject = async (projectId = '') => {
587
- const { cache, env, log, messages, session } = this;
662
+ SetProject = (projectId = 'website') => {
663
+ const { env, log, messages, session } = this;
588
664
  let nextProjectId: string | undefined;
589
665
  if (env?.projects.length > 0 && env?.lastUserId) {
590
666
  nextProjectId = env.projects.find(
@@ -592,10 +668,9 @@ class ContensisCli {
592
668
  );
593
669
  if (nextProjectId) {
594
670
  env.currentProject = nextProjectId;
595
- session.Update({
596
- environments: cache.environments,
597
- });
671
+ session.UpdateEnv(env);
598
672
  log.success(messages.projects.set(projectId));
673
+ log.raw('');
599
674
  } else {
600
675
  log.error(messages.projects.failedSet(projectId));
601
676
  }
@@ -607,8 +682,8 @@ class ContensisCli {
607
682
  return nextProjectId;
608
683
  };
609
684
 
610
- SetVersion = async (versionStatus: 'latest' | 'published') => {
611
- const { cache, env, log, messages, session } = this;
685
+ SetVersion = (versionStatus: 'latest' | 'published') => {
686
+ const { env, log, messages, session } = this;
612
687
  if (!['latest', 'published'].includes(versionStatus)) {
613
688
  log.error(messages.version.invalid(versionStatus));
614
689
  return false;
@@ -618,10 +693,7 @@ class ContensisCli {
618
693
  return false;
619
694
  }
620
695
  if (env?.projects.length > 0 && env?.lastUserId) {
621
- env.versionStatus = versionStatus;
622
- session.Update({
623
- environments: cache.environments,
624
- });
696
+ session.UpdateEnv({ versionStatus });
625
697
  log.success(messages.version.set(this.currentEnv, versionStatus));
626
698
  return true;
627
699
  } else {
@@ -632,30 +704,13 @@ class ContensisCli {
632
704
  }
633
705
  };
634
706
 
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
707
  PrintApiKeys = async () => {
653
708
  const { currentEnv, log, messages } = this;
654
- if (!this.contensis) await this.ConnectContensis();
709
+ const contensis = await this.ConnectContensis();
655
710
 
656
- if (this.contensis) {
711
+ if (contensis) {
657
712
  // Retrieve keys list for env
658
- const [keysErr, apiKeys] = await this.contensis.apiKeys.GetKeys();
713
+ const [keysErr, apiKeys] = await contensis.apiKeys.GetKeys();
659
714
 
660
715
  if (Array.isArray(apiKeys)) {
661
716
  log.success(messages.keys.list(currentEnv));
@@ -689,13 +744,10 @@ class ContensisCli {
689
744
 
690
745
  CreateApiKey = async (name: string, description?: string) => {
691
746
  const { currentEnv, log, messages } = this;
692
- if (!this.contensis) await this.ConnectContensis();
747
+ const contensis = await this.ConnectContensis();
693
748
 
694
- if (this.contensis) {
695
- const [err, key] = await this.contensis.apiKeys.CreateKey(
696
- name,
697
- description
698
- );
749
+ if (contensis) {
750
+ const [err, key] = await contensis.apiKeys.CreateKey(name, description);
699
751
 
700
752
  if (key) {
701
753
  log.success(messages.keys.created(currentEnv, name));
@@ -719,10 +771,10 @@ class ContensisCli {
719
771
 
720
772
  RemoveApiKey = async (id: string) => {
721
773
  const { currentEnv, log, messages } = this;
722
- if (!this.contensis) await this.ConnectContensis({ commit: true });
774
+ const contensis = await this.ConnectContensis({ commit: true });
723
775
 
724
- if (this.contensis) {
725
- const [err, key] = await this.contensis.apiKeys.RemoveKey(id);
776
+ if (contensis) {
777
+ const [err, key] = await contensis.apiKeys.RemoveKey(id);
726
778
 
727
779
  if (!err) {
728
780
  log.success(messages.keys.removed(currentEnv, id));
@@ -733,26 +785,225 @@ class ContensisCli {
733
785
  }
734
786
  };
735
787
 
736
- GetContentTypes = async () => {
788
+ CreateProject = async (project: Project) => {
789
+ const { currentEnv, log, messages } = this;
790
+ const contensis = await this.ConnectContensis();
791
+
792
+ if (contensis) {
793
+ const [err, created] = await contensis.projects.CreateProject(project);
794
+
795
+ if (created) {
796
+ log.success(messages.projects.created(currentEnv, project.id));
797
+
798
+ this.HandleFormattingAndOutput(created, () => {
799
+ // set the CLI project to the newly created project
800
+ this.SetProject(project.id);
801
+ // print all the projects to console
802
+ this.PrintProjects();
803
+ });
804
+ return project.id;
805
+ }
806
+
807
+ if (err) {
808
+ log.error(messages.projects.failedCreate(currentEnv, project.id), err);
809
+ }
810
+ }
811
+ };
812
+
813
+ UpdateProject = async (project: Partial<Project>) => {
814
+ const { currentEnv, currentProject, log, messages } = this;
815
+ const contensis = await this.ConnectContensis();
816
+
817
+ if (contensis) {
818
+ const [err, updated] = await contensis.projects.UpdateProject({
819
+ id: currentProject,
820
+ ...project,
821
+ });
822
+
823
+ if (updated) {
824
+ log.success(messages.projects.updated(currentEnv, currentProject));
825
+
826
+ this.HandleFormattingAndOutput(updated, log.object);
827
+ return updated.id;
828
+ }
829
+
830
+ if (err) {
831
+ log.error(
832
+ messages.projects.failedUpdate(currentEnv, currentProject),
833
+ err
834
+ );
835
+ }
836
+ }
837
+ };
838
+
839
+ PrintContentModels = async (modelIds: string[] = []) => {
840
+ const { currentProject, log, messages } = this;
841
+ const contensis = await this.ConnectContensis();
842
+ if (contensis) {
843
+ // Retrieve models list for env
844
+ const models = await contensis.models.contentModels();
845
+ const contentTypes = await contensis.models.contentTypes();
846
+ const components = await contensis.models.components();
847
+
848
+ // Models to output to console
849
+ const returnModels = modelIds?.length
850
+ ? models?.filter((m: Model) =>
851
+ modelIds.some(id => id.toLowerCase() === m.id.toLowerCase())
852
+ )
853
+ : undefined;
854
+
855
+ // Generate a list of contentTypeIds and componentIds from all models
856
+ // and dependencies
857
+ const contentTypeIds = Array.from(
858
+ new Set([
859
+ ...(returnModels || models || []).map(m => m.id),
860
+ ...(returnModels || models || [])
861
+ .map(m => m.dependencies?.contentTypes?.map(c => c[0]) || [])
862
+ .flat(),
863
+ ])
864
+ );
865
+ const componentIds = Array.from(
866
+ new Set(
867
+ (returnModels || models || [])
868
+ .map(m => m.dependencies?.components?.map(c => c[0]) || [])
869
+ .flat()
870
+ )
871
+ );
872
+
873
+ // Create an array of all the content types and component definitions
874
+ // we will use this when outputting to a file
875
+ const contentModelBackup = [
876
+ ...contentTypes.filter(c =>
877
+ contentTypeIds.map(i => i.toLowerCase()).includes(c.id.toLowerCase())
878
+ ),
879
+ ...components.filter(c =>
880
+ componentIds.map(i => i.toLowerCase()).includes(c.id.toLowerCase())
881
+ ),
882
+ ];
883
+
884
+ if (Array.isArray(returnModels)) {
885
+ log.success(messages.models.list(currentProject));
886
+ this.HandleFormattingAndOutput(contentModelBackup, () => {
887
+ // print the content models to console
888
+ for (const model of returnModels) {
889
+ log.raw('');
890
+ log.object(model);
891
+ }
892
+ log.raw('');
893
+ });
894
+ } else {
895
+ log.success(
896
+ messages.models.get(currentProject, models?.length.toString() || '0')
897
+ );
898
+ log.raw('');
899
+ if (models?.length) {
900
+ this.HandleFormattingAndOutput(contentModelBackup, () => {
901
+ // print the content models to console
902
+ for (const model of models) {
903
+ const components = model.components?.length || 0;
904
+ const contentTypes = model.contentTypes?.length || 0;
905
+ const dependencies =
906
+ (model.dependencies?.components?.length || 0) +
907
+ (model.dependencies?.contentTypes?.length || 0);
908
+ const dependencyOf =
909
+ (model.dependencyOf?.components?.length || 0) +
910
+ (model.dependencyOf?.contentTypes?.length || 0);
911
+
912
+ const hasAny =
913
+ components + contentTypes + dependencies + dependencyOf;
914
+ log.raw(
915
+ ` - ${log.highlightText(log.boldText(model.id))} ${
916
+ hasAny
917
+ ? log.infoText(
918
+ `{ ${components ? `components: ${components}, ` : ''}${
919
+ contentTypes ? `contentTypes: ${contentTypes}, ` : ''
920
+ }${
921
+ dependencies ? `references: ${dependencies}, ` : ''
922
+ }${
923
+ dependencyOf ? `required by: ${dependencyOf}` : ''
924
+ } }`
925
+ )
926
+ : ''
927
+ }`
928
+ );
929
+ }
930
+ log.raw('');
931
+ });
932
+ }
933
+ }
934
+ }
935
+ };
936
+
937
+ ImportContentModels = async ({
938
+ commit,
939
+ fromFile,
940
+ }: {
941
+ commit: boolean;
942
+ fromFile: string;
943
+ }) => {
737
944
  const { currentProject, log, messages } = this;
738
- let err;
739
- if (!this.contensis) err = await this.HydrateContensis();
740
945
 
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();
946
+ const fileData = fromFile
947
+ ? readJsonFile<(ContentType | Component)[]>(fromFile) || []
948
+ : [];
949
+ if (typeof fileData === 'string')
950
+ throw new Error(`Import file format must be of type JSON`);
951
+
952
+ const contensis = await this.ConnectContensisImport({
953
+ commit,
954
+ fromFile,
955
+ importDataType: 'models',
956
+ });
957
+
958
+ if (contensis) {
959
+ log.line();
960
+ if (contensis.isPreview) {
961
+ console.log(log.successText(` -- IMPORT PREVIEW -- `));
962
+ } else {
963
+ console.log(log.warningText(` *** COMMITTING IMPORT *** `));
964
+ }
965
+
966
+ const [migrateErr, result] = await contensis.MigrateContentModels();
967
+
968
+ if (migrateErr) logError(migrateErr);
969
+ else
970
+ this.HandleFormattingAndOutput(result, () => {
971
+ // print the results to console
972
+ if (!commit) {
973
+ log.raw(log.boldText(`\nContent types:`));
974
+ if (!result.contentTypes) log.info(`- None returned\n`);
975
+ else printModelMigrationAnalysis(this, result.contentTypes);
976
+
977
+ log.raw(log.boldText(`\nComponents:`));
978
+ if (!result.components) log.info(`- None returned\n`);
979
+ else printModelMigrationAnalysis(this, result.components);
980
+ } else {
981
+ const migrateResult = result as MigrateModelsResult;
982
+ log.raw(log.boldText(`\nContent types:`));
983
+ printModelMigrationResult(
984
+ this,
985
+ migrateResult[currentProject].contentTypes
986
+ );
987
+
988
+ log.raw(log.boldText(`\nComponents:`));
989
+ printModelMigrationResult(
990
+ this,
991
+ migrateResult[currentProject].components
992
+ );
993
+ }
994
+ });
745
995
  } else {
746
- log.warning(messages.contenttypes.noList(currentProject));
996
+ log.warning(messages.models.noList(currentProject));
997
+ log.help(messages.connect.tip());
747
998
  }
748
999
  };
749
1000
 
750
1001
  PrintContentTypes = async () => {
751
1002
  const { currentProject, log, messages } = this;
752
- await this.GetContentTypes();
753
- if (this.contensis) {
1003
+ const contensis = await this.ConnectContensis();
1004
+ if (contensis) {
754
1005
  // Retrieve content types list for env
755
- const { contentTypes } = this;
1006
+ const contentTypes = await contensis.models.contentTypes();
756
1007
 
757
1008
  if (Array.isArray(contentTypes)) {
758
1009
  log.success(messages.contenttypes.list(currentProject));
@@ -773,10 +1024,10 @@ class ContensisCli {
773
1024
 
774
1025
  PrintContentType = async (contentTypeId: string) => {
775
1026
  const { currentProject, log, messages } = this;
776
- await this.GetContentTypes();
777
- if (this.contensis) {
1027
+ const contensis = await this.ConnectContensis();
1028
+ if (contensis) {
778
1029
  // Retrieve content types list for env
779
- const { contentTypes } = this;
1030
+ const contentTypes = await contensis.models.contentTypes();
780
1031
 
781
1032
  if (Array.isArray(contentTypes)) {
782
1033
  const contentType = contentTypes.find(
@@ -799,15 +1050,12 @@ class ContensisCli {
799
1050
 
800
1051
  RemoveContentTypes = async (contentTypeIds: string[], commit = false) => {
801
1052
  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
- );
1053
+ const contensis = await this.ConnectContensisImport({
1054
+ commit,
1055
+ importDataType: 'user-input',
1056
+ });
1057
+ if (contensis) {
1058
+ const [err, result] = await contensis.DeleteContentTypes(contentTypeIds);
811
1059
 
812
1060
  if (err) {
813
1061
  log.error(
@@ -822,7 +1070,7 @@ class ContensisCli {
822
1070
  messages.contenttypes.removed(
823
1071
  currentProject,
824
1072
  contentTypeIds.join('", "'),
825
- !this.contensis.isPreview
1073
+ !contensis.isPreview
826
1074
  )
827
1075
  );
828
1076
  // print the results to console
@@ -851,22 +1099,21 @@ class ContensisCli {
851
1099
 
852
1100
  if (!Array.isArray(fileData)) fileData = [fileData];
853
1101
 
854
- await this.ConnectContensisImport({
1102
+ const contensis = await this.ConnectContensisImport({
855
1103
  commit,
856
- source: fromFile ? 'file' : 'contensis',
1104
+ importDataType: fromFile ? 'user-input' : undefined,
857
1105
  });
858
1106
 
859
- if (this.contensis) {
1107
+ if (contensis) {
860
1108
  // Pass each content type to the target repo
861
1109
  for (const contentType of fileData) {
862
1110
  // Fix invalid data
863
1111
  contentType.projectId = currentProject;
864
1112
  delete contentType.uuid;
865
1113
 
866
- const [err, created, createStatus] =
867
- await this.contensis.models.targetRepos[
868
- currentProject
869
- ].repo.UpsertContentType(false, contentType);
1114
+ const [err, created, createStatus] = await contensis.models.targetRepos[
1115
+ currentProject
1116
+ ].repo.UpsertContentType(false, contentType);
870
1117
 
871
1118
  if (err) log.error(err.message, err);
872
1119
  if (createStatus) {
@@ -884,12 +1131,59 @@ class ContensisCli {
884
1131
  }
885
1132
  };
886
1133
 
1134
+ DiffModels = async (
1135
+ {
1136
+ fromFile,
1137
+ }: {
1138
+ fromFile: string;
1139
+ },
1140
+ modelIds: string[] = []
1141
+ ) => {
1142
+ const { log } = this;
1143
+
1144
+ let fileData = fromFile ? readJsonFile<ContentType[]>(fromFile) || [] : [];
1145
+ if (typeof fileData === 'string')
1146
+ throw new Error(`Import file format must be of type JSON`);
1147
+
1148
+ if (!Array.isArray(fileData)) fileData = [fileData];
1149
+
1150
+ const contensis = await this.ConnectContensisImport({
1151
+ fromFile,
1152
+ importDataType: 'models',
1153
+ });
1154
+
1155
+ if (contensis) {
1156
+ const [err, result] = (await to(
1157
+ contensis.models.Diff(fileData.length ? fileData : modelIds)
1158
+ )) as [Error | null, ContentTypesResult | undefined];
1159
+
1160
+ if (err) log.error(err.message, err);
1161
+ if (result)
1162
+ // print the content type to console
1163
+ this.HandleFormattingAndOutput(result, () => {
1164
+ log.success(
1165
+ `Queried models ${log.infoText(
1166
+ `"${result.query.modelIds?.join(', ')}"`
1167
+ )}\n`
1168
+ );
1169
+
1170
+ log.raw(log.boldText(`Content types:`));
1171
+ if (!result.contentTypes) log.info(`- None returned\n`);
1172
+ else printModelMigrationAnalysis(this, result.contentTypes);
1173
+
1174
+ log.raw(log.boldText(`Components:`));
1175
+ if (!result.components) log.info(`- None returned\n`);
1176
+ else printModelMigrationAnalysis(this, result.components);
1177
+ });
1178
+ }
1179
+ };
1180
+
887
1181
  PrintComponents = async () => {
888
1182
  const { currentProject, log, messages } = this;
889
- await this.GetContentTypes();
890
- if (this.contensis) {
1183
+ const contensis = await this.ConnectContensis();
1184
+ if (contensis) {
891
1185
  // Retrieve components list for env
892
- const { components } = this;
1186
+ const components = await contensis.models.components();
893
1187
 
894
1188
  if (Array.isArray(components)) {
895
1189
  log.success(messages.components.list(currentProject));
@@ -911,10 +1205,10 @@ class ContensisCli {
911
1205
 
912
1206
  PrintComponent = async (componentId: string) => {
913
1207
  const { currentProject, log, messages } = this;
914
- await this.GetContentTypes();
915
- if (this.contensis) {
1208
+ const contensis = await this.ConnectContensis();
1209
+ if (contensis) {
916
1210
  // Retrieve content types list for env
917
- const { components } = this;
1211
+ const components = await contensis.models.components();
918
1212
 
919
1213
  if (Array.isArray(components)) {
920
1214
  const component = components.find(
@@ -933,13 +1227,12 @@ class ContensisCli {
933
1227
 
934
1228
  RemoveComponents = async (componentIds: string[], commit = false) => {
935
1229
  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(
1230
+ const contensis = await this.ConnectContensisImport({
1231
+ commit,
1232
+ importDataType: 'user-input',
1233
+ });
1234
+ if (contensis) {
1235
+ const [err, result] = await contensis.DeleteContentTypes(
943
1236
  undefined,
944
1237
  componentIds
945
1238
  );
@@ -957,7 +1250,7 @@ class ContensisCli {
957
1250
  messages.components.removed(
958
1251
  currentProject,
959
1252
  componentIds.join('", "'),
960
- !this.contensis.isPreview
1253
+ !contensis.isPreview
961
1254
  )
962
1255
  );
963
1256
  // print the results to console
@@ -986,22 +1279,21 @@ class ContensisCli {
986
1279
 
987
1280
  if (!Array.isArray(fileData)) fileData = [fileData];
988
1281
 
989
- await this.ConnectContensisImport({
1282
+ const contensis = await this.ConnectContensisImport({
990
1283
  commit,
991
- source: fromFile ? 'file' : 'contensis',
1284
+ importDataType: fromFile ? 'user-input' : undefined,
992
1285
  });
993
1286
 
994
- if (this.contensis) {
1287
+ if (contensis) {
995
1288
  // Pass each component to the target repo
996
1289
  for (const component of fileData) {
997
1290
  // Fix invalid data
998
1291
  component.projectId = currentProject;
999
1292
  delete component.uuid;
1000
1293
 
1001
- const [err, created, createStatus] =
1002
- await this.contensis.models.targetRepos[
1003
- currentProject
1004
- ].repo.UpsertComponent(false, component);
1294
+ const [err, created, createStatus] = await contensis.models.targetRepos[
1295
+ currentProject
1296
+ ].repo.UpsertComponent(false, component);
1005
1297
 
1006
1298
  if (err) log.error(err.message, err);
1007
1299
  if (createStatus) {
@@ -1019,38 +1311,42 @@ class ContensisCli {
1019
1311
  }
1020
1312
  };
1021
1313
 
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
- });
1314
+ RemoveEntries = async (commit = false) => {
1315
+ const { currentEnv, currentProject, log, messages } = this;
1316
+ const contensis = await this.ConnectContensisImport({
1317
+ commit,
1318
+ importDataType: 'user-input',
1319
+ });
1029
1320
 
1030
- if (this.contensis) {
1031
- if (this.contensis.isPreview) {
1321
+ if (contensis) {
1322
+ if (contensis.isPreview) {
1032
1323
  console.log(log.successText(` -- PREVIEW -- `));
1033
1324
  } else {
1034
1325
  console.log(log.warningText(` *** COMMITTING DELETE *** `));
1035
1326
  }
1036
- const [err, result] = await this.contensis.DeleteEntries();
1327
+ const [err, result] = await contensis.DeleteEntries();
1037
1328
  if (result)
1038
1329
  this.HandleFormattingAndOutput(result, () => {
1039
1330
  // print the migrateResult to console
1040
- printMigrateResult(this, result, { action: 'delete' });
1331
+ printMigrateResult(this, result, {
1332
+ action: 'delete',
1333
+ showAllEntries: true,
1334
+ });
1041
1335
  });
1042
1336
  if (
1043
1337
  !err &&
1044
- ((!commit &&
1045
- Object.values(result.entriesToMigrate)?.[0].totalCount > 0) ||
1338
+ ((!commit && result.entriesToMigrate[currentProject].totalCount) ||
1046
1339
  (commit && result.migrateResult?.deleted))
1047
1340
  ) {
1048
- log.success(messages.entries.removed(currentEnv, id, commit));
1049
- if (!commit) log.help(messages.entries.commitTip());
1341
+ log.success(messages.entries.removed(currentEnv, commit));
1342
+ if (!commit) {
1343
+ log.raw(``);
1344
+ log.help(messages.entries.commitTip());
1345
+ }
1050
1346
  } 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));
1347
+ log.error(messages.entries.failedRemove(currentEnv), err);
1348
+ if (!result.entriesToMigrate[currentProject].totalCount)
1349
+ log.help(messages.entries.notFound(currentEnv));
1054
1350
  }
1055
1351
  }
1056
1352
  };
@@ -1061,21 +1357,21 @@ class ContensisCli {
1061
1357
  withDependents?: boolean;
1062
1358
  }) => {
1063
1359
  const { currentProject, log, messages } = this;
1064
- await this.ConnectContensis();
1360
+ const contensis = await this.ConnectContensis();
1065
1361
 
1066
- if (this.contensis) {
1362
+ if (contensis) {
1067
1363
  log.line();
1068
- const entries = await this.contensis.GetEntries({ withDependents });
1364
+ const entries = await contensis.GetEntries({ withDependents });
1069
1365
  this.HandleFormattingAndOutput(entries, () =>
1070
1366
  // print the entries to console
1071
1367
  logEntriesTable(
1072
1368
  entries,
1073
1369
  currentProject,
1074
- this.contensis?.payload.query?.fields
1370
+ contensis.payload.query?.fields
1075
1371
  )
1076
1372
  );
1077
1373
  } else {
1078
- log.warning(messages.contenttypes.noList(currentProject));
1374
+ log.warning(messages.models.noList(currentProject));
1079
1375
  log.help(messages.connect.tip());
1080
1376
  }
1081
1377
  };
@@ -1083,41 +1379,68 @@ class ContensisCli {
1083
1379
  ImportEntries = async ({
1084
1380
  commit,
1085
1381
  fromFile,
1382
+ logOutput,
1086
1383
  }: {
1087
1384
  commit: boolean;
1088
1385
  fromFile: string;
1386
+ logOutput: string;
1089
1387
  }) => {
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`);
1388
+ const { currentEnv, currentProject, log, messages } = this;
1095
1389
 
1096
- await this.ConnectContensisImport({
1390
+ const contensis = await this.ConnectContensisImport({
1097
1391
  commit,
1098
- source: fromFile ? 'file' : 'contensis',
1099
- fileData,
1100
- fileDataType: 'entries',
1392
+ fromFile,
1393
+ importDataType: 'entries',
1101
1394
  });
1102
1395
 
1103
- if (this.contensis) {
1396
+ if (contensis) {
1104
1397
  log.line();
1105
- if (this.contensis.isPreview) {
1398
+ if (contensis.isPreview) {
1106
1399
  console.log(log.successText(` -- IMPORT PREVIEW -- `));
1107
1400
  } else {
1108
1401
  console.log(log.warningText(` *** COMMITTING IMPORT *** `));
1109
1402
  }
1110
1403
 
1111
- const [migrateErr, migrateResult] = await this.contensis.MigrateEntries();
1404
+ const [err, result] = await contensis.MigrateEntries();
1112
1405
 
1113
- if (migrateErr) logError(migrateErr);
1406
+ if (err) logError(err);
1114
1407
  else
1115
- this.HandleFormattingAndOutput(migrateResult, () => {
1408
+ this.HandleFormattingAndOutput(result, () => {
1116
1409
  // print the migrateResult to console
1117
- printMigrateResult(this, migrateResult);
1410
+ printMigrateResult(this, result, {
1411
+ showAllEntries: logOutput === 'all',
1412
+ showChangedEntries: logOutput === 'changes',
1413
+ });
1118
1414
  });
1415
+
1416
+ if (
1417
+ !err &&
1418
+ !result.errors?.length &&
1419
+ ((!commit && result.entriesToMigrate[currentProject].totalCount) ||
1420
+ (commit &&
1421
+ (result.migrateResult?.created || result.migrateResult?.updated)))
1422
+ ) {
1423
+ log.success(
1424
+ messages.entries.imported(
1425
+ currentEnv,
1426
+ commit,
1427
+ commit
1428
+ ? (result.migrateResult?.created || 0) +
1429
+ (result.migrateResult?.updated || 0)
1430
+ : result.entriesToMigrate[currentProject].totalCount
1431
+ )
1432
+ );
1433
+ if (!commit) {
1434
+ log.raw(``);
1435
+ log.help(messages.entries.commitTip());
1436
+ }
1437
+ } else {
1438
+ log.error(messages.entries.failedImport(currentEnv), err);
1439
+ if (!result.entriesToMigrate[currentProject].totalCount)
1440
+ log.help(messages.entries.notFound(currentEnv));
1441
+ }
1119
1442
  } else {
1120
- log.warning(messages.contenttypes.noList(currentProject));
1443
+ log.warning(messages.models.noList(currentProject));
1121
1444
  log.help(messages.connect.tip());
1122
1445
  }
1123
1446
  };
@@ -1127,19 +1450,19 @@ class ContensisCli {
1127
1450
  name?: string
1128
1451
  ) => {
1129
1452
  const { currentEnv, log, messages } = this;
1130
- if (!this.contensis) await this.ConnectContensis();
1131
- if (this.contensis) {
1453
+ const contensis = await this.ConnectContensis();
1454
+ if (contensis) {
1132
1455
  // Retrieve webhooks list for env
1133
1456
  const [webhooksErr, webhooks] =
1134
- await this.contensis.subscriptions.webhooks.GetSubscriptions();
1457
+ await contensis.subscriptions.webhooks.GetSubscriptions();
1135
1458
 
1136
1459
  const filteredResults =
1137
1460
  typeof name === 'string'
1138
- ? webhooks.filter(w =>
1461
+ ? webhooks?.filter(w =>
1139
1462
  w.name?.toLowerCase().includes(name.toLowerCase())
1140
1463
  )
1141
1464
  : Array.isArray(subscriptionIds)
1142
- ? webhooks.filter(w => subscriptionIds?.some(id => id === w.id))
1465
+ ? webhooks?.filter(w => subscriptionIds?.some(id => id === w.id))
1143
1466
  : webhooks;
1144
1467
 
1145
1468
  if (Array.isArray(filteredResults)) {
@@ -1176,16 +1499,16 @@ class ContensisCli {
1176
1499
  };
1177
1500
 
1178
1501
  PrintBlocks = async () => {
1179
- const { currentEnv, log, messages } = this;
1180
- if (!this.contensis) await this.ConnectContensis();
1181
- if (this.contensis) {
1502
+ const { currentEnv, env, log, messages } = this;
1503
+ const contensis = await this.ConnectContensis();
1504
+ if (contensis) {
1182
1505
  // Retrieve blocks list for env
1183
- const [err, blocks] = await this.contensis.blocks.GetBlocks();
1506
+ const [err, blocks] = await contensis.blocks.GetBlocks();
1184
1507
 
1185
1508
  if (Array.isArray(blocks)) {
1186
1509
  this.HandleFormattingAndOutput(blocks, () => {
1187
1510
  // print the blocks to console
1188
- log.success(messages.blocks.list(currentEnv));
1511
+ log.success(messages.blocks.list(currentEnv, env.currentProject));
1189
1512
  for (const {
1190
1513
  id,
1191
1514
  description,
@@ -1226,10 +1549,10 @@ class ContensisCli {
1226
1549
  version: string
1227
1550
  ) => {
1228
1551
  const { currentEnv, env, log, messages } = this;
1229
- if (!this.contensis) await this.ConnectContensis();
1230
- if (this.contensis) {
1552
+ const contensis = await this.ConnectContensis();
1553
+ if (contensis) {
1231
1554
  // Retrieve block version
1232
- const [err, blocks] = await this.contensis.blocks.GetBlockVersions(
1555
+ const [err, blocks] = await contensis.blocks.GetBlockVersions(
1233
1556
  blockId,
1234
1557
  branch,
1235
1558
  version
@@ -1239,7 +1562,7 @@ class ContensisCli {
1239
1562
  this.HandleFormattingAndOutput(blocks, () => {
1240
1563
  // print the version detail to console
1241
1564
  log.success(
1242
- messages.blocks.get(`${currentEnv}:${env.currentProject}`)
1565
+ messages.blocks.get(blockId, currentEnv, env.currentProject)
1243
1566
  );
1244
1567
  for (const block of blocks)
1245
1568
  printBlockVersion(
@@ -1268,18 +1591,20 @@ class ContensisCli {
1268
1591
  const { currentEnv, env, log, messages } = this;
1269
1592
 
1270
1593
  // Output request to console
1271
- messages.blocks.tryPush(
1272
- block.id,
1273
- block.source.branch,
1274
- currentEnv,
1275
- env.currentProject
1594
+ log.info(
1595
+ messages.blocks.tryPush(
1596
+ block.id,
1597
+ block.source.branch,
1598
+ currentEnv,
1599
+ env.currentProject
1600
+ )
1276
1601
  );
1277
1602
  console.log(jsonFormatter(block));
1278
1603
 
1279
- if (!this.contensis) await this.ConnectContensis();
1280
- if (this.contensis) {
1604
+ const contensis = await this.ConnectContensis();
1605
+ if (contensis) {
1281
1606
  // Push new block version
1282
- const [err, blockVersion] = await this.contensis.blocks.PushBlockVersion(
1607
+ const [err, blockVersion] = await contensis.blocks.PushBlockVersion(
1283
1608
  block
1284
1609
  );
1285
1610
  if (!err) {
@@ -1291,7 +1616,6 @@ class ContensisCli {
1291
1616
  env.currentProject
1292
1617
  )
1293
1618
  );
1294
- console.log(jsonFormatter(blockVersion));
1295
1619
  }
1296
1620
  if (blockVersion) {
1297
1621
  this.HandleFormattingAndOutput(blockVersion, () => {
@@ -1303,6 +1627,40 @@ class ContensisCli {
1303
1627
  throw new Error(
1304
1628
  messages.blocks.failedPush(block.id, currentEnv, env.currentProject)
1305
1629
  );
1630
+ } else {
1631
+ throw new Error(
1632
+ messages.blocks.failedPush(block.id, currentEnv, env.currentProject)
1633
+ );
1634
+ }
1635
+ };
1636
+
1637
+ ReleaseBlock = async (blockId: string, version: string) => {
1638
+ const { currentEnv, env, log, messages } = this;
1639
+ const contensis = await this.ConnectContensis();
1640
+ if (contensis) {
1641
+ // Retrieve block version
1642
+ const [err, blockVersion] = await contensis.blocks.BlockAction(
1643
+ blockId,
1644
+ 'release',
1645
+ version
1646
+ );
1647
+
1648
+ if (blockVersion) {
1649
+ this.HandleFormattingAndOutput(blockVersion, () => {
1650
+ // print the version detail to console
1651
+ log.success(
1652
+ messages.blocks.released(blockId, currentEnv, env.currentProject)
1653
+ );
1654
+ printBlockVersion(this, blockVersion);
1655
+ });
1656
+ }
1657
+
1658
+ if (err) {
1659
+ log.error(
1660
+ messages.blocks.failedRelease(blockId, currentEnv, env.currentProject)
1661
+ );
1662
+ log.error(jsonFormatter(err));
1663
+ }
1306
1664
  }
1307
1665
  };
1308
1666
 
@@ -1312,11 +1670,15 @@ class ContensisCli {
1312
1670
  version: string,
1313
1671
  dataCenter: 'hq' | 'manchester' | 'london'
1314
1672
  ) => {
1315
- const { currentEnv, log, messages } = this;
1316
- if (!this.contensis) await this.ConnectContensis();
1317
- if (this.contensis) {
1673
+ const { currentEnv, env, log, messages } = this;
1674
+ const contensis = await this.ConnectContensis();
1675
+ if (contensis) {
1318
1676
  // Retrieve block logs
1319
- const [err, blockLogs] = await this.contensis.blocks.GetBlockLogs({
1677
+ log.success(
1678
+ messages.blocks.getLogs(blockId, branch, currentEnv, env.currentProject)
1679
+ );
1680
+
1681
+ const [err, blockLogs] = await contensis.blocks.GetBlockLogs({
1320
1682
  blockId,
1321
1683
  branchId: branch,
1322
1684
  version,
@@ -1325,8 +1687,7 @@ class ContensisCli {
1325
1687
 
1326
1688
  if (blockLogs) {
1327
1689
  this.HandleFormattingAndOutput(blockLogs, () => {
1328
- // print the logs to console
1329
- log.success(messages.blocks.list(currentEnv));
1690
+ // print the logs to console
1330
1691
  console.log(
1331
1692
  ` - ${blockId} ${branch} ${
1332
1693
  Number(version) ? `v${version}` : version
@@ -1339,7 +1700,9 @@ class ContensisCli {
1339
1700
  }
1340
1701
 
1341
1702
  if (err) {
1342
- log.error(messages.blocks.noList(currentEnv));
1703
+ log.error(
1704
+ messages.blocks.failedGetLogs(blockId, currentEnv, env.currentProject)
1705
+ );
1343
1706
  log.error(jsonFormatter(err));
1344
1707
  }
1345
1708
  }
@@ -1347,6 +1710,21 @@ class ContensisCli {
1347
1710
 
1348
1711
  HandleFormattingAndOutput = <T>(obj: T, logFn: (obj: T) => void) => {
1349
1712
  const { format, log, messages, output } = this;
1713
+ if (!format) {
1714
+ // print the object to console
1715
+ logFn(obj);
1716
+ } else if (format === 'csv') {
1717
+ log.raw('');
1718
+ log.raw(log.infoText(csvFormatter(obj)));
1719
+ } else if (format === 'xml') {
1720
+ log.raw('');
1721
+ log.raw(log.infoText(xmlFormatter(obj)));
1722
+ } else if (format === 'json') {
1723
+ log.raw('');
1724
+ log.raw(log.infoText(jsonFormatter(obj)));
1725
+ }
1726
+ log.raw('');
1727
+
1350
1728
  if (output) {
1351
1729
  let writeString = '';
1352
1730
  if (format === 'csv') {
@@ -1361,21 +1739,6 @@ class ContensisCli {
1361
1739
  } else {
1362
1740
  log.info(messages.app.noFileOutput());
1363
1741
  }
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
1742
  }
1380
1743
  };
1381
1744
  }