contensis-cli 1.1.2-beta.0 → 1.1.2-beta.10

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 (75) hide show
  1. package/dist/commands/copy.js +70 -0
  2. package/dist/commands/copy.js.map +7 -0
  3. package/dist/commands/create.js.map +2 -2
  4. package/dist/commands/dev.js +11 -4
  5. package/dist/commands/dev.js.map +2 -2
  6. package/dist/commands/get.js +1 -0
  7. package/dist/commands/get.js.map +2 -2
  8. package/dist/commands/globalOptions.js +24 -3
  9. package/dist/commands/globalOptions.js.map +2 -2
  10. package/dist/commands/import.js +1 -6
  11. package/dist/commands/import.js.map +2 -2
  12. package/dist/commands/index.js +7 -3
  13. package/dist/commands/index.js.map +2 -2
  14. package/dist/factories/RequestHandlerFactory.js +214 -0
  15. package/dist/factories/RequestHandlerFactory.js.map +7 -0
  16. package/dist/localisation/en-GB.js +30 -5
  17. package/dist/localisation/en-GB.js.map +2 -2
  18. package/dist/mappers/DevRequests-to-RequestHanderCliArgs.js +159 -0
  19. package/dist/mappers/DevRequests-to-RequestHanderCliArgs.js.map +7 -0
  20. package/dist/providers/GitHubCliModuleProvider.js +117 -0
  21. package/dist/providers/GitHubCliModuleProvider.js.map +7 -0
  22. package/dist/providers/HttpProvider.js +72 -0
  23. package/dist/providers/HttpProvider.js.map +7 -0
  24. package/dist/providers/ManifestProvider.js +53 -0
  25. package/dist/providers/ManifestProvider.js.map +7 -0
  26. package/dist/providers/file-provider.js +14 -0
  27. package/dist/providers/file-provider.js.map +2 -2
  28. package/dist/services/ContensisAuthService.js +19 -11
  29. package/dist/services/ContensisAuthService.js.map +2 -2
  30. package/dist/services/ContensisCliService.js +88 -14
  31. package/dist/services/ContensisCliService.js.map +2 -2
  32. package/dist/services/ContensisDevService.js +34 -58
  33. package/dist/services/ContensisDevService.js.map +3 -3
  34. package/dist/shell.js +1 -0
  35. package/dist/shell.js.map +2 -2
  36. package/dist/util/api-ids.js +110 -0
  37. package/dist/util/api-ids.js.map +7 -0
  38. package/dist/util/console.printer.js.map +2 -2
  39. package/dist/util/debug.js +29 -0
  40. package/dist/util/debug.js.map +7 -0
  41. package/dist/util/fetch.js +65 -0
  42. package/dist/util/fetch.js.map +7 -0
  43. package/dist/util/index.js.map +1 -1
  44. package/dist/util/logger.js.map +2 -2
  45. package/dist/version.js +1 -1
  46. package/dist/version.js.map +1 -1
  47. package/package.json +5 -3
  48. package/src/commands/copy.ts +79 -0
  49. package/src/commands/create.ts +0 -1
  50. package/src/commands/dev.ts +14 -6
  51. package/src/commands/get.ts +12 -11
  52. package/src/commands/globalOptions.ts +25 -2
  53. package/src/commands/import.ts +4 -8
  54. package/src/commands/index.ts +7 -3
  55. package/src/factories/RequestHandlerFactory.ts +246 -0
  56. package/src/localisation/en-GB.ts +55 -12
  57. package/src/mappers/DevRequests-to-RequestHanderCliArgs.ts +200 -0
  58. package/src/providers/GitHubCliModuleProvider.ts +127 -0
  59. package/src/providers/HttpProvider.ts +50 -0
  60. package/src/providers/ManifestProvider.ts +43 -0
  61. package/src/providers/file-provider.ts +13 -0
  62. package/src/services/ContensisAuthService.ts +23 -14
  63. package/src/services/ContensisCliService.ts +112 -15
  64. package/src/services/ContensisDevService.ts +52 -87
  65. package/src/shell.ts +2 -1
  66. package/src/util/api-ids.ts +111 -0
  67. package/src/util/console.printer.ts +2 -1
  68. package/src/util/debug.ts +1 -0
  69. package/src/util/fetch.ts +74 -0
  70. package/src/util/index.ts +1 -1
  71. package/src/util/logger.ts +0 -1
  72. package/src/version.ts +1 -1
  73. package/dist/mappers/DevRequests-to-RequestHanderSiteConfigYaml.js +0 -56
  74. package/dist/mappers/DevRequests-to-RequestHanderSiteConfigYaml.js.map +0 -7
  75. package/src/mappers/DevRequests-to-RequestHanderSiteConfigYaml.ts +0 -44
