@hutusi/amytis 1.14.0 → 1.16.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 (128) hide show
  1. package/.github/workflows/ci.yml +1 -1
  2. package/.github/workflows/publish.yml +2 -2
  3. package/CHANGELOG.md +42 -0
  4. package/CLAUDE.md +90 -219
  5. package/README.md +33 -1
  6. package/README.zh.md +33 -1
  7. package/TODO.md +10 -0
  8. package/bun.lock +205 -539
  9. package/content/books/sample-book/index.mdx +3 -0
  10. package/content/posts/code-block-features-showcase.mdx +223 -0
  11. package/content/series/rst-legacy/deeper-notes/images/test.svg +4 -0
  12. package/content/series/rst-legacy/deeper-notes/index.rst +15 -0
  13. package/content/series/rst-legacy/getting-started.rst +24 -0
  14. package/content/series/rst-legacy/index.rst +9 -0
  15. package/content/series/rst-readme/README.rst +9 -0
  16. package/content/series/rst-readme/readme-index-post.rst +10 -0
  17. package/content/series/rst-toctree/first-post.rst +6 -0
  18. package/content/series/rst-toctree/index.rst +10 -0
  19. package/content/series/rst-toctree/second-post.rst +6 -0
  20. package/content/series/rst-toctree-precedence/first-post.rst +6 -0
  21. package/content/series/rst-toctree-precedence/index.rst +12 -0
  22. package/content/series/rst-toctree-precedence/second-post.rst +6 -0
  23. package/docs/ALERTS.md +112 -0
  24. package/docs/ARCHITECTURE.md +239 -8
  25. package/docs/CODE-BLOCKS.md +238 -0
  26. package/docs/CONTRIBUTING.md +36 -0
  27. package/docs/guides/README.md +11 -0
  28. package/docs/guides/importing-vuepress-books.md +178 -0
  29. package/eslint.config.mjs +20 -6
  30. package/next.config.ts +2 -2
  31. package/package.json +52 -24
  32. package/packages/create-amytis/package.json +1 -1
  33. package/packages/create-amytis/src/index.test.ts +43 -1
  34. package/packages/create-amytis/src/index.ts +64 -8
  35. package/public/next-image-export-optimizer-hashes.json +14 -73
  36. package/scripts/build-pagefind.ts +172 -0
  37. package/scripts/copy-assets.ts +246 -56
  38. package/scripts/generate-code-group-icons.ts +79 -0
  39. package/scripts/generate-knowledge-graph.ts +2 -1
  40. package/scripts/render-rst.py +923 -0
  41. package/scripts/run-with-rst-python.ts +42 -0
  42. package/scripts/sync-vuepress-book.ts +499 -0
  43. package/src/app/[slug]/[postSlug]/page.tsx +20 -10
  44. package/src/app/[slug]/page/[page]/page.tsx +15 -0
  45. package/src/app/books/[slug]/{[chapter] → [...chapter]}/page.tsx +32 -10
  46. package/src/app/books/[slug]/page.tsx +67 -32
  47. package/src/app/globals.css +639 -94
  48. package/src/app/page.tsx +1 -1
  49. package/src/app/series/[slug]/page/[page]/page.tsx +74 -6
  50. package/src/app/series/[slug]/page.tsx +11 -13
  51. package/src/app/series/page.tsx +3 -3
  52. package/src/app/sitemap.ts +3 -3
  53. package/src/components/ArticleCopyCleaner.tsx +64 -0
  54. package/src/components/AuthorCard.tsx +25 -16
  55. package/src/components/BookMobileNav.tsx +44 -50
  56. package/src/components/BookSidebar.tsx +0 -0
  57. package/src/components/CodeBlock.test.tsx +93 -8
  58. package/src/components/CodeBlock.tsx +39 -101
  59. package/src/components/CodeBlockToolbar.tsx +88 -0
  60. package/src/components/CodeGroup.tsx +81 -0
  61. package/src/components/CoverImage.tsx +6 -2
  62. package/src/components/ExternalLinkIcon.tsx +15 -0
  63. package/src/components/FeaturedStoriesSection.tsx +3 -3
  64. package/src/components/GithubAlert.tsx +97 -0
  65. package/src/components/MarkdownRenderer.test.tsx +30 -4
  66. package/src/components/MarkdownRenderer.tsx +148 -24
  67. package/src/components/Mermaid.tsx +32 -1
  68. package/src/components/PostList.tsx +1 -1
  69. package/src/components/PostNavigation.tsx +13 -2
  70. package/src/components/PostSidebar.tsx +13 -2
  71. package/src/components/RstRenderer.test.tsx +93 -0
  72. package/src/components/RstRenderer.tsx +157 -0
  73. package/src/components/Search.tsx +18 -4
  74. package/src/components/SeriesCatalog.tsx +1 -1
  75. package/src/components/ShareBar.tsx +5 -0
  76. package/src/components/TocPanel.tsx +10 -2
  77. package/src/i18n/translations.ts +2 -0
  78. package/src/layouts/BookLayout.tsx +35 -4
  79. package/src/layouts/PostLayout.tsx +10 -2
  80. package/src/layouts/SimpleLayout.tsx +10 -3
  81. package/src/lib/code-group-icons.test.ts +78 -0
  82. package/src/lib/code-group-icons.ts +148 -0
  83. package/src/lib/image-utils.test.ts +19 -0
  84. package/src/lib/image-utils.ts +11 -0
  85. package/src/lib/markdown.test.ts +195 -14
  86. package/src/lib/markdown.ts +928 -254
  87. package/src/lib/normalize-vuepress-math.ts +118 -0
  88. package/src/lib/rehype-fence-meta.ts +22 -0
  89. package/src/lib/rehype-image-metadata.ts +2 -2
  90. package/src/lib/remark-book-chapter-links.ts +106 -0
  91. package/src/lib/remark-code-group.ts +54 -0
  92. package/src/lib/remark-github-alerts.test.ts +83 -0
  93. package/src/lib/remark-github-alerts.ts +65 -0
  94. package/src/lib/remark-vuepress-containers.ts +130 -0
  95. package/src/lib/rst-renderer.test.ts +355 -0
  96. package/src/lib/rst-renderer.ts +629 -0
  97. package/src/lib/rst.test.ts +350 -0
  98. package/src/lib/rst.ts +674 -0
  99. package/src/lib/series-redirects.ts +42 -0
  100. package/src/lib/shiki-rst.ts +185 -0
  101. package/src/lib/shiki.test.ts +153 -0
  102. package/src/lib/shiki.ts +292 -0
  103. package/src/lib/urls.ts +57 -0
  104. package/src/test-utils/render.ts +23 -0
  105. package/tests/fixtures/sync-vuepress-book/docs/.vuepress/config.js +43 -0
  106. package/tests/fixtures/sync-vuepress-book/docs/intro/welcome.md +7 -0
  107. package/tests/fixtures/sync-vuepress-book/docs/maths/linear/assets/diagram.png +1 -0
  108. package/tests/fixtures/sync-vuepress-book/docs/maths/linear/matrices.md +7 -0
  109. package/tests/fixtures/sync-vuepress-book/docs/maths/linear/vectors.md +9 -0
  110. package/tests/helpers/env.ts +19 -0
  111. package/tests/integration/book-chapter-links.test.ts +107 -0
  112. package/tests/integration/books-nested-toc.test.ts +176 -0
  113. package/tests/integration/books.test.ts +3 -2
  114. package/tests/integration/code-block-features.test.ts +188 -0
  115. package/tests/integration/code-group.test.ts +183 -0
  116. package/tests/integration/code-notation.test.ts +97 -0
  117. package/tests/integration/feed-utils.test.ts +13 -0
  118. package/tests/integration/github-alerts.test.ts +82 -0
  119. package/tests/integration/markdown-external-links.test.ts +103 -0
  120. package/tests/integration/normalize-vuepress-math.test.ts +149 -0
  121. package/tests/integration/reading-time-headings.test.ts +12 -14
  122. package/tests/integration/series-draft.test.ts +12 -5
  123. package/tests/integration/series.test.ts +93 -0
  124. package/tests/integration/sync-vuepress-book.test.ts +240 -0
  125. package/tests/integration/vuepress-containers.test.ts +107 -0
  126. package/tests/tooling/build-pagefind.test.ts +66 -0
  127. package/tests/tooling/new-post.test.ts +1 -1
  128. package/tests/unit/static-params.test.ts +166 -13
