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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/README.md +1146 -78
  2. package/cli.js +3 -0
  3. package/dist/commands/connect.js +3 -3
  4. package/dist/commands/connect.js.map +2 -2
  5. package/dist/commands/create.js +45 -10
  6. package/dist/commands/create.js.map +2 -2
  7. package/dist/commands/dev.js +71 -0
  8. package/dist/commands/dev.js.map +7 -0
  9. package/dist/commands/diff.js +57 -0
  10. package/dist/commands/diff.js.map +7 -0
  11. package/dist/commands/execute.js +103 -0
  12. package/dist/commands/execute.js.map +7 -0
  13. package/dist/commands/get.js +169 -32
  14. package/dist/commands/get.js.map +3 -3
  15. package/dist/commands/globalOptions.js +37 -12
  16. package/dist/commands/globalOptions.js.map +2 -2
  17. package/dist/commands/import.js +65 -12
  18. package/dist/commands/import.js.map +2 -2
  19. package/dist/commands/index.js +22 -2
  20. package/dist/commands/index.js.map +2 -2
  21. package/dist/commands/list.js +53 -10
  22. package/dist/commands/list.js.map +2 -2
  23. package/dist/commands/login.js +2 -2
  24. package/dist/commands/login.js.map +2 -2
  25. package/dist/commands/push.js +17 -13
  26. package/dist/commands/push.js.map +2 -2
  27. package/dist/commands/remove.js +51 -8
  28. package/dist/commands/remove.js.map +2 -2
  29. package/dist/commands/set.js +139 -12
  30. package/dist/commands/set.js.map +2 -2
  31. package/dist/index.js +1 -1
  32. package/dist/index.js.map +2 -2
  33. package/dist/localisation/en-GB.js +297 -49
  34. package/dist/localisation/en-GB.js.map +2 -2
  35. package/dist/mappers/ContensisCliService-to-RequestHanderSiteConfigYaml.js +56 -0
  36. package/dist/mappers/ContensisCliService-to-RequestHanderSiteConfigYaml.js.map +7 -0
  37. package/dist/mappers/DevInit-to-CIWorkflow.js +374 -0
  38. package/dist/mappers/DevInit-to-CIWorkflow.js.map +7 -0
  39. package/dist/mappers/DevInit-to-RolePermissions.js +56 -0
  40. package/dist/mappers/DevInit-to-RolePermissions.js.map +7 -0
  41. package/dist/mappers/DevRequests-to-RequestHanderSiteConfigYaml.js +56 -0
  42. package/dist/mappers/DevRequests-to-RequestHanderSiteConfigYaml.js.map +7 -0
  43. package/dist/models/CliService.d.js +17 -0
  44. package/dist/models/CliService.d.js.map +7 -0
  45. package/dist/models/DevService.d.js +17 -0
  46. package/dist/models/DevService.d.js.map +7 -0
  47. package/dist/providers/CredentialProvider.js +46 -14
  48. package/dist/providers/CredentialProvider.js.map +3 -3
  49. package/dist/providers/SessionCacheProvider.js +21 -1
  50. package/dist/providers/SessionCacheProvider.js.map +2 -2
  51. package/dist/providers/file-provider.js +12 -6
  52. package/dist/providers/file-provider.js.map +3 -3
  53. package/dist/services/ContensisCliService.js +1211 -420
  54. package/dist/services/ContensisCliService.js.map +3 -3
  55. package/dist/services/ContensisDevService.js +368 -0
  56. package/dist/services/ContensisDevService.js.map +7 -0
  57. package/dist/services/ContensisRoleService.js +114 -0
  58. package/dist/services/ContensisRoleService.js.map +7 -0
  59. package/dist/shell.js +58 -18
  60. package/dist/shell.js.map +3 -3
  61. package/dist/util/console.printer.js +171 -55
  62. package/dist/util/console.printer.js.map +2 -2
  63. package/dist/util/diff.js +116 -0
  64. package/dist/util/diff.js.map +7 -0
  65. package/dist/util/dotenv.js +57 -0
  66. package/dist/util/dotenv.js.map +7 -0
  67. package/dist/util/find.js +31 -0
  68. package/dist/util/find.js.map +7 -0
  69. package/dist/util/git.js +128 -0
  70. package/dist/util/git.js.map +7 -0
  71. package/dist/util/index.js +8 -2
  72. package/dist/util/index.js.map +3 -3
  73. package/dist/util/logger.js +90 -29
  74. package/dist/util/logger.js.map +3 -3
  75. package/dist/util/os.js +42 -0
  76. package/dist/util/os.js.map +7 -0
  77. package/dist/util/timers.js +49 -0
  78. package/dist/util/timers.js.map +7 -0
  79. package/dist/util/yaml.js +45 -0
  80. package/dist/util/yaml.js.map +7 -0
  81. package/dist/version.js +1 -1
  82. package/dist/version.js.map +1 -1
  83. package/esbuild.config.js +3 -1
  84. package/package.json +14 -3
  85. package/src/commands/connect.ts +3 -2
  86. package/src/commands/create.ts +61 -8
  87. package/src/commands/dev.ts +69 -0
  88. package/src/commands/diff.ts +41 -0
  89. package/src/commands/execute.ts +117 -0
  90. package/src/commands/get.ts +242 -28
  91. package/src/commands/globalOptions.ts +42 -12
  92. package/src/commands/import.ts +83 -8
  93. package/src/commands/index.ts +22 -1
  94. package/src/commands/list.ts +85 -11
  95. package/src/commands/login.ts +2 -1
  96. package/src/commands/push.ts +18 -11
  97. package/src/commands/remove.ts +66 -4
  98. package/src/commands/set.ts +189 -9
  99. package/src/index.ts +1 -4
  100. package/src/localisation/en-GB.ts +428 -66
  101. package/src/mappers/ContensisCliService-to-RequestHanderSiteConfigYaml.ts +44 -0
  102. package/src/mappers/DevInit-to-CIWorkflow.ts +526 -0
  103. package/src/mappers/DevInit-to-RolePermissions.ts +32 -0
  104. package/src/mappers/DevRequests-to-RequestHanderSiteConfigYaml.ts +44 -0
  105. package/src/models/CliService.d.ts +36 -0
  106. package/src/models/DevService.d.ts +40 -0
  107. package/src/models/JsModules.d.ts +2 -0
  108. package/src/providers/CredentialProvider.ts +51 -18
  109. package/src/providers/SessionCacheProvider.ts +29 -2
  110. package/src/providers/file-provider.ts +17 -6
  111. package/src/services/ContensisCliService.ts +1532 -508
  112. package/src/services/ContensisDevService.ts +434 -0
  113. package/src/services/ContensisRoleService.ts +108 -0
  114. package/src/shell.ts +68 -18
  115. package/src/util/console.printer.ts +240 -78
  116. package/src/util/diff.ts +124 -0
  117. package/src/util/dotenv.ts +37 -0
  118. package/src/util/find.ts +8 -0
  119. package/src/util/git.ts +131 -0
  120. package/src/util/index.ts +16 -7
  121. package/src/util/logger.ts +145 -31
  122. package/src/util/os.ts +12 -0
  123. package/src/util/timers.ts +24 -0
  124. package/src/util/yaml.ts +13 -0
  125. package/src/version.ts +1 -1
