UNPKG

netstorage

Version:

A TypeScript API and CLI for the Akamai NetStorage REST interface

1,615 lines (1,614 loc) 42.8 kB
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 };