@dynamicweb/cli 1.1.1 → 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 +401 -180
- package/bin/commands/command.js +6 -23
- package/bin/commands/env.js +143 -26
- package/bin/commands/files.js +248 -34
- package/bin/commands/install.js +98 -18
- package/bin/commands/login.js +383 -44
- package/bin/commands/query.js +16 -20
- package/bin/index.js +37 -8
- package/package.json +2 -2
package/bin/commands/files.js
CHANGED
|
@@ -2,7 +2,7 @@ import fetch from 'node-fetch';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import FormData from 'form-data';
|
|
5
|
-
import { setupEnv, getAgent } from './env.js';
|
|
5
|
+
import { setupEnv, getAgent, createCommandError } from './env.js';
|
|
6
6
|
import { setupUser } from './login.js';
|
|
7
7
|
import { interactiveConfirm, formatBytes, createThrottledStatusUpdater } from '../utils.js';
|
|
8
8
|
import { downloadWithProgress, tryGetFileNameFromResponse } from '../downloader.js';
|
|
@@ -59,9 +59,31 @@ export function filesCommand() {
|
|
|
59
59
|
type: 'boolean',
|
|
60
60
|
describe: 'Used with export, keeps zip file instead of unpacking it'
|
|
61
61
|
})
|
|
62
|
+
.option('dangerouslyIncludeLogsAndCache', {
|
|
63
|
+
type: 'boolean',
|
|
64
|
+
describe: 'Includes log and cache folders during export. Risky and usually not recommended'
|
|
65
|
+
})
|
|
62
66
|
.option('iamstupid', {
|
|
63
67
|
type: 'boolean',
|
|
64
|
-
|
|
68
|
+
hidden: true,
|
|
69
|
+
describe: 'Deprecated alias for --dangerouslyIncludeLogsAndCache'
|
|
70
|
+
})
|
|
71
|
+
.option('delete', {
|
|
72
|
+
alias: 'd',
|
|
73
|
+
type: 'boolean',
|
|
74
|
+
describe: 'Deletes the file or directory at [dirPath]. Detects type from path (use --asFile/--asDirectory to override)'
|
|
75
|
+
})
|
|
76
|
+
.option('empty', {
|
|
77
|
+
type: 'boolean',
|
|
78
|
+
describe: 'Used with --delete, empties a directory instead of deleting it'
|
|
79
|
+
})
|
|
80
|
+
.option('copy', {
|
|
81
|
+
type: 'string',
|
|
82
|
+
describe: 'Copies the file or directory at [dirPath] to the given destination path'
|
|
83
|
+
})
|
|
84
|
+
.option('move', {
|
|
85
|
+
type: 'string',
|
|
86
|
+
describe: 'Moves the file or directory at [dirPath] to the given destination path'
|
|
65
87
|
})
|
|
66
88
|
.option('asFile', {
|
|
67
89
|
type: 'boolean',
|
|
@@ -75,32 +97,45 @@ export function filesCommand() {
|
|
|
75
97
|
describe: 'Forces the command to treat the path as a directory, even if its name contains a dot.',
|
|
76
98
|
conflicts: 'asFile'
|
|
77
99
|
})
|
|
100
|
+
.option('output', {
|
|
101
|
+
choices: ['json'],
|
|
102
|
+
describe: 'Outputs a single JSON response for automation-friendly parsing'
|
|
103
|
+
})
|
|
78
104
|
.option('json', {
|
|
79
105
|
type: 'boolean',
|
|
80
|
-
|
|
106
|
+
hidden: true,
|
|
107
|
+
describe: 'Deprecated alias for --output json'
|
|
81
108
|
})
|
|
82
109
|
},
|
|
83
110
|
handler: async (argv) => {
|
|
111
|
+
if (argv.json && !argv.output) {
|
|
112
|
+
argv.output = 'json';
|
|
113
|
+
console.warn('Warning: --json is deprecated and will be removed in a future release. Use --output json instead.');
|
|
114
|
+
}
|
|
115
|
+
if (argv.iamstupid && !argv.dangerouslyIncludeLogsAndCache) {
|
|
116
|
+
argv.dangerouslyIncludeLogsAndCache = true;
|
|
117
|
+
console.warn('Warning: --iamstupid is deprecated and will be removed in a future release. Use --dangerouslyIncludeLogsAndCache instead.');
|
|
118
|
+
}
|
|
84
119
|
const output = createFilesOutput(argv);
|
|
85
120
|
|
|
86
121
|
try {
|
|
87
|
-
output.verboseLog(`Listing directory at: ${argv.dirPath}`);
|
|
88
122
|
await handleFiles(argv, output);
|
|
89
|
-
output.finish();
|
|
90
123
|
} catch (err) {
|
|
91
124
|
output.fail(err);
|
|
125
|
+
process.exitCode = 1;
|
|
126
|
+
} finally {
|
|
92
127
|
output.finish();
|
|
93
|
-
process.exit(1);
|
|
94
128
|
}
|
|
95
129
|
}
|
|
96
130
|
}
|
|
97
131
|
}
|
|
98
132
|
|
|
99
133
|
async function handleFiles(argv, output) {
|
|
100
|
-
let env = await setupEnv(argv);
|
|
134
|
+
let env = await setupEnv(argv, output);
|
|
101
135
|
let user = await setupUser(argv, env);
|
|
102
136
|
|
|
103
137
|
if (argv.list) {
|
|
138
|
+
output.verboseLog(`Listing directory at: ${argv.dirPath}`);
|
|
104
139
|
let files = (await getFilesStructure(env, user, argv.dirPath, argv.recursive, argv.includeFiles)).model;
|
|
105
140
|
output.setStatus(200);
|
|
106
141
|
output.addData(files);
|
|
@@ -115,30 +150,34 @@ async function handleFiles(argv, output) {
|
|
|
115
150
|
if (argv.export) {
|
|
116
151
|
if (argv.dirPath) {
|
|
117
152
|
|
|
118
|
-
const isFile = argv
|
|
119
|
-
? argv.asFile
|
|
120
|
-
: path.extname(argv.dirPath) !== '';
|
|
153
|
+
const isFile = isFilePath(argv, argv.dirPath);
|
|
121
154
|
|
|
122
155
|
if (isFile) {
|
|
123
156
|
let parentDirectory = path.dirname(argv.dirPath);
|
|
124
157
|
parentDirectory = parentDirectory === '.' ? '/' : parentDirectory;
|
|
125
158
|
|
|
126
|
-
await download(env, user, parentDirectory, argv.outPath, false, null, true, argv.
|
|
159
|
+
await download(env, user, parentDirectory, argv.outPath, false, null, true, argv.dangerouslyIncludeLogsAndCache, [argv.dirPath], true, output);
|
|
127
160
|
} else {
|
|
128
|
-
await download(env, user, argv.dirPath, argv.outPath, true, null, argv.raw, argv.
|
|
161
|
+
await download(env, user, argv.dirPath, argv.outPath, true, null, argv.raw, argv.dangerouslyIncludeLogsAndCache, [], false, output);
|
|
129
162
|
}
|
|
130
163
|
} else {
|
|
131
|
-
|
|
164
|
+
const fullExport = async () => {
|
|
132
165
|
output.log('Full export is starting');
|
|
133
166
|
let filesStructure = (await getFilesStructure(env, user, '/', false, true)).model;
|
|
134
167
|
let dirs = filesStructure.directories;
|
|
135
168
|
for (let id = 0; id < dirs.length; id++) {
|
|
136
169
|
const dir = dirs[id];
|
|
137
|
-
await download(env, user, dir.name, argv.outPath, true, null, argv.raw, argv.
|
|
170
|
+
await download(env, user, dir.name, argv.outPath, true, null, argv.raw, argv.dangerouslyIncludeLogsAndCache, [], false, output);
|
|
138
171
|
}
|
|
139
|
-
await download(env, user, '/.', argv.outPath, false, 'Base.zip', argv.raw, argv.
|
|
172
|
+
await download(env, user, '/.', argv.outPath, false, 'Base.zip', argv.raw, argv.dangerouslyIncludeLogsAndCache, Array.from(filesStructure.files.data, f => f.name), false, output);
|
|
140
173
|
if (argv.raw) output.log('The files in the base "files" folder is in Base.zip, each directory in "files" is in its own zip');
|
|
141
|
-
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
if (output.json) {
|
|
177
|
+
await fullExport();
|
|
178
|
+
} else {
|
|
179
|
+
await interactiveConfirm('Are you sure you want a full export of files?', fullExport);
|
|
180
|
+
}
|
|
142
181
|
}
|
|
143
182
|
} else if (argv.import) {
|
|
144
183
|
if (argv.dirPath && argv.outPath) {
|
|
@@ -150,6 +189,44 @@ async function handleFiles(argv, output) {
|
|
|
150
189
|
await uploadFiles(env, user, filesInDir, argv.outPath, argv.createEmpty, argv.overwrite, output);
|
|
151
190
|
}
|
|
152
191
|
}
|
|
192
|
+
} else if (argv.delete) {
|
|
193
|
+
if (!argv.dirPath) {
|
|
194
|
+
throw createCommandError('A path is required for delete operations.', 400);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const isFile = isFilePath(argv, argv.dirPath);
|
|
198
|
+
|
|
199
|
+
if (argv.empty && isFile) {
|
|
200
|
+
throw createCommandError('--empty can only be used with directories.', 400);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const shouldConfirm = !output.json;
|
|
204
|
+
|
|
205
|
+
if (shouldConfirm) {
|
|
206
|
+
const action = argv.empty
|
|
207
|
+
? `empty directory "${argv.dirPath}"`
|
|
208
|
+
: isFile
|
|
209
|
+
? `delete file "${argv.dirPath}"`
|
|
210
|
+
: `delete directory "${argv.dirPath}"`;
|
|
211
|
+
|
|
212
|
+
await interactiveConfirm(`Are you sure you want to ${action}?`, async () => {
|
|
213
|
+
await deleteRemote(env, user, argv.dirPath, isFile, argv.empty, output);
|
|
214
|
+
});
|
|
215
|
+
} else {
|
|
216
|
+
await deleteRemote(env, user, argv.dirPath, isFile, argv.empty, output);
|
|
217
|
+
}
|
|
218
|
+
} else if (argv.copy) {
|
|
219
|
+
if (!argv.dirPath) {
|
|
220
|
+
throw createCommandError('A source path [dirPath] is required for copy operations.', 400);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
await copyRemote(env, user, argv.dirPath, argv.copy, output);
|
|
224
|
+
} else if (argv.move) {
|
|
225
|
+
if (!argv.dirPath) {
|
|
226
|
+
throw createCommandError('A source path [dirPath] is required for move operations.', 400);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
await moveRemote(env, user, argv.dirPath, argv.move, argv.overwrite, output);
|
|
153
230
|
}
|
|
154
231
|
}
|
|
155
232
|
|
|
@@ -206,9 +283,9 @@ function resolveTree(dirs, indentLevel, parentHasFiles, output) {
|
|
|
206
283
|
}
|
|
207
284
|
}
|
|
208
285
|
|
|
209
|
-
async function download(env, user, dirPath, outPath, recursive, outname, raw,
|
|
286
|
+
async function download(env, user, dirPath, outPath, recursive, outname, raw, dangerouslyIncludeLogsAndCache, fileNames, singleFileMode, output) {
|
|
210
287
|
let excludeDirectories = '';
|
|
211
|
-
if (!
|
|
288
|
+
if (!dangerouslyIncludeLogsAndCache) {
|
|
212
289
|
excludeDirectories = 'system/log';
|
|
213
290
|
if (dirPath === 'cache.net') {
|
|
214
291
|
return;
|
|
@@ -320,7 +397,11 @@ async function extractArchive(filename, filePath, outPath, raw, output) {
|
|
|
320
397
|
}
|
|
321
398
|
output.log(`Finished extracting ${filename} to ${outPath}\n`);
|
|
322
399
|
|
|
323
|
-
fs.unlink(filePath, function(err) {
|
|
400
|
+
fs.unlink(filePath, function(err) {
|
|
401
|
+
if (err) {
|
|
402
|
+
output.verboseLog(`Warning: Failed to delete temporary archive ${filePath}: ${err.message}`);
|
|
403
|
+
}
|
|
404
|
+
});
|
|
324
405
|
}
|
|
325
406
|
|
|
326
407
|
async function getFilesStructure(env, user, dirPath, recursive, includeFiles) {
|
|
@@ -338,7 +419,127 @@ async function getFilesStructure(env, user, dirPath, recursive, includeFiles) {
|
|
|
338
419
|
}
|
|
339
420
|
}
|
|
340
421
|
|
|
341
|
-
|
|
422
|
+
async function deleteRemote(env, user, remotePath, isFile, empty, output) {
|
|
423
|
+
let endpoint;
|
|
424
|
+
let mode;
|
|
425
|
+
let data;
|
|
426
|
+
|
|
427
|
+
if (isFile) {
|
|
428
|
+
endpoint = 'FileDelete';
|
|
429
|
+
mode = 'file';
|
|
430
|
+
const parentDir = path.posix.dirname(remotePath);
|
|
431
|
+
data = {
|
|
432
|
+
DirectoryPath: parentDir === '.' ? '/' : parentDir,
|
|
433
|
+
Ids: [remotePath]
|
|
434
|
+
};
|
|
435
|
+
} else if (empty) {
|
|
436
|
+
endpoint = 'DirectoryEmpty';
|
|
437
|
+
mode = 'empty';
|
|
438
|
+
data = { Path: remotePath };
|
|
439
|
+
} else {
|
|
440
|
+
endpoint = 'DirectoryDelete';
|
|
441
|
+
mode = 'directory';
|
|
442
|
+
data = { Path: remotePath };
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
output.log(`${mode === 'empty' ? 'Emptying' : 'Deleting'} ${mode === 'file' ? 'file' : 'directory'}: ${remotePath}`);
|
|
446
|
+
|
|
447
|
+
const res = await fetch(`${env.protocol}://${env.host}/Admin/Api/${endpoint}`, {
|
|
448
|
+
method: 'POST',
|
|
449
|
+
body: JSON.stringify(data),
|
|
450
|
+
headers: {
|
|
451
|
+
'Authorization': `Bearer ${user.apiKey}`,
|
|
452
|
+
'Content-Type': 'application/json'
|
|
453
|
+
},
|
|
454
|
+
agent: getAgent(env.protocol)
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
if (!res.ok) {
|
|
458
|
+
throw createCommandError(`Failed to ${mode === 'empty' ? 'empty' : 'delete'} "${remotePath}".`, res.status, await parseJsonSafe(res));
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const body = await parseJsonSafe(res);
|
|
462
|
+
|
|
463
|
+
output.setStatus(200);
|
|
464
|
+
output.addData({
|
|
465
|
+
type: 'delete',
|
|
466
|
+
path: remotePath,
|
|
467
|
+
mode,
|
|
468
|
+
response: body
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
output.log(`Successfully ${mode === 'empty' ? 'emptied' : 'deleted'}: ${remotePath}`);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
async function copyRemote(env, user, sourcePath, destination, output) {
|
|
475
|
+
output.log(`Copying ${sourcePath} to ${destination}`);
|
|
476
|
+
|
|
477
|
+
const res = await fetch(`${env.protocol}://${env.host}/Admin/Api/AssetCopy`, {
|
|
478
|
+
method: 'POST',
|
|
479
|
+
body: JSON.stringify({
|
|
480
|
+
Destination: destination,
|
|
481
|
+
Ids: [sourcePath]
|
|
482
|
+
}),
|
|
483
|
+
headers: {
|
|
484
|
+
'Authorization': `Bearer ${user.apiKey}`,
|
|
485
|
+
'Content-Type': 'application/json'
|
|
486
|
+
},
|
|
487
|
+
agent: getAgent(env.protocol)
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
if (!res.ok) {
|
|
491
|
+
throw createCommandError(`Failed to copy "${sourcePath}" to "${destination}".`, res.status, await parseJsonSafe(res));
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const body = await parseJsonSafe(res);
|
|
495
|
+
|
|
496
|
+
output.setStatus(200);
|
|
497
|
+
output.addData({
|
|
498
|
+
type: 'copy',
|
|
499
|
+
sourcePath,
|
|
500
|
+
destination,
|
|
501
|
+
response: body
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
output.log(`Successfully copied ${sourcePath} to ${destination}`);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
async function moveRemote(env, user, sourcePath, destination, overwrite, output) {
|
|
508
|
+
output.log(`Moving ${sourcePath} to ${destination}`);
|
|
509
|
+
|
|
510
|
+
const res = await fetch(`${env.protocol}://${env.host}/Admin/Api/AssetMove`, {
|
|
511
|
+
method: 'POST',
|
|
512
|
+
body: JSON.stringify({
|
|
513
|
+
Destination: destination,
|
|
514
|
+
Overwrite: Boolean(overwrite),
|
|
515
|
+
Ids: [sourcePath]
|
|
516
|
+
}),
|
|
517
|
+
headers: {
|
|
518
|
+
'Authorization': `Bearer ${user.apiKey}`,
|
|
519
|
+
'Content-Type': 'application/json'
|
|
520
|
+
},
|
|
521
|
+
agent: getAgent(env.protocol)
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
if (!res.ok) {
|
|
525
|
+
throw createCommandError(`Failed to move "${sourcePath}" to "${destination}".`, res.status, await parseJsonSafe(res));
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const body = await parseJsonSafe(res);
|
|
529
|
+
|
|
530
|
+
output.setStatus(200);
|
|
531
|
+
output.addData({
|
|
532
|
+
type: 'move',
|
|
533
|
+
sourcePath,
|
|
534
|
+
destination,
|
|
535
|
+
overwrite: Boolean(overwrite),
|
|
536
|
+
response: body
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
output.log(`Successfully moved ${sourcePath} to ${destination}`);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
export async function uploadFiles(env, user, localFilePaths, destinationPath, createEmpty = false, overwrite = false, output = createFilesOutput({})) {
|
|
342
543
|
output.log('Uploading files')
|
|
343
544
|
|
|
344
545
|
const chunkSize = 300;
|
|
@@ -348,10 +549,10 @@ export async function uploadFiles(env, user, localFilePaths, destinationPath, cr
|
|
|
348
549
|
chunks.push(localFilePaths.slice(i, i + chunkSize));
|
|
349
550
|
}
|
|
350
551
|
|
|
351
|
-
output.mergeMeta({
|
|
352
|
-
filesProcessed: (
|
|
353
|
-
chunks: (
|
|
354
|
-
});
|
|
552
|
+
output.mergeMeta((meta) => ({
|
|
553
|
+
filesProcessed: (meta.filesProcessed || 0) + localFilePaths.length,
|
|
554
|
+
chunks: (meta.chunks || 0) + chunks.length
|
|
555
|
+
}));
|
|
355
556
|
|
|
356
557
|
for (let i = 0; i < chunks.length; i++) {
|
|
357
558
|
output.log(`Uploading chunk ${i + 1} of ${chunks.length}`);
|
|
@@ -405,13 +606,19 @@ export function resolveFilePath(filePath) {
|
|
|
405
606
|
let resolvedPath = fs.readdirSync(p.dir).filter((allFilesPaths) => allFilesPaths.match(regex) !== null)[0]
|
|
406
607
|
if (resolvedPath === undefined)
|
|
407
608
|
{
|
|
408
|
-
|
|
409
|
-
process.exit(1);
|
|
609
|
+
throw createCommandError(`Could not find any files with the name ${filePath}`, 1);
|
|
410
610
|
}
|
|
411
611
|
return path.join(p.dir, resolvedPath);
|
|
412
612
|
}
|
|
413
613
|
|
|
414
614
|
|
|
615
|
+
function isFilePath(argv, dirPath) {
|
|
616
|
+
if (argv.asFile || argv.asDirectory) {
|
|
617
|
+
return argv.asFile;
|
|
618
|
+
}
|
|
619
|
+
return path.extname(dirPath) !== '';
|
|
620
|
+
}
|
|
621
|
+
|
|
415
622
|
function wildcardToRegExp(wildcard) {
|
|
416
623
|
const escaped = wildcard.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
|
|
417
624
|
return new RegExp('^' + escaped.replace(/\*/g, '.*') + '$');
|
|
@@ -429,7 +636,7 @@ function createFilesOutput(argv) {
|
|
|
429
636
|
};
|
|
430
637
|
|
|
431
638
|
return {
|
|
432
|
-
json: Boolean(argv.json),
|
|
639
|
+
json: argv.output === 'json' || Boolean(argv.json),
|
|
433
640
|
verbose: Boolean(argv.verbose),
|
|
434
641
|
response,
|
|
435
642
|
log(...args) {
|
|
@@ -445,7 +652,8 @@ function createFilesOutput(argv) {
|
|
|
445
652
|
addData(entry) {
|
|
446
653
|
response.data.push(entry);
|
|
447
654
|
},
|
|
448
|
-
mergeMeta(
|
|
655
|
+
mergeMeta(metaOrFn) {
|
|
656
|
+
const meta = typeof metaOrFn === 'function' ? metaOrFn(response.meta) : metaOrFn;
|
|
449
657
|
response.meta = {
|
|
450
658
|
...response.meta,
|
|
451
659
|
...meta
|
|
@@ -483,15 +691,21 @@ function getFilesOperation(argv) {
|
|
|
483
691
|
return 'import';
|
|
484
692
|
}
|
|
485
693
|
|
|
694
|
+
if (argv.delete) {
|
|
695
|
+
return 'delete';
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if (argv.copy) {
|
|
699
|
+
return 'copy';
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (argv.move) {
|
|
703
|
+
return 'move';
|
|
704
|
+
}
|
|
705
|
+
|
|
486
706
|
return 'unknown';
|
|
487
707
|
}
|
|
488
708
|
|
|
489
|
-
function createCommandError(message, status, details = null) {
|
|
490
|
-
const error = new Error(message);
|
|
491
|
-
error.status = status;
|
|
492
|
-
error.details = details;
|
|
493
|
-
return error;
|
|
494
|
-
}
|
|
495
709
|
|
|
496
710
|
async function parseJsonSafe(res) {
|
|
497
711
|
try {
|
package/bin/commands/install.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import fetch from 'node-fetch';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { setupEnv, getAgent } from './env.js';
|
|
3
|
+
import { setupEnv, getAgent, createCommandError } from './env.js';
|
|
4
4
|
import { setupUser } from './login.js';
|
|
5
5
|
import { uploadFiles, resolveFilePath } from './files.js';
|
|
6
6
|
|
|
7
7
|
export function installCommand() {
|
|
8
8
|
return {
|
|
9
|
-
command: 'install [filePath]',
|
|
10
|
-
describe: 'Installs the addin on the given path, allowed file extensions are .dll, .nupkg',
|
|
9
|
+
command: 'install [filePath]',
|
|
10
|
+
describe: 'Installs the addin on the given path, allowed file extensions are .dll, .nupkg',
|
|
11
11
|
builder: (yargs) => {
|
|
12
12
|
return yargs
|
|
13
13
|
.positional('filePath', {
|
|
@@ -18,24 +18,37 @@ export function installCommand() {
|
|
|
18
18
|
type: 'boolean',
|
|
19
19
|
describe: 'Queues the install for next Dynamicweb recycle'
|
|
20
20
|
})
|
|
21
|
+
.option('output', {
|
|
22
|
+
choices: ['json'],
|
|
23
|
+
describe: 'Outputs a single JSON response for automation-friendly parsing'
|
|
24
|
+
})
|
|
21
25
|
},
|
|
22
26
|
handler: async (argv) => {
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
const output = createInstallOutput(argv);
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
output.verboseLog(`Installing file located at: ${argv.filePath}`);
|
|
31
|
+
await handleInstall(argv, output);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
output.fail(err);
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
} finally {
|
|
36
|
+
output.finish();
|
|
37
|
+
}
|
|
25
38
|
}
|
|
26
39
|
}
|
|
27
40
|
}
|
|
28
41
|
|
|
29
|
-
async function handleInstall(argv) {
|
|
30
|
-
let env = await setupEnv(argv);
|
|
42
|
+
async function handleInstall(argv, output) {
|
|
43
|
+
let env = await setupEnv(argv, output);
|
|
31
44
|
let user = await setupUser(argv, env);
|
|
32
45
|
let resolvedPath = resolveFilePath(argv.filePath);
|
|
33
|
-
await uploadFiles(env, user, [ resolvedPath ], 'System/AddIns/Local', false, true);
|
|
34
|
-
await installAddin(env, user, resolvedPath, argv.queue)
|
|
46
|
+
await uploadFiles(env, user, [ resolvedPath ], 'System/AddIns/Local', false, true, output);
|
|
47
|
+
await installAddin(env, user, resolvedPath, argv.queue, output);
|
|
35
48
|
}
|
|
36
49
|
|
|
37
|
-
async function installAddin(env, user, resolvedPath, queue) {
|
|
38
|
-
|
|
50
|
+
async function installAddin(env, user, resolvedPath, queue, output) {
|
|
51
|
+
output.log('Installing addin');
|
|
39
52
|
let filename = path.basename(resolvedPath);
|
|
40
53
|
let data = {
|
|
41
54
|
'Queue': queue,
|
|
@@ -54,12 +67,79 @@ async function installAddin(env, user, resolvedPath, queue) {
|
|
|
54
67
|
});
|
|
55
68
|
|
|
56
69
|
if (res.ok) {
|
|
57
|
-
|
|
58
|
-
|
|
70
|
+
const body = await parseJsonSafe(res);
|
|
71
|
+
output.verboseLog(body);
|
|
72
|
+
output.addData({
|
|
73
|
+
type: 'install',
|
|
74
|
+
filePath: resolvedPath,
|
|
75
|
+
filename,
|
|
76
|
+
queued: Boolean(queue),
|
|
77
|
+
response: body
|
|
78
|
+
});
|
|
79
|
+
output.log('Addin installed');
|
|
80
|
+
} else {
|
|
81
|
+
const body = await parseJsonSafe(res);
|
|
82
|
+
throw createCommandError('Addin installation failed.', res.status, body);
|
|
59
83
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function createInstallOutput(argv) {
|
|
87
|
+
const response = {
|
|
88
|
+
ok: true,
|
|
89
|
+
command: 'install',
|
|
90
|
+
operation: argv.queue ? 'queue' : 'install',
|
|
91
|
+
status: 200,
|
|
92
|
+
data: [],
|
|
93
|
+
errors: [],
|
|
94
|
+
meta: {
|
|
95
|
+
filePath: argv.filePath
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
json: argv.output === 'json',
|
|
101
|
+
response,
|
|
102
|
+
log(...args) {
|
|
103
|
+
if (!this.json) {
|
|
104
|
+
console.log(...args);
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
verboseLog(...args) {
|
|
108
|
+
if (argv.verbose && !this.json) {
|
|
109
|
+
console.info(...args);
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
addData(entry) {
|
|
113
|
+
response.data.push(entry);
|
|
114
|
+
},
|
|
115
|
+
mergeMeta(metaOrFn) {
|
|
116
|
+
const meta = typeof metaOrFn === 'function' ? metaOrFn(response.meta) : metaOrFn;
|
|
117
|
+
response.meta = {
|
|
118
|
+
...response.meta,
|
|
119
|
+
...meta
|
|
120
|
+
};
|
|
121
|
+
},
|
|
122
|
+
fail(err) {
|
|
123
|
+
response.ok = false;
|
|
124
|
+
response.status = err?.status || 1;
|
|
125
|
+
response.errors.push({
|
|
126
|
+
message: err?.message || 'Unknown install command error.',
|
|
127
|
+
details: err?.details ?? null
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
finish() {
|
|
131
|
+
if (this.json) {
|
|
132
|
+
console.log(JSON.stringify(response, null, 2));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
async function parseJsonSafe(res) {
|
|
140
|
+
try {
|
|
141
|
+
return await res.json();
|
|
142
|
+
} catch {
|
|
143
|
+
return null;
|
|
64
144
|
}
|
|
65
|
-
}
|
|
145
|
+
}
|