scp-next 1.0.6 → 1.0.8

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/README.md CHANGED
@@ -44,8 +44,7 @@ scp-next run <job> [source] [destination] [options]
44
44
  CLI commands use `<source> <destination>`. Programmatic upload and download APIs use
45
45
  `localPath` and `remotePath`.
46
46
 
47
- Destination paths follow familiar `cp`/`scp` behavior. If the destination exists as a directory or ends with a path separator, `scp-next` places the source inside that directory using the source basename.
48
- Use `--create-directories` when a destination directory may be missing.
47
+ Destination paths follow familiar `cp`/`scp` behavior. If the destination exists as a directory or ends with a path separator, `scp-next` places the source inside that directory using the source basename. Missing destination directories are created by default.
49
48
 
50
49
  ## Upload Examples
51
50
 
@@ -54,8 +53,7 @@ scp-next upload ./dist /var/www/example \
54
53
  --host example.com \
55
54
  --username deploy \
56
55
  --private-key-file ~/.ssh/id_ed25519 \
57
- --recursive \
58
- --create-directories
56
+ --recursive
59
57
  ```
60
58
 
61
59
  Password authentication:
@@ -77,8 +75,7 @@ Prefer `SCP_NEXT_PASSWORD`, SSH agents, or protected private-key files for share
77
75
  scp-next download /var/log/example.log ./logs/example.log \
78
76
  --host example.com \
79
77
  --username deploy \
80
- --private-key-file ~/.ssh/id_ed25519 \
81
- --create-directories
78
+ --private-key-file ~/.ssh/id_ed25519
82
79
  ```
83
80
 
84
81
  ## CLI Options
@@ -96,7 +93,8 @@ scp-next download /var/log/example.log ./logs/example.log \
96
93
  | `--profile <name>` | Named server profile from the configuration file. |
97
94
  | `--recursive` | Transfer directories recursively. |
98
95
  | `--overwrite` | Allow replacing existing destination files. |
99
- | `--create-directories` | Create missing destination directories when supported. |
96
+ | `--create-directories` | Create missing destination directories. Enabled by default. |
97
+ | `--no-create-directories` | Disable automatic destination directory creation. |
100
98
  | `--dry-run` | Resolve and validate the operation without connecting or transferring. |
101
99
  | `--timeout <milliseconds>` | Connection and operation timeout in milliseconds. |
102
100
  | `--verbose` | Print non-sensitive diagnostic details. |
package/dist/cli/index.js CHANGED
@@ -564,7 +564,7 @@ async function validateDownloadOptions(options) {
564
564
  }
565
565
  assertRemotePath(options.remotePath, "remotePath");
566
566
  validateTimeout(options.timeout);
567
- await assertDownloadDestination(options.localPath, options.createDirectories);
567
+ await assertDownloadDestination(options.localPath, options.createDirectories ?? true);
568
568
  if (!options.dryRun) {
569
569
  await validateServerOptions(options);
570
570
  }
@@ -580,6 +580,9 @@ function percentage(transferredBytes, totalBytes) {
580
580
  function mergeOptions(serverOptions, transferOptions) {
581
581
  return { ...serverOptions, ...transferOptions ?? {} };
582
582
  }
583
+ function shouldCreateDirectories(options) {
584
+ return options.createDirectories ?? true;
585
+ }
583
586
  function hasTrailingPathSeparator(inputPath) {
584
587
  return /[\\/]$/.test(inputPath);
585
588
  }
@@ -596,6 +599,22 @@ async function isExistingLocalDirectory(localPath) {
596
599
  return false;
597
600
  }
598
601
  }
