scp-next 1.0.0
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/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +403 -0
- package/dist/cli/index.js +1294 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.cjs +828 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +180 -0
- package/dist/index.d.ts +180 -0
- package/dist/index.js +778 -0
- package/dist/index.js.map +1 -0
- package/docs/ARCHITECTURE.md +55 -0
- package/package.json +86 -0
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017-2026 Cheng
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
# scp-next
|
|
2
|
+
|
|
3
|
+
`scp-next` is a SCP command-line tool and library for secure file transfers over SSH.
|
|
4
|
+
The package uses SFTP internally through `ssh2-sftp-client` instead of implementing SCP or SFTP protocols manually.
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
|
|
8
|
+
- Upload and download files.
|
|
9
|
+
- Upload and download directories recursively.
|
|
10
|
+
- CLI usage with `<source> <destination>` operands.
|
|
11
|
+
- ESM `import` and CommonJS `require` library usage.
|
|
12
|
+
- Reusable transfer client.
|
|
13
|
+
- JSON configuration files, named profiles, and configured jobs.
|
|
14
|
+
- Environment-variable based credentials.
|
|
15
|
+
- Dry-run mode, progress callbacks, timeouts, overwrite control, and destination directory creation.
|
|
16
|
+
- Typed errors with stable error codes.
|
|
17
|
+
- Secret redaction for passwords, passphrases, and private-key contents.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install --global scp-next
|
|
23
|
+
npm install scp-next
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## CLI Syntax
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
scp-next upload <source> <destination> [options]
|
|
30
|
+
scp-next download <source> <destination> [options]
|
|
31
|
+
scp-next run <job> [source] [destination] [options]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Terminology is intentionally consistent:
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
Operation Source Destination
|
|
38
|
+
Upload Local Remote
|
|
39
|
+
Download Remote Local
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
CLI commands use `<source> <destination>`. Programmatic upload and download APIs use
|
|
43
|
+
`localPath` and `remotePath`.
|
|
44
|
+
|
|
45
|
+
## Upload Examples
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
scp-next upload ./dist /var/www/example \
|
|
49
|
+
--host example.com \
|
|
50
|
+
--username deploy \
|
|
51
|
+
--private-key-file ~/.ssh/id_ed25519 \
|
|
52
|
+
--recursive \
|
|
53
|
+
--create-directories
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Download Examples
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
scp-next download /var/log/example.log ./logs/example.log \
|
|
60
|
+
--host example.com \
|
|
61
|
+
--username deploy \
|
|
62
|
+
--private-key-file ~/.ssh/id_ed25519 \
|
|
63
|
+
--create-directories
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## CLI Options
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
--host
|
|
70
|
+
--port
|
|
71
|
+
--username
|
|
72
|
+
--password
|
|
73
|
+
--private-key
|
|
74
|
+
--private-key-file
|
|
75
|
+
--passphrase
|
|
76
|
+
--config
|
|
77
|
+
--profile
|
|
78
|
+
--recursive
|
|
79
|
+
--overwrite
|
|
80
|
+
--create-directories
|
|
81
|
+
--dry-run
|
|
82
|
+
--timeout
|
|
83
|
+
--verbose
|
|
84
|
+
--quiet
|
|
85
|
+
--help
|
|
86
|
+
--version
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Avoid `--password` when possible because command-line arguments may be visible in shell
|
|
90
|
+
history and process listings. Prefer environment variables, SSH agents, or protected
|
|
91
|
+
private-key files.
|
|
92
|
+
|
|
93
|
+
## Configuration Files
|
|
94
|
+
|
|
95
|
+
`scp-next` searches for these files in the current directory:
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
scp-next.config.json
|
|
99
|
+
.scp-nextrc
|
|
100
|
+
.scp-nextrc.json
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Use `--config` for an explicit path:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
scp-next upload ./dist /var/www/example --config ./deploy/scp-next.json
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Example:
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"server": {
|
|
114
|
+
"host": "example.com",
|
|
115
|
+
"port": 22,
|
|
116
|
+
"username": "deploy",
|
|
117
|
+
"privateKeyFile": "~/.ssh/id_ed25519"
|
|
118
|
+
},
|
|
119
|
+
"transfer": {
|
|
120
|
+
"recursive": true,
|
|
121
|
+
"overwrite": true,
|
|
122
|
+
"createDirectories": true,
|
|
123
|
+
"timeout": 30000
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Named Profiles
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"defaultProfile": "production",
|
|
133
|
+
"profiles": {
|
|
134
|
+
"production": {
|
|
135
|
+
"host": "production.example.com",
|
|
136
|
+
"port": 22,
|
|
137
|
+
"username": "deploy",
|
|
138
|
+
"privateKeyFile": "~/.ssh/id_ed25519"
|
|
139
|
+
},
|
|
140
|
+
"staging": {
|
|
141
|
+
"host": "staging.example.com",
|
|
142
|
+
"port": 22,
|
|
143
|
+
"username": "deploy",
|
|
144
|
+
"privateKeyFile": "~/.ssh/id_ed25519"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
scp-next upload ./dist /var/www/example --profile production
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Configured Jobs
|
|
155
|
+
|
|
156
|
+
Jobs use `source` and `destination`; the `operation` determines which path is local or remote.
|
|
157
|
+
|
|
158
|
+
```json
|
|
159
|
+
{
|
|
160
|
+
"profiles": {
|
|
161
|
+
"production": {
|
|
162
|
+
"host": "production.example.com",
|
|
163
|
+
"username": "deploy",
|
|
164
|
+
"privateKeyFile": "~/.ssh/id_ed25519"
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
"jobs": {
|
|
168
|
+
"deploy": {
|
|
169
|
+
"operation": "upload",
|
|
170
|
+
"profile": "production",
|
|
171
|
+
"source": "./dist",
|
|
172
|
+
"destination": "/var/www/example",
|
|
173
|
+
"recursive": true,
|
|
174
|
+
"overwrite": true
|
|
175
|
+
},
|
|
176
|
+
"download-logs": {
|
|
177
|
+
"operation": "download",
|
|
178
|
+
"profile": "production",
|
|
179
|
+
"source": "/var/log/example",
|
|
180
|
+
"destination": "./logs",
|
|
181
|
+
"recursive": true
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
scp-next run deploy
|
|
189
|
+
scp-next run download-logs
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
`scp-next run <job> [source] [destination]` permits explicit path overrides.
|
|
193
|
+
|
|
194
|
+
## Environment Variables
|
|
195
|
+
|
|
196
|
+
```text
|
|
197
|
+
SCP_NEXT_HOST
|
|
198
|
+
SCP_NEXT_PORT
|
|
199
|
+
SCP_NEXT_USERNAME
|
|
200
|
+
SCP_NEXT_PASSWORD
|
|
201
|
+
SCP_NEXT_PRIVATE_KEY
|
|
202
|
+
SCP_NEXT_PRIVATE_KEY_FILE
|
|
203
|
+
SCP_NEXT_PASSPHRASE
|
|
204
|
+
SCP_NEXT_TIMEOUT
|
|
205
|
+
SCP_NEXT_PROFILE
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Bash:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
export SCP_NEXT_HOST="example.com"
|
|
212
|
+
export SCP_NEXT_USERNAME="deploy"
|
|
213
|
+
export SCP_NEXT_PRIVATE_KEY_FILE="$HOME/.ssh/id_ed25519"
|
|
214
|
+
scp-next upload ./dist /var/www/example --recursive
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
PowerShell:
|
|
218
|
+
|
|
219
|
+
```powershell
|
|
220
|
+
$env:SCP_NEXT_HOST = "example.com"
|
|
221
|
+
$env:SCP_NEXT_USERNAME = "deploy"
|
|
222
|
+
$env:SCP_NEXT_PRIVATE_KEY_FILE = "$HOME\.ssh\id_ed25519"
|
|
223
|
+
scp-next upload .\dist /var/www/example --recursive
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Configuration Precedence
|
|
227
|
+
|
|
228
|
+
Highest to lowest:
|
|
229
|
+
|
|
230
|
+
1. Explicit CLI options
|
|
231
|
+
2. Positional CLI operands
|
|
232
|
+
3. Environment variables
|
|
233
|
+
4. Selected configuration profile
|
|
234
|
+
5. Root-level configuration values
|
|
235
|
+
6. Configured job values
|
|
236
|
+
7. Internal defaults
|
|
237
|
+
|
|
238
|
+
For `run`, job values are loaded first, then root configuration, selected profile,
|
|
239
|
+
environment variables, explicit positional overrides, and CLI options.
|
|
240
|
+
|
|
241
|
+
## Library Usage
|
|
242
|
+
|
|
243
|
+
ESM upload:
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
import { upload } from "scp-next";
|
|
247
|
+
|
|
248
|
+
await upload({
|
|
249
|
+
host: process.env.SCP_NEXT_HOST,
|
|
250
|
+
username: process.env.SCP_NEXT_USERNAME,
|
|
251
|
+
privateKeyFile: process.env.SCP_NEXT_PRIVATE_KEY_FILE,
|
|
252
|
+
localPath: "./dist",
|
|
253
|
+
remotePath: "/var/www/example",
|
|
254
|
+
recursive: true,
|
|
255
|
+
overwrite: true
|
|
256
|
+
});
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
CommonJS download:
|
|
260
|
+
|
|
261
|
+
```js
|
|
262
|
+
const { download } = require("scp-next");
|
|
263
|
+
|
|
264
|
+
async function main() {
|
|
265
|
+
await download({
|
|
266
|
+
host: process.env.SCP_NEXT_HOST,
|
|
267
|
+
username: process.env.SCP_NEXT_USERNAME,
|
|
268
|
+
privateKeyFile: process.env.SCP_NEXT_PRIVATE_KEY_FILE,
|
|
269
|
+
remotePath: "/var/log/example.log",
|
|
270
|
+
localPath: "./logs/example.log"
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
main().catch((error) => {
|
|
275
|
+
console.error(error.message);
|
|
276
|
+
process.exitCode = 1;
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Reusable client:
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
import { createClient } from "scp-next";
|
|
284
|
+
|
|
285
|
+
const client = createClient({
|
|
286
|
+
host: process.env.SCP_NEXT_HOST,
|
|
287
|
+
username: process.env.SCP_NEXT_USERNAME,
|
|
288
|
+
privateKeyFile: process.env.SCP_NEXT_PRIVATE_KEY_FILE
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
await client.connect();
|
|
293
|
+
await client.upload("./dist", "/var/www/example", { recursive: true });
|
|
294
|
+
await client.download("/var/log/example.log", "./logs/example.log");
|
|
295
|
+
} finally {
|
|
296
|
+
await client.close();
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Authentication
|
|
301
|
+
|
|
302
|
+
Supported methods:
|
|
303
|
+
|
|
304
|
+
- Password authentication
|
|
305
|
+
- Private-key content
|
|
306
|
+
- Private-key file
|
|
307
|
+
- Private-key passphrase
|
|
308
|
+
- SSH agent authentication through `agent` or `SSH_AUTH_SOCK`
|
|
309
|
+
|
|
310
|
+
## Host Verification
|
|
311
|
+
|
|
312
|
+
`hostFingerprint` compares the server host key SHA-256 fingerprint. `knownHostsFile`
|
|
313
|
+
supports plain OpenSSH `known_hosts` entries for exact host names. When neither is supplied,
|
|
314
|
+
`scp-next` reads `~/.ssh/known_hosts`. Hashed host names and every OpenSSH marker variant are
|
|
315
|
+
not currently parsed.
|
|
316
|
+
|
|
317
|
+
`scp-next` fails closed if it cannot establish a host verifier. Use `hostFingerprint` for CI
|
|
318
|
+
or deployments where a known-hosts file is not available.
|
|
319
|
+
|
|
320
|
+
## Progress Reporting
|
|
321
|
+
|
|
322
|
+
Interactive terminals display concise progress. Progress is disabled with `--quiet` or when
|
|
323
|
+
stderr is not a TTY, which keeps CI logs readable. Library users can pass `onProgress`.
|
|
324
|
+
|
|
325
|
+
## Dry Run
|
|
326
|
+
|
|
327
|
+
`--dry-run` resolves configuration and validates local paths and transfer direction without
|
|
328
|
+
connecting to the remote server or modifying local/remote files.
|
|
329
|
+
|
|
330
|
+
```text
|
|
331
|
+
Dry run: upload
|
|
332
|
+
Source: ./dist
|
|
333
|
+
Destination: deploy@example.com:/var/www/example
|
|
334
|
+
Recursive: yes
|
|
335
|
+
Overwrite: yes
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## Error Handling
|
|
339
|
+
|
|
340
|
+
Public typed errors:
|
|
341
|
+
|
|
342
|
+
- `ScpNextError`
|
|
343
|
+
- `ConfigurationError`
|
|
344
|
+
- `ValidationError`
|
|
345
|
+
- `AuthenticationError`
|
|
346
|
+
- `ConnectionError`
|
|
347
|
+
- `TransferError`
|
|
348
|
+
- `FileSystemError`
|
|
349
|
+
- `HostVerificationError`
|
|
350
|
+
|
|
351
|
+
Each error includes a stable `code`, readable `message`, optional `cause`, and redacted
|
|
352
|
+
non-sensitive context.
|
|
353
|
+
|
|
354
|
+
```ts
|
|
355
|
+
import { AuthenticationError, upload } from "scp-next";
|
|
356
|
+
|
|
357
|
+
try {
|
|
358
|
+
await upload({ localPath: "./dist", remotePath: "/var/www/example" });
|
|
359
|
+
} catch (error) {
|
|
360
|
+
if (error instanceof AuthenticationError) {
|
|
361
|
+
console.error("SSH authentication failed.");
|
|
362
|
+
}
|
|
363
|
+
throw error;
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
## API Reference
|
|
368
|
+
|
|
369
|
+
Primary functions:
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
upload(options: UploadOptions): Promise<void>
|
|
373
|
+
download(options: DownloadOptions): Promise<void>
|
|
374
|
+
createClient(options: ScpServerOptions): ScpNextClient
|
|
375
|
+
copy(options: CopyOptions): Promise<void>
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Upload and download APIs use explicit `localPath` and `remotePath` names. The optional
|
|
379
|
+
generic `copy()` API accepts typed local/remote endpoint objects.
|
|
380
|
+
|
|
381
|
+
## Development
|
|
382
|
+
|
|
383
|
+
```bash
|
|
384
|
+
npm install
|
|
385
|
+
npm run typecheck
|
|
386
|
+
npm run lint
|
|
387
|
+
npm test
|
|
388
|
+
npm run build
|
|
389
|
+
npm pack --dry-run
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
`prepublishOnly` runs clean, typecheck, lint, tests, and build.
|
|
393
|
+
|
|
394
|
+
## Publishing
|
|
395
|
+
|
|
396
|
+
The published package includes `dist`, README, license, changelog, and docs. The CLI entry is
|
|
397
|
+
`dist/cli/index.js` and contains a Node.js shebang.
|
|
398
|
+
|
|
399
|
+
## Transfer Mechanism
|
|
400
|
+
|
|
401
|
+
Despite the package name, normal transfers use SFTP through `ssh2-sftp-client`, which is built
|
|
402
|
+
on `ssh2`. This avoids remote shell execution for regular transfers and gives better support
|
|
403
|
+
for progress reporting and recursive directory traversal than shelling out to an SCP command.
|