@@ -0,0 +1,127 @@
1
+ import os from 'os';
2
+ import Zip from 'adm-zip';
3
+ import type { Endpoints } from '@octokit/types';
4
+ import HttpProvider from '~/providers/HttpProvider';
5
+ import {
6
+ addExecutePermission,
7
+ checkDir,
8
+ joinPath,
9
+ removeDirectory,
10
+ removeFile,
11
+ } from './file-provider';
12
+ import { doRetry } from '~/util/fetch';
13
+
14
+ type GitHubApiRelease =
15
+ Endpoints['GET /repos/{owner}/{repo}/releases/latest']['response']['data'];
16
+
17
+ class GitHubCliModuleProvider {
18
+ http: HttpProvider;
19
+ repo: string;
20
+ baseUrl = 'https://api.github.com/repos';
21
+
22
+ get releases_url() {
23
+ return `${this.baseUrl}/${this.repo}/releases`;
24
+ }
25
+ get latest_release_url() {
26
+ return `${this.baseUrl}/${this.repo}/releases/latest`;
27
+ }
28
+
29
+ download?: {
30
+ tag: string;
31
+ name: string;
32
+ url: string;
33
+ browser_url: string;
34
+ };
35
+
36
+ constructor(repo: string) {
37
+ this.http = new HttpProvider();
38
+ this.repo = repo;
39
+ }
40
+
41
+ async FindLatestRelease(version?: string) {
42
+ const { http, latest_release_url, releases_url } = this;
43
+ // return latest tag version is:
44
+
45
+ const responses = await Promise.all([
46
+ http.get<GitHubApiRelease>(latest_release_url, {
47
+ doRetry: doRetry({ silent: true }),
48
+ }),
49
+ http.get<GitHubApiRelease[]>(releases_url),
50
+ ]);
51
+
52
+ const [latestErr, latest, latestResponse] = responses[0];
53
+ const [releasesErr, releases] = responses[1];
54
+
55
+ if (releasesErr) {
56
+ throw new Error(`Unable to get releases`, { cause: releasesErr });
57
+ } else if (!releases || releases.length === 0)
58
+ throw new Error(`No releases available`);
59
+ else if (version) {
60
+ const release = releases.find(
61
+ r => r.tag_name.toLowerCase() === version.toLowerCase()
62
+ );
63
+ if (release) return release;
64
+ else throw new Error(`No release for ${version} found`);
65
+ } else if (latestErr && !latest) {
66
+ if (latestResponse?.status === 404 && releases?.length) {
67
+ // No latest release, check releases for prerelease version, fallback to last release
68
+ const release = releases.find(r => r.prerelease) || releases[0];
69
+
70
+ if (release) return release;
71
+ }
72
+ } else {
73
+ return latest;
74
+ }
75
+ }
76
+
77
+ async DownloadRelease(
78
+ release: GitHubApiRelease,
79
+ {
80
+ cmd,
81
+ path,
82
+ platforms,
83
+ unzip = true,
84
+ }: {
85
+ cmd: string;
86
+ path: string;
87
+ unzip?: boolean;
88
+ platforms: [NodeJS.Platform, string][];
89
+ }
90
+ ) {
91
+ // find os-specific asset
92
+ const platform = platforms.find(p => p[0] === os.platform()) || [
93
+ os.platform(),
94
+ os.platform(),
95
+ ];
96
+
97
+ const asset = release.assets.find(r =>
98
+ r.name.toLowerCase().includes(platform[1])
99
+ );
100
+
101
+ // download asset
102
+ if (asset) {
103
+ const filePath = joinPath(path, asset.name);
104
+ removeDirectory(path);
105
+ checkDir(filePath);
106
+ await this.http.downloadFile(asset.browser_download_url, filePath);
107
+
108
+ if (unzip && asset.name.endsWith('.zip')) {
109
+ // unzip the downloaded file
110
+ const zipFile = new Zip(filePath);
111
+ zipFile.extractAllTo(path);
112
+
113
+ // delete the downloaded zip file
114
+ removeFile(filePath);
115
+ }
116
+
117
+ if (os.platform() !== 'win32') addExecutePermission(joinPath(path, cmd));
118
+ } else
119
+ throw new Error(
120
+ `no asset found in release ${
121
+ release.tag_name
122
+ } for platform ${os.platform()}\n${release.html_url}`
123
+ );
124
+ }
125
+ }
126
+
127
+ export default GitHubCliModuleProvider;
@@ -0,0 +1,50 @@
1
+ import to from 'await-to-js';
2
+ import { FetchInit } from 'enterprise-fetch';
3
+ import fs from 'fs';
4
+ import { Readable } from 'stream';
5
+ import { finished } from 'stream/promises';
6
+
7
+ import { isJson, tryParse } from '~/util';
8
+ import { enhancedFetch } from '~/util/fetch';
9
+ class HttpProvider {
10
+ constructor() {}
11
+
12
+ async get<T = any>(url: string, init: FetchInit = {}) {
13
+ return this.fetch<T>(url, { method: 'GET', ...init });
14
+ }
15
+
16
+ async fetch<T = any>(
17
+ uri: string,
18
+ init: FetchInit = {}
19
+ ): Promise<[Error | null, T | undefined, Response | undefined]> {
20
+ const [error, response] = await to(enhancedFetch(uri, init));
21
+
22
+ if (response && !error) {
23
+ const [bodyError, text] = await to(response.text());
24
+ if (bodyError) return [bodyError, undefined, response];
25
+ if (isJson(text)) {
26
+ const err =
27
+ !response.status || !response.ok ? tryParse(text) : undefined;
28
+ const payload =
29
+ response.status && response.ok ? tryParse(text) : undefined;
30
+ return [err, payload, response];
31
+ }
32
+ return [
33
+ response.ok ? null : new Error(text),
34
+ response.ok ? (text as unknown as T) : undefined,
35
+ response,
36
+ ];
37
+ }
38
+ return [error, undefined, response];
39
+ }
40
+
41
+ async downloadFile(url: string, destination: string) {
42
+ const res = await fetch(url);
43
+ if (res.ok && res.body !== null) {
44
+ const fileStream = fs.createWriteStream(destination, { flags: 'wx' });
45
+ await finished(Readable.fromWeb(res.body as any).pipe(fileStream));
46
+ }
47
+ }
48
+ }
49
+
50
+ export default HttpProvider;
@@ -0,0 +1,43 @@
1
+ import { tryParse } from '~/util';
2
+ import { appPath, readFile, writeFile } from './file-provider';
3
+
4
+ export type CliModule = {
5
+ github: string;
6
+ version: string;
7
+ install?: string;
8
+ cmd?: string;
9
+ };
10
+
11
+ type CliManifest = {
12
+ [moduleName: string]: CliModule;
13
+ };
14
+
15
+ const MANIFEST_PATH = appPath('cli-manifest.json');
16
+
17
+ class ManifestProvider {
18
+ private manifest: CliManifest;
19
+
20
+ constructor() {
21
+ const manifest = tryParse(readFile(MANIFEST_PATH));
22
+ this.manifest = manifest || {};
23
+ }
24
+
25
+ get() {
26
+ return this.manifest;
27
+ }
28
+
29
+ getModule(name: string) {
30
+ return this.manifest?.[name];
31
+ }
32
+ writeModule(name: string, moduleInfo: CliModule) {
33
+ if (this.manifest) this.manifest[name] = moduleInfo;
34
+ else
35
+ this.manifest = {
36
+ [name]: moduleInfo,
37
+ };
38
+
39
+ writeFile(MANIFEST_PATH, JSON.stringify(this.manifest, null, 2));
40
+ }
41
+ }
42
+
43
+ export default ManifestProvider;
@@ -50,6 +50,13 @@ export const removeFile = (filePath: string) => {
50
50
  }
