@dynamicweb/cli 1.1.0 → 2.0.0-beta.0

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.
@@ -1,9 +1,9 @@
1
1
  import fetch from 'node-fetch';
2
- import { setupEnv, getAgent } from './env.js';
2
+ import { setupEnv, getAgent, createCommandError } from './env.js';
3
3
  import { setupUser } from './login.js';
4
4
  import { input } from '@inquirer/prompts';
5
5
 
6
- const exclude = ['_', '$0', 'query', 'list', 'i', 'l', 'interactive', 'verbose', 'v', 'host', 'protocol', 'apiKey', 'env']
6
+ const exclude = ['_', '$0', 'query', 'list', 'i', 'l', 'interactive', 'verbose', 'v', 'host', 'protocol', 'apiKey', 'env', 'output', 'auth', 'clientId', 'clientSecret', 'clientIdEnv', 'clientSecretEnv', 'oauth']
7
7
 
8
8
  export function queryCommand() {
9
9
  return {
@@ -22,30 +22,47 @@ export function queryCommand() {
22
22
  alias: 'i',
23
23
  describe: 'Runs in interactive mode to ask for query parameters one by one'
24
24
  })
25
+ .option('output', {
26
+ choices: ['json'],
27
+ describe: 'Outputs a single JSON response for automation-friendly parsing',
28
+ conflicts: 'interactive'
29
+ })
25
30
  },
26
- handler: (argv) => {
27
- if (argv.verbose) console.info(`Running query ${argv.query}`)
28
- handleQuery(argv)
31
+ handler: async (argv) => {
32
+ const output = createQueryOutput(argv);
33
+
34
+ try {
35
+ output.verboseLog(`Running query ${argv.query}`);
36
+ await handleQuery(argv, output);
37
+ } catch (err) {
38
+ output.fail(err);
39
+ if (!output.json) {
40
+ console.error(err.stack || err.message || String(err));
41
+ }
42
+ process.exitCode = 1;
43
+ } finally {
44
+ output.finish();
45
+ }
29
46
  }
30
47
  }
31
48
  }
32
49
 
