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 +5 -7
- package/dist/cli/index.js +30 -10
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +28 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +28 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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");
|