@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.
- package/README.md +413 -171
- package/bin/commands/command.js +102 -41
- package/bin/commands/config.js +14 -2
- package/bin/commands/env.js +181 -38
- package/bin/commands/files.js +393 -110
- package/bin/commands/install.js +55 -47
- package/bin/commands/login.js +384 -45
- package/bin/commands/query.js +130 -38
- package/bin/index.js +41 -8
- package/package.json +3 -2
- package/bin/commands/files.test.js +0 -76
- package/bin/commands/install.test.js +0 -48
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,7 +236,7 @@ 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, output
|
|
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
242
|
await uploadFiles(env, user, filesInDir, isRoot ? outPath : path.join(outPath, path.basename(dirPath)), createEmpty, overwrite, output);
|
|
@@ -155,7 +249,7 @@ async function processDirectory(env, user, dirPath, outPath, originalDir, create
|
|
|
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,30 +306,43 @@ 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, verbose);
|
|
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
|
-
function prepareDownloadCommandData(directoryPath, excludeDirectories, fileNames, recursive, singleFileMode) {
|
|
345
|
+
export function prepareDownloadCommandData(directoryPath, excludeDirectories, fileNames, recursive, singleFileMode) {
|
|
239
346
|
const data = {
|
|
240
347
|
'DirectoryPath': directoryPath ?? '/',
|
|
241
348
|
'ExcludeDirectories': [excludeDirectories],
|
|
@@ -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,38 @@ 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
|
|
400
|
+
await fs.promises.unlink(filePath).catch(err => {
|
|
401
|
+
output.verboseLog(`Warning: Failed to delete temporary archive ${filePath}: ${err.message}`);
|
|
402
|
+
});
|
|
290
403
|
}
|
|
291
404
|
|
|
292
405
|
async function getFilesStructure(env, user, dirPath, recursive, includeFiles) {
|
|
@@ -300,14 +413,131 @@ async function getFilesStructure(env, user, dirPath, recursive, includeFiles) {
|
|
|
300
413
|
if (res.ok) {
|
|
301
414
|
return await res.json();
|
|
302
415
|
} else {
|
|
303
|
-
|
|
304
|
-
console.log(await res.json());
|
|
305
|
-
process.exit(1);
|
|
416
|
+
throw createCommandError('Unable to fetch file structure.', res.status, await parseJsonSafe(res));
|
|
306
417
|
}
|
|
307
418
|
}
|
|
308
419
|
|
|
309
|
-
|
|
310
|
-
|
|
420
|
+
async function deleteRemote(env, user, remotePath, isFile, empty, output) {
|
|
421
|
+
let endpoint;
|
|
422
|
+
let mode;
|
|
423
|
+
let data;
|
|
424
|
+
|
|
425
|
+
if (isFile) {
|
|
426
|
+
endpoint = 'FileDelete';
|
|
427
|
+
mode = 'file';
|
|
428
|
+
const parentDir = path.posix.dirname(remotePath);
|
|
429
|
+
data = {
|
|
430
|
+
DirectoryPath: parentDir === '.' ? '/' : parentDir,
|
|
431
|
+
Ids: [remotePath]
|
|
432
|
+
};
|
|
433
|
+
} else if (empty) {
|
|
434
|
+
endpoint = 'DirectoryEmpty';
|
|
435
|
+
mode = 'empty';
|
|
436
|
+
data = { Path: remotePath };
|
|
437
|
+
} else {
|
|
438
|
+
endpoint = 'DirectoryDelete';
|
|
439
|
+
mode = 'directory';
|
|
440
|
+
data = { Path: remotePath };
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
output.log(`${mode === 'empty' ? 'Emptying' : 'Deleting'} ${mode === 'file' ? 'file' : 'directory'}: ${remotePath}`);
|
|
444
|
+
|
|
445
|
+
const res = await fetch(`${env.protocol}://${env.host}/Admin/Api/${endpoint}`, {
|
|
446
|
+
method: 'POST',
|
|
447
|
+
body: JSON.stringify(data),
|
|
448
|
+
headers: {
|
|
449
|
+
'Authorization': `Bearer ${user.apiKey}`,
|
|
450
|
+
'Content-Type': 'application/json'
|
|
451
|
+
},
|
|
452
|
+
agent: getAgent(env.protocol)
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
if (!res.ok) {
|
|
456
|
+
throw createCommandError(`Failed to ${mode === 'empty' ? 'empty' : 'delete'} "${remotePath}".`, res.status, await parseJsonSafe(res));
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const body = await parseJsonSafe(res);
|
|
460
|
+
|
|
461
|
+
output.setStatus(200);
|
|
462
|
+
output.addData({
|
|
463
|
+
type: 'delete',
|
|
464
|
+
path: remotePath,
|
|
465
|
+
mode,
|
|
466
|
+
response: body
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
output.log(`Successfully ${mode === 'empty' ? 'emptied' : 'deleted'}: ${remotePath}`);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
async function copyRemote(env, user, sourcePath, destination, output) {
|
|
473
|
+
output.log(`Copying ${sourcePath} to ${destination}`);
|
|
474
|
+
|
|
475
|
+
const res = await fetch(`${env.protocol}://${env.host}/Admin/Api/AssetCopy`, {
|
|
476
|
+
method: 'POST',
|
|
477
|
+
body: JSON.stringify({
|
|
478
|
+
Destination: destination,
|
|
479
|
+
Ids: [sourcePath]
|
|
480
|
+
}),
|
|
481
|
+
headers: {
|
|
482
|
+
'Authorization': `Bearer ${user.apiKey}`,
|
|
483
|
+
'Content-Type': 'application/json'
|
|
484
|
+
},
|
|
485
|
+
agent: getAgent(env.protocol)
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
if (!res.ok) {
|
|
489
|
+
throw createCommandError(`Failed to copy "${sourcePath}" to "${destination}".`, res.status, await parseJsonSafe(res));
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const body = await parseJsonSafe(res);
|
|
493
|
+
|
|
494
|
+
output.setStatus(200);
|
|
495
|
+
output.addData({
|
|
496
|
+
type: 'copy',
|
|
497
|
+
sourcePath,
|
|
498
|
+
destination,
|
|
499
|
+
response: body
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
output.log(`Successfully copied ${sourcePath} to ${destination}`);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
async function moveRemote(env, user, sourcePath, destination, overwrite, output) {
|
|
506
|
+
output.log(`Moving ${sourcePath} to ${destination}`);
|
|
507
|
+
|
|
508
|
+
const res = await fetch(`${env.protocol}://${env.host}/Admin/Api/AssetMove`, {
|
|
509
|
+
method: 'POST',
|
|
510
|
+
body: JSON.stringify({
|
|
511
|
+
Destination: destination,
|
|
512
|
+
Overwrite: Boolean(overwrite),
|
|
513
|
+
Ids: [sourcePath]
|
|
514
|
+
}),
|
|
515
|
+
headers: {
|
|
516
|
+
'Authorization': `Bearer ${user.apiKey}`,
|
|
517
|
+
'Content-Type': 'application/json'
|
|
518
|
+
},
|
|
519
|
+
agent: getAgent(env.protocol)
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
if (!res.ok) {
|
|
523
|
+
throw createCommandError(`Failed to move "${sourcePath}" to "${destination}".`, res.status, await parseJsonSafe(res));
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const body = await parseJsonSafe(res);
|
|
527
|
+
|
|
528
|
+
output.setStatus(200);
|
|
529
|
+
output.addData({
|
|
530
|
+
type: 'move',
|
|
531
|
+
sourcePath,
|
|
532
|
+
destination,
|
|
533
|
+
overwrite: Boolean(overwrite),
|
|
534
|
+
response: body
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
output.log(`Successfully moved ${sourcePath} to ${destination}`);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
export async function uploadFiles(env, user, localFilePaths, destinationPath, createEmpty = false, overwrite = false, output = createFilesOutput({})) {
|
|
311
541
|
output.log('Uploading files')
|
|
312
542
|
|
|
313
543
|
const chunkSize = 300;
|
|
@@ -317,10 +547,10 @@ export async function uploadFiles(env, user, localFilePaths, destinationPath, cr
|
|
|
317
547
|
chunks.push(localFilePaths.slice(i, i + chunkSize));
|
|
318
548
|
}
|
|
319
549
|
|
|
320
|
-
output.mergeMeta({
|
|
321
|
-
filesProcessed: (
|
|
322
|
-
chunks: (
|
|
323
|
-
});
|
|
550
|
+
output.mergeMeta((meta) => ({
|
|
551
|
+
filesProcessed: (meta.filesProcessed || 0) + localFilePaths.length,
|
|
552
|
+
chunks: (meta.chunks || 0) + chunks.length
|
|
553
|
+
}));
|
|
324
554
|
|
|
325
555
|
for (let i = 0; i < chunks.length; i++) {
|
|
326
556
|
output.log(`Uploading chunk ${i + 1} of ${chunks.length}`);
|
|
@@ -340,7 +570,7 @@ export async function uploadFiles(env, user, localFilePaths, destinationPath, cr
|
|
|
340
570
|
output.log(`Finished uploading files. Total files: ${localFilePaths.length}, total chunks: ${chunks.length}`);
|
|
341
571
|
}
|
|
342
572
|
|
|
343
|
-
async function uploadChunk(env, user, filePathsChunk, destinationPath, createEmpty, overwrite, output
|
|
573
|
+
async function uploadChunk(env, user, filePathsChunk, destinationPath, createEmpty, overwrite, output) {
|
|
344
574
|
const form = new FormData();
|
|
345
575
|
form.append('path', destinationPath);
|
|
346
576
|
form.append('skipExistingFiles', String(!overwrite));
|
|
@@ -361,50 +591,119 @@ async function uploadChunk(env, user, filePathsChunk, destinationPath, createEmp
|
|
|
361
591
|
});
|
|
362
592
|
|
|
363
593
|
if (res.ok) {
|
|
364
|
-
return await res.json()
|
|
594
|
+
return await res.json();
|
|
365
595
|
}
|
|
366
596
|
else {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
597
|
+
throw createCommandError('File upload failed.', res.status, await parseJsonSafe(res));
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
export function resolveFilePath(filePath) {
|
|
602
|
+
let p = path.parse(path.resolve(filePath))
|
|
603
|
+
let regex = wildcardToRegExp(p.base);
|
|
604
|
+
let resolvedPath = fs.readdirSync(p.dir).filter((allFilesPaths) => allFilesPaths.match(regex) !== null)[0]
|
|
605
|
+
if (resolvedPath === undefined) {
|
|
606
|
+
throw createCommandError(`Could not find any files with the name ${filePath}`, 1);
|
|
607
|
+
}
|
|
608
|
+
return path.join(p.dir, resolvedPath);
|
|
609
|
+
}
|
|
610
|
+
|
|
370
611
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
612
|
+
export function isFilePath(argv, dirPath) {
|
|
613
|
+
if (argv.asFile || argv.asDirectory) {
|
|
614
|
+
return Boolean(argv.asFile);
|
|
374
615
|
}
|
|
616
|
+
return path.extname(dirPath) !== '';
|
|
375
617
|
}
|
|
376
618
|
|
|
377
|
-
export function
|
|
378
|
-
const
|
|
379
|
-
|
|
619
|
+
export function wildcardToRegExp(wildcard) {
|
|
620
|
+
const escaped = wildcard.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
|
|
621
|
+
return new RegExp('^' + escaped.replace(/\*/g, '.*') + '$');
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function createFilesOutput(argv) {
|
|
625
|
+
const response = {
|
|
626
|
+
ok: true,
|
|
627
|
+
command: 'files',
|
|
628
|
+
operation: getFilesOperation(argv),
|
|
629
|
+
status: 200,
|
|
630
|
+
data: [],
|
|
631
|
+
errors: [],
|
|
632
|
+
meta: {}
|
|
633
|
+
};
|
|
380
634
|
|
|
381
635
|
return {
|
|
382
|
-
|
|
636
|
+
json: argv.output === 'json' || Boolean(argv.json),
|
|
637
|
+
verbose: Boolean(argv.verbose),
|
|
383
638
|
response,
|
|
384
|
-
log
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
639
|
+
log(...args) {
|
|
640
|
+
if (!this.json) {
|
|
641
|
+
console.log(...args);
|
|
642
|
+
}
|
|
643
|
+
},
|
|
644
|
+
verboseLog(...args) {
|
|
645
|
+
if (this.verbose && !this.json) {
|
|
646
|
+
console.info(...args);
|
|
647
|
+
}
|
|
648
|
+
},
|
|
649
|
+
addData(entry) {
|
|
650
|
+
response.data.push(entry);
|
|
651
|
+
},
|
|
652
|
+
mergeMeta(metaOrFn) {
|
|
653
|
+
const meta = typeof metaOrFn === 'function' ? metaOrFn(response.meta) : metaOrFn;
|
|
654
|
+
response.meta = {
|
|
655
|
+
...response.meta,
|
|
656
|
+
...meta
|
|
657
|
+
};
|
|
658
|
+
},
|
|
659
|
+
setStatus(status) {
|
|
660
|
+
response.status = status;
|
|
661
|
+
},
|
|
662
|
+
fail(err) {
|
|
663
|
+
response.ok = false;
|
|
664
|
+
response.status = err?.status || 1;
|
|
665
|
+
response.errors.push({
|
|
666
|
+
message: err?.message || 'Unknown files command error.',
|
|
667
|
+
details: err?.details ?? null
|
|
668
|
+
});
|
|
669
|
+
},
|
|
670
|
+
finish() {
|
|
671
|
+
if (this.json) {
|
|
672
|
+
console.log(JSON.stringify(response, null, 2));
|
|
397
673
|
}
|
|
674
|
+
}
|
|
398
675
|
};
|
|
399
676
|
}
|
|
400
677
|
|
|
401
|
-
function
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
678
|
+
export function getFilesOperation(argv) {
|
|
679
|
+
if (argv.list) {
|
|
680
|
+
return 'list';
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
if (argv.export) {
|
|
684
|
+
return 'export';
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if (argv.import) {
|
|
688
|
+
return 'import';
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
if (argv.delete) {
|
|
692
|
+
return 'delete';
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
if (argv.copy) {
|
|
696
|
+
return 'copy';
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
if (argv.move) {
|
|
700
|
+
return 'move';
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
return 'unknown';
|
|
406
704
|
}
|
|
407
705
|
|
|
706
|
+
|
|
408
707
|
async function parseJsonSafe(res) {
|
|
409
708
|
try {
|
|
410
709
|
return await res.json();
|
|
@@ -412,19 +711,3 @@ async function parseJsonSafe(res) {
|
|
|
412
711
|
return null;
|
|
413
712
|
}
|
|
414
713
|
}
|
|
415
|
-
|
|
416
|
-
export function resolveFilePath(filePath) {
|
|
417
|
-
let p = path.parse(path.resolve(filePath))
|
|
418
|
-
let regex = wildcardToRegExp(p.base);
|
|
419
|
-
let resolvedPath = fs.readdirSync(p.dir).filter((allFilesPaths) => allFilesPaths.match(regex) !== null)[0]
|
|
420
|
-
if (resolvedPath === undefined) {
|
|
421
|
-
throw new Error('Could not find any files with the name ' + filePath);
|
|
422
|
-
}
|
|
423
|
-
return path.join(p.dir, resolvedPath);
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
function wildcardToRegExp(wildcard) {
|
|
428
|
-
const escaped = wildcard.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
|
|
429
|
-
return new RegExp('^' + escaped.replace(/\*/g, '.*') + '$');
|
|
430
|
-
}
|