dxfl 0.1.5 → 0.1.7
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 +5 -0
- package/dist/deploy.js +97 -23
- package/dist/index.js +2 -1
- package/package.json +2 -2
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
|
|
@@ -49,6 +52,8 @@ Then to publish a release:
|
|
|
49
52
|
|
|
50
53
|
```bash
|
|
51
54
|
vim package.json # update the version in this file
|
|
55
|
+
vim index.ts # update the version in this file
|
|
56
|
+
npm install # update the version in the package-lock.json
|
|
52
57
|
git commit -a -m 'set version 0.1.5' # commit your change
|
|
53
58
|
git push # send update
|
|
54
59
|
git tag -m 'v0.1.5' v0.1.5 # create associated tag
|
package/dist/deploy.js
CHANGED
|
@@ -100,7 +100,22 @@ function getBucketFiles(client, Bucket) {
|
|
|
100
100
|
return files;
|
|
101
101
|
});
|
|
102
102
|
}
|
|
103
|
-
function
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
|
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
|
|
245
|
+
const filesToDelete = [...remoteFiles]
|
|
211
246
|
.filter(([name, _]) => !localFiles.find(({ s3Path }) => s3Path == name))
|
|
212
|
-
.map(([name,
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
//
|
|
219
|
-
|
|
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
|
|
222
|
-
const
|
|
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
|
-
|
|
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(
|
|
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
|
@@ -6,7 +6,7 @@ import { vhostsList } from "./vhosts.js";
|
|
|
6
6
|
program
|
|
7
7
|
.name('dxfl')
|
|
8
8
|
.description('Deuxfleurs CLI tool')
|
|
9
|
-
.version('0.1.
|
|
9
|
+
.version('0.1.7');
|
|
10
10
|
program.command('login')
|
|
11
11
|
.description('Link your Deuxfleurs account with this tool.')
|
|
12
12
|
.argument('<username>', 'your account username')
|
|
@@ -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,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dxfl",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "EUPL-1.2",
|
|
6
6
|
"author": "Deuxfleurs Team <coucou@deuxfleurs.fr>",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "dist/main.js",
|
|
9
9
|
"bin": {
|
|
10
|
-
"dxfl": "
|
|
10
|
+
"dxfl": "dist/index.js"
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
13
|
"prepare": "npx tsc"
|