@dynamicweb/cli 1.1.2 → 2.0.0-beta.2

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,74 +1,82 @@
1
1
  import fetch from 'node-fetch';
2
2
  import path from 'path';
3
3
  import fs from 'fs';
4
- import { setupEnv, getAgent } from './env.js';
4
+ import { setupEnv, getAgent, createCommandError } from './env.js';
5
5
  import { setupUser } from './login.js';
6
6
 
7
- const exclude = ['_', '$0', 'command', 'list', 'json', 'verbose', 'v', 'host', 'protocol', 'apiKey', 'env']
7
+ const exclude = ['_', '$0', 'command', 'list', 'json', 'verbose', 'v', 'host', 'protocol', 'apiKey', 'env', 'output', 'auth', 'clientId', 'clientSecret', 'clientIdEnv', 'clientSecretEnv', 'oauth']
8
8
 
9
9
  export function commandCommand() {
10
10
  return {
11
- command: 'command [command]',
12
- describe: 'Runs the given command',
11
+ command: 'command [command]',
12
+ describe: 'Runs the given command',
13
13
  builder: (yargs) => {
14
14
  return yargs
15
- .positional('command', {
16
- describe: 'The command to execute'
17
- })
18
- .option('json', {
19
- describe: 'Literal json or location of json file to send'
20
- })
21
- .option('list', {
22
- alias: 'l',
23
- describe: 'Lists all the properties for the command, currently not working'
24
- })
15
+ .positional('command', {
16
+ describe: 'The command to execute'
17
+ })
18
+ .option('json', {
19
+ describe: 'Literal json or location of json file to send'
20
+ })
21
+ .option('list', {
22
+ alias: 'l',
23
+ describe: 'Lists all the properties for the command, currently not working'
24
+ })
25
+ .option('output', {
26
+ choices: ['json'],
27
+ describe: 'Outputs a single JSON response for automation-friendly parsing'
28
+ })
25
29
  },
26
30
  handler: async (argv) => {
27
- if (argv.verbose) console.info(`Running command ${argv.command}`)
28
- await handleCommand(argv)
31
+ const output = createCommandOutput(argv);
32
+
33
+ try {
34
+ output.verboseLog(`Running command ${argv.command}`);
35
+ await handleCommand(argv, output);
36
+ } catch (err) {
37
+ output.fail(err);
38
+ process.exitCode = 1;
39
+ } finally {
40
+ output.finish();
41
+ }
29
42
  }
30
43
  }
31
44
  }
32
45
 
33
- async function handleCommand(argv) {
34
- let env = await setupEnv(argv);
46
+ async function handleCommand(argv, output) {
47
+ let env = await setupEnv(argv, output);
35
48
  let user = await setupUser(argv, env);
36
49
  if (argv.list) {
37
- console.log(await getProperties(env, user, argv.command))
50
+ const properties = await getProperties(env, user, argv.command);
51
+ output.addData(properties);
52
+ output.log(properties);
38
53
  } else {
39
- let response = await runCommand(env, user, argv.command, getQueryParams(argv), parseJsonOrPath(argv.json))
40
- console.log(response)
54
+ let response = await runCommand(env, user, argv.command, getQueryParams(argv), parseJsonOrPath(argv.json));
55
+ output.addData(response);
56
+ output.log(response);
41
57
  }
42
58
  }
43
59
 
44
60
  async function getProperties(env, user, command) {
45
- return `This option currently doesn't work`
46
- let res = await fetch(`${env.protocol}://${env.host}/Admin/Api/CommandByName?name=${command}`, {
47
- method: 'GET',
48
- headers: {
49
- 'Authorization': `Bearer ${user.apiKey}`
50
- },
51
- agent: getAgent(env.protocol)
52
- })
53
- if (res.ok) {
54
- let body = await res.json()
55
- return body.model.propertyNames
56
- }
61
+ throw createCommandError('The --list option is not currently implemented for commands.');
57
62
  }
58
63
 
59
- function getQueryParams(argv) {
64
+ export function getQueryParams(argv) {
60
65
  let params = {}
61
66
  Object.keys(argv).filter(k => !exclude.includes(k)).forEach(k => params['Command.' + k] = argv[k])
62
67
  return params
63
68
  }
64
69
 
65
- function parseJsonOrPath(json) {
70
+ export function parseJsonOrPath(json) {
66
71
  if (!json) return
72
+ const trimmed = json.trim();
73
+ if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
74
+ return JSON.parse(trimmed);
75
+ }
67
76
  if (fs.existsSync(json)) {
68
- return JSON.parse(fs.readFileSync(path.resolve(json)))
69
- } else {
70
- return JSON.parse(json)
77
+ return JSON.parse(fs.readFileSync(path.resolve(json)));
71
78
  }
79
+ return JSON.parse(json);
72
80
  }
73
81
 
74
82
  async function runCommand(env, user, command, queryParams, data) {
@@ -82,8 +90,61 @@ async function runCommand(env, user, command, queryParams, data) {
82
90
  agent: getAgent(env.protocol)
83
91
  })
84
92
  if (!res.ok) {
85
- console.log(`Error when doing request ${res.url}`)
86
- process.exit(1);
93
+ throw createCommandError(`Error when doing request ${res.url}`, res.status, await parseJsonSafe(res));
87
94
  }
88
95
  return await res.json()
89
- }
96
+ }
97
+
98
+ function createCommandOutput(argv) {
99
+ const response = {
100
+ ok: true,
101
+ command: 'command',
102
+ operation: argv.list ? 'list' : 'run',
103
+ status: 200,
104
+ data: [],
105
+ errors: [],
106
+ meta: {
107
+ commandName: argv.command
108
+ }
109
+ };
110
+
111
+ return {
112
+ json: argv.output === 'json',
113
+ response,
114
+ log(value) {
115
+ if (!this.json) {
116
+ console.log(value);
117
+ }
118
+ },
119
+ verboseLog(...args) {
120
+ if (argv.verbose && !this.json) {
121
+ console.info(...args);
122
+ }
123
+ },
124
+ addData(entry) {
125
+ response.data.push(entry);
126
+ },
127
+ fail(err) {
128
+ response.ok = false;
129
+ response.status = err?.status || 1;
130
+ response.errors.push({
131
+ message: err?.message || 'Unknown command error.',
132
+ details: err?.details ?? null
133
+ });
134
+ },
135
+ finish() {
136
+ if (this.json) {
137
+ console.log(JSON.stringify(response, null, 2));
138
+ }
139
+ }
140
+ };
141
+ }
142
+
143
+
144
+ async function parseJsonSafe(res) {
145
+ try {
146
+ return await res.json();
147
+ } catch {
148
+ return null;
149
+ }
150
+ }
@@ -27,10 +27,22 @@ export function setupConfig() {
27
27
  }