602
+ async function ensureLocalDirectory(localPath, createParents = false) {
603
+ try {
604
+ const stats = await stat3(localPath);
605
+ if (stats.isDirectory()) {
606
+ return;
607
+ }
608
+ throw new TransferError(`Local destination already exists and is not a directory: ${localPath}`, {
609
+ context: { localPath }
610
+ });
611
+ } catch (error) {
612
+ if (error instanceof TransferError) {
613
+ throw error;
614
+ }
615
+ await mkdir2(localPath, { recursive: createParents });
616
+ }
617
+ }
599
618
  var ScpNextClientImpl = class {
600
619
  serverOptions;
601
620
  transport;
@@ -705,7 +724,7 @@ var ScpNextClientImpl = class {
705
724
  const remoteKind = await this.transport.exists(remotePath);
706
725
  const isDirectoryLikeDestination = remoteKind === "d" || remotePath.endsWith("/");
707
726
  if (!isDirectoryLikeDestination) {
708
- await this.ensureRemoteParent(remotePath, options.createDirectories);
727
+ await this.ensureRemoteParent(remotePath, shouldCreateDirectories(options));
709
728
  await this.assertRemoteOverwrite(remotePath, options.overwrite);
710
729
  return remotePath;
711
730
  }
@@ -716,7 +735,7 @@ var ScpNextClientImpl = class {
716
735
  }
717
736
  const remoteDirectory = remotePath.replace(/\/+$/g, "") || "/";
718
737
  if (!remoteKind) {
719
- if (!options.createDirectories) {
738
+ if (!shouldCreateDirectories(options)) {
720
739
  throw new TransferError(`Remote destination directory does not exist: ${remoteDirectory}`, {
721
740
  context: { remotePath }
722
741
  });
@@ -742,7 +761,7 @@ var ScpNextClientImpl = class {
742
761
  if (isDirectoryLikeDestination) {
743
762
  const remoteDirectory = trimmedRemoteDirectory(remotePath);
744
763
  if (!remoteKind) {
745
- if (!options.createDirectories) {
764
+ if (!shouldCreateDirectories(options)) {
746
765
  throw new TransferError(`Remote destination directory does not exist: ${remoteDirectory}`, {
747
766
  context: { remotePath }
748
767
  });
@@ -754,7 +773,7 @@ var ScpNextClientImpl = class {
754
773
  return destinationRoot;
755
774
  }
756
775
  if (!remoteKind) {
757
- if (!options.createDirectories) {
776
+ if (!shouldCreateDirectories(options)) {
758
777
  throw new TransferError(`Remote destination directory does not exist: ${remotePath}`, {
759
778
  context: { remotePath }
760
779
  });
@@ -783,7 +802,7 @@ var ScpNextClientImpl = class {
783
802
  async downloadSingleFile(options, remotePath) {
784
803
  const localPath = await this.resolveLocalDestinationForRemoteFile(options, remotePath);
785
804
  await this.assertLocalOverwrite(localPath, options.overwrite);
786
- if (options.createDirectories) {
805
+ if (shouldCreateDirectories(options)) {
787
806
  await mkdir2(path4.dirname(localPath), { recursive: true });
788
807
  }
789
808
  await this.transport.downloadFile(remotePath, localPath, (step) => {
@@ -805,7 +824,7 @@ var ScpNextClientImpl = class {
805
824
  const totalBytes = files.reduce((sum, file) => sum + file.size, 0);
806
825
  let completedBytes = 0;
807
826
  let completedFiles = 0;
808
- await mkdir2(localRoot, { recursive: Boolean(options.createDirectories) });
827
+ await ensureLocalDirectory(localRoot, shouldCreateDirectories(options));
809
828
  for (const file of files) {
810
829
  const localFile = path4.join(localRoot, ...file.relativePath.split("/"));
811
830
  await this.assertLocalOverwrite(localFile, options.overwrite);
@@ -835,7 +854,7 @@ var ScpNextClientImpl = class {
835
854
  return localDestination;
836
855
  }
837
856
  if (!await isExistingLocalDirectory(localDestination)) {
838
- if (!options.createDirectories) {
857
+ if (!shouldCreateDirectories(options)) {
839
858
  throw new TransferError(`Local destination directory does not exist: ${localDestination}`, {
840
859
  context: { localPath: options.localPath }
841
860
  });
@@ -851,7 +870,7 @@ var ScpNextClientImpl = class {
851
870
  return localDestination;
852
871
  }
853
872
  if (!await isExistingLocalDirectory(localDestination)) {
854
- if (!options.createDirectories) {
873
+ if (!shouldCreateDirectories(options)) {
855
874
  throw new TransferError(`Local destination directory does not exist: ${localDestination}`, {
856
875
  context: { localPath: options.localPath }
857
876
  });
@@ -1117,7 +1136,7 @@ var DEFAULT_TRANSFER = {
1117
1136
  port: 22,
1118
1137
  recursive: false,
1119
1138
  overwrite: false,
1120
- createDirectories: false,
1139
+ createDirectories: true,
1121
1140
  dryRun: false
1122
1141
  };
1123
1142
  function assignDefined(target, source) {
@@ -1307,6 +1326,7 @@ function addTransferOptions(command) {
1307
1326
  command.option("--recursive", "Transfer directories recursively");
1308
1327
  command.option("--overwrite", "Overwrite existing destination files");
1309
1328
  command.option("--create-directories", "Create missing destination directories");
1329
+ command.option("--no-create-directories", "Do not create missing destination directories");
1310
1330
  command.option("--dry-run", "Resolve and validate without connecting or transferring");
1311
1331
  command.addOption(new Option("--timeout <milliseconds>", "Connection/operation timeout").argParser(parseInteger2));
1312
1332
  command.option("--verbose", "Print verbose diagnostic output");