@takuhon/cli 0.16.0 → 0.17.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/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  ADMIN_DIST_DIRNAME,
4
4
  resolveAdminBundleDir
5
- } from "./chunk-KF33LYGF.js";
5
+ } from "./chunk-2LA24OFM.js";
6
6
 
7
7
  // src/index.ts
8
8
  import { readFileSync as readFileSync16, realpathSync } from "fs";
@@ -351,581 +351,8 @@ import { Hono } from "hono";
351
351
  // src/dev-command.ts
352
352
  import { readFileSync as readFileSync3 } from "fs";
353
353
  import { createServer } from "http";
354
+ import { escapeHtml, generateSite } from "@takuhon/api";
354
355
  import { applyPublicPrivacyFilter, normalize, validate } from "@takuhon/core";
355
-
356
- // src/build-html.ts
357
- import { generateJsonLd, renderActivitySvg } from "@takuhon/core";
358
-
359
- // src/html-helpers.ts
360
- function escapeHtml(value) {
361
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
362
- }
363
- function safeUrl(url) {
364
- const trimmed = url.trim();
365
- const scheme = /^([a-zA-Z][a-zA-Z0-9+.-]*):/.exec(trimmed)?.[1]?.toLowerCase();
366
- if (scheme === void 0) return trimmed;
367
- return scheme === "http" || scheme === "https" || scheme === "mailto" ? trimmed : void 0;
368
- }
369
- function dateRange(start, end, isCurrent) {
370
- const left = start ?? "";
371
- const right = isCurrent === true || end === null ? "Present" : end ?? "";
372
- if (left && right) return `${left} \u2013 ${right}`;
373
- return left || right;
374
- }
375
- function nonEmpty(values, separator) {
376
- const joined = values.filter((v) => typeof v === "string" && v.length > 0).join(separator);
377
- return joined.length > 0 ? joined : void 0;
378
- }
379
-
380
- // src/build-html.ts
381
- function escapeJsonLd(json) {
382
- return json.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
383
- }
384
- var CSS = `:root{--fg:#1a1a1a;--muted:#666;--accent:#0b5fff;--line:#e5e5e5}
385
- *{box-sizing:border-box}
386
- body{margin:0;color:var(--fg);font:16px/1.6 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;background:#fff}
387
- main{max-width:42rem;margin:0 auto;padding:2rem 1.25rem}
388
- a{color:var(--accent)}
389
- h1{font-size:1.9rem;margin:.2rem 0}
390
- h2{font-size:1.15rem;margin:2rem 0 .75rem;padding-bottom:.3rem;border-bottom:1px solid var(--line)}
391
- h3{font-size:1rem;margin:0}
392
- header .avatar{width:96px;height:96px;border-radius:50%;object-fit:cover}
393
- .tagline{font-size:1.1rem;color:var(--muted);margin:.2rem 0}
394
- .location{color:var(--muted);margin:.2rem 0}
395
- .bio{margin:.75rem 0}
396
- ul{padding:0;margin:0;list-style:none}
397
- .entries>li{margin:0 0 1.1rem}
398
- .sub{margin:.1rem 0;font-weight:600}
399
- .meta{margin:.1rem 0;color:var(--muted);font-size:.9rem}
400
- .links{display:flex;flex-wrap:wrap;gap:.5rem 1rem;margin:.75rem 0}
401
- .skills,.tags{display:flex;flex-wrap:wrap;gap:.4rem}
402
- .skills>li,.tags>li{background:#f2f2f2;border-radius:1rem;padding:.15rem .6rem;font-size:.85rem}
403
- .rec{margin:0 0 1.1rem}
404
- .rec blockquote{margin:0;padding-left:.9rem;border-left:3px solid var(--line)}
405
- .rec figcaption{color:var(--muted);font-size:.9rem;margin-top:.3rem}
406
- nav.locales{display:flex;gap:.75rem;margin-bottom:1rem;font-size:.9rem}
407
- .activity svg{max-width:100%;height:auto}
408
- footer.powered{max-width:42rem;margin:0 auto;padding:1.5rem 1.25rem;color:var(--muted);font-size:.85rem}`;
409
- function renderEntry(entry) {
410
- const href = entry.url ? safeUrl(entry.url) : void 0;
411
- const heading = href ? `<a href="${escapeHtml(href)}">${escapeHtml(entry.heading)}</a>` : escapeHtml(entry.heading);
412
- const parts = [`<h3>${heading}</h3>`];
413
- if (entry.sub) parts.push(`<p class="sub">${escapeHtml(entry.sub)}</p>`);
414
- if (entry.dates) parts.push(`<p class="meta">${escapeHtml(entry.dates)}</p>`);
415
- if (entry.body) parts.push(`<p>${escapeHtml(entry.body)}</p>`);
416
- if (entry.tags && entry.tags.length > 0) {
417
- parts.push(
418
- `<ul class="tags">${entry.tags.map((t) => `<li>${escapeHtml(t)}</li>`).join("")}</ul>`
419
- );
420
- }
421
- return `<li>${parts.join("")}</li>`;
422
- }
423
- function entryList(title, entries) {
424
- if (entries.length === 0) return "";
425
- return `<section><h2>${escapeHtml(title)}</h2><ul class="entries">${entries.map(renderEntry).join("")}</ul></section>`;
426
- }
427
- function renderHeader(p) {
428
- const parts = [];
429
- const avatarSrc = p.avatar?.url ? safeUrl(p.avatar.url) : void 0;
430
- if (avatarSrc) {
431
- parts.push(
432
- `<img class="avatar" src="${escapeHtml(avatarSrc)}" alt="${escapeHtml(p.avatar?.alt ?? "")}">`
433
- );
434
- }
435
- parts.push(`<h1>${escapeHtml(p.displayName)}</h1>`);
436
- if (p.tagline) parts.push(`<p class="tagline">${escapeHtml(p.tagline)}</p>`);
437
- if (p.location?.display) parts.push(`<p class="location">${escapeHtml(p.location.display)}</p>`);
438
- if (p.bio) parts.push(`<p class="bio">${escapeHtml(p.bio)}</p>`);
439
- return `<header>${parts.join("")}</header>`;
440
- }
441
- function renderLinks(links) {
442
- if (links.length === 0) return "";
443
- const items = links.map((l) => {
444
- const text = escapeHtml(l.label ?? l.url);
445
- const href = safeUrl(l.url);
446
- return href ? `<li><a href="${escapeHtml(href)}">${text}</a></li>` : `<li>${text}</li>`;
447
- }).join("");
448
- return `<nav aria-label="Links"><ul class="links">${items}</ul></nav>`;
449
- }
450
- function renderSkills(skills) {
451
- if (skills.length === 0) return "";
452
- const items = skills.map((s) => `<li>${escapeHtml(s.label)}</li>`).join("");
453
- return `<section><h2>Skills</h2><ul class="skills">${items}</ul></section>`;
454
- }
455
- function renderLanguages(languages) {
456
- if (languages.length === 0) return "";
457
- const items = languages.map((l) => `<li>${escapeHtml(`${l.displayName ?? l.language} \u2014 ${l.proficiency}`)}</li>`).join("");
458
- return `<section><h2>Languages</h2><ul class="entries">${items}</ul></section>`;
459
- }
460
- function renderRecommendations(recs) {
461
- if (recs.length === 0) return "";
462
- const items = recs.map((r) => {
463
- const authorHref = r.author.url ? safeUrl(r.author.url) : void 0;
464
- const name = authorHref ? `<a href="${escapeHtml(authorHref)}">${escapeHtml(r.author.name)}</a>` : escapeHtml(r.author.name);
465
- const caption = [name, r.author.headline ? escapeHtml(r.author.headline) : ""].filter(Boolean).join(", ");
466
- const rel = r.relationship ? ` (${escapeHtml(r.relationship)})` : "";
467
- return `<figure class="rec"><blockquote>${escapeHtml(r.body)}</blockquote><figcaption>\u2014 ${caption}${rel}</figcaption></figure>`;
468
- }).join("");
469
- return `<section><h2>Recommendations</h2>${items}</section>`;
470
- }
471
- function renderContact(contact) {
472
- const items = [];
473
- if (contact.email) {
474
- items.push(
475
- `<li><a href="mailto:${escapeHtml(contact.email)}">${escapeHtml(contact.email)}</a></li>`
476
- );
477
- }
478
- const formHref = contact.formUrl ? safeUrl(contact.formUrl) : void 0;
479
- if (formHref) {
480
- items.push(`<li><a href="${escapeHtml(formHref)}">Contact form</a></li>`);
481
- }
482
- if (items.length === 0) return "";
483
- return `<section><h2>Contact</h2><ul class="entries">${items.join("")}</ul></section>`;
484
- }
485
- function renderActivity(snapshot) {
486
- if (!snapshot) return "";
487
- const svg = renderActivitySvg(snapshot);
488
- if (svg === "") return "";
489
- return `<section class="activity"><h2>Activity</h2>${svg}</section>`;
490
- }
491
- function renderJsonLdScript(data) {
492
- const payload = JSON.stringify(generateJsonLd(data));
493
- return `<script type="application/ld+json">${escapeJsonLd(payload)}</script>`;
494
- }
495
- function renderLocaleNav(localeNav) {
496
- const items = localeNav.map(
497
- (l) => l.current ? `<span aria-current="true">${escapeHtml(l.locale)}</span>` : `<a href="${escapeHtml(l.href)}">${escapeHtml(l.locale)}</a>`
498
- ).join("");
499
- return `<nav class="locales" aria-label="Language">${items}</nav>`;
500
- }
501
- function renderProfileHtml(input) {
502
- const d = input.localized;
503
- const p = d.profile;
504
- const description = p.tagline ?? p.bio ?? "";
505
- const head = [
506
- '<meta charset="utf-8">',
507
- '<meta name="viewport" content="width=device-width, initial-scale=1">',
508
- `<title>${escapeHtml(p.displayName)}</title>`,
509
- description ? `<meta name="description" content="${escapeHtml(description.slice(0, 300))}">` : "",
510
- input.canonicalUrl ? `<link rel="canonical" href="${escapeHtml(input.canonicalUrl)}">` : "",
511
- ...input.alternates.map(
512
- (a) => `<link rel="alternate" hreflang="${escapeHtml(a.hreflang)}" href="${escapeHtml(a.href)}">`
513
- ),
514
- input.jsonLd ? renderJsonLdScript(d) : "",
515
- `<style>${CSS}</style>`
516
- ].filter(Boolean).join("\n ");
517
- const body = [
518
- input.localeNav.length > 1 ? renderLocaleNav(input.localeNav) : "",
519
- renderHeader(p),
520
- renderLinks(d.links),
521
- entryList(
522
- "Experience",
523
- d.careers.map((c) => ({
524
- heading: c.role,
525
- sub: c.organization,
526
- dates: dateRange(c.startDate, c.endDate, c.isCurrent),
527
- body: c.description,
528
- url: c.url
529
- }))
530
- ),
531
- entryList(
532
- "Projects",
533
- d.projects.map((x) => ({
534
- heading: x.title,
535
- dates: dateRange(x.startDate, x.endDate),
536
- body: x.description,
537
- url: x.url,
538
- tags: x.tags
539
- }))
540
- ),
541
- renderSkills(d.skills),
542
- renderActivity(input.activitySnapshot),
543
- entryList(
544
- "Education",
545
- d.education.map((e) => {
546
- const degree = nonEmpty([e.degree, e.fieldOfStudy], ", ");
547
- return {
548
- heading: degree ?? e.institution,
549
- sub: degree ? e.institution : void 0,
550
- dates: dateRange(e.startDate, e.endDate, e.isCurrent),
551
- body: e.description,
552
- url: e.url
553
- };
554
- })
555
- ),
556
- entryList(
557
- "Certifications",
558
- d.certifications.map((c) => ({
559
- heading: c.title,
560
- sub: c.issuingOrganization,
561
- dates: dateRange(c.issueDate, c.expirationDate),
562
- url: c.url
563
- }))
564
- ),
565
- entryList(
566
- "Publications",
567
- d.publications.map((x) => ({
568
- heading: x.title,
569
- sub: nonEmpty([x.publisher, x.coAuthors?.join(", ")], " \xB7 "),
570
- dates: dateRange(x.date),
571
- body: x.description,
572
- url: x.url ?? (x.doi ? `https://doi.org/${x.doi}` : void 0)
573
- }))
574
- ),
575
- entryList(
576
- "Honors & awards",
577
- d.honors.map((x) => ({
578
- heading: x.title,
579
- sub: x.issuer,
580
- dates: dateRange(x.date),
581
- body: x.description,
582
- url: x.url
583
- }))
584
- ),
585
- entryList(
586
- "Memberships",
587
- d.memberships.map((x) => ({
588
- heading: x.role ?? x.organization,
589
- sub: x.role ? x.organization : void 0,
590
- dates: dateRange(x.startDate, x.endDate, x.isCurrent),
591
- body: x.description,
592
- url: x.url
593
- }))
594
- ),
595
- entryList(
596
- "Volunteering",
597
- d.volunteering.map((x) => ({
598
- heading: x.role,
599
- sub: nonEmpty([x.organization, x.cause], " \xB7 "),
600
- dates: dateRange(x.startDate, x.endDate, x.isCurrent),
601
- body: x.description,
602
- url: x.url
603
- }))
604
- ),
605
- entryList(
606
- "Courses",
607
- d.courses.map((x) => ({
608
- heading: x.title,
609
- sub: x.provider,
610
- dates: dateRange(x.completionDate),
611
- body: x.description,
612
- url: x.certificateUrl
613
- }))
614
- ),
615
- entryList(
616
- "Patents",
617
- d.patents.map((x) => ({
618
- heading: x.title,
619
- sub: nonEmpty([x.patentNumber, x.office, x.status, x.coInventors?.join(", ")], " \xB7 "),
620
- dates: dateRange(x.filingDate ?? x.grantDate),
621
- body: x.description,
622
- url: x.url
623
- }))
624
- ),
625
- entryList(
626
- "Test scores",
627
- d.testScores.map((x) => ({
628
- heading: `${x.title}: ${x.score}`,
629
- dates: dateRange(x.date),
630
- body: x.description,
631
- url: x.url
632
- }))
633
- ),
634
- renderLanguages(d.languages),
635
- renderRecommendations(d.recommendations),
636
- renderContact(d.contact)
637
- ].filter(Boolean).join("\n");
638
- const footer = d.settings.showPoweredBy === true ? '<footer class="powered">Powered by takuhon</footer>' : "";
639
- return `<!DOCTYPE html>
640
- <html lang="${escapeHtml(d.resolvedLocale)}">
641
- <head>
642
- ${head}
643
- </head>
644
- <body>
645
- <main>
646
- ${body}
647
- </main>
648
- ${footer ? `${footer}
649
- ` : ""}</body>
650
- </html>
651
- `;
652
- }
653
-
654
- // src/site.ts
655
- import { deriveCv, resolveLocale } from "@takuhon/core";
656
-
657
- // src/cv-html.ts
658
- var SECTION_TITLES = {
659
- experience: "Experience",
660
- education: "Education",
661
- skills: "Skills",
662
- certifications: "Certifications",
663
- publications: "Publications",
664
- honors: "Honors & Awards",
665
- courses: "Courses",
666
- patents: "Patents",
667
- languages: "Languages",
668
- volunteering: "Volunteering",
669
- memberships: "Memberships"
670
- };
671
- var CSS2 = `:root{--fg:#1a1a1a;--muted:#555;--accent:#0b5fff;--line:#d9d9d9}
672
- *{box-sizing:border-box}
673
- body{margin:0;background:#f3f4f6;color:var(--fg);font:13px/1.5 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
674
- main{background:#fff;width:210mm;min-height:297mm;margin:1.5rem auto;padding:18mm 16mm;box-shadow:0 1px 6px rgba(0,0,0,.15)}
675
- h1{font-size:1.7rem;margin:0}
676
- .tagline{font-size:1.05rem;color:var(--muted);margin:.15rem 0 0}
677
- .contact{color:var(--muted);font-size:.85rem;margin:.4rem 0 0;display:flex;flex-wrap:wrap;gap:.25rem 1rem}
678
- .contact a{color:var(--accent)}
679
- .bio{margin:.75rem 0 0}
680
- header{border-bottom:2px solid var(--fg);padding-bottom:.6rem;margin-bottom:.4rem}
681
- section{margin-top:1.1rem}
682
- h2{font-size:.95rem;text-transform:uppercase;letter-spacing:.05em;color:var(--accent);border-bottom:1px solid var(--line);margin:0 0 .5rem;padding-bottom:.15rem}
683
- ul{padding:0;margin:0;list-style:none}
684
- .entry{margin:0 0 .7rem;break-inside:avoid}
685
- .entry .row{display:flex;justify-content:space-between;gap:1rem;align-items:baseline}
686
- .entry h3{font-size:.95rem;margin:0}
687
- .entry .dates{color:var(--muted);font-size:.8rem;white-space:nowrap}
688
- .entry .sub{color:var(--muted);margin:.05rem 0}
689
- .entry p{margin:.2rem 0 0}
690
- .entry a{color:var(--accent)}
691
- .chips{display:flex;flex-wrap:wrap;gap:.3rem}
692
- .chips li{border:1px solid var(--line);border-radius:1rem;padding:.05rem .55rem;font-size:.8rem}
693
- @media print{
694
- body{background:#fff}
695
- main{width:auto;min-height:0;margin:0;padding:0;box-shadow:none}
696
- a{color:var(--fg)}
697
- }
698
- @page{size:A4;margin:14mm}`;
699
- function renderEntry2(entry) {
700
- const href = entry.url ? safeUrl(entry.url) : void 0;
701
- const title = href ? `<a href="${escapeHtml(href)}">${escapeHtml(entry.heading)}</a>` : escapeHtml(entry.heading);
702
- const dates = entry.dates ? `<span class="dates">${escapeHtml(entry.dates)}</span>` : "";
703
- const parts = [`<div class="row"><h3>${title}</h3>${dates}</div>`];
704
- if (entry.sub) parts.push(`<p class="sub">${escapeHtml(entry.sub)}</p>`);
705
- if (entry.body) parts.push(`<p>${escapeHtml(entry.body)}</p>`);
706
- return `<li class="entry">${parts.join("")}</li>`;
707
- }
708
- function entryListSection(title, entries) {
709
- return `<section><h2>${escapeHtml(title)}</h2><ul>${entries.map(renderEntry2).join("")}</ul></section>`;
710
- }
711
- function chipSection(title, labels) {
712
- const chips = labels.map((l) => `<li>${escapeHtml(l)}</li>`).join("");
713
- return `<section><h2>${escapeHtml(title)}</h2><ul class="chips">${chips}</ul></section>`;
714
- }
715
- function languageLabel(l) {
716
- return `${l.displayName ?? l.language} \u2014 ${l.proficiency}`;
717
- }
718
- function renderSection(section) {
719
- const title = SECTION_TITLES[section.kind];
720
- switch (section.kind) {
721
- case "experience":
722
- return entryListSection(
723
- title,
724
- section.entries.map((c) => ({
725
- heading: c.role,
726
- sub: c.organization,
727
- dates: dateRange(c.startDate, c.endDate, c.isCurrent),
728
- body: c.description,
729
- url: c.url
730
- }))
731
- );
732
- case "education":
733
- return entryListSection(
734
- title,
735
- section.entries.map((e) => {
736
- const degree = nonEmpty([e.degree, e.fieldOfStudy], ", ");
737
- return {
738
- heading: degree ?? e.institution,
739
- sub: degree ? e.institution : void 0,
740
- dates: dateRange(e.startDate, e.endDate, e.isCurrent),
741
- body: e.description,
742
- url: e.url
743
- };
744
- })
745
- );
746
- case "skills":
747
- return chipSection(
748
- title,
749
- section.entries.map((s) => s.label)
750
- );
751
- case "languages":
752
- return chipSection(title, section.entries.map(languageLabel));
753
- case "certifications":
754
- return entryListSection(
755
- title,
756
- section.entries.map((c) => ({
757
- heading: c.title,
758
- sub: c.issuingOrganization,
759
- dates: dateRange(c.issueDate, c.expirationDate),
760
- url: c.url
761
- }))
762
- );
763
- case "publications":
764
- return entryListSection(
765
- title,
766
- section.entries.map((x) => ({
767
- heading: x.title,
768
- sub: nonEmpty([x.publisher, x.coAuthors?.join(", ")], " \xB7 "),
769
- dates: dateRange(x.date),
770
- body: x.description,
771
- url: x.url ?? (x.doi ? `https://doi.org/${x.doi}` : void 0)
772
- }))
773
- );
774
- case "honors":
775
- return entryListSection(
776
- title,
777
- section.entries.map((x) => ({
778
- heading: x.title,
779
- sub: x.issuer,
780
- dates: dateRange(x.date),
781
- body: x.description,
782
- url: x.url
783
- }))
784
- );
785
- case "courses":
786
- return entryListSection(
787
- title,
788
- section.entries.map((x) => ({
789
- heading: x.title,
790
- sub: x.provider,
791
- dates: dateRange(x.completionDate),
792
- body: x.description,
793
- url: x.certificateUrl
794
- }))
795
- );
796
- case "patents":
797
- return entryListSection(
798
- title,
799
- section.entries.map((x) => ({
800
- heading: x.title,
801
- sub: nonEmpty([x.patentNumber, x.office, x.status], " \xB7 "),
802
- dates: dateRange(x.filingDate ?? x.grantDate),
803
- body: x.description,
804
- url: x.url
805
- }))
806
- );
807
- case "volunteering":
808
- return entryListSection(
809
- title,
810
- section.entries.map((x) => ({
811
- heading: x.role,
812
- sub: nonEmpty([x.organization, x.cause], " \xB7 "),
813
- dates: dateRange(x.startDate, x.endDate, x.isCurrent),
814
- body: x.description,
815
- url: x.url
816
- }))
817
- );
818
- case "memberships":
819
- return entryListSection(
820
- title,
821
- section.entries.map((x) => ({
822
- heading: x.role ?? x.organization,
823
- sub: x.role ? x.organization : void 0,
824
- dates: dateRange(x.startDate, x.endDate, x.isCurrent),
825
- body: x.description,
826
- url: x.url
827
- }))
828
- );
829
- }
830
- }
831
- function renderHeader2(cv) {
832
- const h = cv.header;
833
- const parts = [`<h1>${escapeHtml(h.displayName)}</h1>`];
834
- if (h.tagline) parts.push(`<p class="tagline">${escapeHtml(h.tagline)}</p>`);
835
- const contact = [];
836
- if (h.location) contact.push(`<span>${escapeHtml(h.location)}</span>`);
837
- if (h.email) {
838
- contact.push(`<a href="mailto:${escapeHtml(h.email)}">${escapeHtml(h.email)}</a>`);
839
- }
840
- const formHref = h.formUrl ? safeUrl(h.formUrl) : void 0;
841
- if (formHref) contact.push(`<a href="${escapeHtml(formHref)}">Contact</a>`);
842
- if (contact.length > 0) parts.push(`<div class="contact">${contact.join("")}</div>`);
843
- if (h.bio) parts.push(`<p class="bio">${escapeHtml(h.bio)}</p>`);
844
- return `<header>${parts.join("")}</header>`;
845
- }
846
- function renderCvHtml(cv) {
847
- const title = `${cv.header.displayName} \u2014 CV`;
848
- const body = [renderHeader2(cv), ...cv.sections.map(renderSection)].join("\n");
849
- return `<!DOCTYPE html>
850
- <html lang="${escapeHtml(cv.resolvedLocale)}">
851
- <head>
852
- <meta charset="utf-8">
853
- <meta name="viewport" content="width=device-width, initial-scale=1">
854
- <title>${escapeHtml(title)}</title>
855
- <style>${CSS2}</style>
856
- </head>
857
- <body>
858
- <main>
859
- ${body}
860
- </main>
861
- </body>
862
- </html>
863
- `;
864
- }
865
-
866
- // src/site.ts
867
- function generateSite(profile, options = {}) {
868
- const { baseUrl } = options;
869
- const defaultLocale = profile.settings.defaultLocale;
870
- const locales = [.../* @__PURE__ */ new Set([defaultLocale, ...profile.settings.availableLocales])];
871
- const jsonLd = profile.settings.enableJsonLd !== false;
872
- const activitySnapshot = profile.settings.activity?.enabled === true ? options.activitySnapshot ?? void 0 : void 0;
873
- const pages = [];
874
- for (const locale of locales) {
875
- const localized = resolveLocale(profile, locale);
876
- const isDefault = locale === defaultLocale;
877
- const localeNav = locales.map((to) => ({
878
- locale: to,
879
- href: localeHref(locale, to, defaultLocale),
880
- current: to === locale
881
- }));
882
- const canonicalUrl = baseUrl ? absoluteUrl(baseUrl, locale, defaultLocale) : void 0;
883
- const alternates = baseUrl ? buildAlternates(baseUrl, locales, defaultLocale) : [];
884
- const html = renderProfileHtml({
885
- localized,
886
- canonicalUrl,
887
- alternates,
888
- localeNav,
889
- jsonLd,
890
- activitySnapshot
891
- });
892
- pages.push({
893
- route: isDefault ? "/" : `/${locale}/`,
894
- file: isDefault ? "index.html" : `${locale}/index.html`,
895
- html
896
- });
897
- if (options.cv === true) {
898
- pages.push({
899
- route: isDefault ? "/cv/" : `/${locale}/cv/`,
900
- file: isDefault ? "cv.html" : `${locale}/cv.html`,
901
- html: renderCvHtml(deriveCv(localized))
902
- });
903
- }
904
- }
905
- return pages;
906
- }
907
- function absoluteUrl(baseUrl, locale, defaultLocale) {
908
- return locale === defaultLocale ? `${baseUrl}/` : `${baseUrl}/${locale}/`;
909
- }
910
- function buildAlternates(baseUrl, locales, defaultLocale) {
911
- const alternates = locales.map((locale) => ({
912
- hreflang: locale,
913
- href: absoluteUrl(baseUrl, locale, defaultLocale)
914
- }));
915
- alternates.push({
916
- hreflang: "x-default",
917
- href: absoluteUrl(baseUrl, defaultLocale, defaultLocale)
918
- });
919
- return alternates;
920
- }
921
- function localeHref(from, to, defaultLocale) {
922
- const fromRoot = from === defaultLocale;
923
- const toRoot = to === defaultLocale;
924
- if (fromRoot) return toRoot ? "./" : `${to}/`;
925
- return toRoot ? "../" : `../${to}/`;
926
- }
927
-
928
- // src/dev-command.ts
929
356
  var DEFAULT_PATH2 = "takuhon.json";