28
28
 
29
29
  export function getConfig() {
30
- return localConfig;
30
+ return localConfig || {};
31
+ }
32
+
33
+ /**
34
+ * Overrides the in-memory config for testing.
35
+ * @param {Object} config - Must be a plain, non-null object; handleConfig writes keys directly onto it.
36
+ */
37
+ export function setConfigForTests(config) {
38
+ if (config === null || typeof config !== 'object') {
39
+ throw new Error('setConfigForTests: config must be a plain object');
40
+ }
41
+ localConfig = config;
31
42
  }
32
43
 
33
44
  export function handleConfig(argv) {
45
+ localConfig = localConfig || {};
34
46
  Object.keys(argv).forEach(a => {
35
47
  if (a != '_' && a != '$0') {
36
48
  localConfig[a] = resolveConfig(a, argv[a], localConfig[a] || {});
@@ -52,4 +64,4 @@ function resolveConfig(key, obj, conf) {
52
64
  conf[a] = resolveConfig(key, obj[a], conf[a]);
53
65
  })
54
66
  return conf;
55
- }
67
+ }
@@ -22,6 +22,30 @@ export function getAgent(protocol) {
22
22
  return protocol === 'http' ? httpAgent : httpsAgent;
23
23
  }
24
24
 
25
+ export function parseHostInput(hostValue) {
26
+ if (!hostValue || typeof hostValue !== 'string' || !hostValue.trim()) {
27
+ throw createCommandError(`Invalid host value: ${hostValue}`);
28
+ }
29
+ hostValue = hostValue.trim();
30
+ const hostSplit = hostValue.split('://');
31
+
32
+ if (hostSplit.length === 1) {
33
+ return {
34
+ protocol: 'https',
35
+ host: hostSplit[0]
36
+ };
37
+ }
38
+
39
+ if (hostSplit.length === 2) {
40
+ return {
41
+ protocol: hostSplit[0],
42
+ host: hostSplit[1]
43
+ };
44
+ }
45
+
46
+ throw createCommandError(`Issues resolving host ${hostValue}`);
47
+ }
48
+
25
49
  export function envCommand() {
26
50
  return {
27
51
  command: 'env [env]',
@@ -41,12 +65,29 @@ export function envCommand() {
41
65
  type: 'boolean',
42
66
  description: 'List all users in environment, uses positional [env] if used, otherwise current env'
43
67
  })
68
+ .option('output', {
69
+ choices: ['json'],
70
+ describe: 'Outputs a single JSON response for automation-friendly parsing'
71
+ })
44
72
  },
45
- handler: (argv) => handleEnv(argv)
73
+ handler: async (argv) => {
74
+ const output = createEnvOutput(argv);
75
+
76
+ try {
77
+ await handleEnv(argv, output);
78
+ } catch (err) {
79
+ output.fail(err);
80
+ process.exitCode = 1;
81
+ } finally {
82
+ output.finish();
83
+ }
84
+ }
46
85
  }
