htmlhost-cli 1.5.1 → 1.6.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/package.json +1 -1
- package/src/cli.mjs +1 -1
- package/src/commands/deploy.mjs +149 -1
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
package/src/commands/deploy.mjs
CHANGED
|
@@ -50,10 +50,11 @@ function promptChoice(question, options) {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
|
-
* htmlhost deploy [file|dir] [--ttl 7d] [--slug existing-slug] [--title "My Site"] [--new] [--no-assets]
|
|
53
|
+
* htmlhost deploy [file|dir] [--ttl 7d] [--slug existing-slug] [--title "My Site"] [--new] [--no-assets] [--pages]
|
|
54
54
|
*
|
|
55
55
|
* - Defaults to index.html in the current directory
|
|
56
56
|
* - If a directory (or ".") is given, deploys all *.html files as separate linked sites
|
|
57
|
+
* - Use --pages with a directory to deploy all *.html files as pages within ONE multi-page site
|
|
57
58
|
* - Remembers the site(s) via .htmlhost file (auto re-deploy)
|
|
58
59
|
* - Use --new to force fresh deploys
|
|
59
60
|
*/
|
|
@@ -64,6 +65,7 @@ export async function deploy(args) {
|
|
|
64
65
|
const title = getFlag(args, "--title");
|
|
65
66
|
const forceNew = args.includes("--new");
|
|
66
67
|
const skipAssets = args.includes("--no-assets");
|
|
68
|
+
const multiPage = args.includes("--pages");
|
|
67
69
|
|
|
68
70
|
// Check if the target is a directory
|
|
69
71
|
if (file) {
|
|
@@ -71,6 +73,9 @@ export async function deploy(args) {
|
|
|
71
73
|
try {
|
|
72
74
|
const stat = statSync(filePath);
|
|
73
75
|
if (stat.isDirectory()) {
|
|
76
|
+
if (multiPage) {
|
|
77
|
+
return deployMultiPage(filePath, { ttl, title, forceNew, skipAssets });
|
|
78
|
+
}
|
|
74
79
|
return deployDirectory(filePath, { ttl, forceNew, skipAssets });
|
|
75
80
|
}
|
|
76
81
|
} catch {
|
|
@@ -78,6 +83,11 @@ export async function deploy(args) {
|
|
|
78
83
|
}
|
|
79
84
|
}
|
|
80
85
|
|
|
86
|
+
// --pages without explicit dir defaults to cwd
|
|
87
|
+
if (multiPage && !file) {
|
|
88
|
+
return deployMultiPage(resolve("."), { ttl, title, forceNew, skipAssets });
|
|
89
|
+
}
|
|
90
|
+
|
|
81
91
|
// Default to index.html in the current directory
|
|
82
92
|
if (!file) {
|
|
83
93
|
const defaultFile = resolve("index.html");
|
|
@@ -381,6 +391,144 @@ async function deployDirectory(dirPath, { ttl, forceNew, skipAssets }) {
|
|
|
381
391
|
console.log("");
|
|
382
392
|
}
|
|
383
393
|
|
|
394
|
+
/**
|
|
395
|
+
* Deploy all *.html files in a directory as pages within a single multi-page site.
|
|
396
|
+
* Uses the --pages flag. File names map to URL paths:
|
|
397
|
+
* index.html → /
|
|
398
|
+
* about.html → /about
|
|
399
|
+
* blog/index.html → /blog
|
|
400
|
+
* blog/post.html → /blog/post
|
|
401
|
+
*/
|
|
402
|
+
async function deployMultiPage(dirPath, { ttl, title, forceNew, skipAssets }) {
|
|
403
|
+
// Find all .html files (recursive) — skip dotfiles
|
|
404
|
+
const htmlFiles = findHtmlFiles(dirPath, dirPath);
|
|
405
|
+
|
|
406
|
+
if (htmlFiles.length === 0) {
|
|
407
|
+
err("No .html files found in this directory.");
|
|
408
|
+
process.exit(1);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Read existing link data
|
|
412
|
+
const link = readLink(dirPath);
|
|
413
|
+
const existingSlug = (!forceNew && link?.multipage?.slug) || null;
|
|
414
|
+
|
|
415
|
+
console.log("");
|
|
416
|
+
info(`Multi-page deploy: ${cyan(bold(String(htmlFiles.length)))} page${htmlFiles.length > 1 ? "s" : ""} in ${cyan(basename(dirPath) || ".")}`);
|
|
417
|
+
console.log("");
|
|
418
|
+
|
|
419
|
+
// Show page mapping
|
|
420
|
+
for (const { relativePath, pagePath } of htmlFiles) {
|
|
421
|
+
console.log(` ${dim("📄")} ${relativePath} → ${cyan(pagePath)}`);
|
|
422
|
+
}
|
|
423
|
+
console.log("");
|
|
424
|
+
|
|
425
|
+
// If we have a linked slug, confirm overwrite
|
|
426
|
+
if (existingSlug) {
|
|
427
|
+
info(`Linked to ${cyan(existingSlug + ".htmlhost.co")} ${dim("(multi-page)")}`);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Process assets and build page payloads
|
|
431
|
+
const assetCache = new Map();
|
|
432
|
+
const pagePayloads = [];
|
|
433
|
+
|
|
434
|
+
for (const { filePath, pagePath, relativePath } of htmlFiles) {
|
|
435
|
+
const stat = statSync(filePath);
|
|
436
|
+
info(`${bold(relativePath)} ${dim(`(${formatBytes(stat.size)})`)}`);
|
|
437
|
+
|
|
438
|
+
let html = readFileSync(filePath, "utf8");
|
|
439
|
+
|
|
440
|
+
if (!skipAssets) {
|
|
441
|
+
html = await processAssets(html, dirname(filePath), assetCache);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
pagePayloads.push({
|
|
445
|
+
path: pagePath,
|
|
446
|
+
html,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Deploy all pages as one multi-page site
|
|
451
|
+
const body = { pages: pagePayloads };
|
|
452
|
+
if (ttl) body.ttl = ttl;
|
|
453
|
+
if (existingSlug) body.slug = existingSlug;
|
|
454
|
+
if (title) body.title = title;
|
|
455
|
+
|
|
456
|
+
info(existingSlug ? `Re-deploying to ${cyan(existingSlug)}…` : "Deploying new multi-page site…");
|
|
457
|
+
|
|
458
|
+
try {
|
|
459
|
+
const data = await post("/api/sites", body);
|
|
460
|
+
|
|
461
|
+
// Save link
|
|
462
|
+
writeLink(dirPath, {
|
|
463
|
+
multipage: {
|
|
464
|
+
slug: data.slug,
|
|
465
|
+
url: data.url,
|
|
466
|
+
pageCount: data.pageCount || pagePayloads.length,
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
console.log("");
|
|
471
|
+
console.log(` ${green("━".repeat(40))}`);
|
|
472
|
+
ok(`${bold("Live")} at ${cyan(`https://${data.url}`)}`);
|
|
473
|
+
console.log(` ${dim(`${data.pageCount || pagePayloads.length} pages · ${data.ttl} TTL`)}`);
|
|
474
|
+
console.log("");
|
|
475
|
+
|
|
476
|
+
for (const { pagePath } of htmlFiles) {
|
|
477
|
+
const urlPath = pagePath === "/" ? "" : pagePath;
|
|
478
|
+
console.log(` ${cyan(`https://${data.url}${urlPath}`)}`);
|
|
479
|
+
}
|
|
480
|
+
console.log("");
|
|
481
|
+
console.log(` ${dim("Linked → .htmlhost (multipage)")}`);
|
|
482
|
+
console.log("");
|
|
483
|
+
} catch (e) {
|
|
484
|
+
err(`Deploy failed: ${e.message}`);
|
|
485
|
+
process.exit(1);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Recursively find all .html files and derive their page paths.
|
|
491
|
+
* Returns [{ filePath, relativePath, pagePath }]
|
|
492
|
+
*/
|
|
493
|
+
function findHtmlFiles(baseDir, currentDir) {
|
|
494
|
+
const entries = readdirSync(currentDir, { withFileTypes: true });
|
|
495
|
+
const results = [];
|
|
496
|
+
|
|
497
|
+
for (const entry of entries) {
|
|
498
|
+
if (entry.name.startsWith(".")) continue;
|
|
499
|
+
|
|
500
|
+
const fullPath = join(currentDir, entry.name);
|
|
501
|
+
|
|
502
|
+
if (entry.isDirectory()) {
|
|
503
|
+
results.push(...findHtmlFiles(baseDir, fullPath));
|
|
504
|
+
} else if (entry.name.endsWith(".html")) {
|
|
505
|
+
const relativePath = fullPath.slice(baseDir.length + 1); // e.g. "about.html" or "blog/post.html"
|
|
506
|
+
|
|
507
|
+
// Derive page path from file path
|
|
508
|
+
let pagePath;
|
|
509
|
+
if (entry.name === "index.html") {
|
|
510
|
+
// index.html → parent directory path (or "/" for root)
|
|
511
|
+
const dirRel = dirname(relativePath);
|
|
512
|
+
pagePath = dirRel === "." ? "/" : `/${dirRel}`;
|
|
513
|
+
} else {
|
|
514
|
+
// about.html → /about, blog/post.html → /blog/post
|
|
515
|
+
pagePath = "/" + relativePath.replace(/\.html$/, "");
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
results.push({ filePath: fullPath, relativePath, pagePath });
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Sort: root index first, then alphabetical
|
|
523
|
+
results.sort((a, b) => {
|
|
524
|
+
if (a.pagePath === "/") return -1;
|
|
525
|
+
if (b.pagePath === "/") return 1;
|
|
526
|
+
return a.pagePath.localeCompare(b.pagePath);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
return results;
|
|
530
|
+
}
|
|
531
|
+
|
|
384
532
|
function getFlag(args, flag) {
|
|
385
533
|
const idx = args.indexOf(flag);
|
|
386
534
|
if (idx === -1 || idx >= args.length - 1) return null;
|