contensis-cli 1.0.0-beta.88 → 1.0.0-beta.89

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.
@@ -0,0 +1,36 @@
1
+
2
+ export type OutputFormat = 'json' | 'csv' | 'xml';
3
+
4
+ export type OutputOptions = {
5
+ format?: OutputFormat;
6
+ output?: string;
7
+ };
8
+
9
+ export interface IConnectOptions extends IAuthOptions {
10
+ alias?: string;
11
+ projectId?: string;
12
+ }
13
+
14
+ export interface IAuthOptions {
15
+ user?: string;
16
+ password?: string;
17
+ clientId?: string;
18
+ sharedSecret?: string;
19
+ }
20
+
21
+ export interface IImportOptions {
22
+ sourceAlias?: string;
23
+ sourceProjectId?: string;
24
+ }
25
+
26
+ export type OutputOptionsConstructorArg = OutputOptions &
27
+ IConnectOptions &
28
+ IImportOptions;
29
+
30
+ export interface ContensisCliConstructor {
31
+ new (
32
+ args: string[],
33
+ outputOpts?: OutputOptionsConstructorArg,
34
+ contensisOpts?: Partial<MigrateRequest>
35
+ ): ContensisCli;
36
+ }
@@ -1,9 +1,9 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import fetch from 'node-fetch';
4
- import inquirer from 'inquirer';
5
1
  import to from 'await-to-js';
6
2
  import chalk from 'chalk';
3
+ import fs from 'fs';
4
+ import inquirer from 'inquirer';
5
+ import fetch from 'node-fetch';
6
+ import path from 'path';
7
7
 
8
8
  import { Component, ContentType, Project } from 'contensis-core-api';
9
9
  import { Node } from 'contensis-delivery-api/lib/models';