33
- async function handleQuery(argv) {
34
- let env = await setupEnv(argv);
50
+ async function handleQuery(argv, output) {
51
+ let env = await setupEnv(argv, output);
35
52
  let user = await setupUser(argv, env);
36
53
  if (argv.list) {
37
- console.log(await getProperties(argv))
54
+ const properties = await getProperties(env, user, argv.query);
55
+ output.addData(properties);
56
+ output.log(properties);
38
57
  } else {
39
- let response = await runQuery(env, user, argv.query, await getQueryParams(argv))
40
- console.log(response)
58
+ let response = await runQuery(env, user, argv.query, await getQueryParams(env, user, argv, output));
59
+ output.addData(response);
60
+ output.log(response);
41
61
  }
42
62
  }
43
63
 
44
- async function getProperties(argv) {
45
- let env = await setupEnv(argv);
46
- let user = await setupUser(argv, env);
47
-
48
- let res = await fetch(`${env.protocol}://${env.host}/Admin/Api/QueryByName?name=${argv.query}`, {
64
+ async function getProperties(env, user, query) {
65
+ let res = await fetch(`${env.protocol}://${env.host}/Admin/Api/QueryByName?name=${encodeURIComponent(query)}`, {
49
66
  method: 'GET',
50
67
  headers: {
51
68
  'Authorization': `Bearer ${user.apiKey}`
@@ -54,23 +71,21 @@ async function getProperties(argv) {
54
71
  })
55
72
  if (res.ok) {
56
73
  let body = await res.json()
57
- if (body.model.properties.groups === undefined) {
58
- console.log('Unable to fetch query parameters');
59
- process.exit(1);
74
+ if (body?.model?.properties?.groups === undefined) {
75
+ throw createCommandError('Unable to fetch query parameters.', res.status, body);
60
76
  }
61
77
  return body.model.properties.groups.filter(g => g.name === 'Properties')[0].fields.map(field => `${field.name} (${field.typeName})`)
62
78
  }
63
- console.log('Unable to fetch query parameters');
64
- console.log(res);
65
- process.exit(1);
79
+
80
+ throw createCommandError('Unable to fetch query parameters.', res.status, await parseJsonSafe(res));
66
81
  }
67
82
 
68
- async function getQueryParams(argv) {
83
+ async function getQueryParams(env, user, argv, output) {
69
84
  let params = {}
70
85
  if (argv.interactive) {
71
- let properties = await getProperties(argv);
72
- console.log('The following properties will be requested:')
73
- console.log(properties)
86
+ let properties = await getProperties(env, user, argv.query);
87
+ output.log('The following properties will be requested:')
88
+ output.log(properties)
74
89
  for (const p of properties) {
75
90
  const value = await input({ message: p });
76
91
  if (value) {
@@ -93,8 +108,61 @@ async function runQuery(env, user, query, params) {
93
108
  agent: getAgent(env.protocol)
94
109
  })
95
110
  if (!res.ok) {
96
- console.log(`Error when doing request ${res.url}`)
97
- process.exit(1);
111
+ throw createCommandError(`Error when doing request ${res.url}`, res.status, await parseJsonSafe(res));
98
112
  }
99
113
  return await res.json()
100
- }
114
+ }
115
+
116
+ function createQueryOutput(argv) {
117
+ const response = {
118
+ ok: true,
119
+ command: 'query',
120
+ operation: argv.list ? 'list' : 'run',
121
+ status: 200,
122
+ data: [],
123
+ errors: [],
124
+ meta: {
125
+ query: argv.query
126
+ }
127
+ };
128
+
129
+ return {
130
+ json: argv.output === 'json',
131
+ response,
132
+ log(value) {
133
+ if (!this.json) {
134
+ console.log(value);
135
+ }
136
+ },
137
+ verboseLog(...args) {
138
+ if (argv.verbose && !this.json) {
139
+ console.info(...args);
140
+ }
141
+ },
142
+ addData(entry) {
143
+ response.data.push(entry);
144
+ },
145
+ fail(err) {
146
+ response.ok = false;
147
+ response.status = err?.status || 1;
148
+ response.errors.push({
149
+ message: err?.message || 'Unknown query command error.',
150
+ details: err?.details ?? null
151
+ });
152
+ },
153
+ finish() {
154
+ if (this.json) {
155
+ console.log(JSON.stringify(response, null, 2));
156
+ }
157
+ }
158
+ };
159
+ }
160
+
161
+
162
+ async function parseJsonSafe(res) {
163
+ try {
164
+ return await res.json();
165
+ } catch {
166
+ return null;
167
+ }
168
+ }
package/bin/downloader.js CHANGED
@@ -9,7 +9,7 @@ import fs from 'fs';
9
9
  export function getFileNameFromResponse(res, dirPath) {
10
10
  const header = res.headers.get('content-disposition');
11
11
  const parts = header?.split(';');
12
-
12
+
13
13
  if (!parts) {
14
14
  const msg = `No files found in directory '${dirPath}', if you want to download all folders recursively include the -r flag`;
15
15
  throw new Error(msg);
@@ -24,13 +24,16 @@ export function getFileNameFromResponse(res, dirPath) {
24
24
  *
25
25
  * @param {Object} res - The HTTP response object to extract the file name from.
26
26
  * @param {string} dirPath - The directory path to use for file name resolution.
27
+ * @param {boolean} verbose - Whether to log missing download information.
27
28
  * @returns {string|null} The extracted file name, or null if extraction fails.
28
29
  */
29
- export function tryGetFileNameFromResponse(res, dirPath) {
30
+ export function tryGetFileNameFromResponse(res, dirPath, verbose = false) {
30
31
  try {
31
32
  return getFileNameFromResponse(res, dirPath);
32
33
  } catch (err) {
33
- console.error(err.message);
34
+ if (verbose) {
35
+ console.log(err.message);
36
+ }
34
37
  return null;
35
38
  }
36
39
  }
@@ -56,7 +59,7 @@ export function downloadWithProgress(res, filePath, options) {
56
59
  res.body.on("data", chunk => {
57
60
  const isFirstChunk = receivedBytes === 0;
58
61
  const elapsed = Date.now() - startTime;
59
-
62
+
60
63
  receivedBytes += chunk.length;
61
64
 
62
65
  if (options?.onData) {
@@ -64,4 +67,4 @@ export function downloadWithProgress(res, filePath, options) {
64
67
  }
65
68
  });
66
69
  });
67
- }
70
+ }
package/bin/index.js CHANGED
@@ -33,14 +33,30 @@ yargs(hideBin(process.argv))
33
33
  description: 'Run with verbose logging'
34
34
  })
35
35
  .option('protocol', {
36
- description: 'Allows setting the protocol used, only used together with --host, defaulting to https'
36
+ description: 'Set the protocol used with --host (defaults to https)'
37
37
  })
38
38
  .option('host', {
39
- description: 'Allows setting the host used, only allowed if an --apiKey is specified'
39
+ description: 'Allows setting the host used, only allowed if an --apiKey or OAuth client credentials are specified'
40
40
  })
41
41
  .option('apiKey', {
42
42
  description: 'Allows setting the apiKey for an environmentless execution of the CLI command'
43
43
  })
44
+ .option('auth', {
45
+ choices: ['user', 'oauth'],
46
+ description: 'Overrides the authentication mode for the command'
47
+ })
48
+ .option('clientId', {
49
+ description: 'OAuth client ID used together with --auth oauth'
50
+ })
51
+ .option('clientSecret', {
52
+ description: 'OAuth client secret used together with --auth oauth. WARNING: passing this on the command line can expose the secret via shell history and process listings. Prefer using --clientSecretEnv to reference a secret stored in an environment variable instead.'
53
+ })
54
+ .option('clientIdEnv', {
55
+ description: 'Environment variable name that contains the OAuth client ID'
56
+ })
57
+ .option('clientSecretEnv', {
58
+ description: 'Environment variable name that contains the OAuth client secret'
59
+ })
44
60
  .demandCommand()
45
61
  .parse()
46
62
 
@@ -49,14 +65,27 @@ function baseCommand() {
49
65
  command: '$0',
50
66
  describe: 'Shows the current env and user being used',
51
67
  handler: () => {
52
- if (Object.keys(getConfig()).length === 0) {
68
+ const cfg = getConfig();
69
+ if (Object.keys(cfg).length === 0) {
53
70
  console.log('To login to a solution use `dw login`')
54
71
  return;
55
- }
56
- console.log(`Environment: ${getConfig()?.current?.env}`)
57
- console.log(`User: ${getConfig()?.env[getConfig()?.current?.env]?.current?.user}`)
58
- console.log(`Protocol: ${getConfig()?.env[getConfig()?.current?.env]?.protocol}`)
59
- console.log(`Host: ${getConfig()?.env[getConfig()?.current?.env]?.host}`)
72
+ }
73
+ const currentEnv = cfg?.env?.[cfg?.current?.env];
74
+ if (!currentEnv) {
75
+ console.log(`Environment '${cfg?.current?.env}' is not configured.`);
76
+ console.log('To login to a solution use `dw login`');
77
+ return;
78
+ }
79
+ const authType = currentEnv?.current?.authType;
80
+
81
+ console.log(`Environment: ${cfg?.current?.env}`);
82
+ if (authType === 'oauth_client_credentials') {
83
+ console.log('Authentication: OAuth client credentials');
84
+ } else if (currentEnv?.current?.user) {
85
+ console.log(`User: ${currentEnv.current.user}`);
86
+ }
87
+ console.log(`Protocol: ${currentEnv.protocol}`);
88
+ console.log(`Host: ${currentEnv.host}`);
60
89
  }
61
90
  }
62
91
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@dynamicweb/cli",
3
3
  "type": "module",
4
4
  "description": "CLI for interacting with Dynamicweb 10 solutions from the command line.",
5
- "version": "1.1.0",
5
+ "version": "2.0.0-beta.0",
6
6
  "main": "bin/index.js",
7
7
  "files": [
8
8
  "bin/**"