@iksdev/shard-cli 0.1.8 → 0.1.9
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 +6 -4
- package/bin/shard.js +48 -19
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -24,10 +24,10 @@ shard --help
|
|
|
24
24
|
shard login --server http://localhost:3000 --username admin --password secret
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
2. Partage local (sans stockage serveur)
|
|
27
|
+
2. Partage local (sans stockage serveur, commande simple)
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
shard share ./MonFichier.mp4 --server https://shard-0ow4.onrender.com --
|
|
30
|
+
shard share ./MonFichier.mp4 --server https://shard-0ow4.onrender.com --limits 0 --temps 0
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
3. Sync un dossier
|
|
@@ -41,7 +41,7 @@ shard sync ./MonDossier
|
|
|
41
41
|
- `shard login --username <name> --password <pass> [--server <url>]`
|
|
42
42
|
- `shard whoami [--server <url>]`
|
|
43
43
|
- `shard sync <folder> [--server <url>] [--dry-run] [--force]`
|
|
44
|
-
- `shard share <file> [--server <url>] [--limits <n>] [--temps <jours>] [--
|
|
44
|
+
- `shard share <file> [--server <url>] [--limits <n>] [--temps <jours>] [--port <n>] [--upload]`
|
|
45
45
|
- `shard logout`
|
|
46
46
|
- `shard config show`
|
|
47
47
|
- `shard config set-server <url>`
|
|
@@ -51,4 +51,6 @@ shard sync ./MonDossier
|
|
|
51
51
|
- Le CLI stocke la config dans `~/.shard-cli/config.json`.
|
|
52
52
|
- Le CLI stocke l'etat de sync dans `<ton-dossier>/.shard-sync-state.json`.
|
|
53
53
|
- Les uploads passent par `POST /api/files/upload` avec token `Bearer`.
|
|
54
|
-
-
|
|
54
|
+
- Par défaut `shard share` est en mode local: le fichier reste sur ton PC, le serveur stocke seulement metadata + token.
|
|
55
|
+
- Si aucune URL publique n'est fournie, le CLI crée automatiquement un tunnel public (rien à installer en plus).
|
|
56
|
+
- Utilise `--upload` pour revenir au mode historique (upload serveur).
|
package/bin/shard.js
CHANGED
|
@@ -6,6 +6,7 @@ const os = require('os');
|
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const http = require('http');
|
|
8
8
|
const crypto = require('crypto');
|
|
9
|
+
const localtunnel = require('localtunnel');
|
|
9
10
|
const { Readable } = require('stream');
|
|
10
11
|
const { pipeline } = require('stream/promises');
|
|
11
12
|
|
|
@@ -22,7 +23,7 @@ Usage:
|
|
|
22
23
|
shard login --username <name> --password <pass> [--server <url>]
|
|
23
24
|
shard whoami [--server <url>]
|
|
24
25
|
shard sync <folder> [--server <url>] [--dry-run] [--force] [--once] [--interval-ms <n>]
|
|
25
|
-
shard share <file> [--server <url>] [--limits <n>] [--temps <jours>] [--
|
|
26
|
+
shard share <file> [--server <url>] [--limits <n>] [--temps <jours>] [--port <n>] [--upload]
|
|
26
27
|
shard logout
|
|
27
28
|
shard config show
|
|
28
29
|
shard config set-server <url>
|
|
@@ -32,7 +33,8 @@ Examples:
|
|
|
32
33
|
shard sync ./MonDossier
|
|
33
34
|
shard sync ./MonDossier --once
|
|
34
35
|
shard sync ./MonDossier --dry-run
|
|
35
|
-
shard share ./MonFichier.mp4 --
|
|
36
|
+
shard share ./MonFichier.mp4 --limits 0 --temps 0
|
|
37
|
+
shard share ./MonFichier.mp4 --upload
|
|
36
38
|
`);
|
|
37
39
|
}
|
|
38
40
|
|
|
@@ -316,6 +318,24 @@ function normalizePublicUrl(input) {
|
|
|
316
318
|
return raw.replace(/\/+$/, '');
|
|
317
319
|
}
|
|
318
320
|
|
|
321
|
+
async function startManagedTunnel(port) {
|
|
322
|
+
const tunnel = await localtunnel({ port });
|
|
323
|
+
const publicUrl = normalizePublicUrl(tunnel?.url || '');
|
|
324
|
+
if (!publicUrl) {
|
|
325
|
+
throw new Error('Tunnel demarre mais URL publique manquante');
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
publicUrl,
|
|
329
|
+
close: async () => {
|
|
330
|
+
try {
|
|
331
|
+
await tunnel.close();
|
|
332
|
+
} catch (_) {
|
|
333
|
+
// ignore close errors
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
319
339
|
function startLocalSingleFileServer({ filePath, fileName, mimeType, port, accessKey }) {
|
|
320
340
|
const safeName = encodeURIComponent(fileName);
|
|
321
341
|
const routePath = `/download/${accessKey}`;
|
|
@@ -364,7 +384,7 @@ function startLocalSingleFileServer({ filePath, fileName, mimeType, port, access
|
|
|
364
384
|
async function shareFile(positionals, flags) {
|
|
365
385
|
const target = positionals[0];
|
|
366
386
|
if (!target) {
|
|
367
|
-
throw new Error('Usage: shard share <file> [--server <url>] [--limits <n>] [--temps <jours>] [--
|
|
387
|
+
throw new Error('Usage: shard share <file> [--server <url>] [--limits <n>] [--temps <jours>] [--port <n>] [--upload]');
|
|
368
388
|
}
|
|
369
389
|
|
|
370
390
|
const absPath = path.resolve(process.cwd(), target);
|
|
@@ -378,7 +398,7 @@ async function shareFile(positionals, flags) {
|
|
|
378
398
|
|
|
379
399
|
const limits = parseOptionalPositiveInt(flags.limits, '--limits');
|
|
380
400
|
const temps = parseOptionalPositiveInt(flags.temps, '--temps');
|
|
381
|
-
const localMode = Boolean(flags.
|
|
401
|
+
const localMode = !Boolean(flags.upload);
|
|
382
402
|
|
|
383
403
|
const config = await readConfig();
|
|
384
404
|
const server = getServer(flags, config);
|
|
@@ -394,25 +414,11 @@ async function shareFile(positionals, flags) {
|
|
|
394
414
|
|
|
395
415
|
const fileName = path.basename(absPath);
|
|
396
416
|
if (localMode) {
|
|
397
|
-
const publicUrl = normalizePublicUrl(flags['public-url'] || process.env.SHARD_PUBLIC_URL);
|
|
398
|
-
if (!publicUrl) {
|
|
399
|
-
throw new Error('Mode local: --public-url est requis (ou SHARD_PUBLIC_URL)');
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
let publicUrlParsed;
|
|
403
|
-
try {
|
|
404
|
-
publicUrlParsed = new URL(publicUrl);
|
|
405
|
-
} catch {
|
|
406
|
-
throw new Error(`--public-url invalide: ${publicUrl}`);
|
|
407
|
-
}
|
|
408
|
-
if (!['http:', 'https:'].includes(publicUrlParsed.protocol)) {
|
|
409
|
-
throw new Error('--public-url doit etre en http(s)');
|
|
410
|
-
}
|
|
411
|
-
|
|
412
417
|
const port = Math.max(parseInt(flags.port || process.env.SHARD_LOCAL_PORT || '8787', 10) || 8787, 1);
|
|
413
418
|
const accessKey = crypto.randomBytes(18).toString('base64url');
|
|
414
419
|
const mimeType = guessMime(absPath);
|
|
415
420
|
let localServer = null;
|
|
421
|
+
let tunnel = null;
|
|
416
422
|
try {
|
|
417
423
|
localServer = await startLocalSingleFileServer({
|
|
418
424
|
filePath: absPath,
|
|
@@ -421,6 +427,22 @@ async function shareFile(positionals, flags) {
|
|
|
421
427
|
port,
|
|
422
428
|
accessKey
|
|
423
429
|
});
|
|
430
|
+
let publicUrl = normalizePublicUrl(flags['public-url'] || process.env.SHARD_PUBLIC_URL);
|
|
431
|
+
if (!publicUrl) {
|
|
432
|
+
console.log(`Aucune URL publique fournie, creation automatique du tunnel (port ${port})...`);
|
|
433
|
+
tunnel = await startManagedTunnel(port);
|
|
434
|
+
publicUrl = tunnel.publicUrl;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
let publicUrlParsed;
|
|
438
|
+
try {
|
|
439
|
+
publicUrlParsed = new URL(publicUrl);
|
|
440
|
+
} catch {
|
|
441
|
+
throw new Error(`URL publique invalide: ${publicUrl}`);
|
|
442
|
+
}
|
|
443
|
+
if (!['http:', 'https:'].includes(publicUrlParsed.protocol)) {
|
|
444
|
+
throw new Error('URL publique doit etre en http(s)');
|
|
445
|
+
}
|
|
424
446
|
|
|
425
447
|
const directDownloadUrl = `${publicUrl}${localServer.routePath}`;
|
|
426
448
|
const payload = {
|
|
@@ -444,6 +466,7 @@ async function shareFile(positionals, flags) {
|
|
|
444
466
|
const share = created?.share || {};
|
|
445
467
|
console.log(`Partage local cree pour: ${fileName}`);
|
|
446
468
|
if (share.url) console.log(`URL Shard: ${share.url}`);
|
|
469
|
+
console.log(`URL publique: ${publicUrl}`);
|
|
447
470
|
console.log(`URL agent locale: ${directDownloadUrl}`);
|
|
448
471
|
if (share.token) console.log(`Token: ${share.token}`);
|
|
449
472
|
console.log(`Agent local en ecoute sur: http://0.0.0.0:${port}${localServer.routePath}`);
|
|
@@ -455,12 +478,18 @@ async function shareFile(positionals, flags) {
|
|
|
455
478
|
await new Promise((resolve) => {
|
|
456
479
|
const stop = () => {
|
|
457
480
|
for (const sig of stopSignals) process.off(sig, stop);
|
|
481
|
+
if (tunnel && tunnel.close) {
|
|
482
|
+
void tunnel.close();
|
|
483
|
+
}
|
|
458
484
|
localServer.server.close(() => resolve());
|
|
459
485
|
};
|
|
460
486
|
for (const sig of stopSignals) process.on(sig, stop);
|
|
461
487
|
});
|
|
462
488
|
return;
|
|
463
489
|
} catch (error) {
|
|
490
|
+
if (tunnel && tunnel.close) {
|
|
491
|
+
await tunnel.close();
|
|
492
|
+
}
|
|
464
493
|
if (localServer && localServer.server) {
|
|
465
494
|
await new Promise((resolve) => localServer.server.close(() => resolve()));
|
|
466
495
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iksdev/shard-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "CLI pour synchroniser un dossier local avec Shard",
|
|
5
5
|
"bin": {
|
|
6
6
|
"shard": "bin/shard.js"
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"check": "node --check bin/shard.js"
|
|
11
11
|
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"localtunnel": "^2.0.2"
|
|
14
|
+
},
|
|
12
15
|
"engines": {
|
|
13
16
|
"node": ">=20.0.0"
|
|
14
17
|
},
|