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