netstorage
Version:
A TypeScript API and CLI for the Akamai NetStorage REST interface
1,615 lines (1,614 loc) • 42.8 kB
JavaScript
var he = Object.defineProperty;
var ye = (e, t, r) => t in e ? he(e, t, { enumerable: !0, configurable: !0, writable: !0, value: r }) : e[t] = r;
var z = (e, t, r) => ye(e, typeof t != "symbol" ? t + "" : t, r);
import E, { createWriteStream as pe, createReadStream as we, readdirSync as ge } from "node:fs";
import $ from "node:path";
import G from "p-limit";
import q, { mkdir as $e, stat as D, readFile as be } from "node:fs/promises";
import ve from "crypto";
import { RateLimiter as S } from "limiter";
import { XMLParser as Le } from "fast-xml-parser";
import { createHash as Ce } from "node:crypto";
import ee from "micromatch";
import Ee from "klaw";
import { pipeline as Pe } from "node:stream";
import { request as Re } from "node:http";
import { request as ke } from "node:https";
import { promisify as xe } from "node:util";
import { URLSearchParams as Te } from "node:url";
import x from "winston";
import Me from "node:os";
import B from "chalk";
import Ae from "yocto-spinner";
import { getReasonPhrase as Ne } from "http-status-codes";
import ze from "yargs-parser";
class H extends TypeError {
constructor(r) {
super(`Missing or invalid \`${r}\` in configuration`);
z(this, "field");
this.name = "ConfigValidationError", this.field = r;
}
}
const qe = "netstorage", Nt = "3.1.2";
function j(e, t) {
const r = e.ssl ? "https" : "http", s = new URL(`${r}://${e.host}`), a = [];
return e.cpCode && a.push(e.cpCode), t && a.push(t.replace(/^\/+/, "")), s.pathname = a.join("/"), s.pathname !== "/" && (s.pathname = s.pathname.replace(/\/$/, "")), s.toString();
}
function U(e, t) {
if (typeof e != "string" || !e.trim())
throw new H(t);
}
function Oe(e) {
var n;
U(e.key, "key"), U(e.keyName, "keyName"), U(e.host, "host");
const t = e.ssl ?? !1, r = e.logLevel ?? "info", s = e.logger ?? De(r, qe), a = e.rateLimiters ?? He(e.rateLimitConfig), o = ((n = e == null ? void 0 : e.request) == null ? void 0 : n.timeout) ?? 1e4;
return {
key: e.key,
keyName: e.keyName,
host: e.host,
cpCode: e.cpCode,
ssl: t,
logLevel: r,
logger: s,
rateLimitConfig: e.rateLimitConfig,
rateLimiters: a,
request: {
...e.request,
timeout: o
},
lastReplPath: e.lastReplPath,
uri(i = "") {
return j(this, i);
}
};
}
class M extends Error {
/**
* Creates a new HttpError instance.
*
* @param {string} message - The error message.
* @param {number} code - The HTTP status code.
*/
constructor(r, s, a, o) {
super(r);
z(this, "code");
z(this, "method");
z(this, "url");
this.code = s, this.method = a ?? "", this.url = o ?? "";
}
}
async function Ie(e, { path: t, options: r }) {
return e.logger.verbose(e.uri(t), { method: "dir" }), P(
e,
"dir",
async () => k(e, t, {
request: { method: "GET" },
headers: { action: "dir" },
options: {
signal: C(e, r),
...r
}
})
);
}
async function te(e, { fromRemote: t, toLocal: r, options: s, shouldDownload: a }) {
return e.logger.verbose(e.uri(t), { method: "download" }), a && !await a() ? { status: { code: 0 } } : P(e, "download", async () => {
const o = pe(r), n = j(e, t), i = Y(e, t, {
action: "download"
}), { statusCode: c } = await ne(e, {
method: "GET",
url: n,
headers: i,
outputStream: o,
signal: (s == null ? void 0 : s.signal) ?? C(e, s)
});
return { status: { code: c } };
});
}
async function je(e, { path: t, options: r }) {
return e.logger.verbose(e.uri(t), { method: "du" }), P(
e,
"du",
async () => k(e, t, {
request: { method: "GET" },
headers: { action: "du" },
options: {
signal: C(e, r),
...r
}
})
);
}
async function zt(e, { path: t, options: r }) {
return e.logger.verbose(e.uri(t), { method: "mkdir" }), P(
e,
"mkdir",
async () => k(e, t, {
request: { method: "PUT" },
headers: { action: "mkdir" },
options: {
signal: C(e, r),
...r
}
})
);
}
async function qt(e, { path: t, date: r, options: s }) {
if (!(r instanceof Date))
throw new TypeError("The date has to be an instance of Date");
return e.logger.verbose(`${e.uri(t)}, date: ${r.toISOString()}`, {
method: "mtime"
}), P(
e,
"mtime",
async () => k(e, t, {
request: { method: "PUT" },
headers: {
action: "mtime",
mtime: Math.floor(r.getTime() / 1e3).toString()
},
options: {
signal: C(e, s),
...s
}
})
);
}
async function Ot(e, { pathFrom: t, pathTo: r, options: s }) {
return e.logger.verbose(`from: ${e.uri(t)}, to: ${r}`, {
method: "rename"
}), P(
e,
"rename",
async () => k(e, t, {
request: { method: "PUT" },
headers: {
action: "rename",
destination: r
},
options: {
signal: C(e, s),
...s
}
})
);
}
async function re(e, { path: t, options: r }) {
return e.logger.verbose(e.uri(t), { method: "delete" }), P(
e,
"rm",
async () => k(e, t, {
request: { method: "PUT" },
headers: { action: "delete" },
options: {
signal: C(e, r),
...r
}
})
);
}
async function se(e, { path: t, options: r }) {
return e.logger.verbose(e.uri(t), { method: "rmdir" }), P(
e,
"rmdir",
async () => k(e, t, {
request: { method: "PUT" },
headers: { action: "rmdir" },
options: {
signal: C(e, r),
...r
}
})
);
}
async function Se(e, { path: t, options: r }) {
return e.logger.verbose(e.uri(t), { method: "stat" }), P(
e,
"stat",
async () => k(e, t, {
request: { method: "GET" },
headers: { action: "stat" },
options: {
signal: C(e, r),
...r
}
})
);
}
async function It(e, { pathFileTo: t, pathSymlink: r, options: s }) {
return e.logger.verbose(
`fileTo: ${e.uri(t)}, symlink: ${e.uri(r)}`,
{
method: "symlink"
}
), P(
e,
"symlink",
async () => k(e, r, {
request: { method: "PUT" },
headers: {
action: "symlink",
target: t
},
options: {
signal: C(e, s),
...s
}
})
);
}
async function ae(e, { fromLocal: t, toRemote: r, options: s, shouldUpload: a }) {
return e.logger.verbose(e.uri(r), { method: "upload" }), a && !await a() ? { status: { code: 0 } } : P(e, "upload", async () => {
const o = we(t), n = j(e, r), i = Y(e, r, {
action: "upload",
"upload-type": "binary"
}), { body: c, statusCode: d } = await ne(e, {
method: "PUT",
url: n,
headers: i,
inputStream: o,
signal: (s == null ? void 0 : s.signal) ?? C(e, s)
});
return le(c ?? "", d);
});
}
async function k(e, t, r = {}) {
var c;
const s = j(e, t), a = Y(e, t, r.headers ?? {}), o = ((c = r.request) == null ? void 0 : c.method) ?? "GET";
e.logger.debug(
`Requesting: ${s} (path: ${t}) meta: ${JSON.stringify({
method: o,
headers: a,
body: r.body
})}`,
{ method: "sendRequest" }
);
const n = await fetch(s, {
method: o,
headers: a,
body: r.body,
signal: C(e, r.options)
}), i = await n.text();
if (n.status >= 300) {
let d = `Unexpected HTTP ${n.status} received from server for request to: ${t}`;
throw d += `. Body: ${i}`, new M(d, n.status, o, s);
}
return e.logger.debug(
`Response for path: ${t} meta: ${JSON.stringify({
status: n.status,
body: i
})}`,
{ method: "sendRequest" }
), le(i, n.status);
}
const oe = xe(Pe);
async function Be(e, t) {
e ? await oe(e, t) : t.end();
}
function Ue(e, t, r) {
if (!t || r != null && r.aborted) return;
const s = setTimeout(() => {
e.destroy(new Error("Request timed out"));
}, t);
e.on("close", () => clearTimeout(s));
}
async function We(e, t, r) {
if (t) {
const s = e;
let a = 0;
s.on("data", (o) => {
a += o.length, r == null || r(a);
}), await oe(s, t);
return;
}
return await new Promise((s) => {
let a = "";
e.setEncoding("utf8"), e.on("data", (o) => {
a += o, r == null || r(a.length);
}), e.on("end", () => s(a));
});
}
function _e(e, t, r, s) {
const a = s ? "?" + new Te(s).toString() : "";
return `${e}://${t}${r}${a}`;
}
function W(e, t, r, s, a) {
const o = [
`makeStreamRequest failed: ${e} ${t}`,
s ? ` - HTTP ${s}` : "",
a ? `
${a}` : "",
r != null && r.message && !s ? `
${r.message}` : ""
].filter(Boolean).join(""), n = s ?? 500;
return new M(o, n, e, t);
}
async function ne(e, {
method: t = "GET",
headers: r = {},
inputStream: s,
outputStream: a,
signal: o,
timeout: n,
onProgress: i,
...c
}) {
const d = c.url ?? _e(
c.protocol ?? "http",
c.host ?? "",
c.path ?? "/",
c.query
), l = new URL(d), m = l.protocol === "https:" ? ke : Re, u = l.pathname + l.search;
return new Promise((f, p) => {
const y = m(
{ host: l.hostname, path: u, method: t, headers: r, signal: o },
async (h) => {
if (!h.statusCode)
return p(new Error("No status code in response"));
e.logger.verbose(`Received ${h.statusCode} from ${l.href}`, {
method: t
});
try {
const b = await We(h, a, i);
if (h.statusCode >= 400)
return p(
W(
t,
l.toString(),
void 0,
h.statusCode,
b
)
);
f({ statusCode: h.statusCode, body: b });
} catch (b) {
p(W(t, l.toString(), b));
}
}
);
if (o != null && o.aborted) {
y.destroy(new Error("Request aborted"));
return;
}
const w = () => y.destroy(new Error("Request aborted"));
o == null || o.addEventListener("abort", w), y.on("close", () => o == null ? void 0 : o.removeEventListener("abort", w)), e.logger.verbose(`Requesting ${l.href}`, { method: t }), y.on("error", p), Ue(y, n, o), Be(s, y).catch((h) => {
p(W(t, l.toString(), h));
});
});
}
async function J(e, t) {
var a;
const { path: r, kind: s } = t;
if (e.logger.verbose(e.uri(r), { method: "inspectRemotePath" }), s !== "directory")
try {
const o = await Se(e, { path: r }), n = (a = o == null ? void 0 : o.stat) == null ? void 0 : a.file;
if ((n == null ? void 0 : n.type) === "file") return { file: n };
} catch (o) {
if (!(o instanceof M && o.code === 404)) throw o;
if (s === "file") return {};
}
if (s !== "file")
try {
return { du: await je(e, { path: r }) };
} catch (o) {
if (!(o instanceof M && o.code === 404)) throw o;
}
return {};
}
async function Fe(e, t) {
var s;
const r = await J(e, { path: t, kind: "file" });
return ((s = r == null ? void 0 : r.file) == null ? void 0 : s.type) === "file";
}
async function jt(e, t) {
var s, a;
const r = await J(e, {
path: t,
kind: "directory"
});
return !!((a = (s = r == null ? void 0 : r.du) == null ? void 0 : s.du) != null && a.directory);
}
async function St(e, t) {
var a;
const r = /* @__PURE__ */ new Map();
let s = 0;
for await (const o of O(e, t))
if (r.set(o.depth, [...r.get(o.depth) ?? [], o]), (a = o == null ? void 0 : o.file) != null && a.size) {
const n = parseInt(o.file.size, 10);
Number.isNaN(n) || (s += n);
}
return {
depthBuckets: Array.from(r, ([o, n]) => ({
depth: o,
entries: n
})),
totalSize: s
};
}
async function Bt(e, t) {
const r = [];
for await (const s of O(e, t))
await t.predicate(s) && r.push(s);
return r;
}
async function* O(e, { path: t, maxDepth: r, shouldInclude: s, addSyntheticRoot: a }) {
const o = t.replace(/\/+$/, "");
yield* ie(
e,
o,
o,
r,
s,
0,
a
);
}
async function* ie(e, t, r, s, a, o = 0, n) {
var d, l, m;
if (typeof s == "number" && o > s) return;
let i;
try {
i = await Ie(e, { path: t });
} catch (u) {
e.logger.debug(`Failed to walk ${e.uri(t)}: ${u}`, {
method: "remoteWalk"
});
return;
}
if (n && o === 0 && ((d = i.stat) != null && d.directory)) {
const u = {
file: {
name: "__synthetic_root__",
type: "dir",
implicit: "false",
bytes: "0",
files: "0",
mtime: ""
},
path: r,
parent: "",
relativePath: "",
depth: 0
};
(a ? await a(u) : !0) && (yield u);
}
const c = Array.isArray((l = i.stat) == null ? void 0 : l.file) ? i.stat.file : (m = i.stat) != null && m.file ? [i.stat.file] : [];
for (const u of c) {
const f = [t, u.name].join("/").replace(/\/{2,}/g, "/"), p = f.slice(r.length).replace(/^\/+/, ""), y = {
file: u,
path: f,
parent: t,
depth: o,
relativePath: p
};
(a ? await a(y) : !0) && (yield y), u.type === "dir" && (yield* ie(
e,
f,
r,
s,
a,
o + 1,
n
));
}
}
async function Ut(e, {
remotePath: t,
dryRun: r = !1,
onRemove: s,
onSkip: a,
shouldRemove: o
}) {
const { logger: n } = e;
n.verbose(`Removing ${e.uri(t)}`, {
method: "removeDirectory"
});
const i = [];
for await (const l of O(e, {
path: t,
addSyntheticRoot: !0
}))
i.push(l);
const c = [...i].reverse(), d = [];
for (const l of c) {
const { path: m, file: u } = l;
if (o && !await o(l)) {
n.debug(`Skipping via shouldRemove: ${m}`, {
method: "removeDirectory"
}), a == null || a({ remotePath: m, reason: "filtered" });
continue;
}
if (r) {
n.info(`[dryRun] Would remove ${m}`, {
method: "removeDirectory"
}), a == null || a({ remotePath: m, reason: "dryRun" });
continue;
}
try {
if (u.type === "file" || u.type === "symlink")
await re(e, { path: m });
else if (u.type === "dir") {
if (u.implicit === "true") continue;
try {
await se(e, { path: m });
} catch (f) {
n.debug(`Ignoring rmdir error for ${m}`, {
method: "removeDirectory",
error: f
});
continue;
}
} else
continue;
s == null || s({ remotePath: m }), d.push({ remotePath: m, status: { code: 200 } });
} catch (f) {
n.error(`Failed to remove ${m}; error: ${f}`, {
method: "removeDirectory"
}), a == null || a({ remotePath: m, reason: "error", error: f });
}
}
return d;
}
async function Wt(e, {
localPath: t,
remotePath: r,
overwrite: s = !0,
followSymlinks: a = !1,
ignore: o = [],
dryRun: n = !1,
maxConcurrency: i = 5,
onUpload: c,
onSkip: d,
shouldUpload: l
}) {
const { logger: m } = e, u = G(i), f = [], p = [];
m.verbose(`Uploading ${t} → ${e.uri(r)}`, {
method: "uploadDirectory"
});
for await (const y of ue(t, {
ignore: o,
followSymlinks: a
})) {
if (y.isDirectory) continue;
const w = $.posix.join(
r,
y.relativePath.split($.sep).join("/")
);
if (l && !await l(y)) {
m.debug(`Skipping via shouldUpload: ${y.localPath}`, {
method: "uploadDirectory"
}), d == null || d({
localPath: y.localPath,
remotePath: w,
reason: "filtered"
});
continue;
}
const h = u(async () => {
const b = async (v) => {
d == null || d({ localPath: y.localPath, remotePath: w, reason: v });
};
if (n)
return m.info(`[dryRun] Would upload ${y.localPath} → ${w}`, {
method: "uploadDirectory"
}), b("dryRun");
if (!s && await Fe(e, w))
return m.debug(`Skipping existing file: ${w}`, {
method: "uploadDirectory"
}), b("overwriteFalse");
try {
const v = await ae(e, {
fromLocal: y.localPath,
toRemote: w
});
c == null || c({ localPath: y.localPath, remotePath: w }), p.push({
localPath: y.localPath,
remotePath: w,
status: v.status
});
} catch (v) {
m.error(
`Failed to upload ${y.localPath} → ${e.uri(w)}; error: ${v}`,
{ method: "uploadDirectory" }
), await b("error");
}
});
f.push(h);
}
return await Promise.all(f), p;
}
async function Ge(e) {
try {
return (await D(e)).isFile();
} catch {
return !1;
}
}
async function _t(e, t) {
const {
remotePath: r,
localPath: s,
overwrite: a = !1,
dryRun: o = !1,
maxConcurrency: n = 5,
onDownload: i,
onSkip: c,
shouldDownload: d
} = t;
e.logger.verbose(
`Downloading ${e.uri(r)} → ${$.resolve(s)}`,
{
method: "downloadDirectory"
}
);
const l = G(n), m = [], u = [];
for await (const f of O(e, { path: r })) {
const p = $.join(s, f.relativePath);
if (f.file.type === "dir")
continue;
const y = l(async () => {
if (d && !await d(f)) {
e.logger.debug(`Skipping via shouldDownload: ${f.path}`, {
method: "downloadDirectory"
}), c == null || c({
remotePath: f.path,
localPath: p,
reason: "filtered"
});
return;
}
if (!a && await Ge(p)) {
e.logger.debug(`Skipping existing file: ${p}`, {
method: "downloadDirectory"
}), c == null || c({ remotePath: f.path, localPath: p, reason: "exists" });
return;
}
if (o) {
e.logger.info(`[dryRun] Would download ${f.path} → ${p}`, {
method: "downloadDirectory"
}), c == null || c({ remotePath: f.path, localPath: p, reason: "dryRun" });
return;
}
try {
e.logger.verbose(`Downloading ${f.path} → ${p}`, {
method: "downloadDirectory"
}), await $e($.dirname(p), { recursive: !0 });
const w = await te(e, {
fromRemote: f.path,
toLocal: p
});
i == null || i({ remotePath: f.path, localPath: p }), u.push({
remotePath: f.path,
localPath: p,
status: w.status
});
} catch (w) {
e.logger.error(
`Failed to download ${f.path} → ${p}; error: ${w}`,
{
method: "downloadDirectory"
}
), c == null || c({
remotePath: f.path,
localPath: p,
reason: "error",
error: w
});
}
});
m.push(y);
}
return await Promise.all(m), u;
}
async function Ft(e, {
localPath: t,
remotePath: r,
dryRun: s = !1,
conflictRules: a,
compareStrategy: o = "exists",
syncDirection: n = "upload",
conflictResolution: i = "preferLocal",
deleteExtraneous: c = "none",
onTransfer: d,
onDelete: l,
onSkip: m,
maxConcurrency: u = 5
}) {
const f = {
transferred: [],
skipped: [],
deleted: []
}, p = {
onTransfer: (g) => {
f.transferred.push(g), d == null || d(g);
},
onSkip: (g) => {
f.skipped.push(g), m == null || m(g);
},
onDelete: (g) => {
f.deleted.push(g), l == null || l(g);
}
};
e.logger.verbose(
de({ localPath: t, remotePath: r, syncDirection: n }),
{ method: "syncDirectory" }
), (n === "download" || n === "both") && await E.promises.mkdir(t, { recursive: !0 });
const y = /* @__PURE__ */ new Map(), w = /* @__PURE__ */ new Map(), h = /* @__PURE__ */ new Map(), b = /* @__PURE__ */ new Map();
for await (const g of ue(t, { includeDirs: !0 })) {
const L = g.relativePath.split($.sep).join("/");
g.isDirectory ? b.set(L, g.localPath) : y.set(L, g.localPath);
}
for await (const g of O(e, { path: r }))
g.file.type === "dir" ? h.set(g.relativePath, g.file) : w.set(g.relativePath, g.file);
await me({
config: e,
deleteExtraneous: c,
dryRun: s,
localPath: t,
remotePath: r,
localFiles: y,
remoteFiles: w,
localDirs: b,
remoteDirs: h,
onDelete: p.onDelete
});
const v = G(u);
if (n === "upload" || n === "both") {
e.logger.verbose("Beginning upload phase", {
method: "syncDirectory"
});
const g = [];
for (const [L, A] of y) {
const N = $.posix.join(r, L), T = w.get(L);
g.push(
v(
() => F({
config: e,
direction: "upload",
localPath: A,
remotePath: N,
remoteFileMeta: T,
dryRun: s,
compareStrategy: o,
conflictRules: a,
conflictResolution: i,
onTransfer: p.onTransfer,
onSkip: p.onSkip
})
)
);
}
await Promise.all(g);
}
if (n === "download" || n === "both") {
e.logger.verbose("Beginning download phase", {
method: "syncDirectory"
});
const g = [];
for (const [L, A] of w) {
const N = $.join(t, L), T = $.posix.join(r, L);
g.push(
v(
() => F({
config: e,
direction: "download",
localPath: N,
remotePath: T,
remoteFileMeta: A,
dryRun: s,
compareStrategy: o,
conflictRules: a,
conflictResolution: i,
onTransfer: p.onTransfer,
onSkip: p.onSkip
})
)
);
}
await Promise.all(g);
}
return f;
}
async function Gt(e, {
localPath: t,
remotePath: r,
dryRun: s = !1,
conflictRules: a,
remoteFileMeta: o,
compareStrategy: n = "exists",
syncDirection: i = "upload",
conflictResolution: c = "preferLocal",
deleteExtraneous: d = "none",
onTransfer: l,
onSkip: m,
onDelete: u
}) {
e.logger.verbose(
de({ localPath: t, remotePath: r, syncDirection: i }),
{
method: "syncFile"
}
);
const f = {
transferred: [],
skipped: [],
deleted: []
}, p = {
onTransfer: (y) => {
f.transferred.push(y), l == null || l(y);
},
onSkip: (y) => {
f.skipped.push(y), m == null || m(y);
},
onDelete: (y) => {
f.deleted.push(y), u == null || u(y);
}
};
return await me({
config: e,
deleteExtraneous: d,
dryRun: s,
localPath: t,
remotePath: r,
singleFile: !0,
onDelete: p.onDelete
}), await F({
config: e,
direction: i,
localPath: t,
remotePath: r,
remoteFileMeta: o,
dryRun: s,
compareStrategy: n,
conflictRules: a,
conflictResolution: c,
onTransfer: p.onTransfer,
onSkip: p.onSkip
}), f;
}
function Dt(e) {
var r;
const t = /* @__PURE__ */ new Map();
for (const s of [...e].reverse()) {
const a = s.file.type === "file", o = s.file.type === "dir", n = a ? Number(s.file.size ?? 0) : ((r = t.get(s.path)) == null ? void 0 : r.aggregatedSize) ?? 0;
if (t.has(s.parent) || t.set(s.parent, { aggregatedSize: 0 }), s.depth > 0) {
const i = t.get(s.parent);
i && (i.aggregatedSize += n);
}
o && (t.has(s.path) || t.set(s.path, { aggregatedSize: n }));
}
return t;
}
function Y(e, t, r = {}) {
const s = new URLSearchParams({
version: "1",
action: "du",
format: "xml",
...r
}).toString(), a = [
5,
"0.0.0.0",
"0.0.0.0",
Math.floor(Date.now() / 1e3),
Ye(),
e.keyName
].join(", "), o = e.cpCode && !t.startsWith(`/${e.cpCode}`) ? `/${e.cpCode}${t.startsWith("/") ? t : `/${t}`}` : t, n = [
a + o.replace(/\/$/, ""),
`x-akamai-acs-action:${s}`,
""
].join(`
`), i = ve.createHmac("sha256", e.key).update(n).digest("base64");
return {
"X-Akamai-ACS-Action": s,
"X-Akamai-ACS-Auth-Data": a,
"X-Akamai-ACS-Auth-Sign": i
};
}
x.addColors({
error: "red",
warn: "yellow",
info: "green",
http: "magenta",
verbose: "cyan",
debug: "blue",
silly: "gray"
});
function De(e = "info", t = "") {
const r = x.format.colorize();
return x.createLogger({
level: e,
format: x.format.combine(
x.format.label({
label: t
}),
x.format.timestamp({ format: "HH:mm:ss" }),
x.format.printf((s) => {
const { timestamp: a, level: o, message: n, label: i, method: c } = s, d = o.toUpperCase(), l = r.colorize(o, d), m = c ? r.colorize("info", `[${c}]`) : "";
return `[${l}] ${a} [${i}]: ${m} ${n}`;
})
),
transports: [new x.transports.Console()]
});
}
function He(e) {
const { read: t = 800, write: r = 25, dir: s = 50, time: a = 1e3 } = e || {};
return {
readLimiter: new S({ tokensPerInterval: t, interval: a }),
writeLimiter: new S({ tokensPerInterval: r, interval: a }),
dirLimiter: new S({ tokensPerInterval: s, interval: a })
};
}
function Je(e, t) {
const s = {
dir: "dirLimiter",
download: "readLimiter",
du: "readLimiter",
mkdir: "writeLimiter",
mtime: "writeLimiter",
rename: "writeLimiter",
rm: "writeLimiter",
rmdir: "writeLimiter",
stat: "readLimiter",
symlink: "writeLimiter",
upload: "writeLimiter"
}[e];
if (!s)
throw new Error(`Unsupported method for limiter selection: ${e}`);
return t[s];
}
function _(e, t = 2) {
if (e === 0) return "0 B";
const r = 1024, s = t < 0 ? 0 : t, a = ["B", "KB", "MB", "GB", "TB", "PB"], o = Math.floor(Math.log(e) / Math.log(r));
return `${parseFloat((e / Math.pow(r, o)).toFixed(s))} ${a[o]}`;
}
function ce(e) {
return new Date(Number(e) * 1e3).toISOString().replace("T", " ").split(".")[0];
}
function Ye() {
let e = "", t = 0;
for (let r = 0; r < 6; r++)
(r & 3) === 0 && (t = Math.random() * 4294967296), e += (t >>> ((r & 3) << 3) & 255).toString();
return e + process.pid;
}
function le(e, t) {
return e.trimStart().startsWith("<?xml") ? new Le({
ignoreAttributes: !1,
attributeNamePrefix: ""
}).parse(e) : { status: { code: t } };
}
function C(e, t) {
var r;
if (t != null && t.signal) return t.signal;
if ((t == null ? void 0 : t.timeout) != null) return AbortSignal.timeout(t.timeout);
if (((r = e.request) == null ? void 0 : r.timeout) != null)
return AbortSignal.timeout(e.request.timeout);
}
function Ht(e, t) {
var s;
const r = (s = t == null ? void 0 : t.stat) == null ? void 0 : s.file;
return !r || typeof r != "object" || Object.keys(r).length === 0 ? (e.logger.info("Remote file metadata is missing or empty.", {
method: "isRemoteMissing"
}), !0) : !1;
}
async function Xe(e, t, r) {
var i;
const s = (i = r == null ? void 0 : r.stat) == null ? void 0 : i.file, a = typeof s.size == "string" ? parseInt(s.size, 10) : s.size;
if (!a)
return e.logger.info("Remote size is missing or invalid.", {
method: "isSizeMismatch"
}), !0;
const o = await D(t), n = o.size !== a;
return e.logger.info(
`Local size: ${o.size}, Remote size: ${a}, Mismatch: ${n}`,
{ method: "isSizeMismatch" }
), n;
}
async function Ke(e, t, r) {
var c;
const a = ((c = r == null ? void 0 : r.stat) == null ? void 0 : c.file).mtime;
if (!a)
return e.logger.info("Remote mtime is missing.", { method: "isMtimeNewer" }), !1;
const o = await D(t), n = new Date(parseInt(a, 10) * 1e3).getTime(), i = o.mtimeMs > n;
return e.logger.info(
`Local mtime: ${o.mtimeMs}, Remote mtime: ${n}, Is newer: ${i}`,
{ method: "isMtimeNewer" }
), i;
}
async function Ve(e, t, r) {
var i;
const s = (i = r == null ? void 0 : r.stat) == null ? void 0 : i.file;
if (!s || !s.md5)
return e.logger.info("Remote MD5 is missing or file is invalid.", {
method: "isChecksumMismatch"
}), !0;
const a = await be(t), o = Ce("md5").update(a).digest("hex"), n = o !== s.md5;
return e.logger.info(
`Local MD5: ${o}, Remote MD5: ${s.md5}, Mismatch: ${n}`,
{ method: "isChecksumMismatch" }
), n;
}
async function* ue(e, {
ignore: t = [],
followSymlinks: r = !1,
includeDirs: s = !1,
onEnterDir: a
} = {}) {
var c;
const o = /* @__PURE__ */ new Set(), n = $.resolve(e), i = Ee(n, { preserveSymlinks: !r });
for await (const d of i) {
const l = $.relative(n, d.path);
if (l === "" || t.length && ee.some(l, t)) continue;
const m = d.stats, u = m.isDirectory(), f = (c = m.isSymbolicLink) == null ? void 0 : c.call(m);
u && a && a(d.path, l), !(u && !s) && (f && !r || o.has(d.path) || (o.add(d.path), yield {
localPath: d.path,
relativePath: l,
isDirectory: u
}));
}
}
function Qe(e, t, r, s) {
const a = Math.min(t * 2 ** e, r);
return s ? Math.floor(Math.random() * a) : a;
}
function Ze(e, t, r = "unknown") {
const a = ["ECONNRESET", "ETIMEDOUT", "EAI_AGAIN", "ENOTFOUND"].some((o) => t.message.includes(o));
return a || e.logger.debug(
`Non-retryable system error encountered: ${t.message}`,
{
method: r
}
), a;
}
async function P(e, t, r, s) {
const a = Je(t, e.rateLimiters), o = 3, n = 300, i = 2e3, c = !0, d = async () => {
await a.removeTokens(1);
}, l = (s == null ? void 0 : s.shouldRetry) ?? ((u) => u instanceof M && [429, 500, 502, 503, 504].includes(u.code) ? !0 : u instanceof Error ? Ze(e, u, t) : !1), m = (s == null ? void 0 : s.onRetry) ?? ((u, f, p) => {
const y = u instanceof Error ? u.message : "Unknown error";
e.logger.verbose(
`Retry ${f} due to error: ${y}. Retrying in ${p}ms.`,
{ method: t }
);
});
for (let u = 0; u <= o; u++)
try {
return await d(), await r();
} catch (f) {
if (u >= o || !l(f))
throw f;
const p = Qe(u, n, i, c);
m(f, u + 1, p), await new Promise((y) => setTimeout(y, p));
}
throw new Error("Unexpected execution path in withRetries");
}
function et({
compareStrategy: e,
direction: t,
action: r,
conflictResolution: s
}) {
if (e === "exists" || r === t) return !0;
if (!r) {
const a = s === "preferLocal" && ["upload", "both"].includes(t), o = s === "preferRemote" && ["download", "both"].includes(t);
return a || o;
}
return !1;
}
function tt(e) {
return { stat: { file: e } };
}
async function rt({
config: e,
direction: t,
localAbsPath: r,
remoteFile: s,
compareStrategy: a = "exists"
}) {
const o = tt(s);
switch (a) {
case "size":
return await Xe(e, r, o);
case "mtime":
return await Ke(e, r, o);
case "checksum":
return await Ve(e, r, o);
case "exists":
return (t === "upload" || t === "both") && s === void 0 || (t === "download" || t === "both") && !await q.stat(r).then(() => !0).catch(() => !1);
default:
return !1;
}
}
function st({
relativePath: e,
conflictRules: t
}) {
if (t) {
for (const r in t)
if (ee.isMatch(e, r))
return t[r];
return "skip";
}
}
function de({
localPath: e,
remotePath: t,
syncDirection: r
}) {
const a = {
upload: "→",
download: "←",
both: "↔"
}[r] ?? "?";
return `Syncing ${e} ${a} ${t} [${r}]`;
}
async function F({
config: e,
direction: t,
localPath: r,
remotePath: s,
remoteFileMeta: a,
dryRun: o,
compareStrategy: n,
conflictRules: i,
conflictResolution: c,
onTransfer: d,
onSkip: l
}) {
const m = $.basename(r), u = st({
relativePath: m,
conflictRules: i
});
if (u === "skip") {
l == null || l({
direction: t,
localPath: r,
remotePath: s,
reason: "conflictRules skip"
});
return;
}
const f = await rt({
config: e,
direction: t,
localAbsPath: r,
remoteFile: a,
compareStrategy: n
}), p = et({
compareStrategy: n,
direction: t,
action: u,
conflictResolution: c
});
if (!f || !p) {
l == null || l({ direction: t, localPath: r, remotePath: s, reason: n });
return;
}
o ? e.logger.info(
`[dryRun] Would ${t} ${r} ${t === "upload" ? "→" : "←"} ${s}`
) : t === "upload" || t === "both" && a === void 0 ? await ae(e, { fromLocal: r, toRemote: s }) : (t === "download" || t === "both" && a !== void 0) && (await q.mkdir($.dirname(r), { recursive: !0 }), await te(e, { fromRemote: s, toLocal: r })), d == null || d({ direction: t, localPath: r, remotePath: s });
}
async function me({
config: e,
deleteExtraneous: t,
dryRun: r,
localPath: s,
remotePath: a,
localFiles: o,
remoteFiles: n,
onDelete: i,
singleFile: c = !1,
localDirs: d,
remoteDirs: l
}) {
var f, p;
const m = c ? /* @__PURE__ */ new Map([[$.basename(s), !0]]) : o ?? /* @__PURE__ */ new Map(), u = c ? /* @__PURE__ */ new Map([[$.basename(s), !0]]) : n ?? /* @__PURE__ */ new Map();
if (!(!m.size && !u.size)) {
if (t === "remote" || t === "both") {
e.logger.verbose("Checking for extraneous remote files to delete", {
method: "deleteExtraneous"
});
const y = new Set(
[...(l == null ? void 0 : l.entries()) ?? []].filter(
([, h]) => h.type === "dir" && (h.implicit === void 0 || h.implicit === "false") && h.bytes === "0"
).map(([h]) => h)
);
for (const [h] of u)
if (!m.has(h)) {
const b = $.posix.join(a, h);
r ? e.logger.info(`[dryRun] Would delete remote file at ${b}`) : (await re(e, { path: b }), c || n == null || n.delete(h), i == null || i(b)), y.add($.posix.dirname(h));
}
const w = [...y].sort(
(h, b) => b.split("/").length - h.split("/").length
);
for (const h of w) {
const b = $.posix.join(a, h), v = l == null ? void 0 : l.get(h);
if (!v || (v == null ? void 0 : v.implicit) === "true") continue;
let g = (v == null ? void 0 : v.bytes) === "0";
if (!g)
try {
const { du: L } = await J(e, {
path: b,
kind: "directory"
});
g = ((p = (f = L == null ? void 0 : L.du) == null ? void 0 : f["du-info"]) == null ? void 0 : p.bytes) === "0";
} catch {
continue;
}
if (g)
if (r)
e.logger.info(
`[dryRun] Would remove empty remote directory ${b}`
);
else
try {
await se(e, { path: b }), i == null || i(b);
} catch {
e.logger.verbose(
`Failed to remove empty remote directory ${b}`
);
}
}
}
if (t === "local" || t === "both") {
e.logger.verbose("Checking for extraneous local files to delete", {
method: "deleteExtraneous"
});
for (const [y] of m)
if (!u.has(y)) {
const w = $.join(s, y);
r ? e.logger.info(`[dryRun] Would delete local file at ${w}`) : (await q.rm(w), c || o == null || o.delete(y), i == null || i(w));
}
if (d != null && d.size) {
const y = [...d.keys()].sort(
(w, h) => h.split("/").length - w.split("/").length
);
for (const w of y) {
const h = d.get(w);
if (h)
try {
(await q.readdir(h)).length === 0 && (r ? e.logger.info(
`[dryRun] Would remove empty local directory ${h}`
) : (await q.rmdir(h), i == null || i(h)));
} catch {
}
}
}
}
}
}
let X;
function Jt(e) {
X = e;
}
function Yt() {
return X;
}
function Xt() {
X = void 0;
}
function Kt(e) {
const t = parseInt(e, 10);
if (isNaN(t)) throw new Error("Invalid timeout value");
return t;
}
function Vt(e) {
const t = parseInt(e, 10);
if (isNaN(t)) throw new Error("Invalid cancel-after value");
return t;
}
function Qt(e) {
if (e != null) {
const t = new AbortController();
return setTimeout(() => t.abort(), e), t.signal;
}
}
function Zt(e, t) {
var r;
if (e instanceof H)
t.error(e.message), t.info(`$ npx netstorage config set [${e.field}] [value]`);
else if (e instanceof M) {
const s = Ne(e.code) || "Unknown";
t.error(
`HTTP ${e.code} ${s} (${(r = e.method) == null ? void 0 : r.toUpperCase()} ${e.url})`
);
} else
t.error("Unexpected error"), t.error(e);
}
function er(e, t) {
const r = [
"error",
"warn",
"info",
"verbose",
"debug",
"silly"
], s = e ?? (t ? "verbose" : void 0);
return r.includes(s) ? { logLevel: s } : void 0;
}
function tr(e, t = !1) {
const r = t ? JSON.stringify(e, null, 2) : JSON.stringify(e);
process.stdout.write(r + `
`);
}
function rr(e) {
const t = { dir: 0, file: 1, symlink: 2 };
e.sort((r, s) => (t[r.type] ?? 3) - (t[s.type] ?? 3) || r.name.localeCompare(s.name));
}
function sr(e, t) {
const r = e.type.slice(0, 4).padEnd(4), s = e.type === "file" && e.size ? _(Number(e.size)).padStart(t) : "--".padStart(t), a = ce(e.mtime), o = K(
e.type === "dir" ? `${e.name}/` : e.name,
e.type
);
return `${r} ${s} ${a} ${o}`;
}
function ar(e) {
return K(
e.type === "dir" ? `${e.name}/` : e.name,
e.type
);
}
function at(e, t) {
const r = $.posix.normalize(
e != null && e.startsWith("/") ? e : `${t}/${e ?? ""}`
);
return (r.startsWith("/") ? r : `/${r}`).replace(
/\/+$/,
""
) || "/";
}
function K(e, t) {
switch (t) {
case "dir":
return B.cyan(e);
case "symlink":
return B.blue(e);
default:
return B.gray(e);
}
}
function or(e) {
if (!e.cpCode)
throw new H("cpCode");
}
function ot(e) {
const t = $.dirname(e || ""), r = $.basename(e || "");
try {
const s = t ? $.resolve(process.cwd(), t) : process.cwd();
return ge(s, { withFileTypes: !0 }).map((a) => a.isDirectory() ? a.name + "/" : a.name).filter((a) => a.startsWith(r)).sort();
} catch {
return [];
}
}
function nr(e, t, r, s, a) {
var u, f;
const o = /\s$/.test(e), i = (o ? t.length : t.length - 1) - 1, c = (u = a.localArgIndices) == null ? void 0 : u.has(i), d = (f = a.remoteArgIndices) == null ? void 0 : f.has(i), l = o ? "" : r, m = c ? ot(l) : d ? s.filter((p) => p.startsWith(l)) : [];
return [m.length ? m : [], l];
}
function ir(e) {
const t = ze(e.trim(), {
configuration: {
"camel-case-expansion": !1,
"dot-notation": !1,
"parse-numbers": !1
}
}), [r = ""] = t._, s = String(r), a = t._.slice(1).map(String), o = Object.entries(t).filter(([n]) => n !== "_").flatMap(([n, i]) => {
const c = n.length === 1 ? `-${n}` : `--${n}`;
return i === !0 ? [c] : Array.isArray(i) ? i.flatMap((d) => [c, String(d)]) : [c, String(i)];
});
return { command: s, args: a, options: o };
}
function cr(e, t, r) {
var o;
const s = [], a = Math.max(...Object.keys(t).map(Number));
for (let n = 0; n <= a; n++) {
const i = t[n], c = e[n];
if (i === "passthrough") {
c && s.push(c);
continue;
}
if (i === "local") {
s.push(c ?? "");
continue;
}
if (c !== void 0) {
s.push(at(c, r));
continue;
}
const d = (o = Object.entries(t).find(
([, m]) => m === "local"
)) == null ? void 0 : o[0], l = d !== void 0 ? e[Number(d)] : void 0;
s.push(l ? $.posix.join(r, $.basename(l)) : r);
}
return s;
}
async function nt() {
const e = ["netstorage.json"];
for (const t of e) {
const r = $.resolve(process.cwd(), t);
if (E.existsSync(r)) {
const s = E.readFileSync(r, "utf-8");
return JSON.parse(s);
}
}
return {};
}
function I(e) {
return Object.fromEntries(
Object.entries(e).filter(([, t]) => t !== void 0)
);
}
async function lr(e = {}) {
const t = {
key: process.env.NETSTORAGE_API_KEY,
keyName: process.env.NETSTORAGE_API_KEYNAME,
host: process.env.NETSTORAGE_HOST,
ssl: process.env.NETSTORAGE_SSL === "true",
cpCode: process.env.NETSTORAGE_CP_CODE
}, r = await nt(), s = V(), a = {
...I(s),
...I(r),
...I(t),
...I(e)
};
return Oe(a);
}
const R = $.join(
Me.homedir(),
".config",
"netstorage",
"config.json"
);
function V() {
if (E.existsSync(R)) {
const e = E.readFileSync(R, "utf-8");
return JSON.parse(e);
}
return {};
}
function ur(e) {
const r = { ...V(), ...e };
E.mkdirSync($.dirname(R), { recursive: !0 }), E.writeFileSync(R, JSON.stringify(r, null, 2), "utf-8");
}
function it(e) {
E.mkdirSync($.dirname(R), { recursive: !0 }), E.writeFileSync(R, JSON.stringify(e, null, 2), "utf-8");
}
function dr() {
E.existsSync(R) && E.unlinkSync(R);
}
function mr(e) {
const t = V();
e in t && (delete t[e], it(t));
}
function fr() {
return R;
}
function hr(e) {
return !e.logLevel || e.logLevel === "info" ? Ae() : null;
}
function yr(e) {
const t = Array.isArray(e) ? e : [e];
for (const r of t.filter(Boolean)) {
const s = typeof r == "object" ? JSON.stringify(r) : String(r);
process.stdout.write(s + `
`);
}
}
function ct(e, t, r) {
const s = e.find((a) => a.depth === r);
return s ? s.entries.filter((a) => a.parent === t) : [];
}
function lt(e, t, r = "") {
const {
showSize: s,
showMtime: a,
showChecksum: o,
showSymlinkTarget: n,
showRelativePath: i,
showAbsolutePath: c,
depthBuckets: d,
directorySizeMap: l
} = t, m = [];
return e.forEach((u, f) => {
var Q;
const p = f === e.length - 1, y = p ? "└──" : "├──", w = r + (p ? " " : "│ "), h = u.file, b = h.type === "symlink", v = h.type === "dir", g = K(v ? `${h.name}/` : h.name, h.type), L = ((Q = l.get(u.path)) == null ? void 0 : Q.aggregatedSize) ?? 0, A = h.size ? Number(h.size) : 0, N = _(v ? L : A), T = [
i ? u.relativePath : null,
c ? u.path : null,
s && (v || h.size) ? N : null,
a && h.mtime ? ce(h.mtime) : null,
o && h.md5 ? `md5: ${h.md5}` : null,
n && b && h.target ? `-> ${h.target}` : null
].filter(Boolean), fe = T.length > 0 ? ` (${T.join(" | ")})` : "";
if (m.push(`${r}${y} ${g}${fe}`), v) {
const Z = ct(
d,
u.path,
u.depth + 1
);
Z.length > 0 && m.push(...lt(Z, t, w));
}
}), m;
}
export {
ct as $,
Dt as A,
Y as B,
H as C,
j as D,
De as E,
He as F,
Je as G,
M as H,
_ as I,
ce as J,
Ye as K,
le as L,
C as M,
Xe as N,
Ke as O,
Ve as P,
ue as Q,
Qe as R,
Ze as S,
P as T,
et as U,
tt as V,
rt as W,
st as X,
de as Y,
F as Z,
me as _,
te as a,
lt as a0,
Jt as a1,
Yt as a2,
Xt as a3,
Kt as a4,
Vt as a5,
Qt as a6,
Zt as a7,
er as a8,
tr as a9,
rr as aa,
sr as ab,
ar as ac,
at as ad,
K as ae,
or as af,
ot as ag,
nr as ah,
ir as ai,
cr as aj,
lr as ak,
V as al,
ur as am,
dr as an,
mr as ao,
fr as ap,
hr as aq,
yr as ar,
Nt as as,
je as b,
Oe as c,
Ie as d,
qt as e,
re as f,
se as g,
It as h,
Ht as i,
k as j,
ne as k,
J as l,
zt as m,
Fe as n,
jt as o,
St as p,
Bt as q,
Ot as r,
Se as s,
O as t,
ae as u,
Ut as v,
Wt as w,
_t as x,
Ft as y,
Gt as z
};