package/src/shell.ts CHANGED
@@ -1,13 +1,14 @@
1
- import path from 'path';
2
1
  import figlet from 'figlet';
3
2
  import inquirer from 'inquirer';
4
3
  import inquirerPrompt from 'inquirer-command-prompt';
5
4
  import commands from './commands';
6
5
  import { LogMessages } from './localisation/en-GB';
7
- import { logError, Logger } from './util/logger';
8
6
  import CredentialProvider from './providers/CredentialProvider';
7
+ import { appRootDir } from './providers/file-provider';
9
8
  import ContensisCli, { cliCommand } from './services/ContensisCliService';
10
9
  import { Logging } from './util';
10
+ import { logError, Logger } from './util/logger';
11
+ import { LIB_VERSION } from './version';
11
12
 
12
13
  class ContensisShell {
13
14
  private currentEnvironment!: string;
@@ -39,12 +40,11 @@ class ContensisShell {
39
40
  inquirerPrompt.setConfig({
40
41
  history: {
41
42
  save: true,
42
- folder: path.join(__dirname, '../'),
43
+ folder: appRootDir,
43
44
  limit: 100,
44
45
  blacklist: ['quit'],
45
46
  },
46
47
  });
47
- // inquirer.registerPrompt('command', inquirerPrompt);
48
48
 
49
49
  const { log, messages } = this;
50
50
 
@@ -64,7 +64,7 @@ class ContensisShell {
64
64
  return;
65
65
  }
66
66
  console.log(log.successText(data));
67
- console.log(log.infoText(messages.app.startup()));
67
+ console.log(log.infoText(messages.app.startup(LIB_VERSION)));
68
68
  console.log(log.helpText(messages.app.help()));
69
69
 
70
70
  this.start().catch(ex => log.error(ex));
@@ -72,8 +72,13 @@ class ContensisShell {
72
72
  );
73
73
  }
74
74
 
75
+ restart = async () => {
76
+ this.firstStart = false;
77
+ this.log.line(); // add a line so we can see where the shell has been restarted
78
+ await this.start();
79
+ };
80
+
75
81
  start = async () => {
76
- this.log.line();
77
82
  this.refreshEnvironment();
78
83
  this.userId = '';
79
84
  const { currentEnvironment, env, log, messages } = this;
@@ -98,7 +103,10 @@ class ContensisShell {
98
103
  silent: true,
99
104
  }
100
105
  );
101
- if (token) this.userId = env.lastUserId;
106
+ if (token) {
107
+ this.userId = env.lastUserId;
108
+ if (!env.currentProject) log.warning(messages.projects.tip());
109
+ }
102
110
  this.firstStart = false;
103
111
  this.refreshEnvironment();
104
112
  } else {
@@ -127,26 +135,52 @@ class ContensisShell {
127
135
  availableCommands.push('login', 'list projects', 'set project');
128
136
  if (userId)
129
137
  availableCommands.push(
138
+ 'create key',
139
+ 'create project',
140
+ 'create role',
141
+ 'diff models',
142
+ 'execute block action release',
143
+ 'execute block action makelive',
144
+ 'execute block action rollback',
145
+ 'execute block action markasbroken',
130
146
  'get block',
131
147
  'get block logs',
132
148
  'get contenttype',
133
149
  'get component',
134
150
  'get entries',
151
+ 'get nodes',
152
+ 'get model',
153
+ 'get project',
154
+ 'get role',
155
+ 'get token',
156
+ 'get version',
157
+ 'get webhook',
135
158
  'import contenttypes',
136
159
  'import components',
137
160
  'import entries',
161
+ 'import models',
138
162
  'list blocks',
139
163
  'list contenttypes',
140
164
  'list components',
141
- 'list models',
142
165
  'list keys',
166
+ 'list models',
167
+ 'list proxies',
168
+ 'list renderers',
169
+ 'list roles',
143
170
  'list webhooks',
144
- 'create key',
145
171
  'push block',
146
- 'remove key',
147
- 'remove entry',
172
+ 'remove components',
148
173
  'remove contenttypes',
149
- 'remove components'
174
+ 'remove key',
175
+ 'remove entries',
176
+ 'remove role',
177
+ 'set project name',
178
+ 'set project description',
179
+ 'set role name',
180
+ 'set role description',
181
+ 'set role assignments',
182
+ 'set role enabled',
183
+ 'set role permissions'
150
184
  );
151
185
 
152
186
  const prompt = inquirer.createPromptModule();
@@ -155,7 +189,7 @@ class ContensisShell {
155
189
  {
156
190
  type: 'command',
157
191
  name: 'cmd',
158
- autoCompletion: availableCommands,
192
+ autoCompletion: availableCommands.sort(),
159
193
  autocompletePrompt: log.infoText(messages.app.autocomplete()),
160
194
  message: `${userId ? `${userId}@` : ''}${currentEnvironment || ''}>`,
161
195
  context: 0,
@@ -168,7 +202,7 @@ class ContensisShell {
168
202
  return true;
169
203
  }
170
204
  },
171
- prefix: `${env?.currentProject || 'contensis'}`,
205
+ prefix: `${env?.currentProject || log.infoText('contensis')}`,
172
206
  short: true,
173
207
  },
174
208
  ])
