@lightnet/cli 4.7.0 → 4.7.1
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 +6 -0
- package/package.json +7 -1
- package/src/r2.js +115 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @lightnet/cli
|
|
2
2
|
|
|
3
|
+
## 4.7.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#447](https://github.com/LightNetDev/LightNet/pull/447) [`0d8fece`](https://github.com/LightNetDev/LightNet/commit/0d8fece3527a67a21c6272b422c62152948b32a1) - Only prompt for `lightnet r2 cp` and `lightnet r2 mv` when files would actually be overwritten, and list the destination files before confirmation.
|
|
8
|
+
|
|
3
9
|
## 4.7.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "CLI for managing LightNet projects",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "4.7.
|
|
6
|
+
"version": "4.7.1",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
9
9
|
},
|
|
@@ -28,7 +28,13 @@
|
|
|
28
28
|
"@clack/prompts": "^1.7.0",
|
|
29
29
|
"commander": "^15.0.0"
|
|
30
30
|
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"vitest": "^4.1.9"
|
|
33
|
+
},
|
|
31
34
|
"engines": {
|
|
32
35
|
"node": ">=22"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"test": "vitest"
|
|
33
39
|
}
|
|
34
40
|
}
|
package/src/r2.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
import { stat } from "node:fs/promises"
|
|
3
|
+
import { readdir, stat } from "node:fs/promises"
|
|
4
4
|
import { basename, join, posix, resolve } from "node:path"
|
|
5
5
|
import {
|
|
6
6
|
cwd as processCwd,
|
|
@@ -78,6 +78,7 @@ const defaultRcloneOptions = {
|
|
|
78
78
|
* isInteractive?: boolean
|
|
79
79
|
* promptConfirm?: (message: string) => Promise<boolean>
|
|
80
80
|
* promptText?: (message: string) => Promise<string>
|
|
81
|
+
* createR2Client?: typeof createR2Client
|
|
81
82
|
* writeStdout?: (message: string) => void
|
|
82
83
|
* }} R2Runtime
|
|
83
84
|
*/
|
|
@@ -285,10 +286,12 @@ export async function copyR2(paths, options = {}, runtime = {}) {
|
|
|
285
286
|
(targetType !== "directory" || isDirectoryLikeCopyPath(destinationPath))
|
|
286
287
|
|
|
287
288
|
await confirmCopyOverwrite({
|
|
289
|
+
client,
|
|
288
290
|
destination,
|
|
289
291
|
force: options.force === true,
|
|
290
292
|
noClobber: options.noClobber === true,
|
|
291
293
|
runtime,
|
|
294
|
+
sourcePath,
|
|
292
295
|
sourceType,
|
|
293
296
|
targetPath,
|
|
294
297
|
targetType,
|
|
@@ -383,10 +386,12 @@ export async function moveR2(paths, options = {}, runtime = {}) {
|
|
|
383
386
|
(targetType !== "directory" || isDirectoryLikeCopyPath(destinationPath))
|
|
384
387
|
|
|
385
388
|
await confirmCopyOverwrite({
|
|
389
|
+
client,
|
|
386
390
|
destination,
|
|
387
391
|
force: options.force === true,
|
|
388
392
|
noClobber: options.noClobber === true,
|
|
389
393
|
runtime,
|
|
394
|
+
sourcePath,
|
|
390
395
|
sourceType,
|
|
391
396
|
targetPath,
|
|
392
397
|
targetType,
|
|
@@ -426,7 +431,7 @@ export async function moveR2(paths, options = {}, runtime = {}) {
|
|
|
426
431
|
* @param {R2Runtime} runtime
|
|
427
432
|
*/
|
|
428
433
|
function createR2CommandClient(runtime) {
|
|
429
|
-
return createR2Client({
|
|
434
|
+
return (runtime.createR2Client ?? createR2Client)({
|
|
430
435
|
cwd: runtime.cwd ?? processCwd(),
|
|
431
436
|
interactive: getInteractive(runtime),
|
|
432
437
|
promptText: getPromptText(runtime),
|
|
@@ -580,20 +585,24 @@ function getCopyTargetPath({
|
|
|
580
585
|
|
|
581
586
|
/**
|
|
582
587
|
* @param {{
|
|
588
|
+
* client: ReturnType<typeof createR2CommandClient>,
|
|
583
589
|
* destination: string,
|
|
584
590
|
* force: boolean,
|
|
585
591
|
* noClobber: boolean,
|
|
586
592
|
* runtime: R2Runtime,
|
|
593
|
+
* sourcePath: CopyPath,
|
|
587
594
|
* sourceType: CopyPathType,
|
|
588
595
|
* targetPath: CopyPath,
|
|
589
596
|
* targetType: CopyPathType
|
|
590
597
|
* }} args
|
|
591
598
|
*/
|
|
592
599
|
async function confirmCopyOverwrite({
|
|
600
|
+
client,
|
|
593
601
|
destination,
|
|
594
602
|
force,
|
|
595
603
|
noClobber,
|
|
596
604
|
runtime,
|
|
605
|
+
sourcePath,
|
|
597
606
|
sourceType,
|
|
598
607
|
targetPath,
|
|
599
608
|
targetType,
|
|
@@ -617,11 +626,27 @@ async function confirmCopyOverwrite({
|
|
|
617
626
|
if (noClobber) {
|
|
618
627
|
return
|
|
619
628
|
}
|
|
629
|
+
const overwrittenPaths =
|
|
630
|
+
sourceType === "directory"
|
|
631
|
+
? await getDirectoryOverwritePaths({
|
|
632
|
+
client,
|
|
633
|
+
runtime,
|
|
634
|
+
sourcePath,
|
|
635
|
+
targetPath,
|
|
636
|
+
})
|
|
637
|
+
: [formatCopyPath(targetPath)]
|
|
638
|
+
if (overwrittenPaths.length === 0) {
|
|
639
|
+
return
|
|
640
|
+
}
|
|
620
641
|
if (!getInteractive(runtime)) {
|
|
621
642
|
throw new CliError(
|
|
622
643
|
'Transfer would overwrite existing files. Re-run with "--force" in non-interactive environments.',
|
|
623
644
|
)
|
|
624
645
|
}
|
|
646
|
+
log.warn(`Files to overwrite (${overwrittenPaths.length})`)
|
|
647
|
+
for (const overwrittenPath of overwrittenPaths) {
|
|
648
|
+
log.message(`• ${overwrittenPath}`)
|
|
649
|
+
}
|
|
625
650
|
const message =
|
|
626
651
|
sourceType === "directory"
|
|
627
652
|
? `Overwrite any existing files under destination "${destination}"? [y/N] `
|
|
@@ -632,6 +657,94 @@ async function confirmCopyOverwrite({
|
|
|
632
657
|
}
|
|
633
658
|
}
|
|
634
659
|
|
|
660
|
+
/**
|
|
661
|
+
* @param {{
|
|
662
|
+
* client: ReturnType<typeof createR2CommandClient>
|
|
663
|
+
* runtime: R2Runtime
|
|
664
|
+
* sourcePath: CopyPath
|
|
665
|
+
* targetPath: CopyPath
|
|
666
|
+
* }} args
|
|
667
|
+
*/
|
|
668
|
+
async function getDirectoryOverwritePaths({
|
|
669
|
+
client,
|
|
670
|
+
runtime,
|
|
671
|
+
sourcePath,
|
|
672
|
+
targetPath,
|
|
673
|
+
}) {
|
|
674
|
+
const sourceFiles = await listCopyFiles(sourcePath, client, runtime)
|
|
675
|
+
if (sourceFiles.length === 0) {
|
|
676
|
+
return []
|
|
677
|
+
}
|
|
678
|
+
const targetFiles = new Set(await listCopyFiles(targetPath, client, runtime))
|
|
679
|
+
return sourceFiles
|
|
680
|
+
.filter((relativePath) => targetFiles.has(relativePath))
|
|
681
|
+
.map((relativePath) =>
|
|
682
|
+
formatCopyPath({
|
|
683
|
+
...targetPath,
|
|
684
|
+
path: joinCopyPath(targetPath, relativePath),
|
|
685
|
+
}),
|
|
686
|
+
)
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* @param {CopyPath} copyPath
|
|
691
|
+
* @param {ReturnType<typeof createR2CommandClient>} client
|
|
692
|
+
* @param {R2Runtime} runtime
|
|
693
|
+
*/
|
|
694
|
+
async function listCopyFiles(copyPath, client, runtime) {
|
|
695
|
+
if (copyPath.remote) {
|
|
696
|
+
return parseRcloneFileList(await client.list(copyPath.path))
|
|
697
|
+
}
|
|
698
|
+
return listLocalFiles(resolveLocalPath(copyPath.path, runtime))
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* @param {string} root
|
|
703
|
+
* @param {string} [prefix]
|
|
704
|
+
* @returns {Promise<string[]>}
|
|
705
|
+
*/
|
|
706
|
+
async function listLocalFiles(root, prefix = "") {
|
|
707
|
+
const entries = await readdir(root, { withFileTypes: true })
|
|
708
|
+
const files = []
|
|
709
|
+
for (const entry of entries) {
|
|
710
|
+
const relativePath = prefix
|
|
711
|
+
? joinRelativePath(prefix, entry.name)
|
|
712
|
+
: entry.name
|
|
713
|
+
const absolutePath = join(root, entry.name)
|
|
714
|
+
if (entry.isDirectory()) {
|
|
715
|
+
files.push(...(await listLocalFiles(absolutePath, relativePath)))
|
|
716
|
+
} else if (entry.isFile()) {
|
|
717
|
+
files.push(relativePath)
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
return files
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* @param {string} output
|
|
725
|
+
*/
|
|
726
|
+
function parseRcloneFileList(output) {
|
|
727
|
+
return output
|
|
728
|
+
.split(/\r?\n/)
|
|
729
|
+
.map((line) => line.trim())
|
|
730
|
+
.filter(Boolean)
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* @param {string} parent
|
|
735
|
+
* @param {string} child
|
|
736
|
+
*/
|
|
737
|
+
function joinRelativePath(parent, child) {
|
|
738
|
+
return parent ? posix.join(parent, child) : child
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* @param {CopyPath} copyPath
|
|
743
|
+
*/
|
|
744
|
+
function formatCopyPath(copyPath) {
|
|
745
|
+
return copyPath.remote ? `${remotePathPrefix}${copyPath.path}` : copyPath.path
|
|
746
|
+
}
|
|
747
|
+
|
|
635
748
|
/**
|
|
636
749
|
* @param {CopyPath} copyPath
|
|
637
750
|
* @param {ReturnType<typeof createR2CommandClient>} client
|