scp-next 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -88,6 +88,12 @@ function redactSensitiveValues(value) {
88
88
  }
89
89
  return value;
90
90
  }
91
+ function formatErrorMessage(error) {
92
+ if (error instanceof Error) {
93
+ return String(redactSensitiveValues(error.message));
94
+ }
95
+ return String(redactSensitiveValues(error));
96
+ }
91
97
 
92
98
  // src/errors/index.ts
93
99
  var ScpNextError = class extends Error {
@@ -144,7 +150,7 @@ function toScpNextError(error) {
144
150
  if (lowerMessage.includes("authentication") || lowerMessage.includes("permission denied")) {
145
151
  return new AuthenticationError("SSH authentication failed.", { cause: error });
146
152
  }
147
- if (lowerMessage.includes("host verification") || lowerMessage.includes("host key")) {
153
+ if (lowerMessage.includes("host verification") || lowerMessage.includes("host key") || lowerMessage.includes("fingerprint") || lowerMessage.includes("host") && lowerMessage.includes("denied")) {
148
154
  return new HostVerificationError("SSH host verification failed.", { cause: error });
149
155
  }
150
156
  if (lowerMessage.includes("timed out") || lowerMessage.includes("connection") || lowerMessage.includes("econn")) {
@@ -218,8 +224,39 @@ async function getLocalPathKind(inputPath) {
218
224
  function normalizeFingerprint(fingerprint) {
219
225
  return fingerprint.trim().replace(/^SHA256:/i, "").replace(/^sha256:/i, "").replace(/=+$/g, "");
220
226
  }
227
+ function isHexFingerprint(fingerprint) {
228
+ return /^[0-9a-f]+$/i.test(fingerprint);
229
+ }
230
+ function fingerprintCandidates(fingerprint) {
231
+ const normalized = normalizeFingerprint(fingerprint);
232
+ const candidates = /* @__PURE__ */ new Set([normalized]);
233
+ if (isHexFingerprint(normalized)) {
234
+ candidates.add(normalized.toLowerCase());
235
+ } else {
236
+ try {
237
+ candidates.add(Buffer.from(normalized, "base64").toString("hex"));
238
+ } catch {
239
+ }
240
+ }
241
+ return [...candidates];
242
+ }
221
243
  function isFingerprintMatch(actual, expected) {
222
- return normalizeFingerprint(actual) === normalizeFingerprint(expected);
244
+ const normalizedActual = normalizeFingerprint(actual);
245
+ const normalizedExpected = normalizeFingerprint(expected);
246
+ if (normalizedActual === normalizedExpected) {
247
+ return true;
248
+ }
249
+ return isHexFingerprint(normalizedActual) && isHexFingerprint(normalizedExpected) && normalizedActual.toLowerCase() === normalizedExpected.toLowerCase();
250
+ }
251
+ function hostVerificationHint(host) {
252
+ return [
253
+ "",
254
+ "Typical fix:",
255
+ ` ssh ${host}`,
256
+ "",
257
+ "If you trust the host key, accept it and retry the scp-next command.",
258
+ "Alternatively configure hostFingerprint or knownHostsFile explicitly."
259
+ ].join("\n");
223
260
  }
224
261
  async function resolveAllowedFingerprints(options) {
225
262
  const fingerprints = /* @__PURE__ */ new Set();
@@ -228,7 +265,9 @@ async function resolveAllowedFingerprints(options) {
228
265
  throw new HostVerificationError("A host is required for SSH host verification.");
229
266
  }
230
267
  if (options.hostFingerprint) {
231
- fingerprints.add(normalizeFingerprint(options.hostFingerprint));
268
+ for (const fingerprint of fingerprintCandidates(options.hostFingerprint)) {
269
+ fingerprints.add(fingerprint);
270
+ }
232
271
  }
233
272
  try {
234
273
  await (0, import_promises2.access)(expandHome(knownHostsFile));
@@ -239,11 +278,15 @@ async function resolveAllowedFingerprints(options) {
239
278
  continue;
240
279
  }
241
280
  const [hosts, algorithm, key] = trimmed.split(/\s+/);
242
- if (!hosts || !algorithm || !key || !hosts.split(",").includes(options.host)) {
281
+ const hostPatterns = hosts?.split(",") ?? [];
282
+ const portHost = options.port ? `[${options.host}]:${options.port}` : void 0;
283
+ const matchesHost = hostPatterns.includes(options.host) || Boolean(portHost && hostPatterns.includes(portHost));
284
+ if (!hosts || !algorithm || !key || !matchesHost) {
243
285
  continue;
244
286
  }
245
- const fingerprint = (0, import_node_crypto.createHash)("sha256").update(Buffer.from(key, "base64")).digest("base64").replace(/=+$/g, "");
246
- fingerprints.add(fingerprint);
287
+ const keyBuffer = Buffer.from(key, "base64");
288
+ fingerprints.add((0, import_node_crypto.createHash)("sha256").update(keyBuffer).digest("base64").replace(/=+$/g, ""));
289
+ fingerprints.add((0, import_node_crypto.createHash)("sha256").update(keyBuffer).digest("hex"));
247
290
  }
248
291
  } catch (error) {
249
292
  if (options.knownHostsFile || !options.hostFingerprint) {
@@ -255,7 +298,9 @@ async function resolveAllowedFingerprints(options) {
255
298
  }
256
299
  if (fingerprints.size === 0) {
257
300
  throw new HostVerificationError(
258
- "SSH host verification failed before connecting: no matching host fingerprint was configured."
301
+ `SSH host verification failed before connecting: no matching host fingerprint was configured.${hostVerificationHint(
302
+ options.host
303
+ )}`
259
304
  );
260
305
  }
261
306
  return fingerprints;
@@ -282,7 +327,11 @@ function toRemoteFileInfo(info) {
282
327
  var Ssh2SftpTransport = class {
283
328
  client;
284
329
  constructor(name = "scp-next") {
285
- this.client = new import_ssh2_sftp_client.default(name);
330
+ this.client = new import_ssh2_sftp_client.default(name, {
331
+ error: () => void 0,
332
+ end: () => void 0,
333
+ close: () => void 0
334
+ });
286
335
  }
287
336
  async connect(options) {
288
337
  const allowedFingerprints = await resolveAllowedFingerprints(options);
@@ -312,15 +361,24 @@ var Ssh2SftpTransport = class {
312
361
  } catch (error) {
313
362
  const converted = toScpNextError(error);
314
363
  if (converted instanceof HostVerificationError) {
315
- throw converted;
364
+ throw new HostVerificationError(
365
+ `SSH host verification failed during connection: ${formatErrorMessage(
366
+ error
367
+ )}.${hostVerificationTroubleshooting(options.host)}`,
368
+ { cause: error, context: connectionContext(options) }
369
+ );
316
370
  }
317
- throw new ConnectionError("Unable to connect to SSH server.", {
371
+ if (isLikelyHostVerificationFailure(error)) {
372
+ throw new HostVerificationError(
373
+ `SSH host verification failed during connection.${hostVerificationTroubleshooting(
374
+ options.host
375
+ )}`,
376
+ { cause: error, context: connectionContext(options) }
377
+ );
378
+ }
379
+ throw new ConnectionError(`Unable to connect to SSH server: ${formatErrorMessage(error)}`, {
318
380
  cause: error,
319
- context: {
320
- host: options.host,
321
- port: options.port,
322
- username: options.username
323
- }
381
+ context: connectionContext(options)
324
382
  });
325
383
  }
326
384
  }
@@ -341,7 +399,7 @@ var Ssh2SftpTransport = class {
341
399
  try {
342
400
  await this.client.fastPut(localPath, remotePath, this.transferOptions(onStep));
343
401
  } catch (error) {
344
- throw new TransferError(`Upload failed for ${localPath}.`, {
402
+ throw new TransferError(`Upload failed for ${localPath}: ${formatErrorMessage(error)}`, {
345
403
  cause: error,
346
404
  context: { localPath, remotePath }
347
405
  });
@@ -351,7 +409,7 @@ var Ssh2SftpTransport = class {
351
409
  try {
352
410
  await this.client.fastGet(remotePath, localPath, this.transferOptions(onStep));
353
411
  } catch (error) {
354
- throw new TransferError(`Download failed for ${remotePath}.`, {
412
+ throw new TransferError(`Download failed for ${remotePath}: ${formatErrorMessage(error)}`, {
355
413
  cause: error,
356
414
  context: { remotePath, localPath }
357
415
  });
@@ -368,6 +426,31 @@ var Ssh2SftpTransport = class {
368
426
  };
369
427
  }
370
428
  };
429
+ function connectionContext(options) {
430
+ return {
431
+ host: options.host,
432
+ port: options.port,
433
+ username: options.username
434
+ };
435
+ }
436
+ function isLikelyHostVerificationFailure(error) {
437
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
438
+ return message.includes("host") && (message.includes("verif") || message.includes("fingerprint") || message.includes("host key") || message.includes("key mismatch") || message.includes("denied"));
439
+ }
440
+ function hostVerificationTroubleshooting(host) {
441
+ const displayHost = host ?? "<host>";
442
+ return [
443
+ "",
444
+ "",
445
+ "Typical fix:",
446
+ ` ssh ${displayHost}`,
447
+ "",
448
+ "If plain ssh works but scp-next fails, check that the host entry is available in",
449
+ "`~/.ssh/known_hosts` for the same host and port used by scp-next. For non-default ports,",
450
+ "OpenSSH usually stores entries as `[host]:port`.",
451
+ "You can also configure hostFingerprint or knownHostsFile explicitly."
452
+ ].join("\n");
453
+ }
371
454
 
372
455
  // src/client/walk.ts
373
456
  var import_promises4 = require("fs/promises");
@@ -595,10 +678,9 @@ var ScpNextClientImpl = class {
595
678
  throw new ValidationError("Uploading a directory requires recursive mode.");
596
679
  }
597
680
  if (localKind === "file") {
598
- await this.ensureRemoteParent(remotePath, options.createDirectories);
599
- await this.assertRemoteOverwrite(remotePath, options.overwrite);
681
+ const destinationFile = await this.resolveRemoteDestinationForLocalFile(options, remotePath);
600
682
  const totalBytes2 = void 0;
601
- await this.transport.uploadFile(resolveLocalPath(options.localPath), remotePath, (step) => {
683
+ await this.transport.uploadFile(resolveLocalPath(options.localPath), destinationFile, (step) => {
602
684
  options.onProgress?.({
603
685
  operation: "upload",
604
686
  source: options.localPath,
@@ -651,6 +733,35 @@ var ScpNextClientImpl = class {
651
733
  completedFiles += 1;
652
734
  }
653
735
  }
736
+ async resolveRemoteDestinationForLocalFile(options, remotePath) {
737
+ const remoteKind = await this.transport.exists(remotePath);
738
+ const isDirectoryLikeDestination = remoteKind === "d" || remotePath.endsWith("/");
739
+ if (!isDirectoryLikeDestination) {
740
+ await this.ensureRemoteParent(remotePath, options.createDirectories);
741
+ await this.assertRemoteOverwrite(remotePath, options.overwrite);
742
+ return remotePath;
743
+ }
744
+ if (remoteKind && remoteKind !== "d") {
745
+ throw new TransferError(`Remote destination already exists and is not a directory: ${remotePath}`, {
746
+ context: { remotePath }
747
+ });
748
+ }
749
+ const remoteDirectory = remotePath.replace(/\/+$/g, "") || "/";
750
+ if (!remoteKind) {
751
+ if (!options.createDirectories) {
752
+ throw new TransferError(`Remote destination directory does not exist: ${remoteDirectory}`, {
753
+ context: { remotePath }
754
+ });
755
+ }
756
+ await this.transport.mkdir(remoteDirectory, true);
757
+ }
758
+ const destinationFile = remoteJoin(
759
+ remoteDirectory,
760
+ import_node_path4.default.basename(resolveLocalPath(options.localPath))
761
+ );
762
+ await this.assertRemoteOverwrite(destinationFile, options.overwrite);
763
+ return destinationFile;
764
+ }
654
765
  async downloadWithTransport(options) {
655
766
  const remotePath = normalizeRemotePath(options.remotePath);
656
767
  const remoteKind = await this.transport.exists(remotePath);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/client/client.ts","../src/client/transport.ts","../src/security/redact.ts","../src/errors/index.ts","../src/security/host-verification.ts","../src/paths/local-path.ts","../src/client/walk.ts","../src/paths/remote-path.ts","../src/config/validation.ts","../src/client/upload.ts","../src/client/download.ts","../src/client/copy.ts"],"sourcesContent":["export { createClient, ScpNextClientImpl } from \"./client/client.js\";\nexport { upload } from \"./client/upload.js\";\nexport { download } from \"./client/download.js\";\nexport { copy } from \"./client/copy.js\";\nexport * from \"./types/index.js\";\nexport * from \"./errors/index.js\";\n","import { access, mkdir } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { Ssh2SftpTransport } from \"./transport.js\";\nimport type { SftpTransport } from \"./transport.js\";\nimport { walkLocalFiles, walkRemoteFiles } from \"./walk.js\";\nimport { TransferError, ValidationError } from \"../errors/index.js\";\nimport { getLocalPathKind, resolveLocalPath } from \"../paths/local-path.js\";\nimport { normalizeRemotePath, remoteDirname, remoteJoin } from \"../paths/remote-path.js\";\nimport type {\n DownloadOptions,\n ScpNextClient,\n ScpServerOptions,\n TransferOptions,\n UploadOptions\n} from \"../types/index.js\";\nimport {\n validateDownloadOptions,\n validateServerOptions,\n validateUploadOptions\n} from \"../config/validation.js\";\n\nexport interface ScpNextClientDependencies {\n transport?: SftpTransport;\n}\n\nfunction percentage(transferredBytes: number, totalBytes?: number): number | undefined {\n if (!totalBytes || totalBytes <= 0) {\n return undefined;\n }\n return Math.min(100, Math.round((transferredBytes / totalBytes) * 100));\n}\n\nfunction mergeOptions<T extends TransferOptions>(\n serverOptions: ScpServerOptions,\n transferOptions: T | undefined\n): ScpServerOptions & T {\n return { ...serverOptions, ...(transferOptions ?? {}) } as ScpServerOptions & T;\n}\n\nexport class ScpNextClientImpl implements ScpNextClient {\n private readonly serverOptions: ScpServerOptions;\n private readonly transport: SftpTransport;\n private connected = false;\n\n constructor(serverOptions: ScpServerOptions, dependencies: ScpNextClientDependencies = {}) {\n this.serverOptions = serverOptions;\n this.transport = dependencies.transport ?? new Ssh2SftpTransport();\n }\n\n async connect(): Promise<void> {\n await validateServerOptions(this.serverOptions);\n await this.transport.connect(this.serverOptions);\n this.connected = true;\n }\n\n async upload(\n localPath: string,\n remotePath: string,\n options: TransferOptions = {}\n ): Promise<void> {\n const uploadOptions: UploadOptions = {\n ...mergeOptions(this.serverOptions, options),\n localPath,\n remotePath\n };\n await validateUploadOptions(uploadOptions);\n\n if (options.dryRun) {\n return;\n }\n\n await this.ensureConnected();\n await this.uploadWithTransport(uploadOptions);\n }\n\n async download(\n remotePath: string,\n localPath: string,\n options: TransferOptions = {}\n ): Promise<void> {\n const downloadOptions: DownloadOptions = {\n ...mergeOptions(this.serverOptions, options),\n remotePath,\n localPath\n };\n await validateDownloadOptions(downloadOptions);\n\n if (options.dryRun) {\n return;\n }\n\n await this.ensureConnected();\n await this.downloadWithTransport(downloadOptions);\n }\n\n async close(): Promise<void> {\n if (this.connected) {\n await this.transport.close();\n this.connected = false;\n }\n }\n\n private async ensureConnected(): Promise<void> {\n if (!this.connected) {\n await this.connect();\n }\n }\n\n private async uploadWithTransport(options: UploadOptions): Promise<void> {\n const localKind = await getLocalPathKind(options.localPath);\n const remotePath = normalizeRemotePath(options.remotePath);\n\n if (localKind === \"directory\" && !options.recursive) {\n throw new ValidationError(\"Uploading a directory requires recursive mode.\");\n }\n\n if (localKind === \"file\") {\n await this.ensureRemoteParent(remotePath, options.createDirectories);\n await this.assertRemoteOverwrite(remotePath, options.overwrite);\n const totalBytes = undefined;\n await this.transport.uploadFile(resolveLocalPath(options.localPath), remotePath, (step) => {\n options.onProgress?.({\n operation: \"upload\",\n source: options.localPath,\n destination: options.remotePath,\n transferredBytes: step.transferredBytes,\n totalBytes: step.totalBytes || totalBytes,\n percentage: percentage(step.transferredBytes, step.totalBytes),\n currentFile: options.localPath\n });\n });\n return;\n }\n\n const files = await walkLocalFiles(options.localPath);\n const totalBytes = files.reduce((sum, file) => sum + file.size, 0);\n let completedBytes = 0;\n let completedFiles = 0;\n\n const destinationExists = await this.transport.exists(remotePath);\n if (destinationExists && destinationExists !== \"d\") {\n throw new TransferError(`Remote destination already exists and is not a directory: ${remotePath}`, {\n context: { remotePath }\n });\n }\n if (!destinationExists) {\n if (!options.createDirectories) {\n throw new TransferError(`Remote destination directory does not exist: ${remotePath}`, {\n context: { remotePath }\n });\n }\n await this.transport.mkdir(remotePath, true);\n }\n\n for (const file of files) {\n const destinationFile = remoteJoin(remotePath, file.relativePath.split(path.sep).join(\"/\"));\n await this.ensureRemoteParent(destinationFile, true);\n await this.assertRemoteOverwrite(destinationFile, options.overwrite);\n\n await this.transport.uploadFile(file.absolutePath, destinationFile, (step) => {\n const transferredBytes = completedBytes + step.transferredBytes;\n options.onProgress?.({\n operation: \"upload\",\n source: options.localPath,\n destination: options.remotePath,\n transferredBytes,\n totalBytes,\n percentage: percentage(transferredBytes, totalBytes),\n currentFile: file.relativePath,\n completedFiles,\n totalFiles: files.length\n });\n });\n\n completedBytes += file.size;\n completedFiles += 1;\n }\n }\n\n private async downloadWithTransport(options: DownloadOptions): Promise<void> {\n const remotePath = normalizeRemotePath(options.remotePath);\n const remoteKind = await this.transport.exists(remotePath);\n\n if (!remoteKind) {\n throw new TransferError(`Remote path does not exist: ${options.remotePath}`, {\n context: { remotePath: options.remotePath }\n });\n }\n\n if (remoteKind === \"d\") {\n if (!options.recursive) {\n throw new ValidationError(\"Downloading a directory requires recursive mode.\");\n }\n await this.downloadDirectory(options);\n return;\n }\n\n await this.downloadSingleFile(options, remotePath);\n }\n\n private async downloadSingleFile(options: DownloadOptions, remotePath: string): Promise<void> {\n const localPath = resolveLocalPath(options.localPath);\n await this.assertLocalOverwrite(localPath, options.overwrite);\n if (options.createDirectories) {\n await mkdir(path.dirname(localPath), { recursive: true });\n }\n await this.transport.downloadFile(remotePath, localPath, (step) => {\n options.onProgress?.({\n operation: \"download\",\n source: options.remotePath,\n destination: options.localPath,\n transferredBytes: step.transferredBytes,\n totalBytes: step.totalBytes,\n percentage: percentage(step.transferredBytes, step.totalBytes),\n currentFile: options.remotePath\n });\n });\n }\n\n private async downloadDirectory(options: DownloadOptions): Promise<void> {\n const remotePath = normalizeRemotePath(options.remotePath);\n const localRoot = resolveLocalPath(options.localPath);\n const files = await walkRemoteFiles(this.transport, remotePath);\n const totalBytes = files.reduce((sum, file) => sum + file.size, 0);\n let completedBytes = 0;\n let completedFiles = 0;\n\n await mkdir(localRoot, { recursive: Boolean(options.createDirectories) });\n\n for (const file of files) {\n const localFile = path.join(localRoot, ...file.relativePath.split(\"/\"));\n await this.assertLocalOverwrite(localFile, options.overwrite);\n await mkdir(path.dirname(localFile), { recursive: true });\n await this.transport.downloadFile(file.remotePath, localFile, (step) => {\n const transferredBytes = completedBytes + step.transferredBytes;\n options.onProgress?.({\n operation: \"download\",\n source: options.remotePath,\n destination: options.localPath,\n transferredBytes,\n totalBytes,\n percentage: percentage(transferredBytes, totalBytes),\n currentFile: file.relativePath,\n completedFiles,\n totalFiles: files.length\n });\n });\n completedBytes += file.size;\n completedFiles += 1;\n }\n }\n\n private async ensureRemoteParent(remotePath: string, createDirectories = false): Promise<void> {\n const parent = remoteDirname(remotePath);\n if (parent === \".\" || parent === \"/\") {\n return;\n }\n\n if (createDirectories) {\n await this.transport.mkdir(parent, true);\n return;\n }\n\n const exists = await this.transport.exists(parent);\n if (exists !== \"d\") {\n throw new TransferError(`Remote destination parent does not exist: ${parent}`, {\n context: { remotePath }\n });\n }\n }\n\n private async assertRemoteOverwrite(remotePath: string, overwrite = false): Promise<void> {\n if (overwrite) {\n return;\n }\n const exists = await this.transport.exists(remotePath);\n if (exists) {\n throw new TransferError(`Remote destination already exists: ${remotePath}`, {\n context: { remotePath }\n });\n }\n }\n\n private async assertLocalOverwrite(localPath: string, overwrite = false): Promise<void> {\n if (overwrite) {\n return;\n }\n\n try {\n await access(localPath);\n throw new TransferError(`Local destination already exists: ${localPath}`, {\n context: { localPath }\n });\n } catch (error) {\n if (error instanceof TransferError) {\n throw error;\n }\n }\n }\n}\n\nexport function createClient(options: ScpServerOptions): ScpNextClient {\n return new ScpNextClientImpl(options);\n}\n","import { readFile } from \"node:fs/promises\";\n\nimport SftpClient from \"ssh2-sftp-client\";\nimport type {\n ConnectOptions as SftpConnectOptions,\n FileInfo,\n TransferOptions as SftpTransferOptions\n} from \"ssh2-sftp-client\";\n\nimport { ConnectionError, HostVerificationError, TransferError, toScpNextError } from \"../errors/index.js\";\nimport { createHostVerifier, resolveAllowedFingerprints } from \"../security/host-verification.js\";\nimport type { ScpServerOptions } from \"../types/index.js\";\nimport { expandHome } from \"../paths/local-path.js\";\n\nexport interface RemoteFileInfo {\n type: \"-\" | \"d\" | \"l\";\n name: string;\n size: number;\n}\n\nexport interface TransferStep {\n transferredBytes: number;\n chunkBytes: number;\n totalBytes: number;\n}\n\nexport interface SftpTransport {\n connect(options: ScpServerOptions): Promise<void>;\n close(): Promise<void>;\n exists(remotePath: string): Promise<false | \"-\" | \"d\" | \"l\">;\n mkdir(remotePath: string, recursive?: boolean): Promise<void>;\n list(remotePath: string): Promise<RemoteFileInfo[]>;\n uploadFile(\n localPath: string,\n remotePath: string,\n onStep?: (step: TransferStep) => void\n ): Promise<void>;\n downloadFile(\n remotePath: string,\n localPath: string,\n onStep?: (step: TransferStep) => void\n ): Promise<void>;\n}\n\nfunction toRemoteFileInfo(info: FileInfo): RemoteFileInfo {\n return {\n type: info.type,\n name: info.name,\n size: info.size\n };\n}\n\nexport class Ssh2SftpTransport implements SftpTransport {\n private readonly client: SftpClient;\n\n constructor(name = \"scp-next\") {\n this.client = new SftpClient(name);\n }\n\n async connect(options: ScpServerOptions): Promise<void> {\n const allowedFingerprints = await resolveAllowedFingerprints(options);\n const hostVerifier = createHostVerifier(allowedFingerprints);\n\n const connectOptions: SftpConnectOptions = {};\n if (options.host !== undefined) connectOptions.host = options.host;\n if (options.port !== undefined) connectOptions.port = options.port;\n if (options.username !== undefined) connectOptions.username = options.username;\n if (options.password !== undefined) connectOptions.password = options.password;\n if (options.privateKey !== undefined) {\n connectOptions.privateKey = options.privateKey;\n } else if (options.privateKeyFile) {\n connectOptions.privateKey = await readFile(expandHome(options.privateKeyFile), \"utf8\");\n }\n if (options.passphrase !== undefined) connectOptions.passphrase = options.passphrase;\n const agent = options.agent ?? process.env.SSH_AUTH_SOCK;\n if (agent !== undefined) {\n connectOptions.agent = agent;\n }\n if (options.timeout !== undefined) connectOptions.readyTimeout = options.timeout;\n if (hostVerifier) {\n connectOptions.hostHash = \"sha256\";\n connectOptions.hostVerifier = hostVerifier;\n }\n\n try {\n await this.client.connect(connectOptions);\n } catch (error) {\n const converted = toScpNextError(error);\n if (converted instanceof HostVerificationError) {\n throw converted;\n }\n throw new ConnectionError(\"Unable to connect to SSH server.\", {\n cause: error,\n context: {\n host: options.host,\n port: options.port,\n username: options.username\n }\n });\n }\n }\n\n async close(): Promise<void> {\n await this.client.end();\n }\n\n async exists(remotePath: string): Promise<false | \"-\" | \"d\" | \"l\"> {\n return this.client.exists(remotePath);\n }\n\n async mkdir(remotePath: string, recursive = true): Promise<void> {\n await this.client.mkdir(remotePath, recursive);\n }\n\n async list(remotePath: string): Promise<RemoteFileInfo[]> {\n const files = await this.client.list(remotePath);\n return files.map(toRemoteFileInfo);\n }\n\n async uploadFile(\n localPath: string,\n remotePath: string,\n onStep?: (step: TransferStep) => void\n ): Promise<void> {\n try {\n await this.client.fastPut(localPath, remotePath, this.transferOptions(onStep));\n } catch (error) {\n throw new TransferError(`Upload failed for ${localPath}.`, {\n cause: error,\n context: { localPath, remotePath }\n });\n }\n }\n\n async downloadFile(\n remotePath: string,\n localPath: string,\n onStep?: (step: TransferStep) => void\n ): Promise<void> {\n try {\n await this.client.fastGet(remotePath, localPath, this.transferOptions(onStep));\n } catch (error) {\n throw new TransferError(`Download failed for ${remotePath}.`, {\n cause: error,\n context: { remotePath, localPath }\n });\n }\n }\n\n private transferOptions(onStep?: (step: TransferStep) => void): SftpTransferOptions | undefined {\n if (!onStep) {\n return undefined;\n }\n return {\n step(transferredBytes, chunkBytes, totalBytes) {\n onStep({ transferredBytes, chunkBytes, totalBytes });\n }\n };\n }\n}\n","const SENSITIVE_KEYS = [\n \"password\",\n \"passphrase\",\n \"privatekey\",\n \"privatekeyfile\",\n \"token\",\n \"authorization\",\n \"credential\",\n \"secret\"\n];\n\nconst PRIVATE_KEY_PATTERN =\n /-----BEGIN [A-Z ]*PRIVATE KEY-----[\\s\\S]*?-----END [A-Z ]*PRIVATE KEY-----/g;\n\nfunction isSensitiveKey(key: string): boolean {\n const normalized = key.replace(/[-_\\s]/g, \"\").toLowerCase();\n return SENSITIVE_KEYS.some((sensitive) => normalized.includes(sensitive));\n}\n\nexport function redactSensitiveValues(value: unknown): unknown {\n if (typeof value === \"string\") {\n return value.replace(PRIVATE_KEY_PATTERN, \"[REDACTED]\");\n }\n\n if (Array.isArray(value)) {\n const redacted: unknown[] = value.map((item: unknown) => redactSensitiveValues(item));\n return redacted;\n }\n\n if (value && typeof value === \"object\") {\n const redacted: Record<string, unknown> = {};\n for (const [key, nestedValue] of Object.entries(value)) {\n redacted[key] = isSensitiveKey(key) ? \"[REDACTED]\" : redactSensitiveValues(nestedValue);\n }\n return redacted;\n }\n\n return value;\n}\n\nexport function formatErrorMessage(error: unknown): string {\n if (error instanceof Error) {\n return String(redactSensitiveValues(error.message));\n }\n return String(redactSensitiveValues(error));\n}\n","import { redactSensitiveValues } from \"../security/redact.js\";\n\nexport type ScpNextErrorCode =\n | \"SCP_NEXT_ERROR\"\n | \"SCP_NEXT_CONFIGURATION_ERROR\"\n | \"SCP_NEXT_VALIDATION_ERROR\"\n | \"SCP_NEXT_AUTHENTICATION_ERROR\"\n | \"SCP_NEXT_CONNECTION_ERROR\"\n | \"SCP_NEXT_TRANSFER_ERROR\"\n | \"SCP_NEXT_FILESYSTEM_ERROR\"\n | \"SCP_NEXT_HOST_VERIFICATION_ERROR\";\n\nexport interface ScpNextErrorOptions {\n code?: ScpNextErrorCode;\n cause?: unknown;\n context?: Record<string, unknown>;\n}\n\nexport class ScpNextError extends Error {\n readonly code: ScpNextErrorCode;\n readonly context: Record<string, unknown>;\n\n constructor(message: string, options: ScpNextErrorOptions = {}) {\n super(message, { cause: options.cause });\n this.name = new.target.name;\n this.code = options.code ?? \"SCP_NEXT_ERROR\";\n this.context = redactSensitiveValues(options.context ?? {}) as Record<string, unknown>;\n }\n}\n\nexport class ConfigurationError extends ScpNextError {\n constructor(message: string, options: Omit<ScpNextErrorOptions, \"code\"> = {}) {\n super(message, { ...options, code: \"SCP_NEXT_CONFIGURATION_ERROR\" });\n }\n}\n\nexport class ValidationError extends ScpNextError {\n constructor(message: string, options: Omit<ScpNextErrorOptions, \"code\"> = {}) {\n super(message, { ...options, code: \"SCP_NEXT_VALIDATION_ERROR\" });\n }\n}\n\nexport class AuthenticationError extends ScpNextError {\n constructor(message: string, options: Omit<ScpNextErrorOptions, \"code\"> = {}) {\n super(message, { ...options, code: \"SCP_NEXT_AUTHENTICATION_ERROR\" });\n }\n}\n\nexport class ConnectionError extends ScpNextError {\n constructor(message: string, options: Omit<ScpNextErrorOptions, \"code\"> = {}) {\n super(message, { ...options, code: \"SCP_NEXT_CONNECTION_ERROR\" });\n }\n}\n\nexport class TransferError extends ScpNextError {\n constructor(message: string, options: Omit<ScpNextErrorOptions, \"code\"> = {}) {\n super(message, { ...options, code: \"SCP_NEXT_TRANSFER_ERROR\" });\n }\n}\n\nexport class FileSystemError extends ScpNextError {\n constructor(message: string, options: Omit<ScpNextErrorOptions, \"code\"> = {}) {\n super(message, { ...options, code: \"SCP_NEXT_FILESYSTEM_ERROR\" });\n }\n}\n\nexport class HostVerificationError extends ScpNextError {\n constructor(message: string, options: Omit<ScpNextErrorOptions, \"code\"> = {}) {\n super(message, { ...options, code: \"SCP_NEXT_HOST_VERIFICATION_ERROR\" });\n }\n}\n\nexport function toScpNextError(error: unknown): ScpNextError {\n if (error instanceof ScpNextError) {\n return error;\n }\n\n const message = error instanceof Error ? error.message : \"Unexpected scp-next error.\";\n const lowerMessage = message.toLowerCase();\n\n if (lowerMessage.includes(\"authentication\") || lowerMessage.includes(\"permission denied\")) {\n return new AuthenticationError(\"SSH authentication failed.\", { cause: error });\n }\n\n if (lowerMessage.includes(\"host verification\") || lowerMessage.includes(\"host key\")) {\n return new HostVerificationError(\"SSH host verification failed.\", { cause: error });\n }\n\n if (\n lowerMessage.includes(\"timed out\") ||\n lowerMessage.includes(\"connection\") ||\n lowerMessage.includes(\"econn\")\n ) {\n return new ConnectionError(\"SSH connection failed.\", { cause: error });\n }\n\n return new TransferError(String(redactSensitiveValues(message)), { cause: error });\n}\n","import { createHash } from \"node:crypto\";\nimport { access, readFile } from \"node:fs/promises\";\n\nimport { HostVerificationError } from \"../errors/index.js\";\nimport { expandHome } from \"../paths/local-path.js\";\n\nfunction normalizeFingerprint(fingerprint: string): string {\n return fingerprint\n .trim()\n .replace(/^SHA256:/i, \"\")\n .replace(/^sha256:/i, \"\")\n .replace(/=+$/g, \"\");\n}\n\nexport function isFingerprintMatch(actual: string, expected: string): boolean {\n return normalizeFingerprint(actual) === normalizeFingerprint(expected);\n}\n\nexport interface HostVerifierConfig {\n host?: string | undefined;\n hostFingerprint?: string | undefined;\n knownHostsFile?: string | undefined;\n}\n\nexport async function resolveAllowedFingerprints(\n options: HostVerifierConfig\n): Promise<Set<string>> {\n const fingerprints = new Set<string>();\n const knownHostsFile = options.knownHostsFile ?? \"~/.ssh/known_hosts\";\n\n if (!options.host) {\n throw new HostVerificationError(\"A host is required for SSH host verification.\");\n }\n\n if (options.hostFingerprint) {\n fingerprints.add(normalizeFingerprint(options.hostFingerprint));\n }\n\n try {\n await access(expandHome(knownHostsFile));\n const content = await readFile(expandHome(knownHostsFile), \"utf8\");\n for (const line of content.split(/\\r?\\n/)) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\") || trimmed.startsWith(\"@\")) {\n continue;\n }\n\n const [hosts, algorithm, key] = trimmed.split(/\\s+/);\n if (!hosts || !algorithm || !key || !hosts.split(\",\").includes(options.host)) {\n continue;\n }\n\n const fingerprint = createHash(\"sha256\")\n .update(Buffer.from(key, \"base64\"))\n .digest(\"base64\")\n .replace(/=+$/g, \"\");\n fingerprints.add(fingerprint);\n }\n } catch (error) {\n if (options.knownHostsFile || !options.hostFingerprint) {\n throw new HostVerificationError(\n \"SSH host verification requires hostFingerprint or an accessible knownHostsFile.\",\n { cause: error, context: { knownHostsFile } }\n );\n }\n }\n\n if (fingerprints.size === 0) {\n throw new HostVerificationError(\n \"SSH host verification failed before connecting: no matching host fingerprint was configured.\"\n );\n }\n\n return fingerprints;\n}\n\nexport function createHostVerifier(allowedFingerprints: Set<string>) {\n return (hashedKey: string): boolean => {\n for (const expected of allowedFingerprints) {\n if (isFingerprintMatch(hashedKey, expected)) {\n return true;\n }\n }\n return false;\n };\n}\n","import { constants } from \"node:fs\";\nimport { access, mkdir, stat } from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nimport { FileSystemError, ValidationError } from \"../errors/index.js\";\n\nexport function expandHome(inputPath: string): string {\n if (inputPath === \"~\") {\n return os.homedir();\n }\n if (inputPath.startsWith(`~${path.sep}`) || inputPath.startsWith(\"~/\")) {\n return path.join(os.homedir(), inputPath.slice(2));\n }\n return inputPath;\n}\n\nexport function resolveLocalPath(inputPath: string, baseDir = process.cwd()): string {\n const expanded = expandHome(inputPath);\n return path.isAbsolute(expanded) ? path.normalize(expanded) : path.resolve(baseDir, expanded);\n}\n\nexport async function assertLocalPathExists(inputPath: string): Promise<void> {\n try {\n await access(resolveLocalPath(inputPath), constants.F_OK);\n } catch (error) {\n throw new FileSystemError(`Local path does not exist: ${inputPath}`, {\n cause: error,\n context: { path: inputPath }\n });\n }\n}\n\nexport async function assertLocalFileReadable(inputPath: string): Promise<void> {\n try {\n await access(resolveLocalPath(inputPath), constants.R_OK);\n } catch (error) {\n throw new FileSystemError(`Local file is not readable: ${inputPath}`, {\n cause: error,\n context: { path: inputPath }\n });\n }\n}\n\nexport async function assertDownloadDestination(inputPath: string, createDirectories = false) {\n const resolved = resolveLocalPath(inputPath);\n const parent = path.dirname(resolved);\n\n if (createDirectories) {\n await mkdir(parent, { recursive: true });\n return;\n }\n\n try {\n await access(parent, constants.W_OK);\n } catch (error) {\n throw new FileSystemError(`Local destination parent does not exist or is not writable: ${parent}`, {\n cause: error,\n context: { path: inputPath }\n });\n }\n}\n\nexport async function getLocalPathKind(inputPath: string): Promise<\"file\" | \"directory\"> {\n const stats = await stat(resolveLocalPath(inputPath));\n if (stats.isDirectory()) {\n return \"directory\";\n }\n if (stats.isFile()) {\n return \"file\";\n }\n throw new ValidationError(`Local path must be a file or directory: ${inputPath}`, {\n context: { path: inputPath }\n });\n}\n\nexport async function getLocalFileSize(inputPath: string): Promise<number> {\n const stats = await stat(resolveLocalPath(inputPath));\n return stats.size;\n}\n","import { readdir, stat } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport type { RemoteFileInfo, SftpTransport } from \"./transport.js\";\nimport { resolveLocalPath } from \"../paths/local-path.js\";\nimport { remoteJoin } from \"../paths/remote-path.js\";\n\nexport interface LocalFileEntry {\n absolutePath: string;\n relativePath: string;\n size: number;\n}\n\nexport async function walkLocalFiles(rootPath: string): Promise<LocalFileEntry[]> {\n const resolvedRoot = resolveLocalPath(rootPath);\n const stats = await stat(resolvedRoot);\n\n if (stats.isFile()) {\n return [\n {\n absolutePath: resolvedRoot,\n relativePath: path.basename(resolvedRoot),\n size: stats.size\n }\n ];\n }\n\n const files: LocalFileEntry[] = [];\n\n async function visit(directory: string): Promise<void> {\n const entries = await readdir(directory, { withFileTypes: true });\n for (const entry of entries) {\n const absolutePath = path.join(directory, entry.name);\n if (entry.isDirectory()) {\n await visit(absolutePath);\n } else if (entry.isFile()) {\n const entryStats = await stat(absolutePath);\n files.push({\n absolutePath,\n relativePath: path.relative(resolvedRoot, absolutePath),\n size: entryStats.size\n });\n }\n }\n }\n\n await visit(resolvedRoot);\n return files;\n}\n\nexport interface RemoteFileEntry {\n remotePath: string;\n relativePath: string;\n size: number;\n}\n\nexport async function walkRemoteFiles(\n transport: SftpTransport,\n rootPath: string\n): Promise<RemoteFileEntry[]> {\n const rootEntries = await transport.list(rootPath);\n const files: RemoteFileEntry[] = [];\n\n async function visit(directory: string, relativeBase: string, entries: RemoteFileInfo[]): Promise<void> {\n for (const entry of entries) {\n const fullPath = remoteJoin(directory, entry.name);\n const relativePath = relativeBase ? remoteJoin(relativeBase, entry.name) : entry.name;\n if (entry.type === \"d\") {\n await visit(fullPath, relativePath, await transport.list(fullPath));\n } else if (entry.type === \"-\") {\n files.push({\n remotePath: fullPath,\n relativePath,\n size: entry.size\n });\n }\n }\n }\n\n await visit(rootPath, \"\", rootEntries);\n return files;\n}\n","import path from \"node:path\";\n\nimport { ValidationError } from \"../errors/index.js\";\n\nexport function assertRemotePath(inputPath: string, label = \"remote path\"): void {\n if (!inputPath || inputPath.trim().length === 0) {\n throw new ValidationError(`Invalid ${label}: remote paths must not be empty.`, {\n context: { path: inputPath }\n });\n }\n}\n\nexport function normalizeRemotePath(inputPath: string): string {\n assertRemotePath(inputPath);\n return path.posix.normalize(inputPath.replace(/\\\\/g, \"/\"));\n}\n\nexport function remoteJoin(...parts: string[]): string {\n const joined = path.posix.join(...parts);\n assertRemotePath(joined);\n return joined;\n}\n\nexport function remoteDirname(inputPath: string): string {\n assertRemotePath(inputPath);\n return path.posix.dirname(inputPath);\n}\n","import { access } from \"node:fs/promises\";\n\nimport { ValidationError } from \"../errors/index.js\";\nimport { assertDownloadDestination, assertLocalPathExists, resolveLocalPath } from \"../paths/local-path.js\";\nimport { assertRemotePath } from \"../paths/remote-path.js\";\nimport type { DownloadOptions, ScpServerOptions, UploadOptions } from \"../types/index.js\";\n\nfunction validatePort(port: number | undefined): void {\n if (port === undefined) {\n return;\n }\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n throw new ValidationError(\"Port must be an integer between 1 and 65535.\", {\n context: { port }\n });\n }\n}\n\nfunction validateTimeout(timeout: number | undefined): void {\n if (timeout === undefined) {\n return;\n }\n if (!Number.isFinite(timeout) || timeout <= 0) {\n throw new ValidationError(\"Timeout must be a positive number.\", {\n context: { timeout }\n });\n }\n}\n\nexport async function validateServerOptions(options: ScpServerOptions): Promise<void> {\n if (!options.host) {\n throw new ValidationError(\"Host is required.\");\n }\n if (!options.username) {\n throw new ValidationError(\"Username is required.\");\n }\n\n validatePort(options.port);\n validateTimeout(options.timeout);\n\n if (options.privateKeyFile) {\n try {\n await access(resolveLocalPath(options.privateKeyFile));\n } catch (error) {\n throw new ValidationError(`Private key file does not exist: ${options.privateKeyFile}`, {\n cause: error,\n context: { privateKeyFile: options.privateKeyFile }\n });\n }\n }\n\n const hasAuthentication =\n Boolean(options.password) ||\n Boolean(options.privateKey) ||\n Boolean(options.privateKeyFile) ||\n Boolean(options.agent) ||\n Boolean(process.env.SSH_AUTH_SOCK);\n\n if (!hasAuthentication) {\n throw new ValidationError(\n \"Authentication is required. Provide a password, private key, private key file, or SSH agent.\"\n );\n }\n}\n\nexport async function validateUploadOptions(options: UploadOptions): Promise<void> {\n if (!options.localPath) {\n throw new ValidationError(\"Missing localPath.\");\n }\n if (!options.remotePath) {\n throw new ValidationError(\"Missing remotePath.\");\n }\n assertRemotePath(options.remotePath, \"remotePath\");\n validateTimeout(options.timeout);\n await assertLocalPathExists(options.localPath);\n\n if (!options.dryRun) {\n await validateServerOptions(options);\n }\n}\n\nexport async function validateDownloadOptions(options: DownloadOptions): Promise<void> {\n if (!options.remotePath) {\n throw new ValidationError(\"Missing remotePath.\");\n }\n if (!options.localPath) {\n throw new ValidationError(\"Missing localPath.\");\n }\n assertRemotePath(options.remotePath, \"remotePath\");\n validateTimeout(options.timeout);\n await assertDownloadDestination(options.localPath, options.createDirectories);\n\n if (!options.dryRun) {\n await validateServerOptions(options);\n }\n}\n","import { ScpNextClientImpl } from \"./client.js\";\nimport type { UploadOptions } from \"../types/index.js\";\n\nexport async function upload(options: UploadOptions): Promise<void> {\n const client = new ScpNextClientImpl(options);\n try {\n await client.upload(options.localPath, options.remotePath, options);\n } finally {\n await client.close();\n }\n}\n","import { ScpNextClientImpl } from \"./client.js\";\nimport type { DownloadOptions } from \"../types/index.js\";\n\nexport async function download(options: DownloadOptions): Promise<void> {\n const client = new ScpNextClientImpl(options);\n try {\n await client.download(options.remotePath, options.localPath, options);\n } finally {\n await client.close();\n }\n}\n","import { download } from \"./download.js\";\nimport { upload } from \"./upload.js\";\nimport { ValidationError } from \"../errors/index.js\";\nimport type { CopyOptions } from \"../types/index.js\";\n\nexport async function copy(options: CopyOptions): Promise<void> {\n if (options.source.type === \"local\" && options.destination.type === \"remote\") {\n await upload({\n ...options,\n localPath: options.source.path,\n remotePath: options.destination.path\n });\n return;\n }\n\n if (options.source.type === \"remote\" && options.destination.type === \"local\") {\n await download({\n ...options,\n remotePath: options.source.path,\n localPath: options.destination.path\n });\n return;\n }\n\n throw new ValidationError(\"copy() supports only local-to-remote or remote-to-local transfers.\", {\n context: {\n sourceType: options.source.type,\n destinationType: options.destination.type\n }\n });\n}\n"],"mappings":"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","names":["import_promises","import_node_path","import_promises","import_promises","os","path","path","SftpClient","import_promises","import_node_path","import_node_path","path","path","import_promises","totalBytes","path"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/client/client.ts","../src/client/transport.ts","../src/security/redact.ts","../src/errors/index.ts","../src/security/host-verification.ts","../src/paths/local-path.ts","../src/client/walk.ts","../src/paths/remote-path.ts","../src/config/validation.ts","../src/client/upload.ts","../src/client/download.ts","../src/client/copy.ts"],"sourcesContent":["export { createClient, ScpNextClientImpl } from \"./client/client.js\";\nexport { upload } from \"./client/upload.js\";\nexport { download } from \"./client/download.js\";\nexport { copy } from \"./client/copy.js\";\nexport * from \"./types/index.js\";\nexport * from \"./errors/index.js\";\n","import { access, mkdir } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { Ssh2SftpTransport } from \"./transport.js\";\nimport type { SftpTransport } from \"./transport.js\";\nimport { walkLocalFiles, walkRemoteFiles } from \"./walk.js\";\nimport { TransferError, ValidationError } from \"../errors/index.js\";\nimport { getLocalPathKind, resolveLocalPath } from \"../paths/local-path.js\";\nimport { normalizeRemotePath, remoteDirname, remoteJoin } from \"../paths/remote-path.js\";\nimport type {\n DownloadOptions,\n ScpNextClient,\n ScpServerOptions,\n TransferOptions,\n UploadOptions\n} from \"../types/index.js\";\nimport {\n validateDownloadOptions,\n validateServerOptions,\n validateUploadOptions\n} from \"../config/validation.js\";\n\nexport interface ScpNextClientDependencies {\n transport?: SftpTransport;\n}\n\nfunction percentage(transferredBytes: number, totalBytes?: number): number | undefined {\n if (!totalBytes || totalBytes <= 0) {\n return undefined;\n }\n return Math.min(100, Math.round((transferredBytes / totalBytes) * 100));\n}\n\nfunction mergeOptions<T extends TransferOptions>(\n serverOptions: ScpServerOptions,\n transferOptions: T | undefined\n): ScpServerOptions & T {\n return { ...serverOptions, ...(transferOptions ?? {}) } as ScpServerOptions & T;\n}\n\nexport class ScpNextClientImpl implements ScpNextClient {\n private readonly serverOptions: ScpServerOptions;\n private readonly transport: SftpTransport;\n private connected = false;\n\n constructor(serverOptions: ScpServerOptions, dependencies: ScpNextClientDependencies = {}) {\n this.serverOptions = serverOptions;\n this.transport = dependencies.transport ?? new Ssh2SftpTransport();\n }\n\n async connect(): Promise<void> {\n await validateServerOptions(this.serverOptions);\n await this.transport.connect(this.serverOptions);\n this.connected = true;\n }\n\n async upload(\n localPath: string,\n remotePath: string,\n options: TransferOptions = {}\n ): Promise<void> {\n const uploadOptions: UploadOptions = {\n ...mergeOptions(this.serverOptions, options),\n localPath,\n remotePath\n };\n await validateUploadOptions(uploadOptions);\n\n if (options.dryRun) {\n return;\n }\n\n await this.ensureConnected();\n await this.uploadWithTransport(uploadOptions);\n }\n\n async download(\n remotePath: string,\n localPath: string,\n options: TransferOptions = {}\n ): Promise<void> {\n const downloadOptions: DownloadOptions = {\n ...mergeOptions(this.serverOptions, options),\n remotePath,\n localPath\n };\n await validateDownloadOptions(downloadOptions);\n\n if (options.dryRun) {\n return;\n }\n\n await this.ensureConnected();\n await this.downloadWithTransport(downloadOptions);\n }\n\n async close(): Promise<void> {\n if (this.connected) {\n await this.transport.close();\n this.connected = false;\n }\n }\n\n private async ensureConnected(): Promise<void> {\n if (!this.connected) {\n await this.connect();\n }\n }\n\n private async uploadWithTransport(options: UploadOptions): Promise<void> {\n const localKind = await getLocalPathKind(options.localPath);\n const remotePath = normalizeRemotePath(options.remotePath);\n\n if (localKind === \"directory\" && !options.recursive) {\n throw new ValidationError(\"Uploading a directory requires recursive mode.\");\n }\n\n if (localKind === \"file\") {\n const destinationFile = await this.resolveRemoteDestinationForLocalFile(options, remotePath);\n const totalBytes = undefined;\n await this.transport.uploadFile(resolveLocalPath(options.localPath), destinationFile, (step) => {\n options.onProgress?.({\n operation: \"upload\",\n source: options.localPath,\n destination: options.remotePath,\n transferredBytes: step.transferredBytes,\n totalBytes: step.totalBytes || totalBytes,\n percentage: percentage(step.transferredBytes, step.totalBytes),\n currentFile: options.localPath\n });\n });\n return;\n }\n\n const files = await walkLocalFiles(options.localPath);\n const totalBytes = files.reduce((sum, file) => sum + file.size, 0);\n let completedBytes = 0;\n let completedFiles = 0;\n\n const destinationExists = await this.transport.exists(remotePath);\n if (destinationExists && destinationExists !== \"d\") {\n throw new TransferError(`Remote destination already exists and is not a directory: ${remotePath}`, {\n context: { remotePath }\n });\n }\n if (!destinationExists) {\n if (!options.createDirectories) {\n throw new TransferError(`Remote destination directory does not exist: ${remotePath}`, {\n context: { remotePath }\n });\n }\n await this.transport.mkdir(remotePath, true);\n }\n\n for (const file of files) {\n const destinationFile = remoteJoin(remotePath, file.relativePath.split(path.sep).join(\"/\"));\n await this.ensureRemoteParent(destinationFile, true);\n await this.assertRemoteOverwrite(destinationFile, options.overwrite);\n\n await this.transport.uploadFile(file.absolutePath, destinationFile, (step) => {\n const transferredBytes = completedBytes + step.transferredBytes;\n options.onProgress?.({\n operation: \"upload\",\n source: options.localPath,\n destination: options.remotePath,\n transferredBytes,\n totalBytes,\n percentage: percentage(transferredBytes, totalBytes),\n currentFile: file.relativePath,\n completedFiles,\n totalFiles: files.length\n });\n });\n\n completedBytes += file.size;\n completedFiles += 1;\n }\n }\n\n private async resolveRemoteDestinationForLocalFile(\n options: UploadOptions,\n remotePath: string\n ): Promise<string> {\n const remoteKind = await this.transport.exists(remotePath);\n const isDirectoryLikeDestination = remoteKind === \"d\" || remotePath.endsWith(\"/\");\n\n if (!isDirectoryLikeDestination) {\n await this.ensureRemoteParent(remotePath, options.createDirectories);\n await this.assertRemoteOverwrite(remotePath, options.overwrite);\n return remotePath;\n }\n\n if (remoteKind && remoteKind !== \"d\") {\n throw new TransferError(`Remote destination already exists and is not a directory: ${remotePath}`, {\n context: { remotePath }\n });\n }\n\n const remoteDirectory = remotePath.replace(/\\/+$/g, \"\") || \"/\";\n if (!remoteKind) {\n if (!options.createDirectories) {\n throw new TransferError(`Remote destination directory does not exist: ${remoteDirectory}`, {\n context: { remotePath }\n });\n }\n await this.transport.mkdir(remoteDirectory, true);\n }\n\n const destinationFile = remoteJoin(\n remoteDirectory,\n path.basename(resolveLocalPath(options.localPath))\n );\n await this.assertRemoteOverwrite(destinationFile, options.overwrite);\n return destinationFile;\n }\n\n private async downloadWithTransport(options: DownloadOptions): Promise<void> {\n const remotePath = normalizeRemotePath(options.remotePath);\n const remoteKind = await this.transport.exists(remotePath);\n\n if (!remoteKind) {\n throw new TransferError(`Remote path does not exist: ${options.remotePath}`, {\n context: { remotePath: options.remotePath }\n });\n }\n\n if (remoteKind === \"d\") {\n if (!options.recursive) {\n throw new ValidationError(\"Downloading a directory requires recursive mode.\");\n }\n await this.downloadDirectory(options);\n return;\n }\n\n await this.downloadSingleFile(options, remotePath);\n }\n\n private async downloadSingleFile(options: DownloadOptions, remotePath: string): Promise<void> {\n const localPath = resolveLocalPath(options.localPath);\n await this.assertLocalOverwrite(localPath, options.overwrite);\n if (options.createDirectories) {\n await mkdir(path.dirname(localPath), { recursive: true });\n }\n await this.transport.downloadFile(remotePath, localPath, (step) => {\n options.onProgress?.({\n operation: \"download\",\n source: options.remotePath,\n destination: options.localPath,\n transferredBytes: step.transferredBytes,\n totalBytes: step.totalBytes,\n percentage: percentage(step.transferredBytes, step.totalBytes),\n currentFile: options.remotePath\n });\n });\n }\n\n private async downloadDirectory(options: DownloadOptions): Promise<void> {\n const remotePath = normalizeRemotePath(options.remotePath);\n const localRoot = resolveLocalPath(options.localPath);\n const files = await walkRemoteFiles(this.transport, remotePath);\n const totalBytes = files.reduce((sum, file) => sum + file.size, 0);\n let completedBytes = 0;\n let completedFiles = 0;\n\n await mkdir(localRoot, { recursive: Boolean(options.createDirectories) });\n\n for (const file of files) {\n const localFile = path.join(localRoot, ...file.relativePath.split(\"/\"));\n await this.assertLocalOverwrite(localFile, options.overwrite);\n await mkdir(path.dirname(localFile), { recursive: true });\n await this.transport.downloadFile(file.remotePath, localFile, (step) => {\n const transferredBytes = completedBytes + step.transferredBytes;\n options.onProgress?.({\n operation: \"download\",\n source: options.remotePath,\n destination: options.localPath,\n transferredBytes,\n totalBytes,\n percentage: percentage(transferredBytes, totalBytes),\n currentFile: file.relativePath,\n completedFiles,\n totalFiles: files.length\n });\n });\n completedBytes += file.size;\n completedFiles += 1;\n }\n }\n\n private async ensureRemoteParent(remotePath: string, createDirectories = false): Promise<void> {\n const parent = remoteDirname(remotePath);\n if (parent === \".\" || parent === \"/\") {\n return;\n }\n\n if (createDirectories) {\n await this.transport.mkdir(parent, true);\n return;\n }\n\n const exists = await this.transport.exists(parent);\n if (exists !== \"d\") {\n throw new TransferError(`Remote destination parent does not exist: ${parent}`, {\n context: { remotePath }\n });\n }\n }\n\n private async assertRemoteOverwrite(remotePath: string, overwrite = false): Promise<void> {\n if (overwrite) {\n return;\n }\n const exists = await this.transport.exists(remotePath);\n if (exists) {\n throw new TransferError(`Remote destination already exists: ${remotePath}`, {\n context: { remotePath }\n });\n }\n }\n\n private async assertLocalOverwrite(localPath: string, overwrite = false): Promise<void> {\n if (overwrite) {\n return;\n }\n\n try {\n await access(localPath);\n throw new TransferError(`Local destination already exists: ${localPath}`, {\n context: { localPath }\n });\n } catch (error) {\n if (error instanceof TransferError) {\n throw error;\n }\n }\n }\n}\n\nexport function createClient(options: ScpServerOptions): ScpNextClient {\n return new ScpNextClientImpl(options);\n}\n","import { readFile } from \"node:fs/promises\";\n\nimport SftpClient from \"ssh2-sftp-client\";\nimport type {\n ConnectOptions as SftpConnectOptions,\n FileInfo,\n TransferOptions as SftpTransferOptions\n} from \"ssh2-sftp-client\";\n\nimport {\n ConnectionError,\n HostVerificationError,\n TransferError,\n toScpNextError\n} from \"../errors/index.js\";\nimport { createHostVerifier, resolveAllowedFingerprints } from \"../security/host-verification.js\";\nimport { formatErrorMessage } from \"../security/redact.js\";\nimport type { ScpServerOptions } from \"../types/index.js\";\nimport { expandHome } from \"../paths/local-path.js\";\n\nexport interface RemoteFileInfo {\n type: \"-\" | \"d\" | \"l\";\n name: string;\n size: number;\n}\n\nexport interface TransferStep {\n transferredBytes: number;\n chunkBytes: number;\n totalBytes: number;\n}\n\nexport interface SftpTransport {\n connect(options: ScpServerOptions): Promise<void>;\n close(): Promise<void>;\n exists(remotePath: string): Promise<false | \"-\" | \"d\" | \"l\">;\n mkdir(remotePath: string, recursive?: boolean): Promise<void>;\n list(remotePath: string): Promise<RemoteFileInfo[]>;\n uploadFile(\n localPath: string,\n remotePath: string,\n onStep?: (step: TransferStep) => void\n ): Promise<void>;\n downloadFile(\n remotePath: string,\n localPath: string,\n onStep?: (step: TransferStep) => void\n ): Promise<void>;\n}\n\nfunction toRemoteFileInfo(info: FileInfo): RemoteFileInfo {\n return {\n type: info.type,\n name: info.name,\n size: info.size\n };\n}\n\nexport class Ssh2SftpTransport implements SftpTransport {\n private readonly client: SftpClient;\n\n constructor(name = \"scp-next\") {\n this.client = new SftpClient(name, {\n error: () => undefined,\n end: () => undefined,\n close: () => undefined\n });\n }\n\n async connect(options: ScpServerOptions): Promise<void> {\n const allowedFingerprints = await resolveAllowedFingerprints(options);\n const hostVerifier = createHostVerifier(allowedFingerprints);\n\n const connectOptions: SftpConnectOptions = {};\n if (options.host !== undefined) connectOptions.host = options.host;\n if (options.port !== undefined) connectOptions.port = options.port;\n if (options.username !== undefined) connectOptions.username = options.username;\n if (options.password !== undefined) connectOptions.password = options.password;\n if (options.privateKey !== undefined) {\n connectOptions.privateKey = options.privateKey;\n } else if (options.privateKeyFile) {\n connectOptions.privateKey = await readFile(expandHome(options.privateKeyFile), \"utf8\");\n }\n if (options.passphrase !== undefined) connectOptions.passphrase = options.passphrase;\n const agent = options.agent ?? process.env.SSH_AUTH_SOCK;\n if (agent !== undefined) {\n connectOptions.agent = agent;\n }\n if (options.timeout !== undefined) connectOptions.readyTimeout = options.timeout;\n if (hostVerifier) {\n connectOptions.hostHash = \"sha256\";\n connectOptions.hostVerifier = hostVerifier;\n }\n\n try {\n await this.client.connect(connectOptions);\n } catch (error) {\n const converted = toScpNextError(error);\n if (converted instanceof HostVerificationError) {\n throw new HostVerificationError(\n `SSH host verification failed during connection: ${formatErrorMessage(\n error\n )}.${hostVerificationTroubleshooting(options.host)}`,\n { cause: error, context: connectionContext(options) }\n );\n }\n if (isLikelyHostVerificationFailure(error)) {\n throw new HostVerificationError(\n `SSH host verification failed during connection.${hostVerificationTroubleshooting(\n options.host\n )}`,\n { cause: error, context: connectionContext(options) }\n );\n }\n throw new ConnectionError(`Unable to connect to SSH server: ${formatErrorMessage(error)}`, {\n cause: error,\n context: connectionContext(options)\n });\n }\n }\n\n async close(): Promise<void> {\n await this.client.end();\n }\n\n async exists(remotePath: string): Promise<false | \"-\" | \"d\" | \"l\"> {\n return this.client.exists(remotePath);\n }\n\n async mkdir(remotePath: string, recursive = true): Promise<void> {\n await this.client.mkdir(remotePath, recursive);\n }\n\n async list(remotePath: string): Promise<RemoteFileInfo[]> {\n const files = await this.client.list(remotePath);\n return files.map(toRemoteFileInfo);\n }\n\n async uploadFile(\n localPath: string,\n remotePath: string,\n onStep?: (step: TransferStep) => void\n ): Promise<void> {\n try {\n await this.client.fastPut(localPath, remotePath, this.transferOptions(onStep));\n } catch (error) {\n throw new TransferError(`Upload failed for ${localPath}: ${formatErrorMessage(error)}`, {\n cause: error,\n context: { localPath, remotePath }\n });\n }\n }\n\n async downloadFile(\n remotePath: string,\n localPath: string,\n onStep?: (step: TransferStep) => void\n ): Promise<void> {\n try {\n await this.client.fastGet(remotePath, localPath, this.transferOptions(onStep));\n } catch (error) {\n throw new TransferError(`Download failed for ${remotePath}: ${formatErrorMessage(error)}`, {\n cause: error,\n context: { remotePath, localPath }\n });\n }\n }\n\n private transferOptions(onStep?: (step: TransferStep) => void): SftpTransferOptions | undefined {\n if (!onStep) {\n return undefined;\n }\n return {\n step(transferredBytes, chunkBytes, totalBytes) {\n onStep({ transferredBytes, chunkBytes, totalBytes });\n }\n };\n }\n}\n\nfunction connectionContext(options: ScpServerOptions): Record<string, unknown> {\n return {\n host: options.host,\n port: options.port,\n username: options.username\n };\n}\n\nfunction isLikelyHostVerificationFailure(error: unknown): boolean {\n const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();\n return (\n message.includes(\"host\") &&\n (message.includes(\"verif\") ||\n message.includes(\"fingerprint\") ||\n message.includes(\"host key\") ||\n message.includes(\"key mismatch\") ||\n message.includes(\"denied\"))\n );\n}\n\nfunction hostVerificationTroubleshooting(host: string | undefined): string {\n const displayHost = host ?? \"<host>\";\n return [\n \"\",\n \"\",\n \"Typical fix:\",\n ` ssh ${displayHost}`,\n \"\",\n \"If plain ssh works but scp-next fails, check that the host entry is available in\",\n \"`~/.ssh/known_hosts` for the same host and port used by scp-next. For non-default ports,\",\n \"OpenSSH usually stores entries as `[host]:port`.\",\n \"You can also configure hostFingerprint or knownHostsFile explicitly.\"\n ].join(\"\\n\");\n}\n","const SENSITIVE_KEYS = [\n \"password\",\n \"passphrase\",\n \"privatekey\",\n \"privatekeyfile\",\n \"token\",\n \"authorization\",\n \"credential\",\n \"secret\"\n];\n\nconst PRIVATE_KEY_PATTERN =\n /-----BEGIN [A-Z ]*PRIVATE KEY-----[\\s\\S]*?-----END [A-Z ]*PRIVATE KEY-----/g;\n\nfunction isSensitiveKey(key: string): boolean {\n const normalized = key.replace(/[-_\\s]/g, \"\").toLowerCase();\n return SENSITIVE_KEYS.some((sensitive) => normalized.includes(sensitive));\n}\n\nexport function redactSensitiveValues(value: unknown): unknown {\n if (typeof value === \"string\") {\n return value.replace(PRIVATE_KEY_PATTERN, \"[REDACTED]\");\n }\n\n if (Array.isArray(value)) {\n const redacted: unknown[] = value.map((item: unknown) => redactSensitiveValues(item));\n return redacted;\n }\n\n if (value && typeof value === \"object\") {\n const redacted: Record<string, unknown> = {};\n for (const [key, nestedValue] of Object.entries(value)) {\n redacted[key] = isSensitiveKey(key) ? \"[REDACTED]\" : redactSensitiveValues(nestedValue);\n }\n return redacted;\n }\n\n return value;\n}\n\nexport function formatErrorMessage(error: unknown): string {\n if (error instanceof Error) {\n return String(redactSensitiveValues(error.message));\n }\n return String(redactSensitiveValues(error));\n}\n","import { redactSensitiveValues } from \"../security/redact.js\";\n\nexport type ScpNextErrorCode =\n | \"SCP_NEXT_ERROR\"\n | \"SCP_NEXT_CONFIGURATION_ERROR\"\n | \"SCP_NEXT_VALIDATION_ERROR\"\n | \"SCP_NEXT_AUTHENTICATION_ERROR\"\n | \"SCP_NEXT_CONNECTION_ERROR\"\n | \"SCP_NEXT_TRANSFER_ERROR\"\n | \"SCP_NEXT_FILESYSTEM_ERROR\"\n | \"SCP_NEXT_HOST_VERIFICATION_ERROR\";\n\nexport interface ScpNextErrorOptions {\n code?: ScpNextErrorCode;\n cause?: unknown;\n context?: Record<string, unknown>;\n}\n\nexport class ScpNextError extends Error {\n readonly code: ScpNextErrorCode;\n readonly context: Record<string, unknown>;\n\n constructor(message: string, options: ScpNextErrorOptions = {}) {\n super(message, { cause: options.cause });\n this.name = new.target.name;\n this.code = options.code ?? \"SCP_NEXT_ERROR\";\n this.context = redactSensitiveValues(options.context ?? {}) as Record<string, unknown>;\n }\n}\n\nexport class ConfigurationError extends ScpNextError {\n constructor(message: string, options: Omit<ScpNextErrorOptions, \"code\"> = {}) {\n super(message, { ...options, code: \"SCP_NEXT_CONFIGURATION_ERROR\" });\n }\n}\n\nexport class ValidationError extends ScpNextError {\n constructor(message: string, options: Omit<ScpNextErrorOptions, \"code\"> = {}) {\n super(message, { ...options, code: \"SCP_NEXT_VALIDATION_ERROR\" });\n }\n}\n\nexport class AuthenticationError extends ScpNextError {\n constructor(message: string, options: Omit<ScpNextErrorOptions, \"code\"> = {}) {\n super(message, { ...options, code: \"SCP_NEXT_AUTHENTICATION_ERROR\" });\n }\n}\n\nexport class ConnectionError extends ScpNextError {\n constructor(message: string, options: Omit<ScpNextErrorOptions, \"code\"> = {}) {\n super(message, { ...options, code: \"SCP_NEXT_CONNECTION_ERROR\" });\n }\n}\n\nexport class TransferError extends ScpNextError {\n constructor(message: string, options: Omit<ScpNextErrorOptions, \"code\"> = {}) {\n super(message, { ...options, code: \"SCP_NEXT_TRANSFER_ERROR\" });\n }\n}\n\nexport class FileSystemError extends ScpNextError {\n constructor(message: string, options: Omit<ScpNextErrorOptions, \"code\"> = {}) {\n super(message, { ...options, code: \"SCP_NEXT_FILESYSTEM_ERROR\" });\n }\n}\n\nexport class HostVerificationError extends ScpNextError {\n constructor(message: string, options: Omit<ScpNextErrorOptions, \"code\"> = {}) {\n super(message, { ...options, code: \"SCP_NEXT_HOST_VERIFICATION_ERROR\" });\n }\n}\n\nexport function toScpNextError(error: unknown): ScpNextError {\n if (error instanceof ScpNextError) {\n return error;\n }\n\n const message = error instanceof Error ? error.message : \"Unexpected scp-next error.\";\n const lowerMessage = message.toLowerCase();\n\n if (lowerMessage.includes(\"authentication\") || lowerMessage.includes(\"permission denied\")) {\n return new AuthenticationError(\"SSH authentication failed.\", { cause: error });\n }\n\n if (\n lowerMessage.includes(\"host verification\") ||\n lowerMessage.includes(\"host key\") ||\n lowerMessage.includes(\"fingerprint\") ||\n (lowerMessage.includes(\"host\") && lowerMessage.includes(\"denied\"))\n ) {\n return new HostVerificationError(\"SSH host verification failed.\", { cause: error });\n }\n\n if (\n lowerMessage.includes(\"timed out\") ||\n lowerMessage.includes(\"connection\") ||\n lowerMessage.includes(\"econn\")\n ) {\n return new ConnectionError(\"SSH connection failed.\", { cause: error });\n }\n\n return new TransferError(String(redactSensitiveValues(message)), { cause: error });\n}\n","import { createHash } from \"node:crypto\";\nimport { access, readFile } from \"node:fs/promises\";\n\nimport { HostVerificationError } from \"../errors/index.js\";\nimport { expandHome } from \"../paths/local-path.js\";\n\nfunction normalizeFingerprint(fingerprint: string): string {\n return fingerprint\n .trim()\n .replace(/^SHA256:/i, \"\")\n .replace(/^sha256:/i, \"\")\n .replace(/=+$/g, \"\");\n}\n\nfunction isHexFingerprint(fingerprint: string): boolean {\n return /^[0-9a-f]+$/i.test(fingerprint);\n}\n\nfunction fingerprintCandidates(fingerprint: string): string[] {\n const normalized = normalizeFingerprint(fingerprint);\n const candidates = new Set([normalized]);\n\n if (isHexFingerprint(normalized)) {\n candidates.add(normalized.toLowerCase());\n } else {\n try {\n candidates.add(Buffer.from(normalized, \"base64\").toString(\"hex\"));\n } catch {\n // Keep the original normalized value when it is not valid base64.\n }\n }\n\n return [...candidates];\n}\n\nexport function isFingerprintMatch(actual: string, expected: string): boolean {\n const normalizedActual = normalizeFingerprint(actual);\n const normalizedExpected = normalizeFingerprint(expected);\n if (normalizedActual === normalizedExpected) {\n return true;\n }\n return (\n isHexFingerprint(normalizedActual) &&\n isHexFingerprint(normalizedExpected) &&\n normalizedActual.toLowerCase() === normalizedExpected.toLowerCase()\n );\n}\n\nexport interface HostVerifierConfig {\n host?: string | undefined;\n port?: number | undefined;\n hostFingerprint?: string | undefined;\n knownHostsFile?: string | undefined;\n}\n\nfunction hostVerificationHint(host: string): string {\n return [\n \"\",\n \"Typical fix:\",\n ` ssh ${host}`,\n \"\",\n \"If you trust the host key, accept it and retry the scp-next command.\",\n \"Alternatively configure hostFingerprint or knownHostsFile explicitly.\"\n ].join(\"\\n\");\n}\n\nexport async function resolveAllowedFingerprints(\n options: HostVerifierConfig\n): Promise<Set<string>> {\n const fingerprints = new Set<string>();\n const knownHostsFile = options.knownHostsFile ?? \"~/.ssh/known_hosts\";\n\n if (!options.host) {\n throw new HostVerificationError(\"A host is required for SSH host verification.\");\n }\n\n if (options.hostFingerprint) {\n for (const fingerprint of fingerprintCandidates(options.hostFingerprint)) {\n fingerprints.add(fingerprint);\n }\n }\n\n try {\n await access(expandHome(knownHostsFile));\n const content = await readFile(expandHome(knownHostsFile), \"utf8\");\n for (const line of content.split(/\\r?\\n/)) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\") || trimmed.startsWith(\"@\")) {\n continue;\n }\n\n const [hosts, algorithm, key] = trimmed.split(/\\s+/);\n const hostPatterns = hosts?.split(\",\") ?? [];\n const portHost = options.port ? `[${options.host}]:${options.port}` : undefined;\n const matchesHost =\n hostPatterns.includes(options.host) || Boolean(portHost && hostPatterns.includes(portHost));\n if (!hosts || !algorithm || !key || !matchesHost) {\n continue;\n }\n\n const keyBuffer = Buffer.from(key, \"base64\");\n fingerprints.add(createHash(\"sha256\").update(keyBuffer).digest(\"base64\").replace(/=+$/g, \"\"));\n fingerprints.add(createHash(\"sha256\").update(keyBuffer).digest(\"hex\"));\n }\n } catch (error) {\n if (options.knownHostsFile || !options.hostFingerprint) {\n throw new HostVerificationError(\n \"SSH host verification requires hostFingerprint or an accessible knownHostsFile.\",\n { cause: error, context: { knownHostsFile } }\n );\n }\n }\n\n if (fingerprints.size === 0) {\n throw new HostVerificationError(\n `SSH host verification failed before connecting: no matching host fingerprint was configured.${hostVerificationHint(\n options.host\n )}`\n );\n }\n\n return fingerprints;\n}\n\nexport function createHostVerifier(allowedFingerprints: Set<string>) {\n return (hashedKey: string): boolean => {\n for (const expected of allowedFingerprints) {\n if (isFingerprintMatch(hashedKey, expected)) {\n return true;\n }\n }\n return false;\n };\n}\n","import { constants } from \"node:fs\";\nimport { access, mkdir, stat } from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nimport { FileSystemError, ValidationError } from \"../errors/index.js\";\n\nexport function expandHome(inputPath: string): string {\n if (inputPath === \"~\") {\n return os.homedir();\n }\n if (inputPath.startsWith(`~${path.sep}`) || inputPath.startsWith(\"~/\")) {\n return path.join(os.homedir(), inputPath.slice(2));\n }\n return inputPath;\n}\n\nexport function resolveLocalPath(inputPath: string, baseDir = process.cwd()): string {\n const expanded = expandHome(inputPath);\n return path.isAbsolute(expanded) ? path.normalize(expanded) : path.resolve(baseDir, expanded);\n}\n\nexport async function assertLocalPathExists(inputPath: string): Promise<void> {\n try {\n await access(resolveLocalPath(inputPath), constants.F_OK);\n } catch (error) {\n throw new FileSystemError(`Local path does not exist: ${inputPath}`, {\n cause: error,\n context: { path: inputPath }\n });\n }\n}\n\nexport async function assertLocalFileReadable(inputPath: string): Promise<void> {\n try {\n await access(resolveLocalPath(inputPath), constants.R_OK);\n } catch (error) {\n throw new FileSystemError(`Local file is not readable: ${inputPath}`, {\n cause: error,\n context: { path: inputPath }\n });\n }\n}\n\nexport async function assertDownloadDestination(inputPath: string, createDirectories = false) {\n const resolved = resolveLocalPath(inputPath);\n const parent = path.dirname(resolved);\n\n if (createDirectories) {\n await mkdir(parent, { recursive: true });\n return;\n }\n\n try {\n await access(parent, constants.W_OK);\n } catch (error) {\n throw new FileSystemError(`Local destination parent does not exist or is not writable: ${parent}`, {\n cause: error,\n context: { path: inputPath }\n });\n }\n}\n\nexport async function getLocalPathKind(inputPath: string): Promise<\"file\" | \"directory\"> {\n const stats = await stat(resolveLocalPath(inputPath));\n if (stats.isDirectory()) {\n return \"directory\";\n }\n if (stats.isFile()) {\n return \"file\";\n }\n throw new ValidationError(`Local path must be a file or directory: ${inputPath}`, {\n context: { path: inputPath }\n });\n}\n\nexport async function getLocalFileSize(inputPath: string): Promise<number> {\n const stats = await stat(resolveLocalPath(inputPath));\n return stats.size;\n}\n","import { readdir, stat } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport type { RemoteFileInfo, SftpTransport } from \"./transport.js\";\nimport { resolveLocalPath } from \"../paths/local-path.js\";\nimport { remoteJoin } from \"../paths/remote-path.js\";\n\nexport interface LocalFileEntry {\n absolutePath: string;\n relativePath: string;\n size: number;\n}\n\nexport async function walkLocalFiles(rootPath: string): Promise<LocalFileEntry[]> {\n const resolvedRoot = resolveLocalPath(rootPath);\n const stats = await stat(resolvedRoot);\n\n if (stats.isFile()) {\n return [\n {\n absolutePath: resolvedRoot,\n relativePath: path.basename(resolvedRoot),\n size: stats.size\n }\n ];\n }\n\n const files: LocalFileEntry[] = [];\n\n async function visit(directory: string): Promise<void> {\n const entries = await readdir(directory, { withFileTypes: true });\n for (const entry of entries) {\n const absolutePath = path.join(directory, entry.name);\n if (entry.isDirectory()) {\n await visit(absolutePath);\n } else if (entry.isFile()) {\n const entryStats = await stat(absolutePath);\n files.push({\n absolutePath,\n relativePath: path.relative(resolvedRoot, absolutePath),\n size: entryStats.size\n });\n }\n }\n }\n\n await visit(resolvedRoot);\n return files;\n}\n\nexport interface RemoteFileEntry {\n remotePath: string;\n relativePath: string;\n size: number;\n}\n\nexport async function walkRemoteFiles(\n transport: SftpTransport,\n rootPath: string\n): Promise<RemoteFileEntry[]> {\n const rootEntries = await transport.list(rootPath);\n const files: RemoteFileEntry[] = [];\n\n async function visit(directory: string, relativeBase: string, entries: RemoteFileInfo[]): Promise<void> {\n for (const entry of entries) {\n const fullPath = remoteJoin(directory, entry.name);\n const relativePath = relativeBase ? remoteJoin(relativeBase, entry.name) : entry.name;\n if (entry.type === \"d\") {\n await visit(fullPath, relativePath, await transport.list(fullPath));\n } else if (entry.type === \"-\") {\n files.push({\n remotePath: fullPath,\n relativePath,\n size: entry.size\n });\n }\n }\n }\n\n await visit(rootPath, \"\", rootEntries);\n return files;\n}\n","import path from \"node:path\";\n\nimport { ValidationError } from \"../errors/index.js\";\n\nexport function assertRemotePath(inputPath: string, label = \"remote path\"): void {\n if (!inputPath || inputPath.trim().length === 0) {\n throw new ValidationError(`Invalid ${label}: remote paths must not be empty.`, {\n context: { path: inputPath }\n });\n }\n}\n\nexport function normalizeRemotePath(inputPath: string): string {\n assertRemotePath(inputPath);\n return path.posix.normalize(inputPath.replace(/\\\\/g, \"/\"));\n}\n\nexport function remoteJoin(...parts: string[]): string {\n const joined = path.posix.join(...parts);\n assertRemotePath(joined);\n return joined;\n}\n\nexport function remoteDirname(inputPath: string): string {\n assertRemotePath(inputPath);\n return path.posix.dirname(inputPath);\n}\n","import { access } from \"node:fs/promises\";\n\nimport { ValidationError } from \"../errors/index.js\";\nimport { assertDownloadDestination, assertLocalPathExists, resolveLocalPath } from \"../paths/local-path.js\";\nimport { assertRemotePath } from \"../paths/remote-path.js\";\nimport type { DownloadOptions, ScpServerOptions, UploadOptions } from \"../types/index.js\";\n\nfunction validatePort(port: number | undefined): void {\n if (port === undefined) {\n return;\n }\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n throw new ValidationError(\"Port must be an integer between 1 and 65535.\", {\n context: { port }\n });\n }\n}\n\nfunction validateTimeout(timeout: number | undefined): void {\n if (timeout === undefined) {\n return;\n }\n if (!Number.isFinite(timeout) || timeout <= 0) {\n throw new ValidationError(\"Timeout must be a positive number.\", {\n context: { timeout }\n });\n }\n}\n\nexport async function validateServerOptions(options: ScpServerOptions): Promise<void> {\n if (!options.host) {\n throw new ValidationError(\"Host is required.\");\n }\n if (!options.username) {\n throw new ValidationError(\"Username is required.\");\n }\n\n validatePort(options.port);\n validateTimeout(options.timeout);\n\n if (options.privateKeyFile) {\n try {\n await access(resolveLocalPath(options.privateKeyFile));\n } catch (error) {\n throw new ValidationError(`Private key file does not exist: ${options.privateKeyFile}`, {\n cause: error,\n context: { privateKeyFile: options.privateKeyFile }\n });\n }\n }\n\n const hasAuthentication =\n Boolean(options.password) ||\n Boolean(options.privateKey) ||\n Boolean(options.privateKeyFile) ||\n Boolean(options.agent) ||\n Boolean(process.env.SSH_AUTH_SOCK);\n\n if (!hasAuthentication) {\n throw new ValidationError(\n \"Authentication is required. Provide a password, private key, private key file, or SSH agent.\"\n );\n }\n}\n\nexport async function validateUploadOptions(options: UploadOptions): Promise<void> {\n if (!options.localPath) {\n throw new ValidationError(\"Missing localPath.\");\n }\n if (!options.remotePath) {\n throw new ValidationError(\"Missing remotePath.\");\n }\n assertRemotePath(options.remotePath, \"remotePath\");\n validateTimeout(options.timeout);\n await assertLocalPathExists(options.localPath);\n\n if (!options.dryRun) {\n await validateServerOptions(options);\n }\n}\n\nexport async function validateDownloadOptions(options: DownloadOptions): Promise<void> {\n if (!options.remotePath) {\n throw new ValidationError(\"Missing remotePath.\");\n }\n if (!options.localPath) {\n throw new ValidationError(\"Missing localPath.\");\n }\n assertRemotePath(options.remotePath, \"remotePath\");\n validateTimeout(options.timeout);\n await assertDownloadDestination(options.localPath, options.createDirectories);\n\n if (!options.dryRun) {\n await validateServerOptions(options);\n }\n}\n","import { ScpNextClientImpl } from \"./client.js\";\nimport type { UploadOptions } from \"../types/index.js\";\n\nexport async function upload(options: UploadOptions): Promise<void> {\n const client = new ScpNextClientImpl(options);\n try {\n await client.upload(options.localPath, options.remotePath, options);\n } finally {\n await client.close();\n }\n}\n","import { ScpNextClientImpl } from \"./client.js\";\nimport type { DownloadOptions } from \"../types/index.js\";\n\nexport async function download(options: DownloadOptions): Promise<void> {\n const client = new ScpNextClientImpl(options);\n try {\n await client.download(options.remotePath, options.localPath, options);\n } finally {\n await client.close();\n }\n}\n","import { download } from \"./download.js\";\nimport { upload } from \"./upload.js\";\nimport { ValidationError } from \"../errors/index.js\";\nimport type { CopyOptions } from \"../types/index.js\";\n\nexport async function copy(options: CopyOptions): Promise<void> {\n if (options.source.type === \"local\" && options.destination.type === \"remote\") {\n await upload({\n ...options,\n localPath: options.source.path,\n remotePath: options.destination.path\n });\n return;\n }\n\n if (options.source.type === \"remote\" && options.destination.type === \"local\") {\n await download({\n ...options,\n remotePath: options.source.path,\n localPath: options.destination.path\n });\n return;\n }\n\n throw new ValidationError(\"copy() supports only local-to-remote or remote-to-local transfers.\", {\n context: {\n sourceType: options.source.type,\n destinationType: options.destination.type\n }\n });\n}\n"],"mappings":"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","names":["import_promises","import_node_path","import_promises","import_promises","os","path","path","SftpClient","import_promises","import_node_path","import_node_path","path","path","import_promises","totalBytes","path"]}
package/dist/index.d.cts CHANGED
@@ -128,6 +128,7 @@ declare class ScpNextClientImpl implements ScpNextClient {
128
128
  close(): Promise<void>;
129
129
  private ensureConnected;
130
130
  private uploadWithTransport;
131
+ private resolveRemoteDestinationForLocalFile;
131
132
  private downloadWithTransport;
132
133
  private downloadSingleFile;
133
134
  private downloadDirectory;
package/dist/index.d.ts CHANGED
@@ -128,6 +128,7 @@ declare class ScpNextClientImpl implements ScpNextClient {
128
128
  close(): Promise<void>;
129
129
  private ensureConnected;
130
130
  private uploadWithTransport;
131
+ private resolveRemoteDestinationForLocalFile;
131
132
  private downloadWithTransport;
132
133
  private downloadSingleFile;
133
134
  private downloadDirectory;