@hutusi/amytis 1.9.0 → 1.11.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.
Files changed (143) hide show
  1. package/.entire/settings.json +4 -0
  2. package/AGENTS.md +13 -7
  3. package/CHANGELOG.md +41 -0
  4. package/CLAUDE.md +30 -2
  5. package/README.md +84 -58
  6. package/README.zh.md +195 -0
  7. package/TODO.md +6 -4
  8. package/bun.lock +30 -4
  9. package/content/books/sample-book/images/digital-garden.svg +25 -0
  10. package/content/books/sample-book/index.mdx +1 -1
  11. package/content/books/sample-book/introduction.mdx +2 -0
  12. package/content/posts/2026-01-21-kitchen-sink/index.mdx +26 -1
  13. package/content/posts/legacy-markdown.md +0 -2
  14. package/content/posts/markdown-features.mdx +1 -1
  15. package/content/posts/multilingual-test.mdx +0 -1
  16. package/content/posts/syntax-highlighting-showcase.mdx +1 -0
  17. package/content/posts/understanding-react-hooks.mdx +1 -0
  18. package/content/series/ai-nexus-weekly/index.mdx +1 -1
  19. package/content/series/digital-garden/01-philosophy.mdx +2 -0
  20. package/content/series/digital-garden/index.mdx +1 -1
  21. package/content/series/markdown-showcase/index.mdx +1 -1
  22. package/content/series/markdown-showcase/syntax-highlighting.mdx +2 -0
  23. package/content/series/nextjs-deep-dive/01-getting-started.mdx +2 -1
  24. package/content/series/nextjs-deep-dive/index.mdx +1 -1
  25. package/content/subscribe.mdx +27 -0
  26. package/docs/ARCHITECTURE.md +85 -105
  27. package/docs/CONTRIBUTING.md +11 -3
  28. package/docs/DIGITAL_GARDEN.md +11 -9
  29. package/docs/deployment.md +31 -13
  30. package/eslint.config.mjs +2 -0
  31. package/package.json +13 -4
  32. package/packages/create-amytis/package.json +13 -0
  33. package/packages/create-amytis/src/index.test.ts +187 -0
  34. package/packages/create-amytis/src/index.ts +230 -0
  35. package/packages/create-amytis/tsconfig.json +14 -0
  36. package/public/images/amytis-screenshot.jpg +0 -0
  37. package/public/images/antelope-canyon.jpg +0 -0
  38. package/public/images/avatar.jpg +0 -0
  39. package/public/images/cappadocia.jpg +0 -0
  40. package/public/images/flowers.jpg +0 -0
  41. package/public/images/galaxy.jpg +0 -0
  42. package/public/images/lake.jpg +0 -0
  43. package/public/images/mountains.jpg +0 -0
  44. package/public/images/vibrant-waves.avif +0 -0
  45. package/public/images/wechat-qr.jpg +0 -0
  46. package/public/next-image-export-optimizer-hashes.json +72 -2
  47. package/scripts/deploy.ts +77 -0
  48. package/site.config.example.ts +268 -0
  49. package/site.config.ts +82 -10
  50. package/src/app/[slug]/[postSlug]/page.tsx +161 -0
  51. package/src/app/[slug]/page/[page]/page.tsx +202 -0
  52. package/src/app/[slug]/page.tsx +162 -7
  53. package/src/app/archive/page.tsx +5 -8
  54. package/src/app/books/[slug]/[chapter]/page.tsx +45 -3
  55. package/src/app/books/[slug]/page.tsx +16 -2
  56. package/src/app/books/page.tsx +12 -8
  57. package/src/app/feed.atom/route.ts +62 -0
  58. package/src/app/feed.xml/route.ts +45 -20
  59. package/src/app/flows/[year]/[month]/[day]/page.tsx +9 -1
  60. package/src/app/flows/[year]/page.tsx +1 -1
  61. package/src/app/flows/page/[page]/page.tsx +2 -0
  62. package/src/app/flows/page.tsx +4 -0
  63. package/src/app/globals.css +9 -3
  64. package/src/app/layout.tsx +22 -1
  65. package/src/app/notes/[slug]/page.tsx +2 -3
  66. package/src/app/page.tsx +86 -8
  67. package/src/app/posts/[slug]/page.tsx +33 -7
  68. package/src/app/posts/page/[page]/page.tsx +9 -5
  69. package/src/app/posts/page.tsx +13 -7
  70. package/src/app/series/[slug]/page/[page]/page.tsx +8 -25
  71. package/src/app/series/[slug]/page.tsx +21 -19
  72. package/src/app/series/page.tsx +14 -28
  73. package/src/app/sitemap.ts +2 -1
  74. package/src/app/tags/[tag]/page.tsx +1 -1
  75. package/src/app/tags/page.tsx +1 -1
  76. package/src/components/Analytics.tsx +34 -36
  77. package/src/components/AuthorCard.tsx +56 -14
  78. package/src/components/BookMobileNav.tsx +8 -34
  79. package/src/components/BookSidebar.tsx +17 -107
  80. package/src/components/CodeBlock.test.tsx +19 -0
  81. package/src/components/CodeBlock.tsx +20 -16
  82. package/src/components/Comments.tsx +4 -3
  83. package/src/components/CoverImage.tsx +11 -3
  84. package/src/components/CuratedSeriesSection.tsx +17 -15
  85. package/src/components/ExternalLinks.tsx +1 -1
  86. package/src/components/FeaturedStoriesSection.tsx +124 -58
  87. package/src/components/FlowCalendarSidebar.tsx +10 -7
  88. package/src/components/FlowContent.tsx +33 -5
  89. package/src/components/Footer.tsx +28 -6
  90. package/src/components/Hero.tsx +44 -92
  91. package/src/components/InlineBookToc.tsx +56 -0
  92. package/src/components/KnowledgeGraph.tsx +26 -0
  93. package/src/components/LanguageProvider.tsx +7 -2
  94. package/src/components/LanguageSwitch.tsx +1 -1
  95. package/src/components/LatestWritingSection.tsx +12 -22
  96. package/src/components/MarkdownRenderer.test.tsx +41 -0
  97. package/src/components/MarkdownRenderer.tsx +43 -8
  98. package/src/components/Navbar.tsx +24 -15
  99. package/src/components/NoteSidebar.tsx +6 -76
  100. package/src/components/Pagination.tsx +2 -2
  101. package/src/components/PostCard.tsx +2 -1
  102. package/src/components/PostList.tsx +81 -62
  103. package/src/components/PostNavigation.tsx +8 -7
  104. package/src/components/PostSidebar.tsx +12 -107
  105. package/src/components/PrevNextNav.tsx +63 -0
  106. package/src/components/RecentNotesSection.tsx +2 -2
  107. package/src/components/RelatedPosts.tsx +3 -2
  108. package/src/components/RssFeedWidget.tsx +55 -0
  109. package/src/components/SectionLabel.tsx +16 -0
  110. package/src/components/SelectedBooksSection.tsx +12 -15
  111. package/src/components/SeriesCatalog.tsx +74 -69
  112. package/src/components/SeriesList.tsx +11 -36
  113. package/src/components/SeriesSidebar.tsx +4 -2
  114. package/src/components/TagContentTabs.tsx +17 -12
  115. package/src/components/TagPageHeader.tsx +12 -2
  116. package/src/components/TagSidebar.tsx +3 -2
  117. package/src/components/TagsIndexClient.tsx +1 -1
  118. package/src/components/TocPanel.tsx +67 -0
  119. package/src/hooks/useActiveHeading.ts +32 -0
  120. package/src/hooks/useSidebarAutoScroll.ts +19 -0
  121. package/src/i18n/translations.ts +26 -4
  122. package/src/layouts/BookLayout.tsx +10 -40
  123. package/src/layouts/PostLayout.tsx +37 -26
  124. package/src/layouts/SimpleLayout.tsx +5 -5
  125. package/src/lib/feed-utils.ts +68 -0
  126. package/src/lib/format-utils.test.ts +27 -0
  127. package/src/lib/format-utils.ts +3 -0
  128. package/src/lib/image-utils.ts +12 -0
  129. package/src/lib/json-ld.ts +236 -0
  130. package/src/lib/markdown.ts +62 -7
  131. package/src/lib/rehype-image-metadata.test.ts +124 -0
  132. package/src/lib/rehype-image-metadata.ts +21 -5
  133. package/src/lib/scroll-utils.ts +11 -0
  134. package/src/lib/shuffle.ts +21 -0
  135. package/src/lib/urls.ts +51 -0
  136. package/tests/e2e/navigation.test.ts +15 -1
  137. package/tests/integration/books.test.ts +61 -0
  138. package/tests/integration/feed-utils.test.ts +145 -0
  139. package/tests/integration/series.test.ts +9 -0
  140. package/tests/unit/static-params.test.ts +59 -2
  141. package/src/app/subscribe/page.tsx +0 -17
  142. package/src/components/SubscribePage.tsx +0 -298
  143. /package/public/{screenshot.png → images/screenshot.png} +0 -0
