@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.
- package/dist/cli.js +240 -73
- 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
|
|
@@ -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
|
-
|
|
281
|
-
|
|
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("
|
|
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(
|
|
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(
|
|
310
|
-
|
|
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
|
|
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(
|
|
480
|
+
console.log(chalk2.green("\u2713 Created .jetrignore with default patterns"));
|
|
317
481
|
} else {
|
|
318
|
-
console.log(
|
|
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
|
|
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 ${
|
|
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)}`);
|
|
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(
|
|
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 ?
|
|
364
|
-
const expire = s.expires_at ?
|
|
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
|
-
` ${
|
|
520
|
+
` ${chalk2.bold(s.name)}${lock}${expire} ${chalk2.dim(formatBytes(s.total_size))} ${chalk2.cyan(s.url)}`
|
|
367
521
|
);
|
|
368
522
|
}
|
|
369
|
-
console.log(
|
|
523
|
+
console.log(chalk2.dim(`
|
|
370
524
|
${sites.length} site(s)`));
|
|
371
525
|
} catch (e) {
|
|
372
|
-
console.error(
|
|
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(`${
|
|
380
|
-
console.log(` URL: ${
|
|
381
|
-
console.log(
|
|
382
|
-
|
|
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(
|
|
385
|
-
|
|
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
|
-
${
|
|
550
|
+
${chalk2.dim("Files:")}`);
|
|
389
551
|
for (const f of site.files) {
|
|
390
|
-
console.log(` ${f.path} ${
|
|
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
|
-
${
|
|
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}${
|
|
561
|
+
console.log(` ${t.id}${note}${chalk2.dim(exp)}`);
|
|
400
562
|
}
|
|
401
563
|
}
|
|
402
564
|
} catch (e) {
|
|
403
|
-
console.error(
|
|
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 ${
|
|
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(
|
|
583
|
+
console.log(chalk2.green(`\u2713 Password set for ${chalk2.bold(name)}`));
|
|
422
584
|
} else {
|
|
423
|
-
console.log(
|
|
585
|
+
console.log(chalk2.green(`\u2713 Password removed from ${chalk2.bold(name)}`));
|
|
424
586
|
}
|
|
425
587
|
} catch (e) {
|
|
426
|
-
console.error(
|
|
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(
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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(
|
|
617
|
+
console.log(chalk2.green(`\u2713 Token ${id} revoked`));
|
|
451
618
|
} catch (e) {
|
|
452
|
-
console.error(
|
|
619
|
+
console.error(chalk2.red(e.message));
|
|
453
620
|
process.exit(1);
|
|
454
621
|
}
|
|
455
622
|
});
|