@within-7/jetr 0.2.0 → 0.3.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.
Files changed (2) hide show
  1. package/dist/cli.js +240 -73
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
+ import { resolve as resolve2, basename } from "path";
5
+ import { existsSync as existsSync4, statSync as statSync2 } from "fs";
4
6
  import { Command } from "commander";
5
- import chalk from "chalk";
7
+ import chalk2 from "chalk";
6
8
  import ora from "ora";
7
9
 
8
10
  // src/config.ts
@@ -277,12 +279,19 @@ async function deploy(siteName, directory, onProgress) {
277
279
  `Upload: ${diff.upload.length}, Delete: ${diff.delete.length}, Unchanged: ${diff.unchanged.length}`
278
280
  );
279
281
  if (diff.upload.length > 0) {
280
- for (let i = 0; i < diff.upload.length; i++) {
281
- const fp = diff.upload[i];
282
+ const CONCURRENCY = 10;
283
+ let completed = 0;
284
+ const total = diff.upload.length;
285
+ const uploadOne = async (fp) => {
282
286
  const absPath = join3(absDir, fp);
283
287
  const content = readFileSync3(absPath);
284
- onProgress?.(`Uploading (${i + 1}/${diff.upload.length}) ${fp}`);
285
288
  await api.uploadFile(siteName, fp, content, diff.deploy_id, manifest[fp].hash);
289
+ completed++;
290
+ onProgress?.(`Uploading (${completed}/${total}) ${fp}`);
291
+ };
292
+ for (let i = 0; i < total; i += CONCURRENCY) {
293
+ const batch = diff.upload.slice(i, i + CONCURRENCY);
294
+ await Promise.all(batch.map(uploadOne));
286
295
  }
287
296
  }
288
297
  onProgress?.("Finalizing...");
@@ -297,56 +306,197 @@ async function deploy(siteName, directory, onProgress) {
297
306
  };
298
307
  }
299
308
 
