@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.
Files changed (3) hide show
  1. package/README.md +6 -4
  2. package/bin/shard.js +48 -19
  3. 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 --local --public-url https://xxxx.trycloudflare.com --limits 0 --temps 0
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>] [--local --public-url <url> --port <n>]`
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
- - En mode `--local`, le fichier reste sur ton PC: le serveur stocke seulement metadata + token.
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>] [--local --public-url <url> --port <n>]
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 --local --public-url https://xxxx.trycloudflare.com --limits 0 --temps 0
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>] [--local --public-url <url> --port <n>]');
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.local);
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.8",
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
  },