@@ -1,4 +1,74 @@
1
1
  {
2
- "posts/02-routing-mastery/assets/m-p-model.png": "fDmvlEkZnE-UCvPK4gmDkJD7SU8coOTl4iw5hpsmcWI=",
3
- "/screenshot.png": "dGCNrjp1oMIL3NrVh1VDW0K+7Pp-cI5KH6tW4-6o9zg="
2
+ "books/agentic-design-patterns/images/appendix-a/image1.png": "k8G1J92r2m-rIbN+EDXjtOtXL3Im9pfIF9E+N4gR6+o=",
3
+ "books/agentic-design-patterns/images/appendix-b/image1.png": "j7jY3StmUJFaDcCc37qmkMNApRPw-hqtxo6xGMEvvSI=",
4
+ "books/agentic-design-patterns/images/appendix-d/image1.png": "Jz-nZ-kZPaM1XYPAzWY8jJRrmZHH3TOZKMVX6hD-G0I=",
5
+ "books/agentic-design-patterns/images/appendix-d/image2.png": "flQCnHTEIqAftdXjvY3gwc+OYbcZDaqwr3rtxDU7PjE=",
6
+ "books/agentic-design-patterns/images/appendix-d/image3.png": "FPPSaVvnYJhlRplp25grIwI0+vmtc9NHl+6EcpXIAic=",
7
+ "books/agentic-design-patterns/images/appendix-d/image4.png": "TvxBNTKaZCho+W8eTm67fiYmSwDJzwj1AOOguIV+o04=",
8
+ "books/agentic-design-patterns/images/appendix-d/image5.png": "EPlVRP7VlPQqkF9fawSV43kQg7JKVsjy-dHLFH6LIBY=",
9
+ "books/agentic-design-patterns/images/appendix-d/image6.png": "s7zF5TgNEQfvkakIHhdHXQ7S5imi4+AOtaD+PG7w0xM=",
10
+ "books/agentic-design-patterns/images/appendix-g/image1.png": "r-KvQvaqGSwZrN+30Dg6KZx4dEUzKZwPJ+eezw-KH70=",
11
+ "books/agentic-design-patterns/images/chapter-1/image1.png": "yfUwbocNorvSCdvdxTqQZmmiZsoRWbsn9iby6iKJYFQ=",
12
+ "books/agentic-design-patterns/images/chapter-1/image2.png": "pisZVOmTdn1Y+qs7sAqTqn04BeR3UngCd17kApWgehk=",
13
+ "books/agentic-design-patterns/images/chapter-10/image1.png": "aKJwlDxhZkAglLFTGgT8qpjIAlNQ5gV5ovCAnBjwx7M=",
14
+ "books/agentic-design-patterns/images/chapter-11/image1.png": "n9ou0BpTuPt3kcTEnCJEQRnMKFd9lI666DBxWDJzd3A=",
15
+ "books/agentic-design-patterns/images/chapter-11/image2.png": "30uw9sGMGnE+jyt++vVURBUQNXe5ty+8i8h+PMw4GfM=",
16
+ "books/agentic-design-patterns/images/chapter-12/image1.png": "4e4AD2MJFSB3GByGxGVr4VCQ4H+eYkR3oq39KuZhzw8=",
17
+ "books/agentic-design-patterns/images/chapter-12/image2.png": "1MyjXYovLBeKJmUGnUnx38T-UgkhRfV5opzOcP+OEdw=",
18
+ "books/agentic-design-patterns/images/chapter-13/image1.png": "PB3msIGhOmq1Oyrv6ubGJZJCvO+dD7XNxZiw5Ck-834=",
19
+ "books/agentic-design-patterns/images/chapter-14/image1.png": "p-06-D2Vp2x7HMXwmkTNLneS9sjsfSvmTsLTlVUjY5k=",
20
+ "books/agentic-design-patterns/images/chapter-14/image2.png": "XvXKYbuijYChfq4+MlZYXLBpxTg11ddJ0AFnzvntLPw=",
21
+ "books/agentic-design-patterns/images/chapter-14/image3.png": "YhmOWyJo3pDKVbKW7RMgTbNrbmsE89RkmaJ2z76vhos=",
22
+ "books/agentic-design-patterns/images/chapter-14/image4.png": "QBNe+Wt65qdLfkC5MJqaiNBJGMa-DbCm3gCOqfB7ehk=",
23
+ "books/agentic-design-patterns/images/chapter-15/image1.png": "I90CSqCb8g-h1F0a3sDusDC+LmHdjJTBBk6iFw4ZnJY=",
24
+ "books/agentic-design-patterns/images/chapter-15/image2.png": "HaxOSZ1f67BhEPa3KXtYAlK0cNm3G4H24dnLXpcgyA0=",
25
+ "books/agentic-design-patterns/images/chapter-16/image1.png": "Blc0vDhlie6PM3f6QSUX3argbfezTNDOLYhmT9uuS6E=",
26
+ "books/agentic-design-patterns/images/chapter-16/image2.png": "9-KZ0VOekDwFVL1dEzB9aleyCYF+WU6bx1h3aVNoOAM=",
27
+ "books/agentic-design-patterns/images/chapter-17/image1.png": "jjGM2Ll5bdszl9xFThVxxJzcq76iYDM1zz8qq2MVVYE=",
28
+ "books/agentic-design-patterns/images/chapter-17/image2.png": "O1KLKrodQew2Pdd7k3vBZhE4dCiK9u6JLvXSXy7h1do=",
29
+ "books/agentic-design-patterns/images/chapter-17/image3.png": "Sd9qFn-U5g8dhinNPJEk5LaeaN8DFH2FCus1Wcm6oSg=",
30
+ "books/agentic-design-patterns/images/chapter-17/image4.png": "eV38Dg78Y2Qo-0sqlsvcK8-p19fgRJqwx42sBKTzC+s=",
31
+ "books/agentic-design-patterns/images/chapter-17/image5.png": "bgKPC1TSVlvfkCG5vILT-4j9gJU7LyeGFkXO+8OFdXE=",
32
+ "books/agentic-design-patterns/images/chapter-17/image6.png": "qMku-C8gxZEFQEl8Qq8Y1ld5zUntH1Q-2izcb4QxLA0=",
33
+ "books/agentic-design-patterns/images/chapter-17/image7.png": "20wopLVN59okB+C-F+U5vi79Lg0uH+5P6wIj72gV7wU=",
34
+ "books/agentic-design-patterns/images/chapter-18/image1.png": "kjCIoNU60icXMOHmYNXWNMjdi5nIdJZYDSp0WFnG2Es=",
35
+ "books/agentic-design-patterns/images/chapter-19/image1.png": "XrQVFAWWsMcwKHgcqx8jGLy4q+6RbiGZ5CuKpRlk4Y0=",
36
+ "books/agentic-design-patterns/images/chapter-19/image2.png": "URHY+cH5AR4KvA6HPnXPhZd5fwwGdlwvFvxgCdTkWjg=",
37
+ "books/agentic-design-patterns/images/chapter-19/image3.png": "VLp1qFNcaj126-RuATV6ikSsow8lrXsn3EuZotvR5N4=",
38
+ "books/agentic-design-patterns/images/chapter-19/image4.png": "CnyNMtPiFSdzqrewwLgPu2X1CKdgMqCl8T8+d7lw2Fw=",
39
+ "books/agentic-design-patterns/images/chapter-2/image1.png": "C6odPDB9zhN28YxfU909JAoQWe0mRm3lUsyG8NzHBWk=",
40
+ "books/agentic-design-patterns/images/chapter-20/image1.png": "qkzjKCUjVFD8IWyoZQXuBuZ4zFJ0Y0CQfKepVcrnEN0=",
41
+ "books/agentic-design-patterns/images/chapter-21/image1.png": "3KpW6uLQDru7DFDdgi6ntD5WhuGS383aYwlQLWoVX+w=",
42
+ "books/agentic-design-patterns/images/chapter-21/image2.png": "wSXMrRuAnhQcEM5fg-sJ5ZikM3olj0vz6FDKWKIUyW8=",
43
+ "books/agentic-design-patterns/images/chapter-3/image1.png": "iHnUE2kckwzB0xiIySSe4O34c6bIs3VlO7pUQBmV8iI=",
44
+ "books/agentic-design-patterns/images/chapter-3/image2.png": "9-G6F+aBymQVTnDQy4zwsXDZMkLaYKBe4xw26tC24R8=",
45
+ "books/agentic-design-patterns/images/chapter-4/image1.png": "8h0PBlqt+JFo7Y803cA9MgfTe8TZeZd6te9l+WEsIss=",
46
+ "books/agentic-design-patterns/images/chapter-4/image2.png": "p-arzpUPFdnKmzTOKaDQys5Eml0jHA7VuUV0FyLYpqM=",
47
+ "books/agentic-design-patterns/images/chapter-5/image1.png": "XE5qhAklrW-7eVf7T9x9vo4xQX3WesZjZ9IjuyeSUkc=",
48
+ "books/agentic-design-patterns/images/chapter-5/image2.png": "MqUdsG5aXQECft+lsCRTeMIJP-YzxWFHC8HdNzio6aw=",
49
+ "books/agentic-design-patterns/images/chapter-6/image1.png": "t6mFrbVSNJYF73t+Q+YydXdwRQr1c4yIUkySvCelw1I=",
50
+ "books/agentic-design-patterns/images/chapter-6/image2.png": "zvwqWis6Ad7Iv1-wgYtTgz2ItXsno2PetcHRHon0-wY=",
51
+ "books/agentic-design-patterns/images/chapter-6/image3.png": "al-H2S7UznxQzP3KT3N0lCTNd83B45bqbPppuhSphy0=",
52
+ "books/agentic-design-patterns/images/chapter-6/image4.png": "u2k2jHjTOABLTlJ3OsxkGyCoOR-fwHeDmTXMACbtQ+4=",
53
+ "books/agentic-design-patterns/images/chapter-7/image1.png": "izX9YjrJ+gHGOiR9RHbuMIupCE2zyB01w-gwJAJr4Is=",
54
+ "books/agentic-design-patterns/images/chapter-7/image2.png": "-0zrZR8CNZqtuyaBRwUbZSfthKwuqrBu4fLar9L4Clg=",
55
+ "books/agentic-design-patterns/images/chapter-7/image3.png": "dKr7VICWOTc-ZRhm4Aaokz+xPZ8IXS0SiDOJeF1Y03c=",
56
+ "books/agentic-design-patterns/images/chapter-8/image1.png": "GWG47Om+9GwRvl8x-X269Sa-Mv3rqkW2xkx1WIB-I-E=",
57
+ "books/agentic-design-patterns/images/chapter-9/image1.png": "fIZ4QE7EIQSAv0K2GmU99OmTBAkpvnYUsFFScCv6VPI=",
58
+ "books/agentic-design-patterns/images/chapter-9/image2.png": "6y-8OjY7oEedk5ZhUUU2YIU3tjMxIzpnxcDxIRWPGpI=",
59
+ "books/agentic-design-patterns/images/chapter-9/image3.png": "69nSkOOPfX9FkIJGhKIrhSuCOZt29L5UAukVXfMXN2E=",
60
+ "books/agentic-design-patterns/images/chapter-9/image4.png": "GQCpjBMz5DRxsHIA8-73IMsxeBPc5ZTMaIsx2iihMME=",
61
+ "books/agentic-design-patterns/images/cover.png": "R47dqw-ws79m6VBfzhWuLi9ihoCaU7JcezquBAB1-bQ=",
62
+ "images/amytis-screenshot.jpg": "t9tzciqJPsNz1hlixAFLrrvD4Fsaih2Kbd1Jd2C-fqs=",
63
+ "images/antelope-canyon.jpg": "xuP3HlbwqA4z1Hsq7s1u2u61yN3SHzIMUd6Wl5yqgK8=",
64
+ "images/avatar.jpg": "SUGxOhDUD96YiVxZJkVJ6ou4W5MdCJ9hTBQMSQanZOM=",
65
+ "images/cappadocia.jpg": "EE2Mt8d9ERKd40SfwyhjFsejZLhFx1SfSAQmlu+TQOo=",
66
+ "images/flowers.jpg": "Lq9dhpZvAIgkYnijDsFIzrKiv8trdM-cUnl-5Y86PuM=",
67
+ "images/galaxy.jpg": "-3YwMkU3PGsm4P2yNsA2S02XRL1j0KbBh6nWKwy0hYc=",
68
+ "images/lake.jpg": "imAORQhxpmoU3jzKBMNFJuFSa0UgiSf2Dmea5Rj8-8M=",
69
+ "images/mountains.jpg": "FsrkZws9EKMqHCk1Hc6i6nEIcTRcrMBa4ddgqR6oRaI=",
70
+ "images/screenshot.png": "FAqbAgLRbWbYq9yJ4iggq2aKxRD8hdeDICc3DI14yhg=",
71
+ "images/vibrant-waves.avif": "pP6wNch0dvCr+HGjkA8zIT6ZuZCSSofiVkG922udk54=",
72
+ "images/wechat-qr.jpg": "DNIzz0Wcl8WP0h-jHHgZ9LEvf3ZKOXHgxlvpw3gK2ME=",
73
+ "posts/02-routing-mastery/assets/m-p-model.png": "fDmvlEkZnE-UCvPK4gmDkJD7SU8coOTl4iw5hpsmcWI="
4
74
  }
