@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.
- package/README.md +404 -171
- package/bin/commands/command.js +82 -24
- package/bin/commands/env.js +143 -26
- package/bin/commands/files.js +412 -74
- package/bin/commands/install.js +98 -18
- package/bin/commands/login.js +383 -44
- package/bin/commands/query.js +96 -28
- package/bin/downloader.js +8 -5
- package/bin/index.js +37 -8
- package/package.json +1 -1
package/bin/commands/query.js
CHANGED
|
@@ -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
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
45
|
-
let
|
|
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
|
|
58
|
-
|
|
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
|
-
|
|
64
|
-
|
|
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
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: '
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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