930
357
  var DEFAULT_PORT = 4321;
931
358
  var USAGE = `Usage: takuhon dev [path] [--port <n>] [--base-url <url>]
@@ -1693,11 +1120,12 @@ Run \`takuhon admin --help\` for usage.
1693
1120
  // src/build-command.ts
1694
1121
  import { mkdirSync as mkdirSync3, readFileSync as readFileSync7 } from "fs";
1695
1122
  import { dirname as dirname5, join as join6 } from "path";
1123
+ import { generateSite as generateSite2 } from "@takuhon/api";
1696
1124
  import {
1697
1125
  DARK_PALETTE,
1698
1126
  applyPublicPrivacyFilter as applyPublicPrivacyFilter2,
1699
1127
  normalize as normalize2,
1700
- renderActivitySvg as renderActivitySvg2,
1128
+ renderActivitySvg,
1701
1129
  validate as validate2
1702
1130
  } from "@takuhon/core";
1703
1131
  var DEFAULT_PATH4 = "takuhon.json";
@@ -1841,7 +1269,7 @@ ${lines.join("\n")}
1841
1269
  const written = [];
1842
1270
  const assets = [];
1843
1271
  try {
1844
- for (const page of generateSite(filtered, { baseUrl, activitySnapshot, cv })) {
1272
+ for (const page of generateSite2(filtered, { baseUrl, activitySnapshot, cv })) {
1845
1273
  const outFile = join6(output, page.file);
1846
1274
  mkdirSync3(dirname5(outFile), { recursive: true });
1847
1275
  writeFileAtomic(outFile, page.html);
@@ -1849,8 +1277,8 @@ ${lines.join("\n")}
1849
1277
  }
1850
1278
  if (activitySnapshot) {
1851
1279
  const variants = [
1852
- ["activity.svg", renderActivitySvg2(activitySnapshot)],
1853
- ["activity-dark.svg", renderActivitySvg2(activitySnapshot, { palette: DARK_PALETTE })]
1280
+ ["activity.svg", renderActivitySvg(activitySnapshot)],
1281
+ ["activity-dark.svg", renderActivitySvg(activitySnapshot, { palette: DARK_PALETTE })]
1854
1282
  ];
1855
1283
  for (const [file, svg] of variants) {
1856
1284
  if (svg === "") continue;