dxfl 0.5.5 → 0.6.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/CHANGELOG.md +4 -0
- package/dist/_empty.js +64 -0
- package/dist/deploy.js +4 -2
- package/dist/empty.js +4 -2
- package/dist/index.js +3 -3
- package/dist/inspect.js +3 -2
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/utils.js +13 -0
- package/dist/vhosts.js +1 -1
- package/package.json +2 -7
- package/deuxfleurs.toml +0 -18
- package/dist/deploy_websiteconfig.js +0 -108
- package/dist/errors.js +0 -7
- package/public/TinyCorePure64-15.0.iso +0 -1
- package/public/d/b +0 -2
- package/public/d/c +0 -2
- package/public/d/dd/bla +0 -1
- package/public/d/dd/foo.txt +0 -1
- package/public/def +0 -2
- package/public/error.html +0 -1
- package/public/index.html +0 -1
- package/public/index.html~ +0 -1
- package/public/slt +0 -1
- package/public/test.html +0 -0
- package/public/toto.jpg +0 -0
- package/public/toto.txt +0 -1
- package/public/toto2.txt +0 -1
package/CHANGELOG.md
CHANGED
package/dist/_empty.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// // TODO: Refacto the codebase global before integrate this feature
|
|
2
|
+
export {};
|
|
3
|
+
// export async function empty(
|
|
4
|
+
// vhost: string,
|
|
5
|
+
// options: { dryRun: boolean | undefined },
|
|
6
|
+
// ) {
|
|
7
|
+
// const conf = await openApiConf();
|
|
8
|
+
// // Get website info from guichet (bucket name and keys)
|
|
9
|
+
// const api = new WebsiteApi(conf);
|
|
10
|
+
// let vhostInfo = await api.getWebsite({ vhost }).catch(err => {
|
|
11
|
+
// if (err.response.status == 404) {
|
|
12
|
+
// console.error(`Error: website '${vhost}' does not exist`);
|
|
13
|
+
// } else {
|
|
14
|
+
// console.error(err);
|
|
15
|
+
// }
|
|
16
|
+
// process.exit(1);
|
|
17
|
+
// });
|
|
18
|
+
// // List the files currently stored in the bucket
|
|
19
|
+
// // @FIXME this info could be returned by the guichet API
|
|
20
|
+
// const s3client = new S3Client({
|
|
21
|
+
// endpoint: "https://garage.deuxfleurs.fr",
|
|
22
|
+
// region: "garage",
|
|
23
|
+
// forcePathStyle: true,
|
|
24
|
+
// credentials: {
|
|
25
|
+
// accessKeyId: vhostInfo.accessKeyId!,
|
|
26
|
+
// secretAccessKey: vhostInfo.secretAccessKey!,
|
|
27
|
+
// },
|
|
28
|
+
// });
|
|
29
|
+
// const Bucket = vhostInfo.vhost!.name!;
|
|
30
|
+
// const filesToDelete = [...(await getBucketFiles(s3client, Bucket))].map(
|
|
31
|
+
// ([name, { size }]) => ({
|
|
32
|
+
// name,
|
|
33
|
+
// size,
|
|
34
|
+
// }),
|
|
35
|
+
// );
|
|
36
|
+
// for (const file of filesToDelete) {
|
|
37
|
+
// process.stdout.write(`Deleting ${file.name}\n`);
|
|
38
|
+
// }
|
|
39
|
+
// // If not in dry-run mode, send the delete command
|
|
40
|
+
// if (!options.dryRun) {
|
|
41
|
+
// const resp = await deleteFiles(s3client, Bucket, filesToDelete);
|
|
42
|
+
// if (resp && resp!.$metadata.httpStatusCode != 200) {
|
|
43
|
+
// // TODO: better error handling?
|
|
44
|
+
// console.error(resp);
|
|
45
|
+
// process.exit(1);
|
|
46
|
+
// }
|
|
47
|
+
// }
|
|
48
|
+
// // Display a summary
|
|
49
|
+
// function sum(a: number[]) {
|
|
50
|
+
// return a.reduce((x, y) => x + y, 0);
|
|
51
|
+
// }
|
|
52
|
+
// function formatFiles(n: number) {
|
|
53
|
+
// if (n == 1) {
|
|
54
|
+
// return `${n} file `;
|
|
55
|
+
// } else {
|
|
56
|
+
// return `${n} files`;
|
|
57
|
+
// }
|
|
58
|
+
// }
|
|
59
|
+
// const sizeDeleted = sum(filesToDelete.map(f => f.size ?? 0));
|
|
60
|
+
// process.stdout.write("\nSummary:\n");
|
|
61
|
+
// process.stdout.write(
|
|
62
|
+
// `${formatFiles(filesToDelete.length)} deleted (${formatBytes(sizeDeleted)})\n`,
|
|
63
|
+
// );
|
|
64
|
+
// }
|
package/dist/deploy.js
CHANGED
|
@@ -13,7 +13,7 @@ import { PromisePool } from "@supercharge/promise-pool";
|
|
|
13
13
|
import { deleteBucketFile, deleteBucketFiles, getBucketCredentials, getBucket, getBucketFiles, putEmptyObjectRedirect, uploadFile, setObjectHeaders, } from "./bucket.js";
|
|
14
14
|
import { ErrorMsg } from "./error.js";
|
|
15
15
|
import { fileContentType, supportedHeaders, } from "./headers.js";
|
|
16
|
-
import { confirmationPrompt, filterMap, formatBytesHuman, formatCount, getFileMd5, gzipFile, mapEq, mkTmpDir, sum, } from "./utils.js";
|
|
16
|
+
import { confirmationPrompt, filterMap, formatBytesHuman, formatCount, getFileMd5, gzipFile, mapEq, mkTmpDir, sum, websiteIdBestEffort, } from "./utils.js";
|
|
17
17
|
import { evalHeadersRules, equalBucketRedirect, getBucketConfig, putBucketWebsiteConfig, readConfigFile, } from "./website_config.js";
|
|
18
18
|
// Walks through the local directory at path `dir`, and for each file it contains, returns :
|
|
19
19
|
// - `localPath`: its path on the local filesystem (includes `dir`). On windows, this path
|
|
@@ -429,13 +429,15 @@ export function deploy(website, localFolder, options) {
|
|
|
429
429
|
}
|
|
430
430
|
// Read and validate the local configuration file before doing anything else
|
|
431
431
|
const localWebsiteConfig = yield readConfigFile(options.configFile);
|
|
432
|
+
// Interpret the name of the website
|
|
433
|
+
const websiteId = websiteIdBestEffort(website);
|
|
432
434
|
process.stdout.write("Fetching the website configuration and metadata...\n");
|
|
433
435
|
const [localFiles, [bucket, remoteFiles, remoteWebsiteConfig]] = yield Promise.all([
|
|
434
436
|
// Get paths & size of the local files to deploy
|
|
435
437
|
getLocalFilesWithInfo(localFolder),
|
|
436
438
|
// Get the bucket, list of files stored in the bucket, and bucket website config
|
|
437
439
|
(() => __awaiter(this, void 0, void 0, function* () {
|
|
438
|
-
const bucket = yield getBucket(
|
|
440
|
+
const bucket = yield getBucket(websiteId, yield getBucketCredentials(websiteId));
|
|
439
441
|
const remoteFiles = yield getBucketFiles(bucket);
|
|
440
442
|
// This can be slow because it needs to query each object in the bucket.
|
|
441
443
|
const remoteWebsiteConfig = yield getBucketConfig(bucket, [
|
package/dist/empty.js
CHANGED
|
@@ -9,14 +9,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
};
|
|
10
10
|
import { getBucket, getBucketCredentials, getBucketFiles, deleteBucketFiles, } from "./bucket.js";
|
|
11
11
|
import { ErrorMsg } from "./error.js";
|
|
12
|
-
import { confirmationPrompt, formatBytesHuman, formatCount, sum, } from "./utils.js";
|
|
12
|
+
import { confirmationPrompt, formatBytesHuman, formatCount, sum, websiteIdBestEffort, } from "./utils.js";
|
|
13
13
|
export function empty(website, options) {
|
|
14
14
|
return __awaiter(this, void 0, void 0, function* () {
|
|
15
15
|
if (options.dryRun && options.yes) {
|
|
16
16
|
throw new ErrorMsg("options --yes and --dry-run cannot be passed at the same time");
|
|
17
17
|
}
|
|
18
18
|
process.stdout.write("Fetching the website configuration and metadata...\n");
|
|
19
|
-
|
|
19
|
+
// Interpret the name of the website
|
|
20
|
+
const websiteId = websiteIdBestEffort(website);
|
|
21
|
+
const bucket = yield getBucket(websiteId, yield getBucketCredentials(websiteId));
|
|
20
22
|
const filesToDelete = [...(yield getBucketFiles(bucket))].map(([name, { size }]) => ({
|
|
21
23
|
name,
|
|
22
24
|
size,
|
package/dist/index.js
CHANGED
|
@@ -4,9 +4,9 @@ import { withHandleErrors } from "./error.js";
|
|
|
4
4
|
import { login, logout } from "./auth.js";
|
|
5
5
|
import { deploy } from "./deploy.js";
|
|
6
6
|
import { empty } from "./empty.js";
|
|
7
|
-
import {
|
|
7
|
+
import { list } from "./vhosts.js";
|
|
8
8
|
import { inspect } from "./inspect.js";
|
|
9
|
-
program.name("dxfl").description("Deuxfleurs CLI tool").version("0.
|
|
9
|
+
program.name("dxfl").description("Deuxfleurs CLI tool").version("0.6.0");
|
|
10
10
|
program
|
|
11
11
|
.command("login")
|
|
12
12
|
.description("Link your Deuxfleurs account with this tool.")
|
|
@@ -15,7 +15,7 @@ program
|
|
|
15
15
|
program
|
|
16
16
|
.command("list")
|
|
17
17
|
.description("List all your websites")
|
|
18
|
-
.action(() => withHandleErrors(
|
|
18
|
+
.action(() => withHandleErrors(list));
|
|
19
19
|
program
|
|
20
20
|
.command("inspect")
|
|
21
21
|
.description("Show your website details")
|
package/dist/inspect.js
CHANGED
|
@@ -9,14 +9,15 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
};
|
|
10
10
|
import { WebsiteApi } from "guichet-sdk-ts";
|
|
11
11
|
import { openApiConf } from "./auth.js";
|
|
12
|
-
import { formatBytesHuman, separator } from "./utils.js";
|
|
12
|
+
import { formatBytesHuman, separator, websiteIdBestEffort } from "./utils.js";
|
|
13
13
|
import { styleText } from "node:util";
|
|
14
14
|
export function inspect(website, options) {
|
|
15
15
|
return __awaiter(this, void 0, void 0, function* () {
|
|
16
16
|
var _a, _b, _c;
|
|
17
|
+
const websiteId = websiteIdBestEffort(website); // Interpret the name of the website
|
|
17
18
|
const conf = yield openApiConf();
|
|
18
19
|
const web = new WebsiteApi(conf);
|
|
19
|
-
let siteData = yield web.getWebsite({ vhost:
|
|
20
|
+
let siteData = yield web.getWebsite({ vhost: websiteId });
|
|
20
21
|
const q = siteData.quotaSize;
|
|
21
22
|
const v = siteData.vhost;
|
|
22
23
|
const kId = siteData === null || siteData === void 0 ? void 0 : siteData.accessKeyId;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["../auth.ts","../bucket.ts","../deploy.ts","../empty.ts","../error.ts","../guichet.ts","../headers.ts","../index.ts","../inspect.ts","../utils.ts","../vhosts.ts","../website_config.ts"],"version":"5.9.3"}
|
package/dist/utils.js
CHANGED
|
@@ -23,6 +23,7 @@ import { pipeline } from "node:stream/promises";
|
|
|
23
23
|
import { styleText } from "node:util";
|
|
24
24
|
import zlib from "node:zlib";
|
|
25
25
|
import readline from "readline/promises";
|
|
26
|
+
import URI from "fast-uri";
|
|
26
27
|
import { ErrorMsg } from "./error.js";
|
|
27
28
|
export function getFileMd5(file) {
|
|
28
29
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -150,3 +151,15 @@ export function mapEq(m1, m2) {
|
|
|
150
151
|
return (m1.size === m2.size &&
|
|
151
152
|
Array.from(m1.keys()).every(key => m1.get(key) == m2.get(key)));
|
|
152
153
|
}
|
|
154
|
+
// Accept {http,https}://website{,/} as identifier for "website".
|
|
155
|
+
// (Useful to allow copy-pasting URLs into the terminal.)
|
|
156
|
+
export function websiteIdBestEffort(website) {
|
|
157
|
+
const asURI = URI.parse(website);
|
|
158
|
+
if (!asURI.error &&
|
|
159
|
+
asURI.host &&
|
|
160
|
+
(asURI.scheme == "http" || asURI.scheme == "https") &&
|
|
161
|
+
(asURI.path == "" || asURI.path == "/")) {
|
|
162
|
+
return asURI.host;
|
|
163
|
+
}
|
|
164
|
+
return website;
|
|
165
|
+
}
|
package/dist/vhosts.js
CHANGED
|
@@ -11,7 +11,7 @@ import { WebsiteApi } from "guichet-sdk-ts";
|
|
|
11
11
|
import { openApiConf } from "./auth.js";
|
|
12
12
|
import { separator } from "./utils.js";
|
|
13
13
|
import { styleText } from "node:util";
|
|
14
|
-
export function
|
|
14
|
+
export function list() {
|
|
15
15
|
return __awaiter(this, void 0, void 0, function* () {
|
|
16
16
|
var _a, _b, _c;
|
|
17
17
|
const conf = yield openApiConf();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dxfl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "EUPL-1.2",
|
|
6
6
|
"author": "Deuxfleurs Team <coucou@deuxfleurs.fr>",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"prettier-check": "npx prettier . --check"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@aws-sdk/client-s3": "^3.
|
|
26
|
+
"@aws-sdk/client-s3": "^3.1014.0",
|
|
27
27
|
"@commander-js/extra-typings": "^14.0.0",
|
|
28
28
|
"@supercharge/promise-pool": "^3.2.0",
|
|
29
29
|
"@types/node": "^25.2.1",
|
|
@@ -42,10 +42,5 @@
|
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"onchange": "^7.1.0",
|
|
44
44
|
"prettier": "3.5.3"
|
|
45
|
-
},
|
|
46
|
-
"overrides": {
|
|
47
|
-
"@aws-sdk/xml-builder": {
|
|
48
|
-
"fast-xml-parser": "^5.5.6"
|
|
49
|
-
}
|
|
50
45
|
}
|
|
51
46
|
}
|
package/deuxfleurs.toml
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
error_page = "error.html"
|
|
2
|
-
|
|
3
|
-
# [[redirects]]
|
|
4
|
-
# from = "TinyCorePure64-15.0.iso"
|
|
5
|
-
# to = "/bbc"
|
|
6
|
-
|
|
7
|
-
[[redirects]]
|
|
8
|
-
from = "/a"
|
|
9
|
-
to = "/def"
|
|
10
|
-
|
|
11
|
-
[[cors]]
|
|
12
|
-
allowed_origins = "foo"
|
|
13
|
-
|
|
14
|
-
[[headers]]
|
|
15
|
-
for = ["**/*.txt"]
|
|
16
|
-
compress = "gzip"
|
|
17
|
-
[headers.values]
|
|
18
|
-
Cache-Control = "hello44"
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
|
-
import { PromisePool } from "@supercharge/promise-pool";
|
|
11
|
-
import { GetBucketWebsiteCommand, HeadObjectCommand, } from "@aws-sdk/client-s3";
|
|
12
|
-
export function getBucketWebsite(client, Bucket) {
|
|
13
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
14
|
-
var _a, _b;
|
|
15
|
-
const cmd = new GetBucketWebsiteCommand({ Bucket });
|
|
16
|
-
const response = yield client.send(cmd);
|
|
17
|
-
if (response.RedirectAllRequestsTo) {
|
|
18
|
-
// NB: garage does not currently support RedirectAllRequestsTo so this should never happen
|
|
19
|
-
throw `remote website configuration: RedirectAllRequestsTo is specified; \
|
|
20
|
-
this is currently unsupported by dxfl`;
|
|
21
|
-
}
|
|
22
|
-
const index_page = (_a = response.ErrorDocument) === null || _a === void 0 ? void 0 : _a.Key;
|
|
23
|
-
const error_page = (_b = response.IndexDocument) === null || _b === void 0 ? void 0 : _b.Suffix;
|
|
24
|
-
let redirects = [];
|
|
25
|
-
if (response.RoutingRules) {
|
|
26
|
-
for (const rule of response.RoutingRules) {
|
|
27
|
-
// If no Condition is specified, then the redirect always applies, which is equivalent
|
|
28
|
-
// to matching on an empty prefix
|
|
29
|
-
let prefix = "";
|
|
30
|
-
let if_error = undefined;
|
|
31
|
-
if (rule.Condition) {
|
|
32
|
-
if (rule.Condition.HttpErrorCodeReturnedEquals) {
|
|
33
|
-
if (rule.Condition.HttpErrorCodeReturnedEquals == "404") {
|
|
34
|
-
if_error = 404;
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
37
|
-
// currently not supported by garage
|
|
38
|
-
throw `remote website configuration: 'if_error' specified with a different \
|
|
39
|
-
code than 404; this is currently unsupported by dxfl`;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
if (rule.Condition.KeyPrefixEquals) {
|
|
43
|
-
prefix = rule.Condition.KeyPrefixEquals;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
const hostname = rule.Redirect.HostName;
|
|
47
|
-
const code = rule.Redirect.HttpRedirectCode
|
|
48
|
-
? parseInt(rule.Redirect.HttpRedirectCode)
|
|
49
|
-
: undefined;
|
|
50
|
-
let protocol = undefined;
|
|
51
|
-
if (rule.Redirect.Protocol) {
|
|
52
|
-
if (rule.Redirect.Protocol == "http" ||
|
|
53
|
-
rule.Redirect.Protocol == "https") {
|
|
54
|
-
protocol = rule.Redirect.Protocol;
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
57
|
-
// currently not supported by garage
|
|
58
|
-
throw `remote website configuration: 'protocol' is neither http or https; \
|
|
59
|
-
this is currently unsupported by dxfl`;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
let to;
|
|
63
|
-
if (rule.Redirect.ReplaceKeyPrefixWith) {
|
|
64
|
-
to = {
|
|
65
|
-
kind: "replace_prefix",
|
|
66
|
-
prefix: rule.Redirect.ReplaceKeyPrefixWith,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
else if (rule.Redirect.ReplaceKeyWith) {
|
|
70
|
-
to = { kind: "replace", target: rule.Redirect.ReplaceKeyWith };
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
// not completely sure whether this is the correct behavior, but it should
|
|
74
|
-
// match garage's implementation
|
|
75
|
-
to = { kind: "replace", target: "" };
|
|
76
|
-
}
|
|
77
|
-
redirects.push({
|
|
78
|
-
kind: "bucket",
|
|
79
|
-
r: { prefix, if_error, hostname, code, protocol, to },
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return { index_page, error_page, redirects };
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
// XXX
|
|
87
|
-
const MD5METAFIELD = "dfl-md5sum";
|
|
88
|
-
export function getBucketFilesDetails(client, Bucket, files) {
|
|
89
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
90
|
-
let res = new Map();
|
|
91
|
-
function doFile(file) {
|
|
92
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
93
|
-
var _a;
|
|
94
|
-
const resp = yield client.send(new HeadObjectCommand({ Bucket, Key: file }));
|
|
95
|
-
if (resp.$metadata.httpStatusCode != 200) {
|
|
96
|
-
// TODO: better error handling?
|
|
97
|
-
throw resp;
|
|
98
|
-
}
|
|
99
|
-
res.set(file, {
|
|
100
|
-
md5: (_a = resp.Metadata) === null || _a === void 0 ? void 0 : _a[MD5METAFIELD],
|
|
101
|
-
redirect: resp.WebsiteRedirectLocation,
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
yield PromisePool.for(files).withConcurrency(50).process(doFile);
|
|
106
|
-
return res;
|
|
107
|
-
});
|
|
108
|
-
}
|
package/dist/errors.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
toto
|
package/public/d/b
DELETED
package/public/d/c
DELETED
package/public/d/dd/bla
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
bla
|
package/public/d/dd/foo.txt
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
test
|
package/public/def
DELETED
package/public/error.html
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
oops, 404 error!
|
package/public/index.html
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
hello there! 花見 toto312
|
package/public/index.html~
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
hello there! 花見 toto2
|
package/public/slt
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
abc
|
package/public/test.html
DELETED
|
File without changes
|
package/public/toto.jpg
DELETED
|
File without changes
|
package/public/toto.txt
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
abc defuieu
|
package/public/toto2.txt
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
abc
|