@@ -23,6 +23,7 @@ import {
23
23
  import ContensisAuthService from './ContensisAuthService';
24
24
 
25
25
  import { LogMessages } from '~/localisation/en-GB';
26
+ import { OutputFormat, OutputOptionsConstructorArg } from '~/models/CliService';
26
27
 
27
28
  import { readJsonFile } from '~/providers/file-provider';
28
29
  import SessionCacheProvider from '../providers/SessionCacheProvider';
@@ -48,42 +49,8 @@ import { jsonFormatter } from '~/util/json.formatter';
48
49
  import { diffLogStrings } from '~/util/diff';
49
50
  import { logError, Logger } from '~/util/logger';
50
51
  import { promiseDelay } from '~/util/timers';
52
+ import { findByIdOrName } from '~/util/find';
51
53
 
52
- type OutputFormat = 'json' | 'csv' | 'xml';
53
-
54
- type OutputOptions = {
55
- format?: OutputFormat;
56
- output?: string;
57
- };
58
-
59
- interface IConnectOptions extends IAuthOptions {
60
- alias?: string;
61
- projectId?: string;
62
- }
63
-
64
- interface IAuthOptions {
65
- user?: string;
66
- password?: string;
67
- clientId?: string;
68
- sharedSecret?: string;
69
- }
70
-
71
- interface IImportOptions {
72
- sourceAlias?: string;
73
- sourceProjectId?: string;
74
- }
75
-
76
- export type OutputOptionsConstructorArg = OutputOptions &
77
- IConnectOptions &
78
- IImportOptions;
79
-
80
- export interface ContensisCliConstructor {
81
- new (
82
- args: string[],
83
- outputOpts?: OutputOptionsConstructorArg,
84
- contensisOpts?: Partial<MigrateRequest>
85
- ): ContensisCli;
86
- }
87
54
  let insecurePasswordWarningShown = false;
88
55
 
89
56
  class ContensisCli {
@@ -938,15 +905,7 @@ class ContensisCli {
938
905
  if (Array.isArray(roles)) {
939
906
  log.success(messages.roles.list(currentEnv));
940
907
 
941
- const role =
942
- roles.find(
943
- r =>
944
- r.id === roleNameOrId ||
945
- r.name.toLowerCase() === roleNameOrId.toLowerCase()
946
- ) ||
947
- roles.find(r =>
948
- r.name.toLowerCase().includes(roleNameOrId.toLowerCase())
949
- );
908
+ const role = findByIdOrName(roles, roleNameOrId);
950
909
 
951
910
  if (role) this.HandleFormattingAndOutput(role, log.object);
952
911
  else log.error(messages.roles.failedGet(currentEnv, roleNameOrId));
@@ -997,11 +956,7 @@ class ContensisCli {
997
956
  if (Array.isArray(roles)) {
998
957
  log.success(messages.roles.list(currentEnv));
999
958
 
1000
- const existingRole = roles.find(
1001
- r =>
1002
- r.id === roleNameOrId ||
1003
- r.name.toLowerCase() === roleNameOrId.toLowerCase()
1004
- );
959
+ const existingRole = findByIdOrName(roles, roleNameOrId, true);
1005
960
  if (existingRole) {
1006
961
  log.info(messages.roles.setPayload());
1007
962
  log.object(role);
@@ -1041,11 +996,8 @@ class ContensisCli {
1041
996
  if (Array.isArray(roles)) {
1042
997
  log.success(messages.roles.list(currentEnv));
1043
998
 
1044
- const existingRole = roles.find(
1045
- r =>
1046
- r.id === roleNameOrId ||
1047
- r.name.toLowerCase() === roleNameOrId.toLowerCase()
1048
- );
999
+ const existingRole = findByIdOrName(roles, roleNameOrId, true);
1000
+
1049
1001
  if (existingRole) {
1050
1002
  const [deleteErr] = await contensis.roles.RemoveRole(existingRole.id);
1051
1003
 
@@ -2252,7 +2204,9 @@ class ContensisCli {
2252
2204
  const contensis = await this.ConnectContensis();
2253
2205
  if (contensis) {
2254
2206
  // Retrieve proxies list for env
2255
- const [err, proxies] = await contensis.proxies.GetProxies(proxyId);
2207
+ const [err, proxies] = await (contensis.proxies.GetProxies as any)(
2208
+ proxyId
2209
+ ); // TODO: resolve any cast;
2256
2210
 
2257
2211
  if (Array.isArray(proxies)) {
2258
2212
  this.HandleFormattingAndOutput(proxies, () => {
@@ -2264,7 +2218,9 @@ class ContensisCli {
2264
2218
  version.versionNo
2265
2219
  }] ${id} ${log.infoText`${description}`}`
2266
2220
  );
2267
- for (const [language, endpoint] of Object.entries(endpoints))
2221
+ for (const [language, endpoint] of Object.entries(
2222
+ endpoints as { [k: string]: any }
2223
+ )) // TODO: resolve any cast
2268
2224
  console.log(
2269
2225
  ` - ${log.infoText`language: ${language}
2270
2226
  server: ${endpoint.server}
@@ -2287,9 +2243,9 @@ class ContensisCli {
2287
2243
  const contensis = await this.ConnectContensis();
2288
2244
  if (contensis) {
2289
2245
  // Retrieve renderers list for env
2290
- const [err, renderers] = await contensis.renderers.GetRenderers(
2246
+ const [err, renderers] = await (contensis.renderers.GetRenderers as any)(
2291
2247
  rendererId
2292
- );
2248
+ ); // TODO: resolve any cast
2293
2249
 
2294
2250
  if (Array.isArray(renderers)) {
2295
2251
  this.HandleFormattingAndOutput(renderers, () => {
@@ -1,19 +1,21 @@
1
+ import to from 'await-to-js';
1
2
  import { execFile, spawn } from 'child_process';
2
3
  import inquirer from 'inquirer';
3
4
  import path from 'path';
5
+ import { Entry, Role } from 'contensis-management-api/lib/models';
4
6
 
5
7
  import { MigrateRequest } from 'migratortron';
6
8
  import { stringify } from 'yaml';
7
-
8
- import ContensisCli, {
9
- OutputOptionsConstructorArg,
10
- } from './ContensisCliService';
9
+ import ContensisCli from './ContensisCliService';
10
+ import ContensisRole from './ContensisRoleService';
11
+ import { OutputOptionsConstructorArg } from '~/models/CliService';
11
12
  import { mapSiteConfigYaml } from '~/mappers/ContensisCliService-to-RequestHanderSiteConfigYaml';
12
- import { appRootDir, writeFile } from '~/providers/file-provider';
13
+ import { appRootDir, readFile, writeFile } from '~/providers/file-provider';
13
14
  import { jsonFormatter } from '~/util/json.formatter';
14
15
  import { GitHelper } from '~/util/git';
16
+ import { findByIdOrName } from '~/util/find';
15
17
 
16
- class ContensisDev extends ContensisCli {
18
+ class ContensisDev extends ContensisRole {
17
19
  constructor(
18
20
  args: string[],
19
21
  outputOpts?: OutputOptionsConstructorArg,
@@ -23,6 +25,7 @@ class ContensisDev extends ContensisCli {
23
25
  }
24
26
 
25
27
  DevelopmentInit = async (projectHome: string, opts: any) => {
28
+ const { dryRun = false } = opts || {};
26
29
  const { currentEnv, currentProject, log, messages } = this;
27
30
  const contensis = await this.ConnectContensis();
28
31
 
@@ -45,12 +48,23 @@ class ContensisDev extends ContensisCli {
45
48
  // Retrieve ci workflow info
46
49
  const workflowFiles = git.workflows;
47
50
 
48
- // Set variables for logging etc.
51
+ // Set variables for performing operations and logging etc.
49
52
  let ciFileName = git.ciFileName;
50
53
 
51
- const devKey = `${git.name} development`;
52
- const deployKey = `${git.name} deployment`;
54
+ const devKeyName = `${git.name} development`;
55
+ const devKeyDescription = `${git.name} [contensis-cli]`;
56
+ const devKeyPermissions = { blocks: [] } as Partial<Role['permissions']>;
57
+ let existingDevKey = apiKeyExists(devKeyName);
58
+
59
+ const deployKeyName = `${git.name} deployment`;
60
+ const deployKeyDescription = `${git.name} deploy [contensis-cli]`;
61
+ const deployKeyPermissions = { blocks: ['push', 'release'] } as Partial<
62
+ Role['permissions']
63
+ >;
64
+ let existingDeployKey = apiKeyExists(deployKeyName);
65
+
53
66
  const blockId = git.name;
67
+ const errors = [] as AppError[];
54
68
 
55
69
  // Start render console output
56
70
  log.raw('');
@@ -68,12 +82,12 @@ class ContensisDev extends ContensisCli {
68
82
  );
69
83
  log.raw(
70
84
  log.infoText(
71
- messages.devinit.developmentKey(devKey, !!apiKeyExists(devKey))
85
+ messages.devinit.developmentKey(devKeyName, !!existingDevKey)
72
86
  )
73
87
  );
74
88
  log.raw(
75
89
  log.infoText(
76
- messages.devinit.deploymentKey(deployKey, !!apiKeyExists(deployKey))
90
+ messages.devinit.deploymentKey(deployKeyName, !!existingDeployKey)
77
91
  )
78
92
  );
79
93
  log.raw('');
@@ -95,7 +109,7 @@ class ContensisDev extends ContensisCli {
95
109
  }
96
110
 
97
111
  log.raw(log.infoText(messages.devinit.ciDetails(ciFileName)));
98
- log.help(messages.devinit.ciIntro(git));
112
+ ylog.help(messages.devinit.ciIntro(git));
99
113
 
100
114
  // Confirm prompt
101
115
  const { confirm } = await inquirer.prompt([
@@ -120,25 +134,131 @@ class ContensisDev extends ContensisCli {
120
134
  log.raw('');
121
135
 
122
136
  // Magic happens...
137
+ const checkpoint = (op: string) => {
138
+ if (errors.length) throw errors[0];
139
+ else log.debug(`${op} completed ok`);
140
+ return true;
141
+ };
123
142
 
124
143
  // Arrange API keys for development and deployment
125
- log.success(messages.devinit.createDevKey(devKey, false));
126
- log.success(messages.devinit.createDeployKey(deployKey, true));
144
+ const [getRolesErr, roles] = await to(contensis.roles.GetRoles());
145
+ if (!roles && getRolesErr) errors.push(getRolesErr);
146
+ checkpoint(`fetched ${roles?.length} roles`);
147
+ if (dryRun) {
148
+ checkpoint(`skip api key creation (dry-run)`);
149
+ } else {
150
+ existingDevKey = await this.CreateOrUpdateApiKey(
151
+ existingDevKey,
152
+ devKeyName,
153
+ devKeyDescription
154
+ );
155
+ checkpoint('dev key created');
156
+
157
+ existingDeployKey = await this.CreateOrUpdateApiKey(
158
+ existingDeployKey,
159
+ deployKeyName,
160
+ deployKeyDescription
161
+ );
162
+ checkpoint('deploy key created');
163
+
164
+ // Ensure dev API key is assigned to a role
165
+ let existingDevRole = findByIdOrName(roles || [], devKeyName, true) as
166
+ | Role
167
+ | undefined;
168
+ existingDevRole = await this.CreateOrUpdateRole(
169
+ existingDevRole,
170
+ devKeyName,
171
+ devKeyDescription,
172
+ { apiKeys: [devKeyName] },
173
+ devKeyPermissions
174
+ );
175
+ checkpoint('dev key role assigned');
176
+ log.success(messages.devinit.createDevKey(devKeyName, true));
177
+
178
+ // Ensure deploy API key is assigned to a role with the right permissions
179
+ let existingDeployRole = findByIdOrName(
180
+ roles || [],
181
+ deployKeyName,
182
+ true
183
+ ) as Role | undefined;
184
+ existingDeployRole = await this.CreateOrUpdateRole(
185
+ existingDeployRole,
186
+ deployKeyName,
187
+ deployKeyDescription,
188
+ { apiKeys: [deployKeyName] },
189
+ deployKeyPermissions
190
+ );
191
+
192
+ checkpoint('deploy key role assigned');
193
+ log.success(messages.devinit.createDeployKey(deployKeyName, true));
194
+ checkpoint('api keys done');
195
+ }
127
196
 
128
197
  // Update or create a file called .env in project home
129
- log.success(messages.devinit.writeEnvFile());
130
- // log.help(messages.devinit.useEnvFileTip());
198
+ const envContents = {
199
+ ALIAS: currentEnv,
200
+ PROJECT: currentProject,
201
+ ACCESS_TOKEN: accessToken,
202
+ };
203
+ const envFilePath = `${projectHome}/.env`;
204
+ const existingEnvFile = readFile(envFilePath);
205
+ const existingFileLines = (existingEnvFile || '').split('\n');
206
+ const envFileLines: string[] = [];
207
+
208
+ const updatedEnvKeys: string[] = [];
209
+ for (const ln of existingFileLines) {
210
+ let newline = '';
211
+ for (const [k, v] of Object.entries(envContents))
212
+ if (ln.startsWith(`${k}=`)) {
213
+ newline = `${k}=${v}`;
214
+ updatedEnvKeys.push(k);
215
+ }
216
+ envFileLines.push(newline || ln);
217
+ }
218
+ for (const addKey of existingFileLines
219
+ .filter(
220
+ efl =>
221
+ !updatedEnvKeys.find(uek =>
222
+ uek.startsWith(`${efl.split('=')?.[0]}=`)
223
+ ) && Object.keys(envContents).find(ck => ck === efl.split('=')?.[0])
224
+ )
225
+ .map(fl => fl.split('=')?.[0]) as (keyof typeof envContents)[]) {
226
+ envFileLines.push(`${addKey}=${envContents[addKey]}`);
227
+ }
228
+
229
+ if (dryRun) {
230
+ checkpoint('skip .env file update (dry-run)');
231
+ log.info(`.env file`);
232
+ log.object(envFileLines);
233
+ } else {
234
+ writeFile(envFilePath, envFileLines.join('\n'));
235
+ checkpoint('.env file updated');
236
+ log.success(messages.devinit.writeEnvFile());
237
+ // log.help(messages.devinit.useEnvFileTip());
238
+ }
131
239
 
132
240
  // Update CI file -- different for GH/GL -- create a sample one with build?
133
- log.success(messages.devinit.writeCiFile(`./${ciFileName}`));
134
- log.info(
135
- messages.devinit.ciBlockTip(blockId, currentEnv, currentProject)
136
- );
241
+ if (dryRun) {
242
+ checkpoint('skip CI file update (dry-run)');
243
+ log.info(`${ciFileName} file`);
244
+ // TODO: log what we might add to the file
245
+ //log.object(envFileLines);
246
+ } else {
247
+ log.success(messages.devinit.writeCiFile(`./${ciFileName}`));
248
+ log.info(
249
+ messages.devinit.ciBlockTip(blockId, currentEnv, currentProject)
250
+ );
251
+ checkpoint('CI file updated');
252
+ }
137
253
 
138
254
  // Echo Deployment API key to console, ask user to add secrets to repo
139
255
  log.warning(messages.devinit.addGitSecretsIntro());
140
256
  log.help(
141
- messages.devinit.addGitSecretsHelp(git, '123-456', '789-012-345')
257
+ messages.devinit.addGitSecretsHelp(
258
+ git,
259
+ existingDeployKey?.id,
260
+ existingDeployKey?.sharedSecret
261
+ )
142
262
  );
143
263
 
144
264
  log.success(messages.devinit.success());
@@ -0,0 +1,84 @@
1
+ import { Role } from 'contensis-management-api/lib/models';
2
+ import { ApiKey, MigrateRequest } from 'migratortron';
3
+
4
+ import ContensisCli from './ContensisCliService';
5
+ import { OutputOptionsConstructorArg } from '~/models/CliService';
6
+
7
+ class ContensisRole extends ContensisCli {
8
+ constructor(
9
+ args: string[],
10
+ outputOpts?: OutputOptionsConstructorArg,
11
+ contensisOpts: Partial<MigrateRequest> = {}
12
+ ) {
13
+ super(args, outputOpts, contensisOpts);
14
+ }
15
+
16
+ CreateOrUpdateApiKey = async (
17
+ existingKey: ApiKey | undefined,
18
+ name: string,
19
+ description: string
20
+ ) => {
21
+ const { contensis, currentEnv, messages } = this;
22
+ if (!contensis) throw new Error('shouldnt be here');
23
+ if (existingKey) {
24
+ const [err, key] = await contensis.apiKeys.UpdateKey(existingKey.id, {
25
+ name,
26
+ description,
27
+ });
28
+
29
+ if (err)
30
+ throw new Error(messages.keys.failedUpdate(currentEnv, name), {
31
+ cause: err,
32
+ });
33
+ return key;
34
+ } else {
35
+ const [err, key] = await contensis.apiKeys.CreateKey(name, description);
36
+ if (err)
37
+ throw new Error(messages.keys.failedCreate(currentEnv, name), {
38
+ cause: err,
39
+ });
40
+
41
+ return key;
42
+ }
43
+ };
44
+
45
+ CreateOrUpdateRole = async (
46
+ existingRole: Role | undefined,
47
+ name: string,
48
+ description: string,
49
+ assignments: Role['assignments'],
50
+ permissions: Role['permissions']
51
+ ) => {
52
+ const { contensis, currentEnv, messages } = this;
53
+ if (!contensis) throw new Error('shouldnt be here');
54
+
55
+ if (existingRole) {
56
+ // TODO: check is update needed?
57
+ const [err, updated] = await contensis.roles.UpdateRole(existingRole.id, {
58
+ ...existingRole,
59
+ assignments,
60
+ permissions,
61
+ });
62
+ if (err)
63
+ throw new Error(messages.roles.failedSet(currentEnv, name), {
64
+ cause: err,
65
+ });
66
+ return updated;
67
+ } else {
68
+ const [err, created] = await contensis.roles.CreateRole({
69
+ name,
70
+ description,
71
+ enabled: true,
72
+ assignments,
73
+ permissions,
74
+ } as Role);
75
+ if (err)
76
+ throw new Error(messages.roles.failedCreate(currentEnv, name), {
77
+ cause: err,
78
+ });
79
+
80
+ return created;
81
+ }
82
+ };
83
+ }
84
+ export default ContensisRole;
@@ -0,0 +1,8 @@
1
+ export const findByIdOrName = (arr: any[], idOrName: string, exact = false) =>
2
+ arr.find(
3
+ r =>
4
+ r.id === idOrName ||
5
+ r.name.toLowerCase() === idOrName.toLowerCase()
6
+ ) ||
7
+ (!exact &&
8
+ arr.find(r => r.name.toLowerCase().includes(idOrName.toLowerCase())));
@@ -89,6 +89,13 @@ export class Logger {
89
89
  else console.log(message);
90
90
  progress.current.interrupt(message);
91
91
  };
92
+ static debug: LogMethod = content => {
93
+ const message = `${Logger.getPrefix()} ${
94
+ Logger.isUserTerminal ? chalk.bgGrey(' ⚙ ') : '[DEBUG]'
95
+ } ${Logger.infoText(content)}`;
96
+ if (progress.active) progress.current.interrupt(message);
97
+ else console.log(message);
98
+ };
92
99
  static json: LogJsonDepthMethod = (content, depth = 9) =>
93
100
  console.dir(deepCleaner(content), { colors: true, depth });
94
101
  static mixed: LogArrayMethod = contentArray =>
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const LIB_VERSION = "1.0.0-beta.88";
1
+ export const LIB_VERSION = "1.0.0-beta.89";