feat(scripts): add remote-fs script for SFTP and FTP access
Co-authored-by: Nasyarobby Putra <nasyarobby@gmail.com>
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
"@fastify/jwt": "^9.1.0",
|
||||
"@fastify/static": "^8.2.0",
|
||||
"axios": "^1.19.0",
|
||||
"basic-ftp": "^6.2.0",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"better-sqlite3": "^13.0.3",
|
||||
"fastify": "^5.12.0",
|
||||
@@ -28,6 +29,7 @@
|
||||
"nodemailer": "^9.0.5",
|
||||
"pino": "^10.3.1",
|
||||
"pino-roll": "^4.0.0",
|
||||
"ssh2-sftp-client": "^12.1.1",
|
||||
"yaml": "^2.9.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,11 +18,14 @@ const ALLOWED_MODULES = new Set([
|
||||
"@aws-sdk/client-s3",
|
||||
"@aws-sdk/s3-request-presigner",
|
||||
"axios",
|
||||
"basic-ftp",
|
||||
"jsonata",
|
||||
"mustache",
|
||||
"node-html-parser",
|
||||
"node:stream",
|
||||
"nodemailer",
|
||||
"rss-parser",
|
||||
"ssh2-sftp-client",
|
||||
]);
|
||||
|
||||
const BUILTIN_NAMES = [
|
||||
|
||||
@@ -0,0 +1,521 @@
|
||||
import SftpClient from "ssh2-sftp-client";
|
||||
import { Client } from "basic-ftp";
|
||||
import { Readable, Writable } from "node:stream";
|
||||
|
||||
const PROTOCOLS = new Set(["ftp", "sftp"]);
|
||||
const ACTIONS = new Set(["list", "read", "write", "delete", "stat", "mkdir", "rename"]);
|
||||
|
||||
function passContext(ctx) {
|
||||
if (ctx?.context != null && typeof ctx.context === "object" && !Array.isArray(ctx.context)) {
|
||||
return { ...ctx.context };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
function mergeData(data) {
|
||||
if (data != null && typeof data === "object" && !Array.isArray(data)) {
|
||||
return { ...data };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
function resolveProtocol(ctx) {
|
||||
const raw = ctx.config?.protocol ?? ctx.data?.protocol ?? "sftp";
|
||||
if (typeof raw !== "string" || raw.length === 0) {
|
||||
throw new Error("remote-fs: protocol must be a non-empty string");
|
||||
}
|
||||
const protocol = raw.toLowerCase();
|
||||
if (!PROTOCOLS.has(protocol)) {
|
||||
throw new Error(`remote-fs: unsupported protocol "${raw}" (use ftp or sftp)`);
|
||||
}
|
||||
return protocol;
|
||||
}
|
||||
|
||||
function resolveAction(ctx) {
|
||||
const raw = ctx.config?.action ?? ctx.data?.action ?? "list";
|
||||
if (typeof raw !== "string" || raw.length === 0) {
|
||||
throw new Error("remote-fs: action must be a non-empty string");
|
||||
}
|
||||
const action = raw.toLowerCase();
|
||||
if (!ACTIONS.has(action)) {
|
||||
throw new Error(`remote-fs: unsupported action "${raw}"`);
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
function resolvePath(ctx, { required = false, label = "path" } = {}) {
|
||||
const path = ctx.config?.path ?? ctx.data?.path;
|
||||
if (path == null || path === "") {
|
||||
if (required) throw new Error(`remote-fs: ${label} is required`);
|
||||
return ".";
|
||||
}
|
||||
if (typeof path !== "string") {
|
||||
throw new Error(`remote-fs: ${label} must be a string`);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
function resolveHost(ctx) {
|
||||
const host = ctx.config?.host ?? ctx.data?.host;
|
||||
if (typeof host !== "string" || host.length === 0) {
|
||||
throw new Error("remote-fs: host is required (ctx.config.host or ctx.data.host)");
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
function resolvePort(ctx, protocol) {
|
||||
const raw = ctx.config?.port ?? ctx.data?.port;
|
||||
if (raw == null || raw === "") {
|
||||
return protocol === "sftp" ? 22 : 21;
|
||||
}
|
||||
const port = Number(raw);
|
||||
if (!Number.isFinite(port) || port <= 0) {
|
||||
throw new Error("remote-fs: port must be a positive number");
|
||||
}
|
||||
return port;
|
||||
}
|
||||
|
||||
async function resolveSecretValue(secretName, label) {
|
||||
if (typeof secretName !== "string" || secretName.length === 0) {
|
||||
throw new Error(`remote-fs: ${label} is required`);
|
||||
}
|
||||
return $secrets.reveal(await $secrets.get(secretName));
|
||||
}
|
||||
|
||||
async function resolveAuth(ctx) {
|
||||
const username =
|
||||
typeof ctx.config?.usernameSecret === "string" && ctx.config.usernameSecret.length > 0
|
||||
? await resolveSecretValue(ctx.config.usernameSecret, "usernameSecret")
|
||||
: (ctx.config?.username ?? ctx.data?.username);
|
||||
if (typeof username !== "string" || username.length === 0) {
|
||||
throw new Error("remote-fs: username is required");
|
||||
}
|
||||
|
||||
let password;
|
||||
if (typeof ctx.config?.passwordSecret === "string" && ctx.config.passwordSecret.length > 0) {
|
||||
password = await resolveSecretValue(ctx.config.passwordSecret, "passwordSecret");
|
||||
} else if (typeof ctx.config?.password === "string") {
|
||||
password = ctx.config.password;
|
||||
} else if (typeof ctx.data?.password === "string") {
|
||||
password = ctx.data.password;
|
||||
}
|
||||
|
||||
let privateKey;
|
||||
if (typeof ctx.config?.privateKeySecret === "string" && ctx.config.privateKeySecret.length > 0) {
|
||||
privateKey = await resolveSecretValue(ctx.config.privateKeySecret, "privateKeySecret");
|
||||
} else if (typeof ctx.config?.privateKey === "string") {
|
||||
privateKey = ctx.config.privateKey;
|
||||
}
|
||||
|
||||
let passphrase;
|
||||
if (typeof ctx.config?.passphraseSecret === "string" && ctx.config.passphraseSecret.length > 0) {
|
||||
passphrase = await resolveSecretValue(ctx.config.passphraseSecret, "passphraseSecret");
|
||||
} else if (typeof ctx.config?.passphrase === "string") {
|
||||
passphrase = ctx.config.passphrase;
|
||||
}
|
||||
|
||||
if (!password && !privateKey) {
|
||||
throw new Error(
|
||||
"remote-fs: password or private key is required (passwordSecret/privateKeySecret or inline values)",
|
||||
);
|
||||
}
|
||||
|
||||
return { username, password, privateKey, passphrase };
|
||||
}
|
||||
|
||||
function toIsoDate(value) {
|
||||
if (value == null) return null;
|
||||
const date = value instanceof Date ? value : new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return null;
|
||||
return date.toISOString();
|
||||
}
|
||||
|
||||
function joinRemotePath(parentPath, name) {
|
||||
if (!parentPath || parentPath === ".") return name;
|
||||
if (parentPath.endsWith("/")) return `${parentPath}${name}`;
|
||||
return `${parentPath}/${name}`;
|
||||
}
|
||||
|
||||
function normalizeSftpEntry(entry, parentPath) {
|
||||
const name = entry.name;
|
||||
const type = entry.type === "d" ? "directory" : "file";
|
||||
return {
|
||||
name,
|
||||
path: joinRemotePath(parentPath, name),
|
||||
type,
|
||||
size: type === "directory" ? null : typeof entry.size === "number" ? entry.size : null,
|
||||
modified: toIsoDate(entry.modifyTime),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeFtpEntry(entry, parentPath) {
|
||||
const type = entry.type === 2 ? "directory" : "file";
|
||||
return {
|
||||
name: entry.name,
|
||||
path: joinRemotePath(parentPath, entry.name),
|
||||
type,
|
||||
size: type === "directory" ? null : typeof entry.size === "number" ? entry.size : null,
|
||||
modified: toIsoDate(entry.modifiedAt ?? entry.rawModifiedAt),
|
||||
};
|
||||
}
|
||||
|
||||
function bufferFromWritable(writeFn) {
|
||||
return new Promise((resolve, reject) => {
|
||||
/** @type {Buffer[]} */
|
||||
const chunks = [];
|
||||
const writable = new Writable({
|
||||
write(chunk, _encoding, callback) {
|
||||
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
||||
callback();
|
||||
},
|
||||
});
|
||||
writable.on("finish", () => resolve(Buffer.concat(chunks)));
|
||||
writable.on("error", reject);
|
||||
Promise.resolve(writeFn(writable)).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
function resolveWriteBody(ctx) {
|
||||
if (ctx.config != null && typeof ctx.config === "object" && "body" in ctx.config) {
|
||||
const body = ctx.config.body;
|
||||
if (Buffer.isBuffer(body) || body instanceof Uint8Array) return Buffer.from(body);
|
||||
if (typeof body === "string") return Buffer.from(body, "utf8");
|
||||
return Buffer.from(JSON.stringify(body), "utf8");
|
||||
}
|
||||
if (ctx.data?.file != null) {
|
||||
const file = ctx.data.file;
|
||||
if (Buffer.isBuffer(file) || file instanceof Uint8Array) return Buffer.from(file);
|
||||
}
|
||||
if (ctx.data?.body != null) {
|
||||
const body = ctx.data.body;
|
||||
if (Buffer.isBuffer(body) || body instanceof Uint8Array) return Buffer.from(body);
|
||||
if (typeof body === "string") return Buffer.from(body, "utf8");
|
||||
return Buffer.from(JSON.stringify(body), "utf8");
|
||||
}
|
||||
throw new Error("remote-fs: write requires ctx.config.body, ctx.data.body, or ctx.data.file");
|
||||
}
|
||||
|
||||
async function withSftp(ctx, protocol, fn) {
|
||||
const auth = await resolveAuth(ctx);
|
||||
const client = new SftpClient();
|
||||
/** @type {Record<string, unknown>} */
|
||||
const connectOptions = {
|
||||
host: resolveHost(ctx),
|
||||
port: resolvePort(ctx, protocol),
|
||||
username: auth.username,
|
||||
};
|
||||
if (auth.privateKey) {
|
||||
connectOptions.privateKey = auth.privateKey;
|
||||
if (auth.passphrase) connectOptions.passphrase = auth.passphrase;
|
||||
} else {
|
||||
connectOptions.password = auth.password;
|
||||
}
|
||||
if (ctx.config?.readyTimeout != null) {
|
||||
connectOptions.readyTimeout = Number(ctx.config.readyTimeout);
|
||||
}
|
||||
|
||||
await client.connect(connectOptions);
|
||||
try {
|
||||
return await fn(client);
|
||||
} finally {
|
||||
await client.end();
|
||||
}
|
||||
}
|
||||
|
||||
async function withFtp(ctx, protocol, fn) {
|
||||
const auth = await resolveAuth(ctx);
|
||||
const client = new Client(
|
||||
typeof ctx.config?.timeout === "number" ? ctx.config.timeout : 30000,
|
||||
);
|
||||
const secure = ctx.config?.secure === true || ctx.config?.secure === "implicit";
|
||||
await client.access({
|
||||
host: resolveHost(ctx),
|
||||
port: resolvePort(ctx, protocol),
|
||||
user: auth.username,
|
||||
password: auth.password ?? "",
|
||||
secure,
|
||||
});
|
||||
if (ctx.config?.passive === false) {
|
||||
client.ftp.passive = false;
|
||||
}
|
||||
try {
|
||||
return await fn(client);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
async function runAction(protocol, ctx, action) {
|
||||
const path = resolvePath(ctx, { required: action !== "list", label: "path" });
|
||||
|
||||
if (protocol === "sftp") {
|
||||
return withSftp(ctx, protocol, async (client) => {
|
||||
switch (action) {
|
||||
case "list": {
|
||||
const listPath = resolvePath(ctx);
|
||||
const items = await client.list(listPath);
|
||||
const entries = items.map((item) => normalizeSftpEntry(item, listPath));
|
||||
return { path: listPath, entries, count: entries.length };
|
||||
}
|
||||
case "read": {
|
||||
const outputVar =
|
||||
typeof ctx.config?.outputVar === "string" && ctx.config.outputVar.length > 0
|
||||
? ctx.config.outputVar
|
||||
: "file";
|
||||
const file = await client.get(path);
|
||||
const buffer = Buffer.isBuffer(file) ? file : Buffer.from(file);
|
||||
const encoding = ctx.config?.encoding ?? ctx.data?.encoding;
|
||||
/** @type {Record<string, unknown>} */
|
||||
const out = {
|
||||
path,
|
||||
[outputVar]: buffer,
|
||||
contentLength: buffer.length,
|
||||
};
|
||||
if (encoding === "utf8" || encoding === "text") out.text = buffer.toString("utf8");
|
||||
if (encoding === "base64") out.base64 = buffer.toString("base64");
|
||||
return out;
|
||||
}
|
||||
case "write": {
|
||||
const body = resolveWriteBody(ctx);
|
||||
await client.put(body, path);
|
||||
return { path, written: true, contentLength: body.length };
|
||||
}
|
||||
case "delete": {
|
||||
await client.delete(path);
|
||||
return { path, deleted: true };
|
||||
}
|
||||
case "stat": {
|
||||
const stat = await client.stat(path);
|
||||
return {
|
||||
path,
|
||||
type: stat.isDirectory ? "directory" : "file",
|
||||
size: typeof stat.size === "number" ? stat.size : null,
|
||||
modified: toIsoDate(stat.modifyTime),
|
||||
accessed: toIsoDate(stat.accessTime),
|
||||
};
|
||||
}
|
||||
case "mkdir": {
|
||||
const recursive = ctx.config?.recursive !== false;
|
||||
await client.mkdir(path, recursive);
|
||||
return { path, created: true, recursive };
|
||||
}
|
||||
case "rename": {
|
||||
const destination = ctx.config?.destination ?? ctx.data?.destination;
|
||||
if (typeof destination !== "string" || destination.length === 0) {
|
||||
throw new Error("remote-fs: rename requires destination");
|
||||
}
|
||||
await client.rename(path, destination);
|
||||
return { path, destination, renamed: true };
|
||||
}
|
||||
default:
|
||||
throw new Error(`remote-fs: unsupported action "${action}"`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return withFtp(ctx, protocol, async (client) => {
|
||||
switch (action) {
|
||||
case "list": {
|
||||
const listPath = resolvePath(ctx);
|
||||
const items = await client.list(listPath === "." ? undefined : listPath);
|
||||
const entries = items.map((item) => normalizeFtpEntry(item, listPath));
|
||||
return { path: listPath, entries, count: entries.length };
|
||||
}
|
||||
case "read": {
|
||||
const outputVar =
|
||||
typeof ctx.config?.outputVar === "string" && ctx.config.outputVar.length > 0
|
||||
? ctx.config.outputVar
|
||||
: "file";
|
||||
const buffer = await bufferFromWritable((writable) => client.downloadTo(writable, path));
|
||||
const encoding = ctx.config?.encoding ?? ctx.data?.encoding;
|
||||
/** @type {Record<string, unknown>} */
|
||||
const out = {
|
||||
path,
|
||||
[outputVar]: buffer,
|
||||
contentLength: buffer.length,
|
||||
};
|
||||
if (encoding === "utf8" || encoding === "text") out.text = buffer.toString("utf8");
|
||||
if (encoding === "base64") out.base64 = buffer.toString("base64");
|
||||
return out;
|
||||
}
|
||||
case "write": {
|
||||
const body = resolveWriteBody(ctx);
|
||||
const stream = Readable.from(body);
|
||||
await client.uploadFrom(stream, path);
|
||||
return { path, written: true, contentLength: body.length };
|
||||
}
|
||||
case "delete": {
|
||||
await client.remove(path);
|
||||
return { path, deleted: true };
|
||||
}
|
||||
case "stat": {
|
||||
const size = await client.size(path);
|
||||
const modified = await client.lastMod(path);
|
||||
return {
|
||||
path,
|
||||
type: "file",
|
||||
size: typeof size === "number" ? size : null,
|
||||
modified: toIsoDate(modified),
|
||||
};
|
||||
}
|
||||
case "mkdir": {
|
||||
await client.ensureDir(path);
|
||||
return { path, created: true, recursive: true };
|
||||
}
|
||||
case "rename": {
|
||||
const destination = ctx.config?.destination ?? ctx.data?.destination;
|
||||
if (typeof destination !== "string" || destination.length === 0) {
|
||||
throw new Error("remote-fs: rename requires destination");
|
||||
}
|
||||
await client.rename(path, destination);
|
||||
return { path, destination, renamed: true };
|
||||
}
|
||||
default:
|
||||
throw new Error(`remote-fs: unsupported action "${action}"`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function remoteFs(ctx) {
|
||||
const protocol = resolveProtocol(ctx);
|
||||
const action = resolveAction(ctx);
|
||||
const host = resolveHost(ctx);
|
||||
const port = resolvePort(ctx, protocol);
|
||||
|
||||
log.info({ protocol, action, host, port }, "remote-fs: starting");
|
||||
|
||||
const result = await runAction(protocol, ctx, action);
|
||||
const output = {
|
||||
protocol,
|
||||
action,
|
||||
host,
|
||||
...result,
|
||||
};
|
||||
|
||||
log.info(
|
||||
{ protocol, action, path: output.path ?? null, count: output.count ?? null },
|
||||
"remote-fs: complete",
|
||||
);
|
||||
|
||||
return {
|
||||
output: { ...mergeData(ctx.data), ...output },
|
||||
context: { ...passContext(ctx), ...output },
|
||||
};
|
||||
}
|
||||
|
||||
remoteFs.meta = {
|
||||
description: "Access remote files over SFTP or FTP/FTPS",
|
||||
previewConfigKey: "protocol",
|
||||
tags: ["SFTP", "FTP", "storage"],
|
||||
config: {
|
||||
protocol: {
|
||||
type: "string",
|
||||
default: "sftp",
|
||||
enum: ["sftp", "ftp"],
|
||||
description: "Transfer protocol",
|
||||
},
|
||||
action: {
|
||||
type: "string",
|
||||
default: "list",
|
||||
enum: ["list", "read", "write", "delete", "stat", "mkdir", "rename"],
|
||||
description: "Operation to perform",
|
||||
},
|
||||
host: { type: "string", required: true, description: "Server hostname" },
|
||||
port: { type: "number", required: false, description: "Port (default 22 for SFTP, 21 for FTP)" },
|
||||
path: {
|
||||
type: "string",
|
||||
required: false,
|
||||
description: "Remote directory for list, or file path for other actions",
|
||||
},
|
||||
username: { type: "string", required: false, description: "Login username" },
|
||||
usernameSecret: { type: "string", required: false, description: "Named secret for username" },
|
||||
password: { type: "string", required: false, description: "Login password" },
|
||||
passwordSecret: { type: "string", required: false, description: "Named secret for password" },
|
||||
privateKeySecret: {
|
||||
type: "string",
|
||||
required: false,
|
||||
description: "Named secret holding an SFTP private key (PEM)",
|
||||
},
|
||||
privateKey: { type: "string", required: false, description: "Inline SFTP private key (PEM)" },
|
||||
passphraseSecret: {
|
||||
type: "string",
|
||||
required: false,
|
||||
description: "Named secret for encrypted private key passphrase",
|
||||
},
|
||||
passphrase: { type: "string", required: false, description: "Private key passphrase" },
|
||||
secure: {
|
||||
type: "boolean",
|
||||
default: false,
|
||||
description: "Use FTPS for FTP protocol",
|
||||
},
|
||||
passive: {
|
||||
type: "boolean",
|
||||
default: true,
|
||||
description: "Use passive FTP mode",
|
||||
},
|
||||
recursive: {
|
||||
type: "boolean",
|
||||
default: true,
|
||||
description: "Create parent directories for mkdir",
|
||||
},
|
||||
destination: {
|
||||
type: "string",
|
||||
required: false,
|
||||
description: "Destination path for rename",
|
||||
},
|
||||
body: { type: "any", required: false, description: "Write payload" },
|
||||
outputVar: {
|
||||
type: "string",
|
||||
default: "file",
|
||||
description: "Output key for read action bytes",
|
||||
},
|
||||
encoding: {
|
||||
type: "string",
|
||||
required: false,
|
||||
enum: ["utf8", "text", "base64"],
|
||||
description: "Optional read decoding helper",
|
||||
},
|
||||
timeout: { type: "number", required: false, description: "FTP client timeout in ms" },
|
||||
readyTimeout: { type: "number", required: false, description: "SFTP ready timeout in ms" },
|
||||
},
|
||||
input: {
|
||||
protocol: { type: "string", required: false },
|
||||
action: { type: "string", required: false },
|
||||
host: { type: "string", required: false },
|
||||
path: { type: "string", required: false },
|
||||
username: { type: "string", required: false },
|
||||
password: { type: "string", required: false },
|
||||
body: { type: "any", required: false },
|
||||
file: { type: "buffer", required: false },
|
||||
destination: { type: "string", required: false },
|
||||
},
|
||||
output: {
|
||||
protocol: { type: "string" },
|
||||
action: { type: "string" },
|
||||
host: { type: "string" },
|
||||
entries: { type: "array", required: false, description: "list results" },
|
||||
file: { type: "buffer", required: false, description: "read bytes (or outputVar)" },
|
||||
written: { type: "boolean", required: false },
|
||||
deleted: { type: "boolean", required: false },
|
||||
renamed: { type: "boolean", required: false },
|
||||
created: { type: "boolean", required: false },
|
||||
},
|
||||
context: {
|
||||
protocol: { type: "string" },
|
||||
action: { type: "string" },
|
||||
host: { type: "string" },
|
||||
},
|
||||
example: {
|
||||
data: {},
|
||||
config: {
|
||||
protocol: "sftp",
|
||||
action: "list",
|
||||
host: "sftp.example.com",
|
||||
path: "/incoming",
|
||||
username: "deploy",
|
||||
passwordSecret: "sftp_password",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default remoteFs;
|
||||
Generated
+118
@@ -35,6 +35,9 @@ importers:
|
||||
axios:
|
||||
specifier: ^1.19.0
|
||||
version: 1.19.0
|
||||
basic-ftp:
|
||||
specifier: ^6.2.0
|
||||
version: 6.2.0
|
||||
bcryptjs:
|
||||
specifier: ^3.0.2
|
||||
version: 3.0.3
|
||||
@@ -68,6 +71,9 @@ importers:
|
||||
pino-roll:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
ssh2-sftp-client:
|
||||
specifier: ^12.1.1
|
||||
version: 12.1.1
|
||||
yaml:
|
||||
specifier: ^2.9.0
|
||||
version: 2.9.0
|
||||
@@ -973,6 +979,9 @@ packages:
|
||||
asn1.js@5.4.1:
|
||||
resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==}
|
||||
|
||||
asn1@0.2.6:
|
||||
resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==}
|
||||
|
||||
asynckit@0.4.0:
|
||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||
|
||||
@@ -995,6 +1004,13 @@ packages:
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
basic-ftp@6.2.0:
|
||||
resolution: {integrity: sha512-H8eLjhoYPbOI717FLP8fGE7XInkMI7Ucj/ft0fp1avABtSiV5Bb2kYzScnOqlvJZs/KJZypr9Mp5zJmhaPFAEg==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
bcrypt-pbkdf@1.0.2:
|
||||
resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==}
|
||||
|
||||
bcryptjs@3.0.3:
|
||||
resolution: {integrity: sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==}
|
||||
hasBin: true
|
||||
@@ -1021,6 +1037,13 @@ packages:
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
|
||||
buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
|
||||
buildcheck@0.0.7:
|
||||
resolution: {integrity: sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1047,6 +1070,10 @@ packages:
|
||||
resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
concat-stream@2.0.0:
|
||||
resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==}
|
||||
engines: {'0': node >= 6.0}
|
||||
|
||||
content-disposition@0.5.4:
|
||||
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -1068,6 +1095,10 @@ packages:
|
||||
cose-base@2.2.0:
|
||||
resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==}
|
||||
|
||||
cpu-features@0.0.10:
|
||||
resolution: {integrity: sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
cronstrue@3.24.0:
|
||||
resolution: {integrity: sha512-t/Ji3Ur2c/pzhIAWNwC0ftl3JAE4dLfCjAdZoTZXmPDZwcispnS1PaMcMS4OmIIXyIVouAz+yw+mfQiE3hz5OQ==}
|
||||
hasBin: true
|
||||
@@ -1757,6 +1788,9 @@ packages:
|
||||
resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==}
|
||||
hasBin: true
|
||||
|
||||
nan@2.28.0:
|
||||
resolution: {integrity: sha512-fTsDz99OTq2sVePhGdp4qQhggZFtKr64ZNVyVajRKtMOkJxYekplBh577PiJB12v/D3s2E5cGtOI45LWp6rnLQ==}
|
||||
|
||||
nanoid@3.3.18:
|
||||
resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
@@ -1892,6 +1926,10 @@ packages:
|
||||
resolution: {integrity: sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
readable-stream@3.6.2:
|
||||
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
real-require@0.2.0:
|
||||
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
|
||||
engines: {node: '>= 12.13.0'}
|
||||
@@ -2006,6 +2044,14 @@ packages:
|
||||
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
|
||||
engines: {node: '>= 10.x'}
|
||||
|
||||
ssh2-sftp-client@12.1.1:
|
||||
resolution: {integrity: sha512-wYVDgwkpcKG2iPGQQ+QR33xkWqLFIaVrYvA+uON4pmxTPaPuB81f1aooUEPN75e/9DCK6rrKYXb6zR6zP3+EtA==}
|
||||
engines: {node: '>=18.20.4'}
|
||||
|
||||
ssh2@1.17.0:
|
||||
resolution: {integrity: sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==}
|
||||
engines: {node: '>=10.16.0'}
|
||||
|
||||
state-local@1.0.7:
|
||||
resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==}
|
||||
|
||||
@@ -2016,6 +2062,9 @@ packages:
|
||||
steed@1.1.3:
|
||||
resolution: {integrity: sha512-EUkci0FAUiE4IvGTSKcDJIQ/eRUP2JJb56+fvZ4sdnguLTqIdKjSxUe138poW8mkvKWXW2sFPrgTsxqoISnmoA==}
|
||||
|
||||
string_decoder@1.3.0:
|
||||
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
|
||||
|
||||
stylis@4.4.0:
|
||||
resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==}
|
||||
|
||||
@@ -2065,12 +2114,21 @@ packages:
|
||||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
tweetnacl@0.14.5:
|
||||
resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==}
|
||||
|
||||
typedarray@0.0.6:
|
||||
resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
|
||||
|
||||
update-browserslist-db@1.3.1:
|
||||
resolution: {integrity: sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
browserslist: '>= 4.21.0'
|
||||
|
||||
util-deprecate@1.0.2:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
|
||||
uuid@14.0.1:
|
||||
resolution: {integrity: sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==}
|
||||
hasBin: true
|
||||
@@ -3032,6 +3090,10 @@ snapshots:
|
||||
minimalistic-assert: 1.0.1
|
||||
safer-buffer: 2.1.2
|
||||
|
||||
asn1@0.2.6:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
|
||||
asynckit@0.4.0: {}
|
||||
|
||||
atomic-sleep@1.0.0: {}
|
||||
@@ -3055,6 +3117,12 @@ snapshots:
|
||||
|
||||
baseline-browser-mapping@2.11.14: {}
|
||||
|
||||
basic-ftp@6.2.0: {}
|
||||
|
||||
bcrypt-pbkdf@1.0.2:
|
||||
dependencies:
|
||||
tweetnacl: 0.14.5
|
||||
|
||||
bcryptjs@3.0.3: {}
|
||||
|
||||
better-sqlite3@13.0.3:
|
||||
@@ -3079,6 +3147,11 @@ snapshots:
|
||||
node-releases: 2.0.53
|
||||
update-browserslist-db: 1.3.1(browserslist@4.28.8)
|
||||
|
||||
buffer-from@1.1.2: {}
|
||||
|
||||
buildcheck@0.0.7:
|
||||
optional: true
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
@@ -3098,6 +3171,13 @@ snapshots:
|
||||
|
||||
commander@8.3.0: {}
|
||||
|
||||
concat-stream@2.0.0:
|
||||
dependencies:
|
||||
buffer-from: 1.1.2
|
||||
inherits: 2.0.4
|
||||
readable-stream: 3.6.2
|
||||
typedarray: 0.0.6
|
||||
|
||||
content-disposition@0.5.4:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
@@ -3116,6 +3196,12 @@ snapshots:
|
||||
dependencies:
|
||||
layout-base: 2.0.1
|
||||
|
||||
cpu-features@0.0.10:
|
||||
dependencies:
|
||||
buildcheck: 0.0.7
|
||||
nan: 2.28.0
|
||||
optional: true
|
||||
|
||||
cronstrue@3.24.0: {}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
@@ -3810,6 +3896,9 @@ snapshots:
|
||||
|
||||
mustache@4.2.0: {}
|
||||
|
||||
nan@2.28.0:
|
||||
optional: true
|
||||
|
||||
nanoid@3.3.18: {}
|
||||
|
||||
node-addon-api@8.9.2: {}
|
||||
@@ -3927,6 +4016,12 @@ snapshots:
|
||||
|
||||
react@19.2.8: {}
|
||||
|
||||
readable-stream@3.6.2:
|
||||
dependencies:
|
||||
inherits: 2.0.4
|
||||
string_decoder: 1.3.0
|
||||
util-deprecate: 1.0.2
|
||||
|
||||
real-require@0.2.0: {}
|
||||
|
||||
real-require@1.0.0: {}
|
||||
@@ -4040,6 +4135,19 @@ snapshots:
|
||||
|
||||
split2@4.2.0: {}
|
||||
|
||||
ssh2-sftp-client@12.1.1:
|
||||
dependencies:
|
||||
concat-stream: 2.0.0
|
||||
ssh2: 1.17.0
|
||||
|
||||
ssh2@1.17.0:
|
||||
dependencies:
|
||||
asn1: 0.2.6
|
||||
bcrypt-pbkdf: 1.0.2
|
||||
optionalDependencies:
|
||||
cpu-features: 0.0.10
|
||||
nan: 2.28.0
|
||||
|
||||
state-local@1.0.7: {}
|
||||
|
||||
statuses@2.0.2: {}
|
||||
@@ -4052,6 +4160,10 @@ snapshots:
|
||||
fastseries: 1.7.2
|
||||
reusify: 1.1.0
|
||||
|
||||
string_decoder@1.3.0:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
stylis@4.4.0: {}
|
||||
|
||||
supports-preserve-symlinks-flag@1.0.0: {}
|
||||
@@ -4083,12 +4195,18 @@ snapshots:
|
||||
|
||||
tslib@2.8.1: {}
|
||||
|
||||
tweetnacl@0.14.5: {}
|
||||
|
||||
typedarray@0.0.6: {}
|
||||
|
||||
update-browserslist-db@1.3.1(browserslist@4.28.8):
|
||||
dependencies:
|
||||
browserslist: 4.28.8
|
||||
escalade: 3.2.0
|
||||
picocolors: 1.1.1
|
||||
|
||||
util-deprecate@1.0.2: {}
|
||||
|
||||
uuid@14.0.1: {}
|
||||
|
||||
vite@7.3.6(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0):
|
||||
|
||||
Reference in New Issue
Block a user