@@ -0,0 +1,77 @@
1
+ import { spawnSync } from 'child_process';
2
+ import fs from 'fs';
3
+
4
+ // ─── Argument parsing ─────────────────────────────────────────────────────────
5
+
6
+ function flag(name: string): string | undefined {
7
+ const idx = process.argv.indexOf(`--${name}`);
8
+ if (idx === -1) return undefined;
9
+ const value = process.argv[idx + 1];
10
+ // Treat missing or another flag as absent
11
+ return value && !value.startsWith('--') ? value : undefined;
12
+ }
13
+
14
+ // Flags take priority over .env / .env.local values (loaded automatically by Bun)
15
+ const host = flag('host') ?? process.env.DEPLOY_HOST;
16
+ const user = flag('user') ?? process.env.DEPLOY_USER;
17
+ const password = flag('password') ?? process.env.DEPLOY_PASSWORD;
18
+ const path = flag('path') ?? process.env.DEPLOY_PATH ?? '/var/www/html';
19
+
20
+ // ─── Validation ───────────────────────────────────────────────────────────────
21
+
22
+ const missing = (['host', 'user', 'password'] as const).filter(k => !({ host, user, password }[k]));
23
+ if (missing.length > 0) {
24
+ console.error(`Missing required value(s): ${missing.map(k => `--${k}`).join(', ')}`);
25
+ console.error('');
26
+ console.error('Provide them as flags or set in .env.local:');
27
+ console.error(' DEPLOY_HOST=<ip>');
28
+ console.error(' DEPLOY_USER=<user>');
29
+ console.error(' DEPLOY_PASSWORD=<password>');
30
+ console.error(' DEPLOY_PATH=<remote-path> (optional, default: /var/www/html)');
31
+ console.error('');
32
+ console.error('Usage: bun run deploy [--host <ip>] [--user <user>] [--password <pass>] [--path <path>]');
33
+ process.exit(1);
34
+ }
35
+
36
+ if (!fs.existsSync('out')) {
37
+ console.error('Error: out/ directory not found. Run `bun run build` first.');
38
+ process.exit(1);
39
+ }
40
+
41
+ const check = spawnSync('which', ['sshpass']);
42
+ if (check.status !== 0) {
43
+ console.error('Error: sshpass not found. Install it first:');
44
+ console.error(' macOS: brew install hudochenkov/sshpass/sshpass');
45
+ console.error(' Linux: apt install sshpass');
46
+ process.exit(1);
47
+ }
48
+
49
+ // ─── Deploy ───────────────────────────────────────────────────────────────────
50
+
51
+ const destination = `${user}@${host}:${path}/`;
52
+ console.log(`Deploying out/ → ${destination}`);
53
+
54
+ const rsync = spawnSync(
55
+ 'sshpass',
56
+ ['-p', password!, 'rsync', '-avz', '--delete', '-e', 'ssh -o StrictHostKeyChecking=accept-new', 'out/', destination],
57
+ { stdio: 'inherit' }
58
+ );
59
+
60
+ if (rsync.status !== 0) {
61
+ console.error('\nDeploy failed.');
62
+ process.exit(rsync.status ?? 1);
63
+ }
64
+
65
+ console.log('\nReloading nginx...');
66
+
67
+ const reload = spawnSync(
68
+ 'sshpass',
69
+ ['-p', password!, 'ssh', '-o', 'StrictHostKeyChecking=accept-new', `${user}@${host}`, 'nginx -s reload'],
70
+ { stdio: 'inherit' }
71
+ );
72
+
73
+ if (reload.status !== 0) {
74
+ console.warn('Warning: nginx reload failed (site was still deployed).');
75
+ }
76
+
77
+ console.log('Done.');
@@ -0,0 +1,268 @@
1
+ // Single-language starter template for Amytis.
2
+ // Copy this file to site.config.ts and customize it for your site.
3
+ //
4
+ // This template has i18n disabled. All locale-aware fields use plain strings.
5
+ // To add multi-language support later, switch to { en: '...', zh: '...' } objects
6
+ // and set i18n.enabled: true with your locales.
7
+
8
+ export interface NavChildItem {
9
+ name: string;
10
+ url: string;
11
+ external?: boolean;
12
+ dividerBefore?: boolean; // render a separator line before this item
13
+ }
14
+
15
+ export interface NavItem {
16
+ name: string;
17
+ url: string;
18
+ weight: number;
19
+ external?: boolean;
20
+ dropdown?: string[];
21
+ children?: NavChildItem[]; // static sub-links rendered as a dropdown
22
+ }
23
+
24
+ // Defined up-front so nav and posts config stay in sync — change once here
25
+ const postsBasePath = 'posts'; // Change to e.g. 'articles' to serve all posts at /articles/[slug]
26
+
27
+ // Defined up-front so footer.connect can reference these URLs without duplication
28
+ const social = {
29
+ github: "https://github.com/your-username",
30
+ twitter: "https://twitter.com/your-username",
31
+ email: "mailto:you@example.com",
32
+ };
33
+
34
+ export const siteConfig = {
35
+
36
+ // ── Site identity ─────────────────────────────────────────────────────────
37
+ title: "My Garden",
38
+ logo: {
39
+ // Path to navbar logo image served from public/ (leave empty for the default built-in icon)
40
+ // Accepts SVG, PNG, etc. — e.g. "/logo.svg" or "/images/my-logo.png"
41
+ src: "",
42
+ // Path to favicon served from public/ (defaults to /icon.svg)
43
+ favicon: "/icon.svg",
44
+ },
45
+ description: "A personal digital garden — notes, articles, and ideas.",
46
+ baseUrl: "https://example.com", // Replace with your actual domain
47
+ ogImage: "/og-image.png", // Default OG/social preview image — place a 1200×630 PNG at public/og-image.png
48
+ footerText: `© ${new Date().getFullYear()} My Garden. All rights reserved.`,
49
+
50
+ // ── i18n ──────────────────────────────────────────────────────────────────
51
+ i18n: {
52
+ // i18n is disabled: the language switcher is hidden and defaultLocale is always used.
53
+ // To enable multi-language support, set enabled: true, add more locales, and
54
+ // convert plain string fields to locale maps: e.g. title: { en: '...', zh: '...' }
55
+ enabled: false,
56
+ defaultLocale: 'en',
57
+ locales: ['en'],
58
+ },
59
+
60
+ // ── Navigation ────────────────────────────────────────────────────────────
61
+ nav: [
62
+ { name: "Posts", url: `/${postsBasePath}`, weight: 1 },
63
+ { name: "Series", url: "/series", weight: 2 },
64
+ { name: "About", url: "/about", weight: 3 },
65
+ { name: "More", url: "", weight: 4, children: [
66
+ { name: "Archive", url: "/archive" },
67
+ { name: "Tags", url: "/tags" },
68
+ { name: "Subscribe", url: "/subscribe", dividerBefore: true },
69
+ ]},
70
+ ] as NavItem[],
71
+
72
+ // ── Footer ────────────────────────────────────────────────────────────────
73
+ footer: {
74
+ explore: [
75
+ { name: "Archive", url: "/archive", weight: 1 },
76
+ { name: "Tags", url: "/tags", weight: 2 },
77
+ { name: "About", url: "/about", weight: 3 },
78
+ ],
79
+ connect: [
80
+ { name: "GitHub", url: social.github, weight: 1 },
81
+ { name: "X (Twitter)", url: social.twitter, weight: 2 },
82
+ { name: "RSS Feed", url: "/feed.xml", weight: 3 },
83
+ { name: "Subscribe", url: "/subscribe", weight: 4 },
84
+ ],
85
+ builtWith: {
86
+ show: true,
87
+ url: "https://github.com/hutusi/amytis",
88
+ text: "Built with Amytis",
89
+ },
90
+ // bottomLinks: [
91
+ // { text: '京ICP备12345678号', url: 'https://beian.miit.gov.cn/' },
92
+ // ],
93
+ bottomLinks: [],
94
+ },
95
+
96
+ // ── Social & sharing ──────────────────────────────────────────────────────
97
+ social,
98
+ share: {
99
+ enabled: true,
100
+ // Supported: twitter, facebook, linkedin, weibo, reddit, hackernews,
101
+ // telegram, bluesky, mastodon, douban, zhihu, copy
102
+ platforms: ['twitter', 'facebook', 'linkedin', 'copy'],
103
+ },
104
+ subscribe: {
105
+ substack: '', // Substack publication URL, e.g., 'https://yourname.substack.com'
106
+ telegram: '', // Telegram channel URL, e.g., 'https://t.me/yourchannel'
107
+ wechat: {
108
+ qrCode: '', // Path to QR image in public/, e.g., '/images/wechat-qr.png'
109
+ account: '', // WeChat official account ID/name shown below QR
110
+ },
111
+ email: '', // Newsletter/mailing list URL (distinct from social.email contact address)
112
+ },
113
+
114
+ // ── Features ──────────────────────────────────────────────────────────────
115
+ features: {
116
+ posts: {
117
+ enabled: true,
118
+ name: "Articles",
119
+ },
120
+ series: {
121
+ enabled: true,
122
+ name: "Series",
123
+ },
124
+ books: {
125
+ enabled: false,
126
+ name: "Books",
127
+ },
128
+ flow: {
129
+ enabled: false,
130
+ name: "Flow",
131
+ },
132
+ },
133
+
134
+ // ── Homepage ──────────────────────────────────────────────────────────────
135
+ hero: {
136
+ tagline: "My Personal Digital Garden",
137
+ title: "A place for ideas to grow.",
138
+ subtitle: "Notes, articles, and explorations — written for curiosity, shared for connection.",
139
+ },
140
+ homepage: {
141
+ sections: [
142
+ { id: 'hero', enabled: true, weight: 1 },
143
+ { id: 'featured-posts', enabled: true, weight: 2, maxItems: 4 },
144
+ { id: 'latest-posts', enabled: true, weight: 3, maxItems: 3 },
145
+ { id: 'recent-flows', enabled: false, weight: 4, maxItems: 8 },
146
+ { id: 'featured-series', enabled: true, weight: 5, maxItems: 6, scrollThreshold: 2 },
147
+ { id: 'featured-books', enabled: false, weight: 6, maxItems: 4 },
148
+ ],
149
+ },
150
+
151
+ // ── Content ───────────────────────────────────────────────────────────────
152
+ pagination: {
153
+ posts: 10,
154
+ series: 5,
155
+ flows: 20,
156
+ notes: 20,
157
+ },
158
+ posts: {
159
+ basePath: postsBasePath,
160
+ toc: true,
161
+ showFuturePosts: false,
162
+ includeDateInUrl: false,
163
+ // trailingSlash is configured in next.config.ts (Next.js handles URL normalization)
164
+ authors: {
165
+ // Default author names applied when a post has no author in its frontmatter.
166
+ // Falls back to series authors first, then to this list.
167
+ default: ["Your Name"] as string[],
168
+ showInHeader: true, // Show author byline below the post title
169
+ showAuthorCard: true, // Show author bio card at the end of the post
170
+ },
171
+ // Series slugs whose posts are hidden from the main posts listing.
172
+ // Posts remain accessible via their series page and direct URLs.
173
+ excludeFromListing: [] as string[],
174
+ archive: {
175
+ showAuthors: true,
176
+ },
177
+ },
178
+ series: {
179
+ // Per-series custom URL prefix for posts within that series
180
+ // e.g., { 'weeklies': 'weeklies' } → posts served at /weeklies/[slug]
181
+ customPaths: {} as Record<string, string>,
182
+ },
183
+ flows: {
184
+ recentCount: 5,
185
+ },
186
+
187
+ feed: {
188
+ maxItems: 20, // Max items per feed (0 = no limit)
189
+ format: 'rss' as 'rss' | 'atom' | 'both', // Format(s) to serve and advertise
190
+ content: 'full' as 'excerpt' | 'full', // Full post content or excerpt only
191
+ includeFlows: false, // Include flow notes alongside posts
192
+ },
193
+
194
+ // ── Images ────────────────────────────────────────────────────────────────
195
+ images: {
196
+ // CDN base URL for serving images (leave empty to serve locally)
197
+ // e.g., "https://cdn.example.com" or "https://your-bucket.r2.dev"
198
+ // When set, local image paths like /posts/slug/images/cover.jpg are rewritten
199
+ // to https://cdn.example.com/posts/slug/images/cover.jpg at render time.
200
+ cdnBaseUrl: "",
201
+ },
202
+
203
+ // ── Appearance ────────────────────────────────────────────────────────────
204
+ themeColor: 'default', // 'default' | 'blue' | 'rose' | 'amber'
205
+
206
+ // ── Browser compatibility warning ─────────────────────────────────────────
207
+ browserCheck: {
208
+ // URL shown in the outdated-browser banner. Set to '' to hide the link
209
+ // (useful for corporate/intranet deployments where IT manages upgrades).
210
+ updateUrl: 'https://browsehappy.com/',
211
+ },
212
+
213
+ // ── Analytics ─────────────────────────────────────────────────────────────
214
+ analytics: {
215
+ providers: [], // enable one or many: 'umami' | 'plausible' | 'google'
216
+ umami: {
217
+ websiteId: process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID || '', // Your Umami Website ID
218
+ src: process.env.NEXT_PUBLIC_UMAMI_URL || 'https://us.umami.is/script.js', // Default or self-hosted URL
219
+ },
220
+ plausible: {
221
+ domain: '', // Your domain
222
+ src: 'https://plausible.io/js/script.js',
223
+ },
224
+ google: {
225
+ measurementId: '', // G-XXXXXXXXXX
226
+ },
227
+ },
228
+
229
+ // ── Comments ──────────────────────────────────────────────────────────────
230
+ comments: {
231
+ provider: null, // 'giscus' | 'disqus' | null
232
+ giscus: {
233
+ repo: 'your-username/your-repo', // username/repo
234
+ repoId: '',
235
+ category: 'Announcements',
236
+ categoryId: '',
237
+ },
238
+ disqus: {
239
+ shortname: '',
240
+ },
241
+ },
242
+
243
+ // ── Authors ───────────────────────────────────────────────────────────────
244
+ authors: {
245
+ // Map display name (as used in post frontmatter) to author profile.
246
+ // Example:
247
+ // "Author Name": {
248
+ // bio: "Short bio shown in author card below each post.",
249
+ // avatar: "/images/authors/author-name.jpg", // path under public/
250
+ // social: [
251
+ // { image: "/images/authors/wechat-qr.jpg", description: "WeChat Official Account" },
252
+ // ],
253
+ // },
254
+ "Your Name": {
255
+ bio: "Write a short bio here.",
256
+ avatar: "/images/avatar.jpg",
257
+ social: [],
258
+ },
259
+ } as Record<string, {
260
+ bio?: string;
261
+ avatar?: string; // Avatar image path served from public/
262
+ social?: Array<{
263
+ image: string; // Social image (e.g. QR code) path served from public/
264
+ description: string; // Label shown below the image
265
+ }>;
266
+ }>,
267
+
268
+ };
package/site.config.ts CHANGED
@@ -25,6 +25,13 @@ export const siteConfig = {
25
25
 
26
26
  // ── Site identity ─────────────────────────────────────────────────────────
27
27
  title: { en: "Amytis", zh: "Amytis" },
28
+ logo: {
29
+ // Path to navbar logo image served from public/ (leave empty for the default built-in icon)
30
+ // Accepts SVG, PNG, etc. — e.g. "/logo.svg" or "/images/my-logo.png"
31
+ src: "",
32
+ // Path to favicon served from public/ (defaults to /icon.svg)
33
+ favicon: "/icon.svg",
34
+ },
28
35
  description: { en: "Amytis — an elegant open-source framework for building your personal digital garden.", zh: "Amytis — 优雅的开源数字花园框架。" },
29
36
  baseUrl: "https://example.com", // Replace with your actual domain
30
37
  ogImage: "/og-image.png", // Default OG/social preview image — place a 1200×630 PNG at public/og-image.png
@@ -32,6 +39,11 @@ export const siteConfig = {
32
39
 
33
40
  // ── i18n ──────────────────────────────────────────────────────────────────
34
41
  i18n: {
42
+ // Set enabled: false to disable multi-language support entirely.
43
+ // The language switcher will be hidden and the defaultLocale is always used.
44
+ // When disabled, all locale-aware fields (title, description, hero, etc.)
45
+ // accept plain strings instead of { en: '...', zh: '...' } objects.
46
+ enabled: true,
35
47
  defaultLocale: 'en',
36
48
  locales: ['en', 'zh'],
37
49
  },
@@ -61,7 +73,7 @@ export const siteConfig = {
61
73
  ],
62
74
  connect: [
63
75
  { name: "GitHub", url: social.github, weight: 1 },
64
- { name: "Twitter", url: social.twitter, weight: 2 },
76
+ { name: "X (Twitter)", url: social.twitter, weight: 2 },
65
77
  { name: "RSS Feed", url: "/feed.xml", weight: 3 },
66
78
  { name: "Subscribe", url: "/subscribe", weight: 4 },
67
79
  ],
@@ -70,6 +82,14 @@ export const siteConfig = {
70
82
  url: "https://github.com/hutusi/amytis",
71
83
  text: { en: "Built with Amytis", zh: "基于 Amytis 构建" },
72
84
  },
85
+ // Optional custom links shown in the footer bottom bar.
86
+ // Common uses: ICP registration (China), PSB registration, cookie policy, sitemap, etc.
87
+ // Example:
88
+ // bottomLinks: [
89
+ // { text: '京ICP备12345678号', url: 'https://beian.miit.gov.cn/' },
90
+ // { text: 'Cookie Policy' }, // url is optional — renders as plain text
91
+ // ],
92
+ bottomLinks: [] as { text: string | Record<string, string>; url?: string }[],
73
93
  },
74
94
 
75
95
  // ── Social & sharing ──────────────────────────────────────────────────────
@@ -119,11 +139,11 @@ export const siteConfig = {
119
139
  homepage: {
120
140
  sections: [
121
141
  { id: 'hero', enabled: true, weight: 1 },
122
- { id: 'featured-series', enabled: true, weight: 2, maxItems: 6, scrollThreshold: 2 },
123
- { id: 'featured-books', enabled: true, weight: 3, maxItems: 4, scrollThreshold: 2 },
124
- { id: 'featured-posts', enabled: true, weight: 4, maxItems: 4, scrollThreshold: 1 },
125
- { id: 'latest-posts', enabled: true, weight: 5, maxItems: 5 },
126
- { id: 'recent-flows', enabled: true, weight: 6, maxItems: 5 },
142
+ { id: 'featured-posts', enabled: true, weight: 2, maxItems: 4 },
143
+ { id: 'latest-posts', enabled: true, weight: 3, maxItems: 3 },
144
+ { id: 'recent-flows', enabled: true, weight: 4, maxItems: 8 },
145
+ { id: 'featured-series', enabled: true, weight: 5, maxItems: 6, scrollThreshold: 2 },
146
+ { id: 'featured-books', enabled: true, weight: 6, maxItems: 4 },
127
147
  ],
128
148
  },
129
149
 
@@ -135,17 +155,48 @@ export const siteConfig = {
135
155
  notes: 20,
136
156
  },
137
157
  posts: {
158
+ basePath: 'posts', // Change to e.g. 'articles' to serve all posts at /articles/[slug]
138
159
  toc: true,
139
160
  showFuturePosts: false,
140
161
  includeDateInUrl: false,
141
162
  // trailingSlash is configured in next.config.ts (Next.js handles URL normalization)
163
+ authors: {
164
+ // Default author names applied when a post has no author in its frontmatter.
165
+ // Falls back to series authors first, then to this list.
166
+ default: ["John Hu"] as string[],
167
+ showInHeader: true, // Show author byline below the post title
168
+ showAuthorCard: true, // Show author bio card at the end of the post
169
+ },
170
+ // Series slugs whose posts are hidden from the main posts listing.
171
+ // Posts remain accessible via their series page and direct URLs.
172
+ excludeFromListing: [] as string[],
142
173
  archive: {
143
174
  showAuthors: true,
144
175
  },
145
176
  },
177
+ series: {
178
+ // Per-series custom URL prefix for posts within that series
179
+ // e.g., { 'weeklies': 'weeklies' } → posts served at /weeklies/[slug]
180
+ customPaths: {} as Record<string, string>,
181
+ },
146
182
  flows: {
147
183
  recentCount: 5,
148
184
  },
185
+ feed: {
186
+ maxItems: 20, // Max items per feed (0 = no limit)
187
+ format: 'rss' as 'rss' | 'atom' | 'both', // Format(s) to serve and advertise
188
+ content: 'full' as 'excerpt' | 'full', // Full post content or excerpt only
189
+ includeFlows: false, // Include flow notes alongside posts
190
+ },
191
+
192
+ // ── Images ────────────────────────────────────────────────────────────────
193
+ images: {
194
+ // CDN base URL for serving images (leave empty to serve locally)
195
+ // e.g., "https://cdn.example.com" or "https://your-bucket.r2.dev"
196
+ // When set, local image paths like /posts/slug/images/cover.jpg are rewritten
197
+ // to https://cdn.example.com/posts/slug/images/cover.jpg at render time.
198
+ cdnBaseUrl: "",
199
+ },
149
200
 
150
201
  // ── Appearance ────────────────────────────────────────────────────────────
151
202
  themeColor: 'default', // 'default' | 'blue' | 'rose' | 'amber'
@@ -159,7 +210,7 @@ export const siteConfig = {
159
210
 
160
211
  // ── Analytics ─────────────────────────────────────────────────────────────
161
212
  analytics: {
162
- provider: 'umami', // 'umami' | 'plausible' | 'google' | null
213
+ providers: ['umami'] as ('umami' | 'plausible' | 'google')[], // enable one or many; [] disables analytics
163
214
  umami: {
164
215
  websiteId: process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID || '', // Your Umami Website ID
165
216
  src: process.env.NEXT_PUBLIC_UMAMI_URL || 'https://us.umami.is/script.js', // Default or self-hosted URL
@@ -189,8 +240,29 @@ export const siteConfig = {
189
240
 
190
241
  // ── Authors ───────────────────────────────────────────────────────────────
191
242
  authors: {
192
- // Map display name (as used in post frontmatter) to author profile
193
- // "Author Name": { bio: "Short bio shown in author card below each post." },
194
- } as Record<string, { bio?: string }>,
243
+ // Map display name (as used in post frontmatter) to author profile.
244
+ // Example:
245
+ // "Author Name": {
246
+ // bio: "Short bio shown in author card below each post.",
247
+ // avatar: "/images/authors/author-name.jpg", // path under public/
248
+ // social: [
249
+ // { image: "/images/authors/wechat-qr.jpg", description: "WeChat Official Account" },
250
+ // ],
251
+ // },
252
+ "John Hu": {
253
+ bio: "Coder, Writer, Creator.",
254
+ avatar: "/images/avatar.jpg",
255
+ social: [
256
+ { image: "/images/wechat-qr.jpg", description: "Follow on WeChat" },
257
+ ],
258
+ },
259
+ } as Record<string, {
260
+ bio?: string;
261
+ avatar?: string; // Avatar image path served from public/
262
+ social?: Array<{
263
+ image: string; // Social image (e.g. QR code) path served from public/
264
+ description: string; // Label shown below the image
265
+ }>;
266
+ }>,
195
267
 
196
268
  };