309
+ // src/rc.ts
310
+ import { existsSync as existsSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
311
+ import { join as join4 } from "path";
312
+ import { createInterface } from "readline";
313
+ import chalk from "chalk";
314
+ var RC_FILE = ".jetrrc";
315
+ function loadRc(dir) {
316
+ const rcPath = join4(dir, RC_FILE);
317
+ if (!existsSync3(rcPath)) return {};
318
+ try {
319
+ return JSON.parse(readFileSync4(rcPath, "utf-8"));
320
+ } catch {
321
+ return {};
322
+ }
323
+ }
324
+ function saveRc(dir, config) {
325
+ writeFileSync3(join4(dir, RC_FILE), JSON.stringify(config, null, 2) + "\n");
326
+ }
327
+ function updateRc(config, siteName) {
328
+ if (!config.directory) {
329
+ config.directory = { default: siteName, history: [siteName] };
330
+ } else {
331
+ config.directory.default = siteName;
332
+ if (!config.directory.history.includes(siteName)) {
333
+ config.directory.history.push(siteName);
334
+ }
335
+ }
336
+ return config;
337
+ }
338
+ async function resolveSiteName(dir, explicitName) {
339
+ if (explicitName) return explicitName;
340
+ const rc = loadRc(dir);
341
+ const saved = rc.directory;
342
+ if (!saved) return null;
343
+ const history = saved.history || [saved.default];
344
+ const options = [
345
+ saved.default,
346
+ ...history.filter((h) => h !== saved.default)
347
+ ];
348
+ if (options.length === 1) {
349
+ console.log(chalk.dim(`Using saved site: ${options[0]}`));
350
+ return options[0];
351
+ }
352
+ return promptSelection(options);
353
+ }
354
+ async function promptSelection(options) {
355
+ const rl = createInterface({
356
+ input: process.stdin,
357
+ output: process.stdout
358
+ });
359
+ console.log();
360
+ console.log(chalk.bold("Previous deployments:"));
361
+ for (let i = 0; i < options.length; i++) {
362
+ const marker = i === 0 ? chalk.green(" (default)") : "";
363
+ console.log(` ${chalk.cyan(String(i + 1))}) ${options[i]}${marker}`);
364
+ }
365
+ console.log(
366
+ ` ${chalk.cyan(String(options.length + 1))}) ${chalk.dim("Enter new site name")}`
367
+ );
368
+ console.log();
369
+ return new Promise((resolve3) => {
370
+ rl.question(chalk.bold("Select [1]: "), (answer) => {
371
+ rl.close();
372
+ const num = parseInt(answer, 10);
373
+ if (!answer || isNaN(num) || num < 1 || num > options.length + 1) {
374
+ resolve3(options[0]);
375
+ } else if (num <= options.length) {
376
+ resolve3(options[num - 1]);
377
+ } else {
378
+ const rl2 = createInterface({
379
+ input: process.stdin,
380
+ output: process.stdout
381
+ });
382
+ rl2.question(chalk.bold("Site name: "), (name) => {
383
+ rl2.close();
384
+ resolve3(name.trim());
385
+ });
386
+ }
387
+ });
388
+ });
389
+ }
390
+
300
391
  // src/cli.ts
301
392
  var program = new Command();
302
- program.name("jetr").description("CLI for Jetr static site hosting").version("0.2.0");
393
+ program.name("jetr").description("Deploy static sites instantly").version("0.2.0");
394
+ program.argument("[directory]", "Directory to deploy (default: current directory)").argument("[name]", "Site name (reads from .jetrrc if omitted)").option("--no-ignore", "Skip .jetrignore rules").action(async (directory, name) => {
395
+ const dir = resolve2(directory || ".");
396
+ if (!existsSync4(dir)) {
397
+ console.error(chalk2.red(`Path not found: ${dir}`));
398
+ process.exit(1);
399
+ }
400
+ if (!statSync2(dir).isDirectory()) {
401
+ console.error(chalk2.red(`Not a directory: ${dir}`));
402
+ process.exit(1);
403
+ }
404
+ const config = loadConfig();
405
+ if (!config.token) {
406
+ console.error(chalk2.red("Not logged in."));
407
+ console.error(`Run: ${chalk2.cyan("jetr login <token>")}`);
408
+ console.error(
409
+ chalk2.dim("Get your token from auth-gateway: POST /user/login")
410
+ );
411
+ process.exit(1);
412
+ }
413
+ let siteName = await resolveSiteName(dir, name);
414
+ if (!siteName) {
415
+ const dirName = basename(resolve2(dir));
416
+ const { createInterface: createInterface2 } = await import("readline");
417
+ const rl = createInterface2({
418
+ input: process.stdin,
419
+ output: process.stdout
420
+ });
421
+ siteName = await new Promise((res) => {
422
+ rl.question(
423
+ chalk2.bold(`Site name [${dirName}]: `),
424
+ (answer) => {
425
+ rl.close();
426
+ res(answer.trim() || dirName);
427
+ }
428
+ );
429
+ });
430
+ }
431
+ siteName = siteName.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^-+|-+$/g, "").replace(/-+/g, "-");
432
+ if (!siteName) {
433
+ console.error(chalk2.red("Invalid site name"));
434
+ process.exit(1);
435
+ }
436
+ const spinner = ora("").start();
437
+ try {
438
+ spinner.text = "Checking site...";
439
+ try {
440
+ await api.getSite(siteName);
441
+ } catch {
442
+ spinner.text = `Creating site ${siteName}...`;
443
+ await api.createSite(siteName);
444
+ }
445
+ const result = await deploy(siteName, dir, (msg) => {
446
+ spinner.text = msg;
447
+ });
448
+ spinner.succeed("Deployed!");
449
+ console.log();
450
+ console.log(` ${chalk2.bold("Site:")} ${chalk2.cyan(siteName)}`);
451
+ console.log(` ${chalk2.bold("URL:")} ${chalk2.green(result.url)}`);
452
+ console.log(
453
+ ` ${chalk2.bold("Files:")} ${result.filesUploaded} uploaded` + (result.filesDeleted > 0 ? `, ${result.filesDeleted} deleted` : "") + (result.filesUnchanged > 0 ? `, ${result.filesUnchanged} unchanged` : "") + (result.filesIgnored > 0 ? chalk2.dim(` (${result.filesIgnored} ignored)`) : "")
454
+ );
455
+ console.log(` ${chalk2.bold("Size:")} ${formatBytes(result.totalSize)}`);
456
+ console.log();
457
+ const rc = loadRc(dir);
458
+ saveRc(dir, updateRc(rc, siteName));
459
+ } catch (e) {
460
+ spinner.fail(e.message);
461
+ process.exit(1);
462
+ }
463
+ });
303
464
  program.command("login").description("Save JWT token for authentication").argument("<token>", "JWT token from auth-gateway login").option("--api-url <url>", "API base URL").action((token, opts) => {
304
465
  saveConfig({ token, ...opts.apiUrl ? { apiUrl: opts.apiUrl } : {} });
305
- console.log(chalk.green("\u2713 Token saved to ~/.jetr/config.json"));
466
+ console.log(chalk2.green("\u2713 Token saved to ~/.jetr/config.json"));
306
467
  });
