contensis-cli 1.0.0-beta.53 → 1.0.0-beta.54

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