dxfl 0.1.5 → 0.1.6

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 CHANGED
@@ -36,8 +36,11 @@ dxfl deploy example.com _public
36
36
  git clone https://git.deuxfleurs.fr/Deuxfleurs/dxfl
37
37
  cd dxfl
38
38
  npm install
39
+ # build and install the tool as the `dxfl` command
39
40
  npm link
40
41
  dxfl
42
+ # alternatively, run the tool from the sources directly
43
+ npx dxfl
41
44
  ```
42
45
 
43
46
  ## Release
package/dist/deploy.js CHANGED
@@ -100,7 +100,22 @@ function getBucketFiles(client, Bucket) {
100
100
  return files;
101
101
  });
102
102
  }
103
- function uploadFile(client, Bucket, Key, Body, md5) {
103
+ function formatBytes(bytes) {
104
+ if (bytes < 1000) {
105
+ return `${bytes}B`;
106
+ }
107
+ bytes /= 1000;
108
+ if (bytes < 1000) {
109
+ return `${bytes.toFixed(2)}KB`;
110
+ }
111
+ bytes /= 1000;
112
+ if (bytes < 1000) {
113
+ return `${bytes.toFixed(2)}MB`;
114
+ }
115
+ bytes /= 1000;
116
+ return `${bytes.toFixed(2)}GB`;
117
+ }
118
+ function uploadFile(client, Bucket, Key, Body, md5, display) {
104
119
  return __awaiter(this, void 0, void 0, function* () {
105
120
  var _a;
106
121
  // use `path.posix` because `Key` is a path in a bucket that uses `/` as separator.
@@ -115,13 +130,29 @@ function uploadFile(client, Bucket, Key, Body, md5) {
115
130
  const params = { Bucket, Key, Body, ContentType, Metadata };
116
131
  const parallelUpload = new Upload({ client, params });
117
132
  parallelUpload.on("httpUploadProgress", progress => {
118
- process.stdout.moveCursor(0, -1);
119
- process.stdout.clearLine(1);
120
- process.stdout.write(`Sending ${progress.Key}`);
121
- if (!(progress.loaded == progress.total && progress.part == 1)) {
122
- process.stdout.write(` (${progress.loaded}/${progress.total})`);
133
+ function writeLine() {
134
+ process.stdout.write(`Sending ${progress.Key}`);
135
+ if (progress.loaded && progress.total &&
136
+ !(progress.loaded == progress.total && progress.part == 1)) {
137
+ process.stdout.write(` (${formatBytes(progress.loaded)}/${formatBytes(progress.total)})`);
138
+ }
139
+ process.stdout.write("\n");
140
+ }
141
+ ;
142
+ if (progress.Key) {
143
+ if (display.lines.has(progress.Key)) {
144
+ const offset = display.nblines - display.lines.get(progress.Key);
145
+ process.stdout.moveCursor(0, -offset);
146
+ process.stdout.clearLine(1);
147
+ writeLine();
148
+ process.stdout.moveCursor(0, offset - 1);
149
+ }
150
+ else {
151
+ display.lines.set(progress.Key, display.nblines);
152
+ display.nblines++;
153
+ writeLine();
154
+ }
123
155
  }
124
- process.stdout.write("\n");
125
156
  });
126
157
  yield parallelUpload.done();
127
158
  });
@@ -134,7 +165,7 @@ function deleteFiles(client, Bucket, files) {
134
165
  return yield client.send(new DeleteObjectsCommand({
135
166
  Bucket,
136
167
  Delete: {
137
- Objects: files.map(f => { return { Key: f }; }),
168
+ Objects: files.map(f => { return { Key: f.name }; }),
138
169
  },
139
170
  }));
140
171
  });
@@ -167,11 +198,11 @@ function needsUpdate(client, localFile, localMd5, Bucket, Key, remoteSize) {
167
198
  return (localMd5 != remoteMd5);
168
199
  });
169
200
  }
170
- export function deploy(vhost, localFolder) {
201
+ export function deploy(vhost, localFolder, options) {
171
202
  return __awaiter(this, void 0, void 0, function* () {
172
203
  const conf = yield openApiConf();
173
204
  // Get paths of the local files to deploy
174
- const localFiles = yield getLocalFiles(localFolder, "").catch(err => {
205
+ const localPaths = yield getLocalFiles(localFolder, "").catch(err => {
175
206
  if (err.errno = -2) {
176
207
  console.error(`Error: directory '${localFolder}' does not exist`);
177
208
  }
@@ -180,6 +211,10 @@ export function deploy(vhost, localFolder) {
180
211
  }
181
212
  process.exit(1);
182
213
  });
214
+ // Compute local file sizes
215
+ const localFiles = yield Promise.all(localPaths.map((_a) => __awaiter(this, [_a], void 0, function* ({ localPath, s3Path }) {
216
+ return { localPath, s3Path, size: (yield fs.promises.stat(localPath)).size };
217
+ })));
183
218
  // Get website info from guichet (bucket name and keys)
184
219
  const api = new WebsiteApi(conf);
185
220
  let vhostInfo = yield api.getWebsite({ vhost }).catch(err => {
@@ -207,22 +242,40 @@ export function deploy(vhost, localFolder) {
207
242
  // Delete files that are present in the bucket but not locally.
208
243
  // Do this before sending the new files to avoid hitting the size quota
209
244
  // unnecessarily.
210
- const resp = yield deleteFiles(s3client, Bucket, [...remoteFiles]
245
+ const filesToDelete = [...remoteFiles]
211
246
  .filter(([name, _]) => !localFiles.find(({ s3Path }) => s3Path == name))
212
- .map(([name, _]) => name));
213
- if (resp && resp.$metadata.httpStatusCode != 200) {
214
- // TODO: better error handling?
215
- console.error(resp);
216
- process.exit(1);
247
+ .map(([name, { size }]) => ({ name, size }));
248
+ for (const file of filesToDelete) {
249
+ process.stdout.write(`Deleting ${file.name}\n`);
217
250
  }
218
- // Uploads a local file unless the remote copy is the same
219
- function processFile(localPath, s3Path) {
251
+ // If not in dry-run mode, send the delete command
252
+ if (!options.dryRun) {
253
+ const resp = yield deleteFiles(s3client, Bucket, filesToDelete);
254
+ if (resp && resp.$metadata.httpStatusCode != 200) {
255
+ // TODO: better error handling?
256
+ console.error(resp);
257
+ process.exit(1);
258
+ }
259
+ }
260
+ // Upload files.
261
+ let displaystate = {
262
+ lines: new Map(),
263
+ nblines: 0
264
+ };
265
+ let filesSent = [];
266
+ function processFile(f) {
220
267
  return __awaiter(this, void 0, void 0, function* () {
221
- const localMd5 = yield getFileMd5(localPath);
222
- const remoteFile = remoteFiles.get(s3Path);
268
+ const remoteFile = remoteFiles.get(f.s3Path);
269
+ const localMd5 = yield getFileMd5(f.localPath);
223
270
  if (!remoteFile ||
224
- (yield needsUpdate(s3client, localPath, localMd5, Bucket, s3Path, remoteFile.size))) {
225
- uploadFile(s3client, Bucket, s3Path, fs.createReadStream(localPath), localMd5);
271
+ (yield needsUpdate(s3client, f.localPath, localMd5, Bucket, f.s3Path, remoteFile.size))) {
272
+ filesSent.push({ s3Path: f.s3Path, size: f.size });
273
+ if (options.dryRun) {
274
+ process.stdout.write(`Sending ${f.s3Path}\n`);
275
+ }
276
+ else {
277
+ yield uploadFile(s3client, Bucket, f.s3Path, fs.createReadStream(f.localPath), localMd5, displaystate);
278
+ }
226
279
  }
227
280
  });
228
281
  }
@@ -231,6 +284,27 @@ export function deploy(vhost, localFolder) {
231
284
  yield PromisePool
232
285
  .for(localFiles)
233
286
  .withConcurrency(6)
234
- .process(({ localPath, s3Path }) => processFile(localPath, s3Path));
287
+ .process(processFile);
288
+ // Display a summary
289
+ function sum(a) { return a.reduce(((x, y) => x + y), 0); }
290
+ function formatFiles(n) {
291
+ if (n == 1) {
292
+ return `${n} file `;
293
+ }
294
+ else {
295
+ return `${n} files`;
296
+ }
297
+ }
298
+ const sizeSent = sum(filesSent.map((f) => f.size));
299
+ const sizeDeleted = sum(filesToDelete.map((f) => { var _a; return (_a = f.size) !== null && _a !== void 0 ? _a : 0; }));
300
+ let filesUnchanged = new Map(localFiles.map((f) => [f.s3Path, f.size]));
301
+ for (const f of filesSent) {
302
+ filesUnchanged.delete(f.s3Path);
303
+ }
304
+ const sizeUnchanged = sum([...filesUnchanged.values()]);
305
+ process.stdout.write("\nSummary:\n");
306
+ process.stdout.write(`${formatFiles(filesSent.length)} uploaded (${formatBytes(sizeSent)})\n`);
307
+ process.stdout.write(`${formatFiles(filesToDelete.length)} deleted (${formatBytes(sizeDeleted)})\n`);
308
+ process.stdout.write(`${formatFiles(filesUnchanged.size)} unchanged (${formatBytes(sizeUnchanged)})\n`);
235
309
  });
236
310
  }
package/dist/index.js CHANGED
@@ -18,5 +18,6 @@ program.command('deploy')
18
18
  .description('Deploy your website')
19
19
  .argument('<vhost>', 'selected vhost')
20
20
  .argument('<local_folder>', 'your local folder')
21
+ .option('-n, --dry-run', 'do a trial run without making actual changes')
21
22
  .action(deploy);
22
23
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dxfl",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "",
5
5
  "license": "EUPL-1.2",
6
6
  "author": "Deuxfleurs Team <coucou@deuxfleurs.fr>",