@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/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,64 +97,136 @@ 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
|
+
})
|
|
104
|
+
.option('json', {
|
|
105
|
+
type: 'boolean',
|
|
106
|
+
hidden: true,
|
|
107
|
+
describe: 'Deprecated alias for --output json'
|
|
108
|
+
})
|
|
78
109
|
},
|
|
79
110
|
handler: async (argv) => {
|
|
80
|
-
if (argv.
|
|
81
|
-
|
|
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
|
+
}
|
|
119
|
+
const output = createFilesOutput(argv);
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
await handleFiles(argv, output);
|
|
123
|
+
} catch (err) {
|
|
124
|
+
output.fail(err);
|
|
125
|
+
process.exitCode = 1;
|
|
126
|
+
} finally {
|
|
127
|
+
output.finish();
|
|
128
|
+
}
|
|
82
129
|
}
|
|
83
130
|
}
|
|
84
131
|
}
|
|
85
132
|
|
|
86
|
-
async function handleFiles(argv) {
|
|
87
|
-
let env = await setupEnv(argv);
|
|
133
|
+
async function handleFiles(argv, output) {
|
|
134
|
+
let env = await setupEnv(argv, output);
|
|
88
135
|
let user = await setupUser(argv, env);
|
|
89
136
|
|
|
90
137
|
if (argv.list) {
|
|
138
|
+
output.verboseLog(`Listing directory at: ${argv.dirPath}`);
|
|
91
139
|
let files = (await getFilesStructure(env, user, argv.dirPath, argv.recursive, argv.includeFiles)).model;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
140
|
+
output.setStatus(200);
|
|
141
|
+
output.addData(files);
|
|
142
|
+
if (!output.json) {
|
|
143
|
+
output.log(files.name);
|
|
144
|
+
let hasFiles = files.files?.data && files.files?.data.length !== 0;
|
|
145
|
+
resolveTree(files.directories, '', hasFiles, output);
|
|
146
|
+
resolveTree(files.files?.data ?? [], '', false, output);
|
|
147
|
+
}
|
|
96
148
|
}
|
|
97
149
|
|
|
98
150
|
if (argv.export) {
|
|
99
151
|
if (argv.dirPath) {
|
|
100
152
|
|
|
101
|
-
const isFile = argv
|
|
102
|
-
? argv.asFile
|
|
103
|
-
: path.extname(argv.dirPath) !== '';
|
|
153
|
+
const isFile = isFilePath(argv, argv.dirPath);
|
|
104
154
|
|
|
105
155
|
if (isFile) {
|
|
106
156
|
let parentDirectory = path.dirname(argv.dirPath);
|
|
107
157
|
parentDirectory = parentDirectory === '.' ? '/' : parentDirectory;
|
|
108
158
|
|
|
109
|
-
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);
|
|
110
160
|
} else {
|
|
111
|
-
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);
|
|
112
162
|
}
|
|
113
163
|
} else {
|
|
114
|
-
|
|
115
|
-
|
|
164
|
+
const fullExport = async () => {
|
|
165
|
+
output.log('Full export is starting');
|
|
116
166
|
let filesStructure = (await getFilesStructure(env, user, '/', false, true)).model;
|
|
117
167
|
let dirs = filesStructure.directories;
|
|
118
168
|
for (let id = 0; id < dirs.length; id++) {
|
|
119
169
|
const dir = dirs[id];
|
|
120
|
-
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);
|
|
121
171
|
}
|
|
122
|
-
await download(env, user, '/.', argv.outPath, false, 'Base.zip', argv.raw, argv.
|
|
123
|
-
if (argv.raw)
|
|
124
|
-
}
|
|
172
|
+
await download(env, user, '/.', argv.outPath, false, 'Base.zip', argv.raw, argv.dangerouslyIncludeLogsAndCache, Array.from(filesStructure.files.data, f => f.name), false, output);
|
|
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');
|
|
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
|
+
}
|
|
125
181
|
}
|
|
126
182
|
} else if (argv.import) {
|
|
127
183
|
if (argv.dirPath && argv.outPath) {
|
|
128
184
|
let resolvedPath = path.resolve(argv.dirPath);
|
|
129
185
|
if (argv.recursive) {
|
|
130
|
-
await processDirectory(env, user, resolvedPath, argv.outPath, resolvedPath, argv.createEmpty, true, argv.overwrite);
|
|
186
|
+
await processDirectory(env, user, resolvedPath, argv.outPath, resolvedPath, argv.createEmpty, true, argv.overwrite, output);
|
|
131
187
|
} else {
|
|
132
188
|
let filesInDir = getFilesInDirectory(resolvedPath);
|
|
133
|
-
await uploadFiles(env, user, filesInDir, argv.outPath, argv.createEmpty, argv.overwrite);
|
|
189
|
+
await uploadFiles(env, user, filesInDir, argv.outPath, argv.createEmpty, argv.overwrite, output);
|
|
134
190
|
}
|
|
135
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);
|
|
136
230
|
}
|
|
137
231
|
}
|
|
138
232
|
|
|
@@ -142,20 +236,20 @@ function getFilesInDirectory(dirPath) {
|
|
|
142
236
|
.filter(file => fs.statSync(file).isFile());
|
|
143
237
|
}
|
|
144
238
|
|
|
145
|
-
async function processDirectory(env, user, dirPath, outPath, originalDir, createEmpty, isRoot = false, overwrite = false) {
|
|
239
|
+
async function processDirectory(env, user, dirPath, outPath, originalDir, createEmpty, isRoot = false, overwrite = false, output) {
|
|
146
240
|
let filesInDir = getFilesInDirectory(dirPath);
|
|
147
241
|
if (filesInDir.length > 0)
|
|
148
|
-
await uploadFiles(env, user, filesInDir, isRoot ? outPath : path.join(outPath, path.basename(dirPath)), createEmpty, overwrite);
|
|
242
|
+
await uploadFiles(env, user, filesInDir, isRoot ? outPath : path.join(outPath, path.basename(dirPath)), createEmpty, overwrite, output);
|
|
149
243
|
|
|
150
244
|
const subDirectories = fs.readdirSync(dirPath)
|
|
151
245
|
.map(subDir => path.join(dirPath, subDir))
|
|
152
246
|
.filter(subDir => fs.statSync(subDir).isDirectory());
|
|
153
247
|
for (let subDir of subDirectories) {
|
|
154
|
-
await processDirectory(env, user, subDir, isRoot ? outPath : path.join(outPath, path.basename(dirPath)), originalDir, createEmpty, false, overwrite);
|
|
248
|
+
await processDirectory(env, user, subDir, isRoot ? outPath : path.join(outPath, path.basename(dirPath)), originalDir, createEmpty, false, overwrite, output);
|
|
155
249
|
}
|
|
156
250
|
}
|
|
157
251
|
|
|
158
|
-
function resolveTree(dirs, indentLevel, parentHasFiles) {
|
|
252
|
+
function resolveTree(dirs, indentLevel, parentHasFiles, output) {
|
|
159
253
|
let end = `└──`
|
|
160
254
|
let mid = `├──`
|
|
161
255
|
for (let id = 0; id < dirs.length; id++) {
|
|
@@ -163,35 +257,35 @@ function resolveTree(dirs, indentLevel, parentHasFiles) {
|
|
|
163
257
|
let indentPipe = true;
|
|
164
258
|
if (dirs.length == 1) {
|
|
165
259
|
if (parentHasFiles) {
|
|
166
|
-
|
|
260
|
+
output.log(indentLevel + mid, dir.name)
|
|
167
261
|
} else {
|
|
168
|
-
|
|
262
|
+
output.log(indentLevel + end, dir.name)
|
|
169
263
|
indentPipe = false;
|
|
170
264
|
}
|
|
171
265
|
} else if (id != dirs.length - 1) {
|
|
172
|
-
|
|
266
|
+
output.log(indentLevel + mid, dir.name)
|
|
173
267
|
} else {
|
|
174
268
|
if (parentHasFiles) {
|
|
175
|
-
|
|
269
|
+
output.log(indentLevel + mid, dir.name)
|
|
176
270
|
} else {
|
|
177
|
-
|
|
271
|
+
output.log(indentLevel + end, dir.name)
|
|
178
272
|
indentPipe = false;
|
|
179
273
|
}
|
|
180
274
|
}
|
|
181
275
|
let hasFiles = dir.files?.data && dir.files?.data.length !== 0;
|
|
182
276
|
if (indentPipe) {
|
|
183
|
-
resolveTree(dir.directories ?? [], indentLevel + '│\t', hasFiles);
|
|
184
|
-
resolveTree(dir.files?.data ?? [], indentLevel + '│\t', false);
|
|
277
|
+
resolveTree(dir.directories ?? [], indentLevel + '│\t', hasFiles, output);
|
|
278
|
+
resolveTree(dir.files?.data ?? [], indentLevel + '│\t', false, output);
|
|
185
279
|
} else {
|
|
186
|
-
resolveTree(dir.directories ?? [], indentLevel + '\t', hasFiles);
|
|
187
|
-
resolveTree(dir.files?.data ?? [], indentLevel + '\t', false);
|
|
280
|
+
resolveTree(dir.directories ?? [], indentLevel + '\t', hasFiles, output);
|
|
281
|
+
resolveTree(dir.files?.data ?? [], indentLevel + '\t', false, output);
|
|
188
282
|
}
|
|
189
283
|
}
|
|
190
284
|
}
|
|
191
285
|
|
|
192
|
-
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) {
|
|
193
287
|
let excludeDirectories = '';
|
|
194
|
-
if (!
|
|
288
|
+
if (!dangerouslyIncludeLogsAndCache) {
|
|
195
289
|
excludeDirectories = 'system/log';
|
|
196
290
|
if (dirPath === 'cache.net') {
|
|
197
291
|
return;
|
|
@@ -200,7 +294,7 @@ async function download(env, user, dirPath, outPath, recursive, outname, raw, ia
|
|
|
200
294
|
|
|
201
295
|
const { endpoint, data } = prepareDownloadCommandData(dirPath, excludeDirectories, fileNames, recursive, singleFileMode);
|
|
202
296
|
|
|
203
|
-
displayDownloadMessage(dirPath, fileNames, recursive, singleFileMode);
|
|
297
|
+
displayDownloadMessage(dirPath, fileNames, recursive, singleFileMode, output);
|
|
204
298
|
|
|
205
299
|
const res = await fetch(`${env.protocol}://${env.host}/Admin/Api/${endpoint}`, {
|
|
206
300
|
method: 'POST',
|
|
@@ -212,27 +306,40 @@ async function download(env, user, dirPath, outPath, recursive, outname, raw, ia
|
|
|
212
306
|
agent: getAgent(env.protocol)
|
|
213
307
|
});
|
|
214
308
|
|
|
215
|
-
const filename = outname || tryGetFileNameFromResponse(res, dirPath);
|
|
309
|
+
const filename = outname || tryGetFileNameFromResponse(res, dirPath, output.verbose);
|
|
216
310
|
if (!filename) return;
|
|
217
311
|
|
|
218
312
|
const filePath = path.resolve(`${path.resolve(outPath)}/${filename}`)
|
|
219
|
-
const updater = createThrottledStatusUpdater();
|
|
313
|
+
const updater = output.json ? null : createThrottledStatusUpdater();
|
|
220
314
|
|
|
221
315
|
await downloadWithProgress(res, filePath, {
|
|
222
316
|
onData: (received) => {
|
|
223
|
-
updater
|
|
317
|
+
if (updater) {
|
|
318
|
+
updater.update(`Received:\t${formatBytes(received)}`);
|
|
319
|
+
}
|
|
224
320
|
}
|
|
225
321
|
});
|
|
226
322
|
|
|
227
|
-
updater
|
|
323
|
+
if (updater) {
|
|
324
|
+
updater.stop();
|
|
325
|
+
}
|
|
228
326
|
|
|
229
327
|
if (singleFileMode) {
|
|
230
|
-
|
|
328
|
+
output.log(`Successfully downloaded: ${filename}`);
|
|
231
329
|
} else {
|
|
232
|
-
|
|
330
|
+
output.log(`Finished downloading`, dirPath === '/.' ? '.' : dirPath, 'Recursive=' + recursive);
|
|
233
331
|
}
|
|
234
332
|
|
|
235
|
-
|
|
333
|
+
output.addData({
|
|
334
|
+
type: 'download',
|
|
335
|
+
directoryPath: dirPath,
|
|
336
|
+
filename,
|
|
337
|
+
outPath: path.resolve(outPath),
|
|
338
|
+
recursive,
|
|
339
|
+
raw
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
await extractArchive(filename, filePath, outPath, raw, output);
|
|
236
343
|
}
|
|
237
344
|
|
|
238
345
|
function prepareDownloadCommandData(directoryPath, excludeDirectories, fileNames, recursive, singleFileMode) {
|
|
@@ -249,10 +356,10 @@ function prepareDownloadCommandData(directoryPath, excludeDirectories, fileNames
|
|
|
249
356
|
return { endpoint: 'FileDownload', data };
|
|
250
357
|
}
|
|
251
358
|
|
|
252
|
-
function displayDownloadMessage(directoryPath, fileNames, recursive, singleFileMode) {
|
|
359
|
+
function displayDownloadMessage(directoryPath, fileNames, recursive, singleFileMode, output) {
|
|
253
360
|
if (singleFileMode) {
|
|
254
361
|
const fileName = path.basename(fileNames[0] || 'unknown');
|
|
255
|
-
|
|
362
|
+
output.log('Downloading file: ' + fileName);
|
|
256
363
|
|
|
257
364
|
return;
|
|
258
365
|
}
|
|
@@ -261,32 +368,40 @@ function displayDownloadMessage(directoryPath, fileNames, recursive, singleFileM
|
|
|
261
368
|
? 'Base'
|
|
262
369
|
: directoryPath;
|
|
263
370
|
|
|
264
|
-
|
|
371
|
+
output.log('Downloading', directoryPathDisplayName, 'Recursive=' + recursive);
|
|
265
372
|
}
|
|
266
373
|
|
|
267
|
-
async function extractArchive(filename, filePath, outPath, raw) {
|
|
374
|
+
async function extractArchive(filename, filePath, outPath, raw, output) {
|
|
268
375
|
if (raw) {
|
|
269
376
|
return;
|
|
270
377
|
}
|
|
271
378
|
|
|
272
|
-
|
|
379
|
+
output.log(`\nExtracting ${filename} to ${outPath}`);
|
|
273
380
|
let destinationFilename = filename.replace('.zip', '');
|
|
274
381
|
if (destinationFilename === 'Base')
|
|
275
382
|
destinationFilename = '';
|
|
276
383
|
|
|
277
384
|
const destinationPath = `${path.resolve(outPath)}/${destinationFilename}`;
|
|
278
|
-
const updater = createThrottledStatusUpdater();
|
|
385
|
+
const updater = output.json ? null : createThrottledStatusUpdater();
|
|
279
386
|
|
|
280
387
|
await extractWithProgress(filePath, destinationPath, {
|
|
281
388
|
onEntry: (processedEntries, totalEntries, percent) => {
|
|
282
|
-
updater
|
|
389
|
+
if (updater) {
|
|
390
|
+
updater.update(`Extracted:\t${processedEntries} of ${totalEntries} files (${percent}%)`);
|
|
391
|
+
}
|
|
283
392
|
}
|
|
284
393
|
});
|
|
285
394
|
|
|
286
|
-
updater
|
|
287
|
-
|
|
395
|
+
if (updater) {
|
|
396
|
+
updater.stop();
|
|
397
|
+
}
|
|
398
|
+
output.log(`Finished extracting ${filename} to ${outPath}\n`);
|
|
288
399
|
|
|
289
|
-
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
|
+
});
|
|
290
405
|
}
|
|
291
406
|
|
|
292
407
|
async function getFilesStructure(env, user, dirPath, recursive, includeFiles) {
|
|
@@ -300,14 +415,132 @@ async function getFilesStructure(env, user, dirPath, recursive, includeFiles) {
|
|
|
300
415
|
if (res.ok) {
|
|
301
416
|
return await res.json();
|
|
302
417
|
} else {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
418
|
+
throw createCommandError('Unable to fetch file structure.', res.status, await parseJsonSafe(res));
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
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));
|
|
306
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}`);
|
|
307
472
|
}
|
|
308
473
|
|
|
309
|
-
|
|
310
|
-
|
|
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({})) {
|
|
543
|
+
output.log('Uploading files')
|
|
311
544
|
|
|
312
545
|
const chunkSize = 300;
|
|
313
546
|
const chunks = [];
|
|
@@ -316,26 +549,37 @@ export async function uploadFiles(env, user, localFilePaths, destinationPath, cr
|
|
|
316
549
|
chunks.push(localFilePaths.slice(i, i + chunkSize));
|
|
317
550
|
}
|
|
318
551
|
|
|
552
|
+
output.mergeMeta((meta) => ({
|
|
553
|
+
filesProcessed: (meta.filesProcessed || 0) + localFilePaths.length,
|
|
554
|
+
chunks: (meta.chunks || 0) + chunks.length
|
|
555
|
+
}));
|
|
556
|
+
|
|
319
557
|
for (let i = 0; i < chunks.length; i++) {
|
|
320
|
-
|
|
558
|
+
output.log(`Uploading chunk ${i + 1} of ${chunks.length}`);
|
|
321
559
|
|
|
322
560
|
const chunk = chunks[i];
|
|
323
|
-
await uploadChunk(env, user, chunk, destinationPath, createEmpty, overwrite);
|
|
324
|
-
|
|
325
|
-
|
|
561
|
+
const body = await uploadChunk(env, user, chunk, destinationPath, createEmpty, overwrite, output);
|
|
562
|
+
output.addData({
|
|
563
|
+
type: 'upload',
|
|
564
|
+
destinationPath,
|
|
565
|
+
files: chunk.map(filePath => path.resolve(filePath)),
|
|
566
|
+
response: body
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
output.log(`Finished uploading chunk ${i + 1} of ${chunks.length}`);
|
|
326
570
|
}
|
|
327
571
|
|
|
328
|
-
|
|
572
|
+
output.log(`Finished uploading files. Total files: ${localFilePaths.length}, total chunks: ${chunks.length}`);
|
|
329
573
|
}
|
|
330
574
|
|
|
331
|
-
async function uploadChunk(env, user, filePathsChunk, destinationPath, createEmpty, overwrite) {
|
|
575
|
+
async function uploadChunk(env, user, filePathsChunk, destinationPath, createEmpty, overwrite, output) {
|
|
332
576
|
const form = new FormData();
|
|
333
577
|
form.append('path', destinationPath);
|
|
334
578
|
form.append('skipExistingFiles', String(!overwrite));
|
|
335
579
|
form.append('allowOverwrite', String(overwrite));
|
|
336
580
|
|
|
337
581
|
filePathsChunk.forEach(fileToUpload => {
|
|
338
|
-
|
|
582
|
+
output.log(`${fileToUpload}`)
|
|
339
583
|
form.append('files', fs.createReadStream(path.resolve(fileToUpload)));
|
|
340
584
|
});
|
|
341
585
|
|
|
@@ -349,12 +593,10 @@ async function uploadChunk(env, user, filePathsChunk, destinationPath, createEmp
|
|
|
349
593
|
});
|
|
350
594
|
|
|
351
595
|
if (res.ok) {
|
|
352
|
-
|
|
596
|
+
return await res.json();
|
|
353
597
|
}
|
|
354
598
|
else {
|
|
355
|
-
|
|
356
|
-
console.log(await res.json())
|
|
357
|
-
process.exit(1);
|
|
599
|
+
throw createCommandError('File upload failed.', res.status, await parseJsonSafe(res));
|
|
358
600
|
}
|
|
359
601
|
}
|
|
360
602
|
|
|
@@ -364,15 +606,111 @@ export function resolveFilePath(filePath) {
|
|
|
364
606
|
let resolvedPath = fs.readdirSync(p.dir).filter((allFilesPaths) => allFilesPaths.match(regex) !== null)[0]
|
|
365
607
|
if (resolvedPath === undefined)
|
|
366
608
|
{
|
|
367
|
-
|
|
368
|
-
process.exit(1);
|
|
609
|
+
throw createCommandError(`Could not find any files with the name ${filePath}`, 1);
|
|
369
610
|
}
|
|
370
611
|
return path.join(p.dir, resolvedPath);
|
|
371
612
|
}
|
|
372
613
|
|
|
373
614
|
|
|
615
|
+
function isFilePath(argv, dirPath) {
|
|
616
|
+
if (argv.asFile || argv.asDirectory) {
|
|
617
|
+
return argv.asFile;
|
|
618
|
+
}
|
|
619
|
+
return path.extname(dirPath) !== '';
|
|
620
|
+
}
|
|
621
|
+
|
|
374
622
|
function wildcardToRegExp(wildcard) {
|
|
375
623
|
const escaped = wildcard.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
|
|
376
624
|
return new RegExp('^' + escaped.replace(/\*/g, '.*') + '$');
|
|
377
625
|
}
|
|
378
626
|
|
|
627
|
+
function createFilesOutput(argv) {
|
|
628
|
+
const response = {
|
|
629
|
+
ok: true,
|
|
630
|
+
command: 'files',
|
|
631
|
+
operation: getFilesOperation(argv),
|
|
632
|
+
status: 200,
|
|
633
|
+
data: [],
|
|
634
|
+
errors: [],
|
|
635
|
+
meta: {}
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
return {
|
|
639
|
+
json: argv.output === 'json' || Boolean(argv.json),
|
|
640
|
+
verbose: Boolean(argv.verbose),
|
|
641
|
+
response,
|
|
642
|
+
log(...args) {
|
|
643
|
+
if (!this.json) {
|
|
644
|
+
console.log(...args);
|
|
645
|
+
}
|
|
646
|
+
},
|
|
647
|
+
verboseLog(...args) {
|
|
648
|
+
if (this.verbose && !this.json) {
|
|
649
|
+
console.info(...args);
|
|
650
|
+
}
|
|
651
|
+
},
|
|
652
|
+
addData(entry) {
|
|
653
|
+
response.data.push(entry);
|
|
654
|
+
},
|
|
655
|
+
mergeMeta(metaOrFn) {
|
|
656
|
+
const meta = typeof metaOrFn === 'function' ? metaOrFn(response.meta) : metaOrFn;
|
|
657
|
+
response.meta = {
|
|
658
|
+
...response.meta,
|
|
659
|
+
...meta
|
|
660
|
+
};
|
|
661
|
+
},
|
|
662
|
+
setStatus(status) {
|
|
663
|
+
response.status = status;
|
|
664
|
+
},
|
|
665
|
+
fail(err) {
|
|
666
|
+
response.ok = false;
|
|
667
|
+
response.status = err?.status || 1;
|
|
668
|
+
response.errors.push({
|
|
669
|
+
message: err?.message || 'Unknown files command error.',
|
|
670
|
+
details: err?.details ?? null
|
|
671
|
+
});
|
|
672
|
+
},
|
|
673
|
+
finish() {
|
|
674
|
+
if (this.json) {
|
|
675
|
+
console.log(JSON.stringify(response, null, 2));
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function getFilesOperation(argv) {
|
|
682
|
+
if (argv.list) {
|
|
683
|
+
return 'list';
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
if (argv.export) {
|
|
687
|
+
return 'export';
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
if (argv.import) {
|
|
691
|
+
return 'import';
|
|
692
|
+
}
|
|
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
|
+
|
|
706
|
+
return 'unknown';
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
async function parseJsonSafe(res) {
|
|
711
|
+
try {
|
|
712
|
+
return await res.json();
|
|
713
|
+
} catch {
|
|
714
|
+
return null;
|
|
715
|
+
}
|
|
716
|
+
}
|