@@ -191,7 +225,14 @@ class ContensisShell {
191
225
  } catch (ex: any) {
192
226
  const str = ex.toString();
193
227
  if (!str.includes('CommanderError'))
194
- logError(ex, `Shell ${ex.toString()}`);
228
+ logError(
229
+ ex,
230
+ `Shell ${
231
+ ex instanceof Error
232
+ ? ex.toString()
233
+ : JSON.stringify(ex, null, 2)
234
+ }`
235
+ );
195
236
  } finally {
196
237
  return this.contensisPrompt();
197
238
  }
@@ -221,7 +262,14 @@ class ContensisShell {
221
262
  let globalShell: ContensisShell;
222
263
 
223
264
  export const shell = () => {
224
- if (typeof process.argv?.[2] !== 'undefined') return { start() {} } as any;
265
+ // Return a benign function for shell().restart() when used in cli context
266
+ // as some commands need to restart the shell to show an updated prompt
267
+ // after successful connect / login / set project
268
+ if (typeof process.argv?.[2] !== 'undefined')
269
+ return {
270
+ quit: ContensisCli.quit,
271
+ restart() {},
272
+ } as any;
225
273
  if (!globalShell) globalShell = new ContensisShell();
226
274
  return globalShell;
227
275
  };
@@ -232,12 +280,14 @@ process.on('uncaughtException', function (err) {
232
280
  });
233
281
 
234
282
  process.on('SIGINT', () => {
235
- console.log('catching SIGINT');
283
+ Logger.warning('received SIGINT');
236
284
  shell().quit();
285
+ // setTimeout(() => {
286
+ // }, 2000);
237
287
  });
238
288
 
239
289
  process.on('SIGTERM', () => {
240
- console.log('catching SIGTERM');
290
+ Logger.warning('received SIGTERM');
241
291
  shell().quit();
242
292
  });
243
293
 
@@ -1,6 +1,7 @@
1
1
  import dayjs from 'dayjs';
2
- import { BlockVersion, MigrateStatus } from 'migratortron';
2
+ import { BlockVersion, MigrateModelsResult, MigrateStatus } from 'migratortron';
3
3
  import ContensisCli from '~/services/ContensisCliService';
4
+ import { Logger } from './logger';
4
5
 
5
6
  const formatDate = (date: Date | string, format = 'DD/MM/YYYY HH:mm') =>
6
7
  dayjs(date).format(format);
@@ -104,57 +105,22 @@ export const printBlockVersion = (
104
105
  };
105
106
 
106
107
  export const printMigrateResult = (
107
- { log, messages, contensis, currentProject }: ContensisCli,
108
+ { log, messages, currentProject }: ContensisCli,
108
109
  migrateResult: any,
109
- { action = 'import' }: { action?: 'import' | 'delete' } = {}
110
+ {
111
+ action = 'import',
112
+ showDiff = false,
113
+ showAllEntries = false,
114
+ showChangedEntries = false,
115
+ }: {
116
+ action?: 'import' | 'delete';
117
+ showDiff?: boolean;
118
+ showAllEntries?: boolean;
119
+ showChangedEntries?: boolean;
120
+ } = {}
110
121
  ) => {
111
122
  console.log(``);
112
123
 
113
- if (action === 'import') {
114
- for (const [projectId, contentTypeCounts] of Object.entries(
115
- migrateResult.entries || {}
116
- ) as [string, any][]) {
117
- console.log(
118
- `import from project ${log.highlightText(projectId)} to ${log.boldText(
119
- log.successText(currentProject)
120
- )}`
121
- );
122
- for (const [contentTypeId, count] of Object.entries(
123
- contentTypeCounts
124
- ) as [string, number][]) {
125
- const entriesToMigrate =
126
- migrateResult.entriesToMigrate?.[projectId]?.[contentTypeId];
127
-
128
- console.log(
129
- ` - ${
130
- contentTypeId === 'totalCount'
131
- ? log.warningText(`${contentTypeId}: ${count}`)
132
- : log.helpText(`${contentTypeId}: ${count}`)
133
- } ${log.infoText`[existing: ${(
134
- ((migrateResult.existing?.[projectId]?.[contentTypeId] || 0) /
135
- count) *
136
- 100
137
- ).toFixed(0)}%]`} [${
138
- typeof entriesToMigrate !== 'number' ? `unchanged` : `to update`
139
- }: ${(
140
- ((typeof entriesToMigrate !== 'number'
141
- ? entriesToMigrate?.['no change'] || 0
142
- : entriesToMigrate) /
143
- count) *
144
- 100
145
- ).toFixed(0)}%]`
146
- );
147
- }
148
- console.log(``);
149
- }
150
- }
151
- if (
152
- contensis?.isPreview &&
153
- migrateResult.entriesToMigrate?.[currentProject]?.totalCount > 0 &&
154
- !migrateResult.errors
155
- ) {
156
- log.help(messages.entries.commitTip());
157
- }
158
124
  for (const [contentTypeId, entryRes] of Object.entries(
159
125
  migrateResult.entriesToMigrate.entryIds
160
126
  ) as [string, any]) {
@@ -162,42 +128,238 @@ export const printMigrateResult = (
162
128
  string,
163
129
  any
164
130
  ][]) {
131
+ if (
132
+ showAllEntries ||
133
+ (showChangedEntries &&
134
+ (
135
+ Object.entries(
136
+ Object.entries(entryStatus[currentProject])[0]
137
+ )[1][1] as any
138
+ ).status !== 'no change')
139
+ ) {
140
+ console.log(
141
+ log.infoText(
142
+ `${originalId} ${Object.entries(entryStatus || {})
143
+ .filter(x => x[0] !== 'entryTitle')
144
+ .map(([projectId, projectStatus]) => {
145
+ const [targetGuid, { status }] = (Object.entries(
146
+ projectStatus || {}
147
+ )?.[0] as [string, { status: MigrateStatus }]) || [
148
+ '',
149
+ { x: { status: undefined } },
150
+ ];
151
+ return `${messages.migrate.status(status)(`${status}`)}${
152
+ targetGuid !== originalId ? `-> ${targetGuid}` : ''
153
+ }`;
154
+ })}`
155
+ ) + ` ${log.helpText(contentTypeId)} ${entryStatus.entryTitle}`
156
+ );
157
+
158
+ for (const [projectId, projectStatus] of Object.entries(
159
+ entryStatus
160
+ ).filter(([key]) => key !== 'entryTitle') as [string, any][]) {
161
+ const [targetGuid, { error, diff, status }] = Object.entries(
162
+ projectStatus
163
+ )[0] as [string, any];
164
+ if (error) log.error(error);
165
+ if (diff && showDiff) {
166
+ console.log(
167
+ ` ${log.highlightText(`diff:`)} ${log.infoText(
168
+ highlightDiffText(diff)
169
+ )}\n`
170
+ );
171
+ }
172
+ }
173
+ }
174
+ }
175
+ }
176
+ if (showAllEntries || showChangedEntries) console.log(``);
177
+
178
+ for (const [projectId, contentTypeCounts] of Object.entries(
179
+ migrateResult.entries || {}
180
+ ) as [string, any][]) {
181
+ log.help(
182
+ `${action} from project ${
183
+ action === 'delete'
184
+ ? log.warningText(currentProject)
185
+ : `${log.highlightText(projectId)} to ${log.boldText(
186
+ log.warningText(currentProject)
187
+ )}`
188
+ }`
189
+ );
190
+ for (const [contentTypeId, count] of Object.entries(contentTypeCounts) as [
191
+ string,
192
+ number
193
+ ][]) {
194
+ const isTotalCountRow = contentTypeId === 'totalCount';
195
+ const migrateStatusAndCount =
196
+ migrateResult.entriesToMigrate[currentProject][contentTypeId];
197
+ const existingCount =
198
+ migrateResult.existing?.[currentProject]?.[contentTypeId] || 0;
199
+ const existingPercent = ((existingCount / count) * 100).toFixed(0);
200
+ const noChangeOrTotalEntriesCount =
201
+ typeof migrateStatusAndCount !== 'number'
202
+ ? migrateStatusAndCount?.['no change'] || 0
203
+ : migrateStatusAndCount;
204
+
205
+ const changedPercentage = (
206
+ (noChangeOrTotalEntriesCount / count) *
207
+ 100
208
+ ).toFixed(0);
209
+
210
+ const existingColor =
211
+ existingPercent === '0' || action === 'delete'
212
+ ? log.warningText
213
+ : log.infoText;
214
+
215
+ const changedColor = isTotalCountRow
216
+ ? log.helpText
217
+ : changedPercentage === '100'
218
+ ? log.successText
219
+ : log.warningText;
220
+
165
221
  console.log(
166
- log.infoText(
167
- `${originalId} [${Object.entries(entryStatus || {})
168
- .filter(x => x[0] !== 'entryTitle')
169
- .map(([projectId, projectStatus]) => {
170
- const [targetGuid, { status }] = (Object.entries(
171
- projectStatus || {}
172
- )?.[0] as [string, { status: MigrateStatus }]) || [
173
- '',
174
- { x: { status: undefined } },
175
- ];
176
- return `${messages.entries.migrateStatus(status)(
177
- `${projectId}: ${status}`
178
- )}${targetGuid !== originalId ? `-> ${targetGuid}` : ''}`;
179
- })}]`
180
- )
222
+ ` - ${
223
+ isTotalCountRow
224
+ ? log.highlightText(
225
+ `${contentTypeId}: ${noChangeOrTotalEntriesCount}`
226
+ )
227
+ : `${contentTypeId}: ${log.helpText(count)}`
228
+ }${
229
+ changedPercentage === '100' || isTotalCountRow
230
+ ? ''
231
+ : existingColor(` [existing: ${`${existingPercent}%`}]`)
232
+ }${
233
+ existingPercent === '0' || (action === 'import' && isTotalCountRow)
234
+ ? ''
235
+ : changedColor(
236
+ ` ${
237
+ isTotalCountRow
238
+ ? `[to ${action}: ${noChangeOrTotalEntriesCount}]`
239
+ : changedPercentage === '100'
240
+ ? 'up to date'
241
+ : `[needs update: ${100 - Number(changedPercentage)}%]`
242
+ }`
243
+ )
244
+ }`
245
+ );
246
+ }
247
+ }
248
+ if (migrateResult.errors?.length) {
249
+ console.log(
250
+ ` - ${log.errorText(`errors: ${migrateResult.errors.length}`)}\n`
251
+ );
252
+ for (const error of migrateResult.errors)
253
+ log.error(error.message || error, null, '');
254
+ }
255
+ };
256
+
257
+ const highlightDiffText = (str: string) => {
258
+ const addedRegex = new RegExp(/<<\+>>(.*?)<<\/\+>>/, 'g');
259
+ const removedRegex = new RegExp(/<<->>(.*?)<<\/->>/, 'g');
260
+ return str
261
+ .replace(addedRegex, match => {
262
+ return Logger.successText(
263
+ match.replace(/<<\+>>/g, '<+>').replace(/<<\/\+>>/g, '</+>')
264
+ );
265
+ })
266
+ .replace(removedRegex, match => {
267
+ return Logger.errorText(
268
+ match.replace(/<<->>/g, '<->').replace(/<<\/->>/g, '</->')
181
269
  );
182
- console.log(` ${log.helpText(contentTypeId)} ${entryStatus.entryTitle}`);
270
+ });
271
+ };
183
272
 