47
86
  }
48
87
 
49
- export async function setupEnv(argv) {
88
+ export async function setupEnv(argv, output = null, deps = {}) {
89
+ const interactiveEnvFn = deps.interactiveEnvFn || interactiveEnv;
90
+ const cfg = getConfig();
50
91
  let env = {};
51
92
  let askEnv = true;
52
93
 
@@ -60,36 +101,58 @@ export async function setupEnv(argv) {
60
101
  }
61
102
  }
62
103
 
63
- if (askEnv && getConfig().env) {
64
- env = getConfig().env[argv.env] || getConfig().env[getConfig()?.current?.env];
65
- if (!env.protocol) {
66
- console.log('Protocol for environment not set, defaulting to https');
104
+ if (askEnv && cfg.env) {
105
+ env = cfg.env[argv.env] || cfg.env[cfg?.current?.env];
106
+ if (env && !env.protocol) {
107
+ logMessage(argv, 'Protocol for environment not set, defaulting to https');
67
108
  env.protocol = 'https';
68
109
  }
69
110
  }
70
111
  else if (askEnv) {
71
- console.log('Current environment not set, please set it')
72
- await interactiveEnv(argv, {
112
+ if (isJsonOutput(argv)) {
113
+ throw createCommandError('Current environment not set, please set it');
114
+ }
115
+
116
+ logMessage(argv, 'Current environment not set, please set it');
117
+ await interactiveEnvFn(argv, {
73
118
  environment: {
74
119
  type: 'input'
75
120
  },
76
121
  interactive: {
77
122
  default: true
78
123
  }
79
- })
80
- env = getConfig().env[getConfig()?.current?.env];
124
+ }, output)
125
+ const updatedConfig = getConfig();
126
+ env = updatedConfig.env?.[updatedConfig?.current?.env];
127
+ }
128
+
129
+ if (!env || Object.keys(env).length === 0) {
130
+ throw createCommandError('Unable to resolve the current environment.');
81
131
  }
132
+
82
133
  return env;
83
134
  }
84
135
 
85
- async function handleEnv(argv) {
136
+ async function handleEnv(argv, output) {
86
137
  if (argv.users) {
87
- let env = argv.env || getConfig().current.env;
88
- console.log(`Users in environment ${env}: ${Object.keys(getConfig().env[env].users || {})}`);
138
+ const cfg = getConfig();
139
+ let env = argv.env || cfg.current?.env;
140
+ const envConfig = cfg.env?.[env];
141
+ if (!envConfig) {
142
+ throw createCommandError(`Environment '${env}' does not exist`, 404);
143
+ }
144
+ const users = Object.keys(envConfig.users || {});
145
+ output.addData({ environment: env, users });
146
+ output.log(`Users in environment ${env}: ${users}`);
89
147
  } else if (argv.env) {
90
- changeEnv(argv)
148
+ const result = await changeEnv(argv, output);
149
+ if (result !== null) {
150
+ output.addData(result);
151
+ }
91
152
  } else if (argv.list) {
92
- console.log(`Existing environments: ${Object.keys(getConfig().env || {})}`)
153
+ const environments = Object.keys(getConfig().env || {});
154
+ output.addData({ environments });
155
+ output.log(`Existing environments: ${environments}`);
93
156
  } else {
94
157
  await interactiveEnv(argv, {
95
158
  environment: {
@@ -102,12 +165,12 @@ async function handleEnv(argv) {
102
165
  interactive: {
103
166
  default: true
104
167
  }
105
- })
168
+ }, output)
106
169
  }
107
170
  }
108
171
 
109
- export async function interactiveEnv(argv, options) {
110
- if (argv.verbose) console.info('Setting up new environment')
172
+ export async function interactiveEnv(argv, options, output) {
173
+ verboseLog(argv, 'Setting up new environment');
111
174
  const result = {};
112
175
  for (const [key, config] of Object.entries(options)) {
113
176
  if (key === 'interactive') continue;
@@ -122,35 +185,46 @@ export async function interactiveEnv(argv, options) {
122
185
  }
123
186
  getConfig().env = getConfig().env || {};
124
187
  if (!result.environment || !result.environment.trim()) {
125
- console.log('Environment name cannot be empty');
126
- return;
188
+ throw createCommandError('Environment name cannot be empty');
127
189
  }
128
190
  getConfig().env[result.environment] = getConfig().env[result.environment] || {};
129
191
  if (result.host) {
130
- const hostSplit = result.host.split("://");
131
- if (hostSplit.length == 1) {
132
- getConfig().env[result.environment].protocol = 'https';
133
- getConfig().env[result.environment].host = hostSplit[0];
134
- } else if (hostSplit.length == 2) {
135
- getConfig().env[result.environment].protocol = hostSplit[0];
136
- getConfig().env[result.environment].host = hostSplit[1];
137
- } else {
138
- console.log(`Issues resolving host ${result.host}`);
139
- return;
140
- }
192
+ const resolvedHost = parseHostInput(result.host);
193
+ getConfig().env[result.environment].protocol = resolvedHost.protocol;
194
+ getConfig().env[result.environment].host = resolvedHost.host;
141
195
  }
142
196
  if (result.environment) {
143
197
  getConfig().current = getConfig().current || {};
144
198
  getConfig().current.env = result.environment;
145
199
  }
146
200
  updateConfig();
147
- console.log(`Your current environment is now ${getConfig().current.env}`);
148
- console.log(`To change the host of your environment, use the command 'dw env'`)
201
+ logMessage(argv, `Your current environment is now ${getConfig().current.env}`);
202
+ logMessage(argv, `To change the host of your environment, use the command 'dw env'`);
203
+
204
+ const currentEnv = getConfig().env[result.environment];
205
+ const data = {
206
+ environment: result.environment,
207
+ protocol: currentEnv.protocol || null,
208
+ host: currentEnv.host || null,
209
+ current: getConfig().current.env
210
+ };
211
+
212
+ if (output) {
213
+ output.addData(data);
214
+ }
215
+
216
+ return data;
149
217
  }
150
218
 
151
- async function changeEnv(argv) {
152
- if (!Object.keys(getConfig().env).includes(argv.env)) {
153
- console.log(`The specified environment ${argv.env} doesn't exist, please create it`);
219
+ async function changeEnv(argv, output) {
220
+ const environments = getConfig().env || {};
221
+
222
+ if (!Object.hasOwn(environments, argv.env)) {
223
+ if (isJsonOutput(argv)) {
224
+ throw createCommandError(`The specified environment ${argv.env} doesn't exist, please create it`, 404);
225
+ }
226
+
227
+ logMessage(argv, `The specified environment ${argv.env} doesn't exist, please create it`);
154
228
  await interactiveEnv(argv, {
155
229
  environment: {
156
230
  type: 'input',
@@ -165,10 +239,79 @@ async function changeEnv(argv) {
165
239
  interactive: {
166
240
  default: true
167
241
  }
168
- })
242
+ }, output)
243
+ return null;
169
244
  } else {
170
245
  getConfig().current.env = argv.env;
171
246
  updateConfig();
172
- console.log(`Your current environment is now ${getConfig().current.env}`);
247
+ const data = {
248
+ environment: argv.env,
249
+ current: getConfig().current.env
250
+ };
251
+ logMessage(argv, `Your current environment is now ${getConfig().current.env}`);
252
+ if (output) {
253
+ output.addData(data);
254
+ }
255
+ return null;
173
256
  }
174
257
  }
258
+
259
+ export function isJsonOutput(argv) {
260
+ return argv?.output === 'json';
261
+ }
262
+
263
+ export function createCommandError(message, status = 1, details = null) {
264
+ const error = new Error(message);
265
+ error.status = status;
266
+ error.details = details;
267
+ return error;
268
+ }
269
+
270
+ function logMessage(argv, ...args) {
271
+ if (!isJsonOutput(argv)) {
272
+ console.log(...args);
273
+ }
274
+ }
275
+
276
+ function verboseLog(argv, ...args) {
277
+ if (argv?.verbose && !isJsonOutput(argv)) {
278
+ console.info(...args);
279
+ }
280
+ }
281
+
282
+ function createEnvOutput(argv) {
283
+ const response = {
284
+ ok: true,
285
+ command: 'env',
286
+ operation: argv.users ? 'users' : argv.list ? 'list' : argv.env ? 'select' : 'setup',
287
+ status: 200,
288
+ data: [],
289
+ errors: [],
290
+ meta: {}
291
+ };
292
+
293
+ return {
294
+ json: isJsonOutput(argv),
295
+ addData(entry) {
296
+ response.data.push(entry);
297
+ },
298
+ log(...args) {
299
+ if (!this.json) {
300
+ console.log(...args);
301
+ }
302
+ },
303
+ fail(err) {
304
+ response.ok = false;
305
+ response.status = err?.status || 1;
306
+ response.errors.push({
307
+ message: err?.message || 'Unknown env command error.',
308
+ details: err?.details ?? null
309
+ });
310
+ },
311
+ finish() {
312
+ if (this.json) {
313
+ console.log(JSON.stringify(response, null, 2));
314
+ }
315
+ }
316
+ };
317
+ }