307
468
  program.command("whoami").description("Show current config").action(() => {
308
469
  const config = loadConfig();
309
- console.log(`API: ${config.apiUrl}`);
310
- console.log(`Token: ${config.token ? config.token.slice(0, 20) + "..." : chalk.red("not set")}`);
470
+ console.log(
471
+ `API: ${config.apiUrl}`
472
+ );
473
+ console.log(
474
+ `Token: ${config.token ? config.token.slice(0, 20) + "..." : chalk2.red("not set")}`
475
+ );
311
476
  });
312
- program.command("init").description("Create a .jetrignore file with default patterns").argument("[directory]", "Target directory", ".").action(async (directory) => {
313
- const { resolve: resolve2 } = await import("path");
477
+ program.command("init").description("Create .jetrignore with default patterns").argument("[directory]", "Target directory", ".").action(async (directory) => {
314
478
  const created = createJetrignore(resolve2(directory));
315
479
  if (created) {
316
- console.log(chalk.green("\u2713 Created .jetrignore with default patterns"));
480
+ console.log(chalk2.green("\u2713 Created .jetrignore with default patterns"));
317
481
  } else {
318
- console.log(chalk.yellow(".jetrignore already exists"));
482
+ console.log(chalk2.yellow(".jetrignore already exists"));
319
483
  }
320
484
  });
321
- program.command("create").description("Create a new site").argument("<name>", "Site name (becomes {name}.jetr.within-7.com)").option("-p, --password <password>", "Set access password").option("-e, --expires <seconds>", "Expire after N seconds", parseInt).action(async (name, opts) => {
485
+ program.command("create").description("Create a new site").argument("<name>", "Site name").option("-p, --password <password>", "Set access password").option("-e, --expires <seconds>", "Expire after N seconds", parseInt).action(async (name, opts) => {
322
486
  const spinner = ora("Creating site...").start();
323
487
  try {
324
488
  const site = await api.createSite(name, {
325
489
  password: opts.password,
326
490
  expires_in: opts.expires
327
491
  });
328
- spinner.succeed(`Created ${chalk.bold(site.name)}`);
329
- console.log(` URL: ${chalk.cyan(site.url)}`);
330
- if (site.password_protected) console.log(` Password: ${chalk.yellow("enabled")}`);
331
- if (site.expires_at) console.log(` Expires: ${new Date(site.expires_at * 1e3).toISOString()}`);
332
- } catch (e) {
333
- spinner.fail(e.message);
334
- process.exit(1);
335
- }
336
- });
337
- program.command("deploy").description("Deploy a directory to a site").argument("<name>", "Site name").argument("[directory]", "Directory to deploy", ".").action(async (name, directory) => {
338
- const spinner = ora("").start();
339
- try {
340
- const result = await deploy(name, directory, (msg) => {
341
- spinner.text = msg;
342
- });
343
- spinner.succeed("Deployed!");
344
- console.log(` URL: ${chalk.cyan(result.url)}`);
345
- console.log(` Uploaded: ${result.filesUploaded} files`);
346
- if (result.filesDeleted > 0) console.log(` Deleted: ${result.filesDeleted} files`);
347
- if (result.filesUnchanged > 0) console.log(` Unchanged: ${result.filesUnchanged} files`);
348
- if (result.filesIgnored > 0) console.log(` Ignored: ${chalk.dim(`${result.filesIgnored} files`)}`);
349
- console.log(` Size: ${formatBytes(result.totalSize)}`);
492
+ spinner.succeed(`Created ${chalk2.bold(site.name)}`);
493
+ console.log(` URL: ${chalk2.cyan(site.url)}`);
494
+ if (site.password_protected)
495
+ console.log(` Password: ${chalk2.yellow("enabled")}`);
496
+ if (site.expires_at)
497
+ console.log(
498
+ ` Expires: ${new Date(site.expires_at * 1e3).toISOString()}`
499
+ );
350
500
  } catch (e) {
351
501
  spinner.fail(e.message);
352
502
  process.exit(1);
@@ -356,51 +506,63 @@ program.command("list").alias("ls").description("List your sites").action(async
356
506
  try {
357
507
  const { sites } = await api.listSites();
358
508
  if (sites.length === 0) {
359
- console.log(chalk.dim("No sites yet. Create one: jetr create <name>"));
509
+ console.log(
510
+ chalk2.dim("No sites yet. Run: jetr <directory> to deploy")
511
+ );
360
512
  return;
361
513
  }
362
514
  for (const s of sites) {
363
- const lock = s.password_protected ? chalk.yellow(" \u{1F512}") : "";
364
- const expire = s.expires_at ? chalk.dim(` expires ${new Date(s.expires_at * 1e3).toLocaleDateString()}`) : "";
515
+ const lock = s.password_protected ? chalk2.yellow(" \u{1F512}") : "";
516
+ const expire = s.expires_at ? chalk2.dim(
517
+ ` expires ${new Date(s.expires_at * 1e3).toLocaleDateString()}`
518
+ ) : "";
365
519
  console.log(
366
- ` ${chalk.bold(s.name)}${lock}${expire} ${chalk.dim(formatBytes(s.total_size))} ${chalk.cyan(s.url)}`
520
+ ` ${chalk2.bold(s.name)}${lock}${expire} ${chalk2.dim(formatBytes(s.total_size))} ${chalk2.cyan(s.url)}`
367
521
  );
368
522
  }
369
- console.log(chalk.dim(`
523
+ console.log(chalk2.dim(`
370
524
  ${sites.length} site(s)`));
371
525
  } catch (e) {
372
- console.error(chalk.red(e.message));
526
+ console.error(chalk2.red(e.message));
373
527
  process.exit(1);
374
528
  }
375
529
  });
376
530
  program.command("info").description("Show site details").argument("<name>", "Site name").action(async (name) => {
377
531
  try {
378
532
  const site = await api.getSite(name);
379
- console.log(`${chalk.bold(site.name)}`);
380
- console.log(` URL: ${chalk.cyan(site.url)}`);
381
- console.log(` Password: ${site.password_protected ? chalk.yellow("yes") : "no"}`);
382
- console.log(` Expires: ${site.expires_at ? new Date(site.expires_at * 1e3).toISOString() : "never"}`);
533
+ console.log(`${chalk2.bold(site.name)}`);
534
+ console.log(` URL: ${chalk2.cyan(site.url)}`);
535
+ console.log(
536
+ ` Password: ${site.password_protected ? chalk2.yellow("yes") : "no"}`
537
+ );
538
+ console.log(
539
+ ` Expires: ${site.expires_at ? new Date(site.expires_at * 1e3).toISOString() : "never"}`
540
+ );
383
541
  console.log(` Files: ${site.files.length}`);
384
- console.log(` Created: ${new Date(site.created_at * 1e3).toISOString()}`);
385
- console.log(` Updated: ${new Date(site.updated_at * 1e3).toISOString()}`);
542
+ console.log(
543
+ ` Created: ${new Date(site.created_at * 1e3).toISOString()}`
544
+ );
545
+ console.log(
546
+ ` Updated: ${new Date(site.updated_at * 1e3).toISOString()}`
547
+ );
386
548
  if (site.files.length > 0) {
387
549
  console.log(`
388
- ${chalk.dim("Files:")}`);
550
+ ${chalk2.dim("Files:")}`);
389
551
  for (const f of site.files) {
390
- console.log(` ${f.path} ${chalk.dim(formatBytes(f.size))}`);
552
+ console.log(` ${f.path} ${chalk2.dim(formatBytes(f.size))}`);
391
553
  }
392
554
  }
393
555
  if (site.tokens.length > 0) {
394
556
  console.log(`
395
- ${chalk.dim("Tokens:")}`);
557
+ ${chalk2.dim("Tokens:")}`);
396
558
  for (const t of site.tokens) {
397
559
  const note = t.note ? ` (${t.note})` : "";
398
560
  const exp = t.expires_at ? ` expires ${new Date(t.expires_at * 1e3).toLocaleDateString()}` : "";
399
- console.log(` ${t.id}${note}${chalk.dim(exp)}`);
561
+ console.log(` ${t.id}${note}${chalk2.dim(exp)}`);
400
562
  }
401
563
  }
402
564
  } catch (e) {
403
- console.error(chalk.red(e.message));
565
+ console.error(chalk2.red(e.message));
404
566
  process.exit(1);
405
567
  }
406
568
  });
@@ -408,7 +570,7 @@ program.command("delete").alias("rm").description("Delete a site and all its fil
408
570
  const spinner = ora(`Deleting ${name}...`).start();
409
571
  try {
410
572
  await api.deleteSite(name);
411
- spinner.succeed(`Deleted ${chalk.bold(name)}`);
573
+ spinner.succeed(`Deleted ${chalk2.bold(name)}`);
412
574
  } catch (e) {
413
575
  spinner.fail(e.message);
414
576
  process.exit(1);
@@ -418,38 +580,43 @@ program.command("password").description("Set or remove site password").argument(
418
580
  try {
419
581
  const site = await api.updateSite(name, { password: password ?? null });
420
582
  if (site.password_protected) {
421
- console.log(chalk.green(`\u2713 Password set for ${chalk.bold(name)}`));
583
+ console.log(chalk2.green(`\u2713 Password set for ${chalk2.bold(name)}`));
422
584
  } else {
423
- console.log(chalk.green(`\u2713 Password removed from ${chalk.bold(name)}`));
585
+ console.log(chalk2.green(`\u2713 Password removed from ${chalk2.bold(name)}`));
424
586
  }
425
587
  } catch (e) {
426
- console.error(chalk.red(e.message));
588
+ console.error(chalk2.red(e.message));
427
589
  process.exit(1);
428
590
  }
429
591
  });
430
592
  var tokenCmd = program.command("token").description("Manage share tokens");
431
- tokenCmd.command("create").description("Create a share token").argument("<site>", "Site name").option("-n, --note <note>", "Token note").option("-e, --expires <seconds>", "Expire after N seconds", parseInt).action(async (site, opts) => {
432
- try {
433
- const token = await api.createToken(site, {
434
- note: opts.note,
435
- expires_in: opts.expires
436
- });
437
- console.log(chalk.green("\u2713 Token created"));
438
- console.log(` ID: ${token.id}`);
439
- console.log(` URL: ${chalk.cyan(token.url)}`);
440
- if (token.note) console.log(` Note: ${token.note}`);
441
- if (token.expires_at) console.log(` Expires: ${new Date(token.expires_at * 1e3).toISOString()}`);
442
- } catch (e) {
443
- console.error(chalk.red(e.message));
444
- process.exit(1);
593
+ tokenCmd.command("create").description("Create a share token").argument("<site>", "Site name").option("-n, --note <note>", "Token note").option("-e, --expires <seconds>", "Expire after N seconds", parseInt).action(
594
+ async (site, opts) => {
595
+ try {
596
+ const token = await api.createToken(site, {
597
+ note: opts.note,
598
+ expires_in: opts.expires
599
+ });
600
+ console.log(chalk2.green("\u2713 Token created"));
601
+ console.log(` ID: ${token.id}`);
602
+ console.log(` URL: ${chalk2.cyan(token.url)}`);
603
+ if (token.note) console.log(` Note: ${token.note}`);
604
+ if (token.expires_at)
605
+ console.log(
606
+ ` Expires: ${new Date(token.expires_at * 1e3).toISOString()}`
607
+ );
608
+ } catch (e) {
609
+ console.error(chalk2.red(e.message));
610
+ process.exit(1);
611
+ }
445
612
  }
446
- });
613
+ );
447
614
  tokenCmd.command("revoke").description("Revoke a share token").argument("<site>", "Site name").argument("<id>", "Token ID").action(async (site, id) => {
448
615
  try {
449
616
  await api.revokeToken(site, id);
450
- console.log(chalk.green(`\u2713 Token ${id} revoked`));
617
+ console.log(chalk2.green(`\u2713 Token ${id} revoked`));
451
618
  } catch (e) {
452
- console.error(chalk.red(e.message));
619
+ console.error(chalk2.red(e.message));
453
620
  process.exit(1);
454
621
  }
455
622
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@within-7/jetr",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "CLI for Jetr static site hosting",
5
5
  "type": "module",
6
6
  "bin": {