@@ -0,0 +1,178 @@
1
+ # Importing a VuePress 2 book
2
+
3
+ Amytis can host a VuePress 2 book natively. `bun run sync-vuepress-book`
4
+ copies the upstream `docs/` tree into a slug under `content/books/`, derives
5
+ Amytis's nested-section book TOC from the VuePress sidebar config, and
6
+ preserves any user-controlled fields you've added to the book's `index.mdx`.
7
+
8
+ The importer is idempotent — re-running mirrors the current state of the
9
+ source (including upstream deletions). You should *not* edit chapter
10
+ markdown files in the dest: they get overwritten on every sync. Customize
11
+ the book's metadata in `index.mdx` (preserved) or extend the source repo.
12
+
13
+ ---
14
+
15
+ ## Prerequisites
16
+
17
+ - The source must be a VuePress 2 project where the docs root contains a
18
+ `.vuepress/` directory with a `config.js` or `config.mjs`.
19
+ - `config.ts` is **not supported** — acorn (used to AST-extract the sidebar)
20
+ parses JS only. Compile to JS first (`tsc`, `bun build --no-bundle`, …) or
21
+ rename to `.mjs` if the config is pure ESM.
22
+ - The destination directory under `content/books/<slug>/` may already exist
23
+ with a partial `index.mdx`; user-controlled frontmatter is preserved.
24
+
25
+ ## Quick start
26
+
27
+ ```bash
28
+ bun run sync-vuepress-book \
29
+ --source /path/to/your-book/docs \
30
+ --dest content/books/your-book
31
+
32
+ # Positional shorthand:
33
+ bun run sync-vuepress-book /path/to/your-book/docs content/books/your-book
34
+
35
+ # Then rebuild + preview locally:
36
+ bun run build:dev
37
+ bun dev
38
+ ```
39
+
40
+ The script prints a one-line summary on completion:
41
+
42
+ ```
43
+ [sync-vuepress-book] Done. 74 markdown files, 104 asset files copied, 61 chapters mapped.
44
+ ```
45
+
46
+ It will also warn about anomalies it noticed in the sidebar — empty section
47
+ placeholders, sections with their own page-link header, dropped meta-nav
48
+ leaves (see [Conventions](#conventions) below), etc.
49
+
50
+ ## What the script does
51
+
52
+ 1. Locates `.vuepress/config.{js,mjs}` under `<source>`.
53
+ 2. AST-parses the file with acorn, walks the tree for the `sidebar:` array
54
+ wherever it lives (theme wrapper, plain export, etc.), and converts its
55
+ literals to a plain JS structure. Unsupported AST shapes throw — silent
56
+ drops would produce a half-correct TOC.
57
+ 3. Maps every VuePress sidebar item to one of:
58
+ - `{ title, id }` for a leaf (`{ text, link }` in VuePress).
59
+ - `{ section, items, collapsible? }` for a group (`{ text, children }`),
60
+ recursive — VuePress's two layers of nesting map 1:1.
61
+ - Mixed/unknown shapes are warned and skipped.
62
+ 4. Validates that every leaf's resolved chapter id has a real source file at
63
+ one of `<id>.md`, `<id>.mdx`, `<id>/README.md(x)`, or `<id>/index.md(x)`.
64
+ Throws and lists the missing files if any.
65
+ 5. **Mirrors** the source tree into `<dest>` (`fs.copyFileSync`):
66
+ - Copies every file except `.vuepress/`, `node_modules`, `.git`, and
67
+ dotfiles.
68
+ - Prunes any importer-managed dest file/dir whose path is no longer in
69
+ the source — re-running after an upstream rename or deletion is clean.
70
+ - Preserves `index.mdx` (regenerated separately, not mirrored) and any
71
+ dest dotfiles you added (`.gitkeep`, etc.).
72
+ 6. Rewrites `<dest>/index.mdx` with the new `chapters:` TOC, merging into
73
+ any existing frontmatter rather than replacing it. See
74
+ [User-controlled fields](#user-controlled-fields-in-indexmdx) below.
75
+
76
+ ## Conventions
77
+
78
+ - The sidebar leaf with id `contents` is dropped from the generated TOC —
79
+ it's a VuePress convention for a hand-written table-of-contents page, and
80
+ Amytis's book landing page already renders one. The `contents.md` file
81
+ itself is still copied so the dest layout matches upstream; it just
82
+ isn't reachable from the TOC.
83
+ - Sections with `collapsible: false` on the VuePress side keep that hint —
84
+ the Amytis sidebar honors it (forces the section open).
85
+ - A group whose VuePress entry has both `link` and `children` is treated as
86
+ a pure group; the group's own page link is dropped (warned).
87
+
88
+ ## User-controlled fields in `index.mdx`
89
+
90
+ The script forces `chapters:` to whatever the current sidebar produces, but
91
+ leaves the rest of the frontmatter alone if it's already populated:
92
+
93
+ | Field | Behavior |
94
+ | --- | --- |
95
+ | `title` | Preserved if set; else derived from the VuePress config's `title` |
96
+ | `excerpt` | Preserved |
97
+ | `date` | Preserved if set; else today |
98
+ | `coverImage` | Preserved |
99
+ | `featured` | Preserved (defaults to `false` on first sync) |
100
+ | `draft` | Preserved (defaults to `false` on first sync) |
101
+ | `authors` | Preserved |
102
+ | `latex` | Preserved — set to `true` for math-heavy books to enable KaTeX globally for the book |
103
+ | `showChapterExcerpt` | Preserved (defaults to `false`). Set to `true` if you want the chapter's `excerpt` rendered as a subtitle under the chapter title. The default suppresses it because most chapters open with their own lede paragraph that duplicates the excerpt. |
104
+ | `chapters` | **Always rewritten** from the sidebar |
105
+
106
+ The prose body below the frontmatter is also preserved, so you can write a
107
+ custom landing-page introduction and re-running the script won't blow it
108
+ away.
109
+
110
+ ## What about VuePress-specific content?
111
+
112
+ `MarkdownRenderer` quietly handles the syntax shapes the VuePress ecosystem
113
+ uses, so you don't have to rewrite chapters by hand:
114
+
115
+ - **`:::note` / `:::tip` / `:::warning` / `:::danger` / `:::info`** —
116
+ rewritten to Amytis's existing `<GithubAlert>` component. Custom titles
117
+ (e.g. `:::tip 智慧的疆界`) are preserved via `data-alert-title`.
118
+ - **Mermaid fences** — `` ```mermaid `` blocks render via the existing
119
+ `<Mermaid>` client component. The `compact` modifier dmla uses is harmless
120
+ (it lives in fence meta, not the language tag).
121
+ - **Inline `$$ ... $$` block math** — VuePress allows
122
+ `$$ \mathbf{A} = \begin{bmatrix}` openers and `\end{bmatrix} $$` closers
123
+ on the same line as the math body; `remark-math` requires `$$` to be on
124
+ its own line. A pre-processor (`normalizeVuepressBlockMath`) splits them
125
+ before parsing, preserving any list-item indent.
126
+ - **Inter-chapter `[X](other.md)` links** — `remark-book-chapter-links`
127
+ rewrites these to canonical `/books/<slug>/<chapter-id>` URLs. Targets
128
+ outside the book throw; targets not in the TOC (work-in-progress chapters
129
+ the author commented out of the sidebar) warn and pass through unrewritten.
130
+ - **CJK in math** — KaTeX's `unicodeTextInMathMode` warning is silenced via
131
+ `strict: 'ignore'`. Chinese-language math like `$输入$` or `$h_{隐藏状态}$`
132
+ renders without flooding the dev console.
133
+ - **Custom Vue components** — `<Swiper>` / `<Slide>` / `<ClientOnly>` /
134
+ `<GlobalTOC>` / `<HomeHero>` / `<ChatDemo>` get passive overrides so
135
+ React doesn't warn about unknown HTML tags. Children pass through where
136
+ reasonable (slides stack vertically); UI-component nulls render nothing.
137
+ - **Inline-styled `<img>` tags** — author-supplied `style` attributes
138
+ (typical for small social-media icons in author bios) are respected and
139
+ the image isn't pushed through `next/image` optimization.
140
+
141
+ ## Re-running
142
+
143
+ Running the sync again against the same source/dest is the common case:
144
+
145
+ ```bash
146
+ bun run sync-vuepress-book --source ... --dest ...
147
+ ```
148
+
149
+ It is **safe** and **idempotent** so long as you haven't hand-edited the
150
+ synced chapter `.md` files. The script:
151
+
152
+ - Overwrites every chapter file from source.
153
+ - Prunes importer-managed dest files whose path is not in the current source.
154
+ - Re-derives `chapters:` from the current sidebar.
155
+ - Preserves user-controlled `index.mdx` fields + body.
156
+
157
+ After re-running, run `bun run build:dev` to regenerate the image-optimization
158
+ output under `public/books/<slug>/` and the Pagefind search index.
159
+
160
+ ## Troubleshooting
161
+
162
+ | Symptom | Likely cause | Fix |
163
+ | --- | --- | --- |
164
+ | `[amytis] No VuePress config found …` | Source isn't a VuePress 2 docs root | Pass the parent of `.vuepress/`, e.g. `<repo>/docs`, not the repo root. |
165
+ | `[amytis] Found config.ts …` error | TS config not supported | Compile to JS first, or rename to `.mjs` if pure ESM. |
166
+ | `[amytis] N sidebar leaf chapters point to source files that do not exist` | Sidebar entry has no matching `.md` | Fix the sidebar in the VuePress config or write the missing file. |
167
+ | `Could not locate a sidebar: [...] property` | Sidebar uses an unsupported shape | The walker only handles plain literal arrays/objects; a sidebar built by a function call won't work. Extract to a literal array. |
168
+ | Chapter page 404s after sync | Chapter file moved/renamed upstream, but the dest dir still has the old file | This shouldn't happen now (mirror prunes deletions); if it does, rerun the sync and clear `.next` / `public/books/<slug>`. |
169
+ | Stale image after upstream rename | Pagefind / Next image cache | `bun run clean && bun run build:dev`. |
170
+
171
+ ## Related
172
+
173
+ - `scripts/sync-vuepress-book.ts` — the implementation.
174
+ - `docs/ARCHITECTURE.md` — book schema, route map, and the markdown
175
+ pipeline plugins listed above.
176
+ - `tests/integration/sync-vuepress-book.test.ts` — covers AST extraction,
177
+ mirror semantics, folder-index links, TS-config rejection, dotfile
178
+ preservation, and the `contents` skip.
package/eslint.config.mjs CHANGED
@@ -1,13 +1,10 @@
1
1
  import { defineConfig, globalIgnores } from "eslint/config";
2
- import nextVitals from "eslint-config-next/core-web-vitals";
3
- import nextTs from "eslint-config-next/typescript";
2
+ import nextPlugin from "@next/eslint-plugin-next";
3
+ import reactHooksPlugin from "eslint-plugin-react-hooks";
4
+ import tseslint from "typescript-eslint";
4
5
 
5
6
  const eslintConfig = defineConfig([
6
- ...nextVitals,
7
- ...nextTs,
8
- // Override default ignores of eslint-config-next.
9
7
  globalIgnores([
10
- // Default ignores of eslint-config-next:
11
8
  ".next/**",
12
9
  "out/**",
13
10
  "build/**",
@@ -16,7 +13,24 @@ const eslintConfig = defineConfig([
16
13
  "public/pagefind/**",
17
14
  // Package compiled output — not authored code
18
15
  "packages/*/dist/**",
16
+ // Local Python renderer virtualenv
17
+ ".venv-rst/**",
19
18
  ]),
19
+
20
+ ...tseslint.configs.recommended,
21
+
22
+ {
23
+ files: ["**/*.{js,jsx,mjs,ts,tsx,mts,cts}"],
24
+ plugins: {
25
+ "@next/next": nextPlugin,
26
+ "react-hooks": reactHooksPlugin,
27
+ },
28
+ rules: {
29
+ ...nextPlugin.configs.recommended.rules,
30
+ ...nextPlugin.configs["core-web-vitals"].rules,
31
+ ...reactHooksPlugin.configs.recommended.rules,
32
+ },
33
+ },
20
34
  ]);
21
35
 
22
36
  export default eslintConfig;
package/next.config.ts CHANGED
@@ -15,8 +15,8 @@ const nextConfig: NextConfig = {
15
15
  output: "export",
16
16
  images: {
17
17
  loader: "custom",
18
- imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
19
- deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
18
+ imageSizes: [64, 128, 256],
19
+ deviceSizes: [640, 1200, 1920],
20
20
  },
21
21
  transpilePackages: ["next-image-export-optimizer"],
22
22
  env: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hutusi/amytis",
3
- "version": "1.14.0",
3
+ "version": "1.16.0",
4
4
  "description": "A high-performance digital garden and blog engine with Next.js 16 and Tailwind CSS v4",
5
5
  "repository": {
6
6
  "type": "git",
@@ -11,14 +11,18 @@
11
11
  },
12
12
  "homepage": "https://github.com/hutusi/amytis#readme",
13
13
  "private": false,
14
- "packageManager": "bun@1.3.4",
14
+ "packageManager": "bun@1.3.11",
15
15
  "scripts": {
16
- "dev": "next dev",
17
- "build": "bun scripts/copy-assets.ts && bun run build:graph && next build && next-image-export-optimizer && pagefind --site out",
18
- "build:dev": "bun scripts/copy-assets.ts && bun run build:graph && next build && pagefind --site out --output-path public/pagefind",
16
+ "dev": "bun scripts/run-with-rst-python.ts next dev",
17
+ "build": "bun scripts/copy-assets.ts && bun run build:graph && bun scripts/run-with-rst-python.ts next build && next-image-export-optimizer && bun run build:search",
18
+ "build:dev": "bun scripts/copy-assets.ts && bun run build:graph && bun scripts/run-with-rst-python.ts next build && bun run build:search:dev",
19
19
  "build:graph": "NODE_ENV=production bun scripts/generate-knowledge-graph.ts",
20
+ "build:search": "bun scripts/build-pagefind.ts --site out --output-path out/pagefind",
21
+ "build:search:dev": "bun scripts/build-pagefind.ts --site out --output-path public/pagefind",
22
+ "build:search:cli": "pagefind --site out",
23
+ "build:search:cli:dev": "pagefind --site out --output-path public/pagefind",
20
24
  "validate": "bun run lint && bun run test && bun run build:dev",
21
- "clean": "rm -rf .next out public/posts public/books public/flows",
25
+ "clean": "rm -rf .next out public/posts public/books public/flows public/images/nextImageExportOptimizer",
22
26
  "new": "bun scripts/new-post.ts",
23
27
  "new-weekly": "bun scripts/new-post.ts --series ai-nexus-weekly --md --folder --prefix weekly",
24
28
  "new-series": "bun scripts/new-series.ts",
@@ -30,6 +34,7 @@
30
34
  "import-obsidian": "bun scripts/import-obsidian.ts",
31
35
  "import-book": "bun scripts/import-book.ts",
32
36
  "sync-book": "bun scripts/sync-book-chapters.ts",
37
+ "sync-vuepress-book": "bun scripts/sync-vuepress-book.ts",
33
38
  "series-draft": "bun scripts/series-draft.ts",
34
39
  "add-series-redirects": "bun scripts/add-series-redirects.ts",
35
40
  "deploy": "bun scripts/deploy.ts",
@@ -43,52 +48,75 @@
43
48
  },
44
49
  "dependencies": {
45
50
  "@giscus/react": "^3.1.0",
51
+ "@shikijs/transformers": "^4.1.0",
46
52
  "@tailwindcss/typography": "^0.5.19",
47
53
  "d3": "^7.9.0",
48
54
  "github-slugger": "^2.0.0",
49
55
  "gray-matter": "^4.0.3",
56
+ "hast-util-to-html": "^9.0.5",
50
57
  "image-size": "^2.0.2",
51
- "katex": "^0.16.42",
52
- "mermaid": "^11.13.0",
53
- "next": "16.2.1",
58
+ "katex": "^0.16.47",
59
+ "mermaid": "^11.15.0",
60
+ "next": "16.2.6",
54
61
  "next-image-export-optimizer": "^1.20.1",
55
62
  "next-themes": "^0.4.6",
56
- "react": "19.2.4",
57
- "react-dom": "19.2.4",
63
+ "react": "19.2.6",
64
+ "react-dom": "19.2.6",
58
65
  "react-icons": "^5.6.0",
59
66
  "react-markdown": "^10.1.0",
60
- "react-syntax-highlighter": "^16.1.1",
61
67
  "rehype-katex": "^7.0.1",
68
+ "rehype-parse": "^9.0.1",
62
69
  "rehype-raw": "^7.0.0",
63
70
  "rehype-slug": "^6.0.0",
64
71
  "rehype-stringify": "^10.0.1",
72
+ "remark-directive": "^4.0.0",
65
73
  "remark-gfm": "^4.0.1",
66
74
  "remark-math": "^6.0.0",
67
75
  "remark-parse": "^11.0.0",
68
76
  "remark-rehype": "^11.1.2",
77
+ "sanitize-html": "^2.17.4",
78
+ "shiki": "^4.1.0",
69
79
  "unified": "^11.0.5",
70
80
  "unist-util-visit": "^5.1.0",
71
- "zod": "^4.3.6"
81
+ "zod": "^4.4.3"
72
82
  },
73
83
  "devDependencies": {
74
- "@playwright/test": "^1.58.2",
75
- "@tailwindcss/postcss": "^4.2.2",
76
- "@types/bun": "^1.3.11",
84
+ "@iconify-json/logos": "^1.2.11",
85
+ "@iconify-json/vscode-icons": "^1.2.52",
86
+ "@next/eslint-plugin-next": "^16.2.6",
87
+ "@playwright/test": "^1.60.0",
88
+ "@tailwindcss/postcss": "^4.3.0",
89
+ "@types/bun": "^1.3.14",
77
90
  "@types/d3": "^7.4.3",
78
91
  "@types/hast": "^3.0.4",
79
92
  "@types/image-size": "^0.8.0",
80
93
  "@types/mdast": "^4.0.4",
81
- "@types/node": "^24.12.0",
94
+ "@types/node": "^25.8.0",
82
95
  "@types/react": "^19.2.14",
83
96
  "@types/react-dom": "^19.2.3",
84
- "@types/react-syntax-highlighter": "^15.5.13",
97
+ "@types/sanitize-html": "^2.16.1",
98
+ "acorn": "^8.16.0",
85
99
  "babel-plugin-react-compiler": "1.0.0",
86
- "eslint": "^9.39.4",
87
- "eslint-config-next": "16.2.1",
88
- "pagefind": "^1.4.0",
89
- "pdf-to-img": "^5.0.0",
90
- "tailwindcss": "^4.2.2",
91
- "typescript": "^5.9.3"
100
+ "eslint": "^10.4.0",
101
+ "eslint-plugin-react-hooks": "^7.1.1",
102
+ "pagefind": "^1.5.2",
103
+ "pdf-to-img": "^6.1.0",
104
+ "tailwindcss": "^4.3.0",
105
+ "typescript": "^6.0.3",
106
+ "typescript-eslint": "^8.59.3"
107
+ },
108
+ "overrides": {
109
+ "@typescript-eslint/eslint-plugin": "^8.59.3",
110
+ "@typescript-eslint/parser": "^8.59.3",
111
+ "@typescript-eslint/project-service": "^8.59.3",
112
+ "@typescript-eslint/scope-manager": "^8.59.3",
113
+ "@typescript-eslint/tsconfig-utils": "^8.59.3",
114
+ "@typescript-eslint/type-utils": "^8.59.3",
115
+ "@typescript-eslint/types": "^8.59.3",
116
+ "@typescript-eslint/typescript-estree": "^8.59.3",
117
+ "@typescript-eslint/utils": "^8.59.3",
118
+ "@typescript-eslint/visitor-keys": "^8.59.3",
119
+ "typescript-eslint": "^8.59.3"
92
120
  },
93
121
  "ignoreScripts": [
94
122
  "sharp",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-amytis",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Create a new Amytis digital garden",
5
5
  "license": "MIT",
6
6
  "bin": { "create-amytis": "./dist/index.js" },
@@ -2,7 +2,7 @@ import { describe, test, expect, afterAll } from "bun:test";
2
2
  import * as fs from "fs";
3
3
  import * as os from "os";
4
4
  import * as path from "path";
5
- import { patchSiteConfig, patchPackageJson } from "./index";
5
+ import { buildExtractCommand, getArchiveMetadata, patchSiteConfig, patchPackageJson } from "./index";
6
6
 
7
7
  // Minimal site.config.ts that mirrors the real file's patchable fields.
8
8
  // Inner backticks and `${` are escaped so they appear literally in the string.
@@ -185,3 +185,45 @@ describe("patchPackageJson", () => {
185
185
  expect(() => patchPackageJson(dir, "my-garden")).not.toThrow();
186
186
  });
187
187
  });
188
+
189
+ describe("getArchiveMetadata", () => {
190
+ test("uses zip archives on Windows", () => {
191
+ const archive = getArchiveMetadata("v1.13.0", "win32");
192
+ expect(archive.kind).toBe("zip");
193
+ expect(archive.filename).toBe("amytis-v1.13.0.zip");
194
+ expect(archive.url).toBe("https://github.com/hutusi/amytis/archive/refs/tags/v1.13.0.zip");
195
+ });
196
+
197
+ test("uses tar.gz archives on non-Windows platforms", () => {
198
+ const archive = getArchiveMetadata("v1.13.0", "darwin");
199
+ expect(archive.kind).toBe("tar.gz");
200
+ expect(archive.filename).toBe("amytis-v1.13.0.tar.gz");
201
+ expect(archive.url).toBe("https://github.com/hutusi/amytis/archive/refs/tags/v1.13.0.tar.gz");
202
+ });
203
+ });
204
+
205
+ describe("buildExtractCommand", () => {
206
+ test("builds the PowerShell Expand-Archive command on Windows", () => {
207
+ const command = buildExtractCommand("C:\\tmp\\amytis-v1.13.0.zip", "C:\\tmp\\my-garden.__tmp__", "zip", "win32");
208
+ expect(command.command).toBe("powershell.exe");
209
+ expect(command.args).toEqual([
210
+ "-NoProfile",
211
+ "-NonInteractive",
212
+ "-ExecutionPolicy",
213
+ "Bypass",
214
+ "-Command",
215
+ "Expand-Archive",
216
+ "-LiteralPath",
217
+ "C:\\tmp\\amytis-v1.13.0.zip",
218
+ "-DestinationPath",
219
+ "C:\\tmp\\my-garden.__tmp__",
220
+ "-Force",
221
+ ]);
222
+ });
223
+
224
+ test("builds the tar extraction command on non-Windows platforms", () => {
225
+ const command = buildExtractCommand("/tmp/amytis-v1.13.0.tar.gz", "/tmp/my-garden.__tmp__", "tar.gz", "darwin");
226
+ expect(command.command).toBe("tar");
227
+ expect(command.args).toEqual(["xzf", "/tmp/amytis-v1.13.0.tar.gz", "-C", "/tmp/my-garden.__tmp__"]);
228
+ });
229
+ });
@@ -4,7 +4,7 @@ import * as fs from "fs";
4
4
  import * as path from "path";
5
5
  import * as https from "https";
6
6
  import * as readline from "readline";
7
- import { execSync } from "child_process";
7
+ import { execFileSync, execSync } from "child_process";
8
8
 
9
9
  // ---------------------------------------------------------------------------
10
10
  // Helpers
@@ -77,11 +77,67 @@ function downloadFile(url: string, dest: string): Promise<void> {
77
77
  });
78
78
  }
79
79
 
80
- function extractTarball(tarPath: string, outDir: string): void {
80
+ export function getArchiveMetadata(tag: string, platform: NodeJS.Platform = process.platform): {
81
+ url: string;
82
+ filename: string;
83
+ kind: "zip" | "tar.gz";
84
+ } {
85
+ if (platform === "win32") {
86
+ return {
87
+ url: `https://github.com/hutusi/amytis/archive/refs/tags/${tag}.zip`,
88
+ filename: `amytis-${tag}.zip`,
89
+ kind: "zip",
90
+ };
91
+ }
92
+
93
+ return {
94
+ url: `https://github.com/hutusi/amytis/archive/refs/tags/${tag}.tar.gz`,
95
+ filename: `amytis-${tag}.tar.gz`,
96
+ kind: "tar.gz",
97
+ };
98
+ }
99
+
100
+ export function buildExtractCommand(
101
+ archivePath: string,
102
+ tmpDir: string,
103
+ kind: "zip" | "tar.gz",
104
+ platform: NodeJS.Platform = process.platform,
105
+ ): { command: string; args: string[] } {
106
+ if (kind === "zip") {
107
+ if (platform !== "win32") {
108
+ throw new Error("ZIP extraction is only supported on Windows in create-amytis");
109
+ }
110
+
111
+ return {
112
+ command: "powershell.exe",
113
+ args: [
114
+ "-NoProfile",
115
+ "-NonInteractive",
116
+ "-ExecutionPolicy",
117
+ "Bypass",
118
+ "-Command",
119
+ "Expand-Archive",
120
+ "-LiteralPath",
121
+ archivePath,
122
+ "-DestinationPath",
123
+ tmpDir,
124
+ "-Force",
125
+ ],
126
+ };
127
+ }
128
+
129
+ return {
130
+ command: "tar",
131
+ args: ["xzf", archivePath, "-C", tmpDir],
132
+ };
133
+ }
134
+
135
+ function extractArchive(archivePath: string, outDir: string, kind: "zip" | "tar.gz", platform: NodeJS.Platform = process.platform): void {
81
136
  // Extract into a temp dir, then move the inner folder out
82
137
  const tmpDir = `${outDir}.__tmp__`;
83
138
  fs.mkdirSync(tmpDir, { recursive: true });
84
- execSync(`tar xzf "${tarPath}" -C "${tmpDir}"`);
139
+ const { command, args } = buildExtractCommand(archivePath, tmpDir, kind, platform);
140
+ execFileSync(command, args, { stdio: "inherit" });
85
141
 
86
142
  // The tarball unpacks to a single top-level dir like "amytis-1.2.0/"
87
143
  const entries = fs.readdirSync(tmpDir);
@@ -91,7 +147,7 @@ function extractTarball(tarPath: string, outDir: string): void {
91
147
  const innerDir = path.join(tmpDir, entries[0]);
92
148
  fs.renameSync(innerDir, outDir);
93
149
  fs.rmdirSync(tmpDir);
94
- fs.unlinkSync(tarPath);
150
+ fs.unlinkSync(archivePath);
95
151
  }
96
152
 
97
153
  // ---------------------------------------------------------------------------
@@ -180,14 +236,14 @@ async function main(): Promise<void> {
180
236
  console.log(` Found: ${tag}`);
181
237
 
182
238
  // 3. Download tarball
183
- const tarUrl = `https://github.com/hutusi/amytis/archive/refs/tags/${tag}.tar.gz`;
184
- const tarDest = path.join(process.cwd(), `amytis-${tag}.tar.gz`);
239
+ const archive = getArchiveMetadata(tag);
240
+ const archiveDest = path.join(process.cwd(), archive.filename);
185
241
  console.log("Downloading tarball...");
186
- await downloadFile(tarUrl, tarDest);
242
+ await downloadFile(archive.url, archiveDest);
187
243
 
188
244
  // 4. Extract
189
245
  console.log("Extracting...");
190
- extractTarball(tarDest, targetDir);
246
+ extractArchive(archiveDest, targetDir, archive.kind);
191
247
  console.log(` Scaffolded: ${targetDir}`);
192
248
 
193
249
  // 5-6. Prompt for site metadata
@@ -1,75 +1,16 @@
1
1
  {
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.jpg": "vdBm72ev5ETPM5H2CDK6tqph5t8N5nTCSApBJp2lW6U=",
72
- "images/wechat-qr.jpg": "DNIzz0Wcl8WP0h-jHHgZ9LEvf3ZKOXHgxlvpw3gK2ME=",
73
- "posts/02-routing-mastery/assets/m-p-model.png": "fDmvlEkZnE-UCvPK4gmDkJD7SU8coOTl4iw5hpsmcWI=",
74
- "posts/images/vibrant-waves.jpg": "YogfFJOm4PQV1g3iMRpCL1pKtjPXpMeJDFf6NTILcBQ="
2
+ "images/amytis-screenshot.jpg": "rWj-Nh3prlVpvj8efQnsSHJTSOOVzgz9PGnQRj98jXc=",
3
+ "images/antelope-canyon.jpg": "sub7CbOTPj1Vred2Ow9yJKMqjbP9+0nkjmmaaa5Di2k=",
4
+ "images/avatar.jpg": "4c7EI6nQ6UHOj9drx0D42heSmiYWTt0Gd3Nsri3BQeo=",
5
+ "images/cappadocia.jpg": "eDzJe+p3qkAP83ATa7x0m0od4LxwkZltWsVQ2s9rgDI=",
6
+ "images/flowers.jpg": "oxwbVQa8o5AcEu-vRbHj9o64BTZwCFxPOSA7D53n7tI=",
7
+ "images/galaxy.jpg": "yDGttkksBSUhLDWlVL2BFGg1al1+GHy3BXcEVp07bys=",
8
+ "images/lake.jpg": "NEaTNMMXdrgHLMU7CDEWEVwguVdv1E4y5FN8CMHqTvg=",
9
+ "images/mountains.jpg": "0YIE1wGkreZ1MzM8zGnXCQ9I-IcmA+zO52-dtfL4X94=",
10
+ "images/screenshot.png": "zuBVQ-P-dCmcM9tYPdDvixwAHo7rGTZQE1y+X4o2pJ4=",
11
+ "images/vibrant-waves.jpg": "uWywQzN1RbusnZFato8mb7pqvCUkAr4us50HU6rFYUg=",
12
+ "images/wechat-qr.jpg": "Vhxz8S8TDfXZUjB3M5o5jvis3hu2n8xJEdW3bPjtd+A=",
13
+ "posts/02-routing-mastery/assets/m-p-model.png": "LZQHFy9SfWdBHatT9DwrARlD1sRqLT2blB573YyTv-U=",
14
+ "posts/images/vibrant-waves.jpg": "J+C7EolOmfP3fHl-22UoIx-h0hImPk9iEhL+66I60P4=",
15
+ "posts/welcome-to-amytis/images/vibrant-waves.jpg": "gAUaQXipxs2k7jJ6OoyIJ1+hj9HjM5QeuUuWTQoByps="
75
16
  }