51
51
  };
52
52
 
53
+ export const removeDirectory = (filePath: string) => {
54
+ const directoryPath = appPath(filePath);
55
+ if (fs.existsSync(directoryPath)) {
56
+ fs.rmSync(directoryPath, { force: true, recursive: true });
57
+ }
58
+ };
59
+
53
60
  export const moveFile = (file: string, fromPath: string, toPath: string) => {
54
61
  const from = path.join(appRootDir, `${fromPath}${file}`);
55
62
  const to = path.join(appRootDir, `${toPath}${file}`);
@@ -84,6 +91,12 @@ export const appPath = (filePath: string) =>
84
91
  export const cwdPath = (filePath: string) =>
85
92
  path.isAbsolute(filePath) ? filePath : path.join(process.cwd(), filePath);
86
93
 
94
+ export const joinPath = path.join;
95
+
96
+ export const addExecutePermission = (filePath: string) =>
97
+ // Fails in windows with `TypeError [ERR_INVALID_ARG_TYPE]: The "mode" argument must be of type number. Received undefined`
98
+ fs.chmodSync(filePath, fs.constants.S_IRWXU);
99
+
87
100
  type DetectedFileType =
88
101
  | { type: 'json'; contents: any }
89
102
  | { type: 'xml' | 'csv'; contents: string };
@@ -3,6 +3,17 @@ import { ClientGrants, ClientGrantType } from 'contensis-core-api';
3
3
 
4
4
  class ContensisAuthService {
5
5
  private client: NodejsClient;
6
+ private credentials: {
7
+ clientType: ClientGrantType;
8
+ clientDetails: ClientGrants;
9
+ };
10
+
11
+ get clientType() {
12
+ return this.credentials.clientType;
13
+ }
14
+ get clientDetails() {
15
+ return this.credentials.clientDetails;
16
+ }
6
17
 
7
18
  constructor({
8
19
  clientId = '',
@@ -21,39 +32,37 @@ class ContensisAuthService {
21
32
  projectId: string;
22
33
  rootUrl: string;
23
34
  }) {
24
- let credentials: {
25
- clientType: ClientGrantType;
26
- clientDetails: ClientGrants;
27
- };
28
- if (clientId && clientSecret) {
29
- credentials = {
35
+ if (clientId && clientSecret)
36
+ this.credentials = {
30
37
  clientType: 'client_credentials',
31
38
  clientDetails: {
32
39
  clientId,
33
40
  clientSecret,
34
41
  },
35
42
  };
36
- } else if (username && password) {
37
- credentials = {
43
+ else if (username && password)
44
+ this.credentials = {
38
45
  clientType: 'contensis_classic',
39
46
  clientDetails: {
40
47
  username,
41
48
  password,
42
49
  },
43
50
  };
44
- } else if (refreshToken) {
45
- credentials = {
51
+ else if (refreshToken)
52
+ this.credentials = {
46
53
  clientType: 'contensis_classic_refresh_token',
47
54
  clientDetails: {
48
55
  refreshToken,
49
56
  },
50
57
  };
51
- } else {
52
- credentials = { clientType: 'none', clientDetails: { refreshToken: '' } };
53
- }
58
+ else
59
+ this.credentials = {
60
+ clientType: 'none',
61
+ clientDetails: { refreshToken: '' },
62
+ };
54
63
 
55
64
  this.client = NodejsClient.create({
56
- ...credentials,
65
+ ...this.credentials,
57
66
  projectId,
58
67
  rootUrl,
59
68
  });
@@ -36,6 +36,7 @@ import {
36
36
  tryStringify,
37
37
  url,
38
38
  } from '~/util';
39
+ import { sanitiseIds } from '~/util/api-ids';
39
40
  import {
40
41
  printBlockVersion,
41
42
  printEntriesMigrateResult,
@@ -45,12 +46,13 @@ import {
45
46
  printNodesMigrateResult,
46
47
  } from '~/util/console.printer';
47
48
  import { csvFormatter } from '~/util/csv.formatter';
48
- import { xmlFormatter } from '~/util/xml.formatter';
49
49
  import { jsonFormatter, limitFields } from '~/util/json.formatter';
50
+ import { xmlFormatter } from '~/util/xml.formatter';
51
+ import { isDebug } from '~/util/debug';
50
52
  import { diffLogStrings } from '~/util/diff';
53
+ import { findByIdOrName } from '~/util/find';
51
54
  import { logError, Logger } from '~/util/logger';
52
55
  import { promiseDelay } from '~/util/timers';
53
- import { findByIdOrName } from '~/util/find';
54
56
 
55
57
  let insecurePasswordWarningShown = false;
56
58
 
@@ -72,6 +74,7 @@ class ContensisCli {
72
74
  contensis?: ContensisMigrationService;
73
75
  contensisOpts: Partial<MigrateRequest>;
74
76
  currentProject: string;
77
+ debug = isDebug();
75
78
 
76
79
  sourceAlias?: string;
77
80
  targetEnv?: string;
@@ -140,6 +143,14 @@ class ContensisCli {
140
143
  this.session = new SessionCacheProvider();
141
144
 
142
145
  this.contensisOpts = contensisOpts;
146
+
147
+ // Explicitly sanitise supplied fields to api-friendly ids
148
+ if (Array.isArray(this.contensisOpts.query?.fields)) {
149
+ this.contensisOpts.query.fields = sanitiseIds(
150
+ this.contensisOpts.query.fields
151
+ );
152
+ }
153
+
143
154
  this.format = outputOpts?.format;
144
155
  this.output =
145
156
  outputOpts?.output && path.join(process.cwd(), outputOpts.output);
@@ -1786,6 +1797,79 @@ class ContensisCli {
1786
1797
  }
1787
1798
  };
1788
1799
 
1800
+ CopyEntryField = async ({
1801
+ commit,
1802
+ fromFile,
1803
+ logOutput,
1804
+ }: {
1805
+ commit: boolean;
1806
+ fromFile: string;
1807
+ logOutput: string;
1808
+ }) => {
1809
+ const { currentEnv, currentProject, log, messages } = this;
1810
+
1811
+ const contensis = await this.ConnectContensisImport({
1812
+ commit,
1813
+ fromFile,
1814
+ importDataType: 'entries',
1815
+ });
1816
+
1817
+ if (contensis) {
1818
+ log.line();
1819
+ if (contensis.isPreview) {
1820
+ console.log(log.successText(` -- IMPORT PREVIEW -- `));
1821
+ } else {
1822
+ console.log(log.warningText(` *** COMMITTING IMPORT *** `));
1823
+ }
1824
+
1825
+ const [err, result] = await to(
1826
+ contensis.content.copy.MigrateFieldContent()
1827
+ );
1828
+
1829
+ if (err) logError(err);
1830
+ if (result)
1831
+ await this.HandleFormattingAndOutput(result, () => {
1832
+ // print the migrateResult to console
1833
+ printEntriesMigrateResult(this, result, {
1834
+ showAll: logOutput === 'all',
1835
+ showDiff: logOutput === 'all' || logOutput === 'changes',
1836
+ showChanged: logOutput === 'changes',
1837
+ });
1838
+ });
1839
+
1840
+ if (
1841
+ result &&
1842
+ !err &&
1843
+ !result.errors?.length &&
1844
+ ((!commit && result.entriesToMigrate[currentProject].totalCount) ||
1845
+ (commit &&
1846
+ (result.migrateResult?.created || result.migrateResult?.updated)))
1847
+ ) {
1848
+ log.success(
1849
+ messages.entries.imported(
1850
+ currentEnv,
1851
+ commit,
1852
+ commit
1853
+ ? (result.migrateResult?.created || 0) +
1854
+ (result.migrateResult?.updated || 0)
1855
+ : result.entriesToMigrate[currentProject].totalCount
1856
+ )
1857
+ );
1858
+ if (!commit) {
1859
+ log.raw(``);
1860
+ log.help(messages.entries.commitTip());
1861
+ }
1862
+ } else {
1863
+ log.error(messages.entries.failedImport(currentEnv), err);
1864
+ if (!result?.entriesToMigrate?.[currentProject]?.totalCount)
1865
+ log.help(messages.entries.notFound(currentEnv));
1866
+ }
1867
+ } else {
1868
+ log.warning(messages.models.noList(currentProject));
1869
+ log.help(messages.connect.tip());
1870
+ }
1871
+ };
1872
+
1789
1873
  GetNodes = async (rootPath: string, depth = 0) => {
1790
1874
  const { currentProject, log, messages } = this;
1791
1875
  const contensis = await this.ConnectContensis();
@@ -2048,7 +2132,7 @@ class ContensisCli {
2048
2132
  // Retrieve blocks list for env
2049
2133
  const [err, blocks] = await contensis.blocks.GetBlocks();
2050
2134
 
2051
- if (Array.isArray(blocks)) {
2135
+ if (Array.isArray(blocks) && blocks.length) {
2052
2136
  await this.HandleFormattingAndOutput(blocks, () => {
2053
2137
  // print the blocks to console
2054
2138
  log.success(messages.blocks.list(currentEnv, env.currentProject));
@@ -2082,8 +2166,8 @@ class ContensisCli {
2082
2166
  }
2083
2167
 
2084
2168
  if (err) {
2085
- log.error(messages.blocks.noList(currentEnv));
2086
- log.error(jsonFormatter(err));
2169
+ log.error(messages.blocks.noList(currentEnv, env.currentProject));
2170
+ // log.error(jsonFormatter(err));
2087
2171
  }
2088
2172
  }
2089
2173
  };
@@ -2103,11 +2187,29 @@ class ContensisCli {
2103
2187
  version
2104
2188
  );
2105
2189
 
2106
- if (blocks) {
2190
+ if (err || blocks?.length === 0) {
2191
+ log.warning(
2192
+ messages.blocks.noGet(
2193
+ blockId,
2194
+ branch,
2195
+ version,
2196
+ currentEnv,
2197
+ env.currentProject
2198
+ )
2199
+ );
2200
+ log.help(messages.blocks.noGetTip());
2201
+ // if (err) log.error(jsonFormatter(err));
2202
+ } else if (blocks) {
2107
2203
  await this.HandleFormattingAndOutput(blocks, () => {
2108
2204
  // print the version detail to console
2109
2205
  log.success(
2110
- messages.blocks.get(blockId, currentEnv, env.currentProject)
2206
+ messages.blocks.get(
2207
+ blockId,
2208
+ branch,
2209
+ version,
2210
+ currentEnv,
2211
+ env.currentProject
2212
+ )
2111
2213
  );
2112
2214
  for (const block of blocks)
2113
2215
  printBlockVersion(
@@ -2126,11 +2228,6 @@ class ContensisCli {
2126
2228
 
2127
2229
  return blocks;
2128
2230
  }
2129
-
2130
- if (err) {
2131
- log.error(messages.blocks.noList(currentEnv, env.currentProject));
2132
- log.error(jsonFormatter(err));
2133
- }
2134
2231
  }
2135
2232
  };
2136
2233
 
@@ -2454,9 +2551,7 @@ class ContensisCli {
2454
2551
  const contensis = await this.ConnectContensis();
2455
2552
  if (contensis) {
2456
2553
  // Retrieve renderers list for env
2457
- const [err, renderers] = await (contensis.renderers.GetRenderers as any)(
2458
- rendererId
2459
- ); // TODO: resolve any cast
2554
+ const [err, renderers] = await contensis.renderers.GetRenderers();
2460
2555
 
2461
2556
  if (Array.isArray(renderers)) {
2462
2557
  await this.HandleFormattingAndOutput(renderers, () => {
@@ -2487,6 +2582,7 @@ class ContensisCli {
2487
2582
  );
2488
2583
  }
2489
2584
  });
2585
+ return renderers;
2490
2586
  }
2491
2587
 
2492
2588
  if (err) {
@@ -2495,6 +2591,7 @@ class ContensisCli {
2495
2591
  }
2496
2592
  }
2497
2593
  };
2594
+
2498
2595
  HandleFormattingAndOutput = async <T>(obj: T, logFn: (obj: T) => void) => {
2499
2596
  const { format, log, messages, output } = this;
2500
2597
  const fields = this.contensis?.payload.query?.fields;