184
- for (const [projectId, projectStatus] of Object.entries(
185
- entryStatus
186
- ).filter(([key]) => key !== 'entryTitle') as [string, any][]) {
187
- const [targetGuid, { error, diff, status }] = Object.entries(
188
- projectStatus
189
- )[0] as [string, any];
190
- if (error) log.error(error);
191
- if (diff) {
192
- console.log(
193
- ` ${messages.entries.migrateStatus(status)(status)} ${log.infoText(
194
- targetGuid
195
- )} ${log.infoText(contentTypeId)} ${log.infoText(projectId)}`
273
+ export const printModelMigrationAnalysis = (
274
+ { log, messages }: ContensisCli,
275
+ result: any = {}
276
+ ) => {
277
+ for (const [contentTypeId, model] of Object.entries(result) as [
278
+ string,
279
+ any
280
+ ][]) {
281
+ let mainOutput = log.standardText(` - ${contentTypeId}`);
282
+ let extraOutput = '';
283
+ let errorOutput = '';
284
+ let diffOutput = '';
285
+ for (const [key, details] of Object.entries(model) as [string, any][]) {
286
+ if (key === 'dependencies') {
287
+ extraOutput += log.infoText(
288
+ ` references: [${details?.join(', ')}]\n`
289
+ );
290
+ }
291
+ if (key === 'dependencyOf') {
292
+ extraOutput += log.infoText(
293
+ ` required by: [${details?.join(', ')}]\n`
294
+ );
295
+ }
296
+ if (key === 'projects') {
297
+ for (const [projectId, projectDetails] of Object.entries(details) as [
298
+ string,
299
+ any
300
+ ][]) {
301
+ mainOutput += log.infoText(
302
+ ` [${messages.migrate.status(projectDetails.status)(
303
+ `${projectId}: ${projectDetails.status}`
304
+ )}] v${projectDetails.versionNo}`
196
305
  );
197
- console.log(``);
198
- console.log(log.highlightText(diff));
306
+ if (projectDetails.diff)
307
+ diffOutput += ` ${log.highlightText(`diff:`)} ${log.infoText(
308
+ highlightDiffText(projectDetails.diff)
309
+ )}\n`;
310
+ if (projectDetails.error)
311
+ errorOutput += ` ${log.highlightText(
312
+ `error::`
313
+ )} ${log.errorText(projectDetails.error)}`;
199
314
  }
200
315
  }
201
316
  }
317
+ console.log(mainOutput);
318
+ if (extraOutput) {
319
+ const search = '\n';
320
+ const replace = '';
321
+ console.log(
322
+ extraOutput.replace(
323
+ new RegExp(search + '([^' + search + ']*)$'),
324
+ replace + '$1'
325
+ )
326
+ );
327
+ }
328
+ if (diffOutput) console.log(diffOutput);
329
+ if (errorOutput) console.log(errorOutput);
330
+ }
331
+ };
332
+
333
+ type MigrateResultSummary = MigrateModelsResult['']['contentTypes'];
334
+ type MigrateResultStatus = keyof MigrateResultSummary;
335
+
336
+ export const printModelMigrationResult = (
337
+ { log, messages }: ContensisCli,
338
+ result: MigrateResultSummary
339
+ ) => {
340
+ for (const [status, ids] of Object.entries(result) as [
341
+ MigrateResultStatus,
342
+ string[]
343
+ ][]) {
344
+ if (ids?.length) {
345
+ if (status === 'errors') {
346
+ const errors: [string, MappedError][] = ids as any;
347
+ log.raw(
348
+ ` - ${status}: [ ${messages.migrate.models.result(status)(
349
+ ids.map(id => id[0]).join(', ')
350
+ )} ]\n`
351
+ );
352
+ for (const [contentTypeId, error] of errors)
353
+ log.error(
354
+ `${log.highlightText(contentTypeId)}: ${error.message}`,
355
+ error
356
+ );
357
+ } else
358
+ log.raw(
359
+ ` - ${status}: [ ${messages.migrate.models.result(status)(
360
+ ids.join(', ')
361
+ )} ]`
362
+ );
363
+ }
202
364
  }
203
365
  };
@@ -0,0 +1,124 @@
1
+ import chalk from 'chalk';
2
+ import { Change, diffLines } from 'diff';
3
+ import { normaliseLineEndings } from './os';
4
+
5
+ export const diffLogStrings = (updates: string, previous: string) => {
6
+ const lastFewLines = previous.split('\n').slice(-10);
7
+ const incomingLines = updates.split('\n');
8
+
9
+ // Find the line indices in the incoming lines
10
+ // of the last few lines previously rendered
11
+ const incomingLineIndices = [];
12
+ for (const lastRenderedLine of lastFewLines) {
13
+ if (lastRenderedLine.length > 10)
14
+ incomingLineIndices.push(incomingLines.lastIndexOf(lastRenderedLine));
15
+ }
16
+
17
+ // Get the new lines from the next position on from the last of the already shown lines
18
+ const differentFromPos = Math.max(...incomingLineIndices, 0) + 1;
19
+ // Return just the incoming lines from the position we matched
20
+ return incomingLines.slice(differentFromPos).join('\n');
21
+ };
22
+
23
+ export const diffFileContent = (
24
+ existingContent: string,
25
+ newContent: string
26
+ ) => {
27
+ const existingContentNormalised = normaliseLineEndings(existingContent, '\n');
28
+ const newContentNormalised = normaliseLineEndings(newContent, '\n');
29
+
30
+ const diff = diffLines(existingContentNormalised, newContentNormalised, {
31
+ newlineIsToken: true,
32
+ });
33
+ const diffRanges = addDiffPositionInfo(diff);
34
+
35
+ // Create formatted output for console
36
+ const output: string[] = [];
37
+ const lnSpaceLength = Math.max(
38
+ ...diffRanges.map(d => d.startLineNumber.toString().length),
39
+ 0
40
+ );
41
+
42
+ const lnSpaces = Array(lnSpaceLength).join(' ');
43
+
44
+ let needsNewLine = false;
45
+ for (let i = 0; i < diffRanges.length; i++) {
46
+ const part = diffRanges[i];
47
+ if (part.added || part.removed) {
48
+ const colour = part.added ? 'green' : part.removed ? 'red' : 'grey';
49
+
50
+ if (part.value !== '\n') {
51
+ if (needsNewLine) {
52
+ output.push('\n### --');
53
+ needsNewLine = false;
54
+ }
55
+ output.push(
56
+ `\n${part.value
57
+ .split('\n')
58
+ .map((ln, idx) =>
59
+ ln.trim() !== ''
60
+ ? `${
61
+ part.startLineNumber ? part.startLineNumber + idx : lnSpaces
62
+ }${part.added ? '+' : part.removed ? '-' : ' '} ${chalk[
63
+ colour
64
+ ](`${ln}`)}`
65
+ : ln
66
+ )
67
+ .join('\n')}`
68
+ );
69
+ } else needsNewLine = true;
70
+ } else needsNewLine = true;
71
+ }
72
+
73
+ return output.join('');
74
+ };
75
+
76
+ const addDiffPositionInfo = (diff: Change[]) => {
77
+ const diffRanges: (Change & {
78
+ startLineNumber: number;
79
+ startColumn: number;
80
+ endLineNumber: number;
81
+ endColumn: number;
82
+ })[] = [];
83
+
84
+ let lineNumber = 0;
85
+ let column = 0;
86
+ for (let partIndex = 0; partIndex < diff.length; partIndex++) {
87
+ const part = diff[partIndex];
88
+
89
+ // // Skip any parts that aren't in `after`
90
+ // if (part.removed === true) {
91
+ // continue;
92
+ // }
93
+
94
+ const startLineNumber = lineNumber;
95
+ const startColumn = column;
96
+
97
+ // Split the part into lines. Loop throug these lines to find
98
+ // the line no. and column at the end of this part.
99
+ const substring = part.value;
100
+ const lines = substring.split('\n');
101
+ lines.forEach((line, lineIndex) => {
102
+ // The first `line` is actually just a continuation of the last line
103
+ if (lineIndex === 0) {
104
+ column += line.length;
105
+ // All other lines come after a line break.
106
+ } else if (lineIndex > 0) {
107
+ lineNumber += 1;
108
+ column = line.length;
109
+ }
110
+ });
111
+
112
+ // Save a range for all of the parts with position info added
113
+ if (part.added === true || part.removed === true) {
114
+ diffRanges.push({
115
+ startLineNumber: startLineNumber + 1,
116
+ startColumn: startColumn,
117
+ endLineNumber: lineNumber,
118
+ endColumn: column,
119
+ ...part,
120
+ });
121
+ }
122
+ }
123
+ return diffRanges;
124
+ };
@@ -0,0 +1,37 @@
1
+ import { EnvContentsToAdd } from "~/models/DevService";
2
+
3
+ export const mergeDotEnvFileContents = (
4
+ existingFileLines: string[],
5
+ envContentsToAdd: EnvContentsToAdd
6
+ ): string[] => {
7
+ const envFileLines: string[] = []; // the new .env file
8
+ if (existingFileLines.length === 0) {
9
+ // There is no env file, just create one from envContentsToAdd
10
+ envFileLines.push(
11
+ ...Object.entries(envContentsToAdd).map(([k, v]) => `${k}=${v}`)
12
+ );
13
+ } else {
14
+ const updatedEnvKeys: string[] = [];
15
+ // Find lines in env that already exist for the keys in envContentsToAdd
16
+ // update them if they exist and add them to envFileLines
17
+ for (const ln of existingFileLines) {
18
+ let newline = '';
19
+ for (const [k, v] of Object.entries(envContentsToAdd))
20
+ if (ln.startsWith(`${k}=`)) {
21
+ newline = `${k}=${v}`;
22
+ updatedEnvKeys.push(k);
23
+ }
24
+ // Ensure an updated line or other lines from the existing env file are re-added
25
+ if (newline || ln) envFileLines.push(newline || ln);
26
+ }
27
+
28
+ // Add the envContentsToAdd lines to the file that did not previously exist or had an update
29
+ for (const addKey of Object.keys(envContentsToAdd).filter(
30
+ efl =>
31
+ !updatedEnvKeys.find(uek => uek.startsWith(`${efl.split('=')?.[0]}`))
32
+ ) as (keyof typeof envContentsToAdd)[]) {
33
+ envFileLines.push(`${addKey}=${envContentsToAdd[addKey]}`);
34
+ }
35
+ }
36
+ return envFileLines;
37
+ };