@lightnet/cli 4.6.1 → 4.6.2
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 +10 -0
- package/package.json +1 -1
- package/src/r2.js +27 -4
- package/src/support/r2.js +47 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @lightnet/cli
|
|
2
2
|
|
|
3
|
+
## 4.6.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#443](https://github.com/LightNetDev/LightNet/pull/443) [`6593139`](https://github.com/LightNetDev/LightNet/commit/65931398471107c03bca6bf4de675599b11db5e5) - Add Clack intro and completion messages to mutating `lightnet r2` commands while keeping `lightnet r2 ls` pipe-friendly.
|
|
8
|
+
|
|
9
|
+
- [#443](https://github.com/LightNetDev/LightNet/pull/443) [`6593139`](https://github.com/LightNetDev/LightNet/commit/65931398471107c03bca6bf4de675599b11db5e5) - Improve `lightnet r2 cp`, `lightnet r2 mv`, and recursive `lightnet r2 rm` performance by passing tuned rclone concurrency settings internally.
|
|
10
|
+
|
|
11
|
+
- [#443](https://github.com/LightNetDev/LightNet/pull/443) [`6593139`](https://github.com/LightNetDev/LightNet/commit/65931398471107c03bca6bf4de675599b11db5e5) - Show a clear install message when `lightnet r2` commands cannot find `rclone` on PATH.
|
|
12
|
+
|
|
3
13
|
## 4.6.1
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
package/package.json
CHANGED
package/src/r2.js
CHANGED
|
@@ -9,13 +9,24 @@ import {
|
|
|
9
9
|
stdout,
|
|
10
10
|
} from "node:process"
|
|
11
11
|
|
|
12
|
-
import { confirm, isCancel, log, text } from "@clack/prompts"
|
|
12
|
+
import { confirm, intro, isCancel, log, outro, text } from "@clack/prompts"
|
|
13
13
|
|
|
14
14
|
import { CliError } from "./support/cli-error.js"
|
|
15
15
|
import { cancelPrompt } from "./support/prompt-cancel.js"
|
|
16
16
|
import { createR2Client } from "./support/r2.js"
|
|
17
17
|
|
|
18
18
|
const remotePathPrefix = "r2:"
|
|
19
|
+
const defaultRcloneOptions = {
|
|
20
|
+
checkers: 16,
|
|
21
|
+
transfers: 8,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {{
|
|
26
|
+
* transfers?: number
|
|
27
|
+
* checkers?: number
|
|
28
|
+
* }} RcloneOptions
|
|
29
|
+
*/
|
|
19
30
|
|
|
20
31
|
/**
|
|
21
32
|
* @typedef {{
|
|
@@ -79,6 +90,7 @@ export async function listR2(path, runtime = {}) {
|
|
|
79
90
|
* @param {R2Runtime} [runtime]
|
|
80
91
|
*/
|
|
81
92
|
export async function removeR2(path, options, runtime = {}) {
|
|
93
|
+
intro("r2 rm")
|
|
82
94
|
const r2Path = normalizeR2Path(path)
|
|
83
95
|
const isBucketRoot = isR2BucketRootPath(path)
|
|
84
96
|
if (isBucketRoot && !options.recursive) {
|
|
@@ -133,7 +145,10 @@ export async function removeR2(path, options, runtime = {}) {
|
|
|
133
145
|
const client = createR2CommandClient(runtime)
|
|
134
146
|
await client.remove(r2Path, {
|
|
135
147
|
recursive: options.recursive === true,
|
|
148
|
+
rcloneOptions:
|
|
149
|
+
options.recursive === true ? defaultRcloneOptions : undefined,
|
|
136
150
|
})
|
|
151
|
+
outro("R2 delete complete.")
|
|
137
152
|
}
|
|
138
153
|
|
|
139
154
|
/**
|
|
@@ -143,6 +158,7 @@ export async function removeR2(path, options, runtime = {}) {
|
|
|
143
158
|
* @param {R2Runtime} [runtime]
|
|
144
159
|
*/
|
|
145
160
|
export async function copyR2(source, destination, options = {}, runtime = {}) {
|
|
161
|
+
intro("r2 cp")
|
|
146
162
|
const sourcePath = parseCopyPath(source)
|
|
147
163
|
const destinationPath = parseCopyPath(destination)
|
|
148
164
|
if (!sourcePath.remote && !destinationPath.remote) {
|
|
@@ -193,6 +209,7 @@ export async function copyR2(source, destination, options = {}, runtime = {}) {
|
|
|
193
209
|
}
|
|
194
210
|
await replaceCopyTargetBeforeCopy({
|
|
195
211
|
client,
|
|
212
|
+
rcloneOptions: defaultRcloneOptions,
|
|
196
213
|
runtime,
|
|
197
214
|
sourceType,
|
|
198
215
|
targetPath,
|
|
@@ -202,8 +219,9 @@ export async function copyR2(source, destination, options = {}, runtime = {}) {
|
|
|
202
219
|
await client.copy(
|
|
203
220
|
await toRcloneCopyPath(sourcePath, client, runtime),
|
|
204
221
|
await toRcloneCopyPath(targetPath, client, runtime),
|
|
205
|
-
{ to: shouldUseCopyTo },
|
|
222
|
+
{ rcloneOptions: defaultRcloneOptions, to: shouldUseCopyTo },
|
|
206
223
|
)
|
|
224
|
+
outro("R2 copy complete.")
|
|
207
225
|
}
|
|
208
226
|
|
|
209
227
|
/**
|
|
@@ -213,6 +231,7 @@ export async function copyR2(source, destination, options = {}, runtime = {}) {
|
|
|
213
231
|
* @param {R2Runtime} [runtime]
|
|
214
232
|
*/
|
|
215
233
|
export async function moveR2(source, destination, options, runtime = {}) {
|
|
234
|
+
intro("r2 mv")
|
|
216
235
|
const sourcePath = parseR2OnlyPath(source, "Move source")
|
|
217
236
|
const destinationPath = parseR2OnlyPath(destination, "Move destination")
|
|
218
237
|
if (isR2BucketRootPath(source) || isR2BucketRootPath(destination)) {
|
|
@@ -251,6 +270,7 @@ export async function moveR2(source, destination, options, runtime = {}) {
|
|
|
251
270
|
})
|
|
252
271
|
await replaceCopyTargetBeforeCopy({
|
|
253
272
|
client,
|
|
273
|
+
rcloneOptions: defaultRcloneOptions,
|
|
254
274
|
runtime,
|
|
255
275
|
sourceType,
|
|
256
276
|
targetPath,
|
|
@@ -260,13 +280,15 @@ export async function moveR2(source, destination, options, runtime = {}) {
|
|
|
260
280
|
await client.move(
|
|
261
281
|
await toRcloneCopyPath(sourcePath, client, runtime),
|
|
262
282
|
await toRcloneCopyPath(targetPath, client, runtime),
|
|
263
|
-
{ to: shouldUseMoveTo },
|
|
283
|
+
{ rcloneOptions: defaultRcloneOptions, to: shouldUseMoveTo },
|
|
264
284
|
)
|
|
285
|
+
outro("R2 move complete.")
|
|
265
286
|
}
|
|
266
287
|
|
|
267
288
|
/**
|
|
268
289
|
* @param {{
|
|
269
290
|
* client: ReturnType<typeof createR2CommandClient>,
|
|
291
|
+
* rcloneOptions?: RcloneOptions,
|
|
270
292
|
* runtime: R2Runtime,
|
|
271
293
|
* sourceType: CopyPathType,
|
|
272
294
|
* targetPath: CopyPath,
|
|
@@ -275,6 +297,7 @@ export async function moveR2(source, destination, options, runtime = {}) {
|
|
|
275
297
|
*/
|
|
276
298
|
async function replaceCopyTargetBeforeCopy({
|
|
277
299
|
client,
|
|
300
|
+
rcloneOptions,
|
|
278
301
|
runtime,
|
|
279
302
|
sourceType,
|
|
280
303
|
targetPath,
|
|
@@ -284,7 +307,7 @@ async function replaceCopyTargetBeforeCopy({
|
|
|
284
307
|
return
|
|
285
308
|
}
|
|
286
309
|
if (targetPath.remote) {
|
|
287
|
-
await client.remove(targetPath.path, { recursive: true })
|
|
310
|
+
await client.remove(targetPath.path, { recursive: true, rcloneOptions })
|
|
288
311
|
return
|
|
289
312
|
}
|
|
290
313
|
await rm(resolveLocalPath(targetPath.path, runtime), {
|
package/src/support/r2.js
CHANGED
|
@@ -21,6 +21,13 @@ const execFileAsync = promisify(execFile)
|
|
|
21
21
|
const sessionSecretEnvName = "LIGHTNET_R2_SECRET_ACCESS_KEY"
|
|
22
22
|
const r2DeleteBatchSize = 1000
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {{
|
|
26
|
+
* transfers?: number
|
|
27
|
+
* checkers?: number
|
|
28
|
+
* }} RcloneOptions
|
|
29
|
+
*/
|
|
30
|
+
|
|
24
31
|
/**
|
|
25
32
|
* @typedef {{
|
|
26
33
|
* publicUrl: string
|
|
@@ -222,7 +229,7 @@ export function createR2Client({ cwd, interactive, promptText }) {
|
|
|
222
229
|
},
|
|
223
230
|
/**
|
|
224
231
|
* @param {string} path
|
|
225
|
-
* @param {{recursive?: boolean}} [options]
|
|
232
|
+
* @param {{recursive?: boolean, rcloneOptions?: RcloneOptions}} [options]
|
|
226
233
|
*/
|
|
227
234
|
async remove(path, options = {}) {
|
|
228
235
|
const r2Config = await getConfig()
|
|
@@ -234,13 +241,14 @@ export function createR2Client({ cwd, interactive, promptText }) {
|
|
|
234
241
|
: ["deletefile", r2Path],
|
|
235
242
|
cwd,
|
|
236
243
|
config: r2Config,
|
|
244
|
+
rcloneOptions: options.rcloneOptions,
|
|
237
245
|
secretAccessKey,
|
|
238
246
|
})
|
|
239
247
|
},
|
|
240
248
|
/**
|
|
241
249
|
* @param {string} source
|
|
242
250
|
* @param {string} destination
|
|
243
|
-
* @param {{to?: boolean}} [options]
|
|
251
|
+
* @param {{to?: boolean, rcloneOptions?: RcloneOptions}} [options]
|
|
244
252
|
*/
|
|
245
253
|
async copy(source, destination, options = {}) {
|
|
246
254
|
const r2Config = await getConfig()
|
|
@@ -248,13 +256,14 @@ export function createR2Client({ cwd, interactive, promptText }) {
|
|
|
248
256
|
args: [options.to ? "copyto" : "copy", source, destination],
|
|
249
257
|
cwd,
|
|
250
258
|
config: r2Config,
|
|
259
|
+
rcloneOptions: options.rcloneOptions,
|
|
251
260
|
secretAccessKey: await getSecretAccessKey(),
|
|
252
261
|
})
|
|
253
262
|
},
|
|
254
263
|
/**
|
|
255
264
|
* @param {string} source
|
|
256
265
|
* @param {string} destination
|
|
257
|
-
* @param {{to?: boolean}} [options]
|
|
266
|
+
* @param {{to?: boolean, rcloneOptions?: RcloneOptions}} [options]
|
|
258
267
|
*/
|
|
259
268
|
async move(source, destination, options = {}) {
|
|
260
269
|
const r2Config = await getConfig()
|
|
@@ -262,6 +271,7 @@ export function createR2Client({ cwd, interactive, promptText }) {
|
|
|
262
271
|
args: [options.to ? "moveto" : "move", source, destination],
|
|
263
272
|
cwd,
|
|
264
273
|
config: r2Config,
|
|
274
|
+
rcloneOptions: options.rcloneOptions,
|
|
265
275
|
secretAccessKey: await getSecretAccessKey(),
|
|
266
276
|
})
|
|
267
277
|
},
|
|
@@ -496,10 +506,17 @@ async function listR2Objects(args) {
|
|
|
496
506
|
* args: string[]
|
|
497
507
|
* cwd: string
|
|
498
508
|
* config: R2Config
|
|
509
|
+
* rcloneOptions?: RcloneOptions
|
|
499
510
|
* secretAccessKey: string
|
|
500
511
|
* }} args
|
|
501
512
|
*/
|
|
502
|
-
async function runConfiguredRclone({
|
|
513
|
+
async function runConfiguredRclone({
|
|
514
|
+
args,
|
|
515
|
+
cwd,
|
|
516
|
+
config,
|
|
517
|
+
rcloneOptions,
|
|
518
|
+
secretAccessKey,
|
|
519
|
+
}) {
|
|
503
520
|
const env = {
|
|
504
521
|
...processEnv,
|
|
505
522
|
RCLONE_S3_SECRET_ACCESS_KEY: secretAccessKey,
|
|
@@ -518,6 +535,7 @@ async function runConfiguredRclone({ args, cwd, config, secretAccessKey }) {
|
|
|
518
535
|
"--s3-endpoint",
|
|
519
536
|
getR2Endpoint(config.accountId),
|
|
520
537
|
"--s3-no-check-bucket",
|
|
538
|
+
...formatRcloneOptions(rcloneOptions),
|
|
521
539
|
],
|
|
522
540
|
cwd,
|
|
523
541
|
env,
|
|
@@ -527,6 +545,21 @@ async function runConfiguredRclone({ args, cwd, config, secretAccessKey }) {
|
|
|
527
545
|
}
|
|
528
546
|
}
|
|
529
547
|
|
|
548
|
+
/**
|
|
549
|
+
* @param {RcloneOptions|undefined} options
|
|
550
|
+
*/
|
|
551
|
+
function formatRcloneOptions(options) {
|
|
552
|
+
/** @type {string[]} */
|
|
553
|
+
const args = []
|
|
554
|
+
if (options?.transfers !== undefined) {
|
|
555
|
+
args.push("--transfers", String(options.transfers))
|
|
556
|
+
}
|
|
557
|
+
if (options?.checkers !== undefined) {
|
|
558
|
+
args.push("--checkers", String(options.checkers))
|
|
559
|
+
}
|
|
560
|
+
return args
|
|
561
|
+
}
|
|
562
|
+
|
|
530
563
|
/**
|
|
531
564
|
* @param {{cwd:string, promptText:(message:string)=>Promise<string>}} args
|
|
532
565
|
*/
|
|
@@ -698,9 +731,16 @@ async function defaultRunRclone(args, cwd, env) {
|
|
|
698
731
|
*/
|
|
699
732
|
function getSanitizedRcloneError(error) {
|
|
700
733
|
if (isPlainObject(error)) {
|
|
701
|
-
const processError =
|
|
702
|
-
|
|
703
|
-
|
|
734
|
+
const processError =
|
|
735
|
+
/** @type {{code?: unknown, stderr?: unknown, stdout?: unknown}} */ (
|
|
736
|
+
error
|
|
737
|
+
)
|
|
738
|
+
if (processError.code === "ENOENT") {
|
|
739
|
+
return [
|
|
740
|
+
"rclone is required for lightnet r2 commands but was not found on PATH.",
|
|
741
|
+
"Install rclone: https://rclone.org/install/",
|
|
742
|
+
].join("\n")
|
|
743
|
+
}
|
|
704
744
|
if (typeof processError.stderr === "string" && processError.stderr.trim()) {
|
|
705
745
|
return processError.stderr.trim()
|
|
706
746
|
}
|