@hutusi/amytis 1.14.0 → 1.15.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 (63) hide show
  1. package/.github/workflows/ci.yml +1 -1
  2. package/.github/workflows/publish.yml +2 -2
  3. package/CHANGELOG.md +16 -0
  4. package/README.md +33 -1
  5. package/README.zh.md +33 -1
  6. package/TODO.md +10 -0
  7. package/bun.lock +69 -41
  8. package/content/series/rst-legacy/deeper-notes/images/test.svg +4 -0
  9. package/content/series/rst-legacy/deeper-notes/index.rst +15 -0
  10. package/content/series/rst-legacy/getting-started.rst +24 -0
  11. package/content/series/rst-legacy/index.rst +9 -0
  12. package/content/series/rst-readme/README.rst +9 -0
  13. package/content/series/rst-readme/readme-index-post.rst +10 -0
  14. package/content/series/rst-toctree/first-post.rst +6 -0
  15. package/content/series/rst-toctree/index.rst +10 -0
  16. package/content/series/rst-toctree/second-post.rst +6 -0
  17. package/content/series/rst-toctree-precedence/first-post.rst +6 -0
  18. package/content/series/rst-toctree-precedence/index.rst +12 -0
  19. package/content/series/rst-toctree-precedence/second-post.rst +6 -0
  20. package/docs/ARCHITECTURE.md +22 -3
  21. package/docs/CONTRIBUTING.md +11 -0
  22. package/eslint.config.mjs +2 -0
  23. package/next.config.ts +2 -2
  24. package/package.json +22 -16
  25. package/packages/create-amytis/package.json +1 -1
  26. package/packages/create-amytis/src/index.test.ts +43 -1
  27. package/packages/create-amytis/src/index.ts +64 -8
  28. package/public/next-image-export-optimizer-hashes.json +14 -73
  29. package/scripts/build-pagefind.ts +172 -0
  30. package/scripts/copy-assets.ts +246 -56
  31. package/scripts/generate-knowledge-graph.ts +2 -1
  32. package/scripts/render-rst.py +719 -0
  33. package/scripts/run-with-rst-python.ts +42 -0
  34. package/src/app/[slug]/[postSlug]/page.tsx +20 -10
  35. package/src/app/[slug]/page/[page]/page.tsx +15 -0
  36. package/src/app/globals.css +165 -0
  37. package/src/app/series/[slug]/page/[page]/page.tsx +74 -6
  38. package/src/app/series/[slug]/page.tsx +11 -13
  39. package/src/app/series/page.tsx +3 -3
  40. package/src/components/AuthorCard.tsx +25 -16
  41. package/src/components/CoverImage.tsx +5 -2
  42. package/src/components/MarkdownRenderer.test.tsx +16 -0
  43. package/src/components/MarkdownRenderer.tsx +4 -1
  44. package/src/components/RstRenderer.test.tsx +93 -0
  45. package/src/components/RstRenderer.tsx +122 -0
  46. package/src/layouts/PostLayout.tsx +5 -1
  47. package/src/layouts/SimpleLayout.tsx +10 -3
  48. package/src/lib/image-utils.test.ts +19 -0
  49. package/src/lib/image-utils.ts +11 -0
  50. package/src/lib/markdown.test.ts +140 -2
  51. package/src/lib/markdown.ts +731 -210
  52. package/src/lib/rehype-image-metadata.ts +2 -2
  53. package/src/lib/rst-renderer.test.ts +355 -0
  54. package/src/lib/rst-renderer.ts +617 -0
  55. package/src/lib/rst.test.ts +140 -0
  56. package/src/lib/rst.ts +470 -0
  57. package/src/lib/series-redirects.ts +42 -0
  58. package/tests/integration/feed-utils.test.ts +13 -0
  59. package/tests/integration/reading-time-headings.test.ts +5 -9
  60. package/tests/integration/series-draft.test.ts +16 -2
  61. package/tests/integration/series.test.ts +93 -0
  62. package/tests/tooling/build-pagefind.test.ts +66 -0
  63. package/tests/unit/static-params.test.ts +140 -0
@@ -0,0 +1,24 @@
1
+ Getting Started With rST
2
+ ========================
3
+
4
+ :date: 2026-01-03
5
+ :excerpt: A simple rST-based post inside a legacy series.
6
+ :category: Legacy
7
+ :tags: rst, migration
8
+ :authors: John Hu
9
+ :redirectFrom: /posts/getting-started-rst
10
+
11
+ Intro paragraph with an inline link to `Amytis <https://github.com/hutusi/amytis>`_.
12
+
13
+ Overview
14
+ --------
15
+
16
+ - Keep the routing layer unchanged.
17
+ - Parse the source format at the series boundary.
18
+
19
+ Code Sample
20
+ ~~~~~~~~~~~
21
+
22
+ .. code-block:: ts
23
+
24
+ export const feature = "rst";
@@ -0,0 +1,9 @@
1
+ Rst Legacy Series
2
+ =================
3
+
4
+ :excerpt: Legacy notes imported from reStructuredText.
5
+ :authors: John Hu
6
+ :sort: manual
7
+ :posts: getting-started, deeper-notes
8
+
9
+ This is a small legacy series used to validate series-scoped rST support.
@@ -0,0 +1,9 @@
1
+ Rst README Series
2
+ =================
3
+
4
+ :excerpt: Legacy series metadata loaded from README.rst.
5
+ :authors: John Hu
6
+ :sort: manual
7
+ :posts: readme-index-post
8
+
9
+ This series uses README.rst as its series index file.
@@ -0,0 +1,10 @@
1
+ README Index Post
2
+ =================
3
+
4
+ :date: 2026-01-09
5
+ :excerpt: Post inside a series whose metadata is loaded from README.rst.
6
+ :category: Legacy
7
+ :tags: rst, readme
8
+ :authors: John Hu
9
+
10
+ Content for the README-indexed series.
@@ -0,0 +1,6 @@
1
+ First Post
2
+ ==========
3
+
4
+ :date: 2024-01-01
5
+
6
+ This post appears second in the toctree order.
@@ -0,0 +1,10 @@
1
+ Rst Toctree Series
2
+ ==================
3
+
4
+ :excerpt: Legacy series order derived from toctree.
5
+
6
+ .. toctree::
7
+ :maxdepth: 1
8
+
9
+ second-post
10
+ first-post
@@ -0,0 +1,6 @@
1
+ Second Post
2
+ ===========
3
+
4
+ :date: 2024-01-02
5
+
6
+ This post appears first in the toctree order.
@@ -0,0 +1,6 @@
1
+ First Post
2
+ ==========
3
+
4
+ :date: 2024-01-01
5
+
6
+ This post should remain first because explicit posts metadata wins.
@@ -0,0 +1,12 @@
1
+ Rst Toctree Precedence Series
2
+ =============================
3
+
4
+ :excerpt: Explicit posts metadata should override toctree order.
5
+ :sort: manual
6
+ :posts: first-post, second-post
7
+
8
+ .. toctree::
9
+ :maxdepth: 1
10
+
11
+ second-post
12
+ first-post
@@ -0,0 +1,6 @@
1
+ Second Post
2
+ ===========
3
+
4
+ :date: 2024-01-02
5
+
6
+ This post should remain second because explicit posts metadata wins.
@@ -1,20 +1,21 @@
1
1
  # Architecture Overview
2
2
 
3
- Amytis is a static-export-first Next.js 16 App Router project for Markdown/MDX publishing across posts, series, books, flows, and notes.
3
+ Amytis is a static-export-first Next.js 16 App Router project for Markdown/MDX publishing across posts, series, books, flows, and notes, with optional series-scoped rST support for legacy content.
4
4
 
5
5
  ## Core Stack
6
6
 
7
- - Framework: Next.js 16.1.6 + React 19
7
+ - Framework: Next.js 16.2.1 + React 19
8
8
  - Runtime/tooling: Bun
9
9
  - Styling: Tailwind CSS v4 + CSS variables + `next-themes`
10
10
  - Content parsing: `gray-matter` + Zod validation in `src/lib/markdown.ts`
11
+ - rST rendering: Python `docutils` bridge in `scripts/render-rst.py` plus normalization in `src/lib/rst-renderer.ts`
11
12
  - Search: Pagefind (`/pagefind/pagefind.js` loaded at runtime)
12
13
  - Tests: Bun test suites in `src/` and `tests/`
13
14
 
14
15
  ## Content Model
15
16
 
16
17
  - `content/posts/`: standalone posts (`.md/.mdx`) and folder posts (`index.mdx`)
17
- - `content/series/<slug>/`: series metadata (`index.mdx`) + series posts
18
+ - `content/series/<slug>/`: series metadata (`index.mdx` / `index.md` / `README.mdx` / `README.md` / `index.rst` / `README.rst`) + series posts in the same format
18
19
  - `content/books/<slug>/`: book metadata + chapter files
19
20
  - `content/flows/YYYY/MM/DD.(md|mdx)`: daily flow entries
20
21
  - `content/notes/`: evergreen notes
@@ -27,6 +28,8 @@ Amytis is a static-export-first Next.js 16 App Router project for Markdown/MDX p
27
28
  3. Draft/future filtering and sorting are applied (based on `site.config.ts`).
28
29
  4. Route files consume typed helpers (`getAllPosts`, `getBookData`, `getAllFlows`, `getAllNotes`, etc.).
29
30
  5. `generateStaticParams()` precomputes dynamic routes for static export.
31
+ 6. Series content format is inferred from the series index file; ambiguous or mixed-format series fail fast during content loading.
32
+ 7. When `docutils` is available, rST files are rendered to HTML through the Python bridge; if the Python runtime is unavailable, Amytis falls back to the lightweight built-in rST compatibility path.
30
33
 
31
34
  ## Route Map (App Router)
32
35
 
@@ -75,10 +78,16 @@ src/app/
75
78
  ## URL Routing Rules
76
79
 
77
80
  - `next.config.ts` sets `output: "export"` and `trailingSlash: true`.
81
+ - Series format is inferred from the index file:
82
+ - Markdown series: `index.md`, `index.mdx`, `README.md`, or `README.mdx`
83
+ - rST series: `index.rst` or `README.rst`
84
+ - A series may not mix Markdown and rST content files; ambiguous or mixed layouts are treated as build errors.
78
85
  - Post URLs use `getPostUrl()` in `src/lib/urls.ts`:
79
86
  - Default: `/<posts.basePath>/<post.slug>` (basePath defaults to `posts`)
80
87
  - Series auto path: `/<series.slug>/<post.slug>` when `series.autoPaths` is enabled
81
88
  - Series override: `/<series.customPaths[seriesSlug]>/<post.slug>`
89
+ - Legacy aliases declared in frontmatter `redirectFrom` are emitted as static redirect pages, so old URLs can continue resolving after a rename or path migration.
90
+ - Dynamic route handlers validate whether a request is canonical or legacy, then either render the content or return `RedirectPage`.
82
91
  - Dynamic route params should return raw segment values from `generateStaticParams()` (do not pre-encode values).
83
92
  - Links should always target concrete paths, not route placeholders such as `/posts/[slug]`.
84
93
  - When moving series posts off the default posts path, `scripts/add-series-redirects.ts` updates frontmatter `redirectFrom` entries so static redirect pages can be generated.
@@ -99,6 +108,16 @@ src/app/
99
108
  - Notes: `getAllNotes`, `getNoteBySlug`, `getNotesByTag`
100
109
  - Discovery: `buildSlugRegistry`, `getBacklinks`, `getAllTags`, `getAllAuthors`
101
110
 
111
+ ## rST Notes
112
+
113
+ - Full-fidelity rST rendering depends on a Python environment with `docutils` (and ideally `pygments`) available.
114
+ - `src/lib/rst-renderer.ts` uses `AMYTIS_RST_PYTHON` when set; otherwise it falls back to `python3`.
115
+ - Top-of-document docinfo is parsed into Amytis metadata, but it is stripped from rendered article HTML so blog-style posts do not show duplicate author/version blocks above the content.
116
+ - Supported legacy roles are normalized or degraded intentionally:
117
+ - `:doc:` resolves to local site URLs when the target exists in the imported content tree
118
+ - `:ref:` / `:numref:` prefer local anchors
119
+ - unresolved legacy roles degrade to readable inline HTML instead of docutils system-message blocks
120
+
102
121
  ## Build Pipeline
103
122
 
104
123
  1. `bun scripts/copy-assets.ts`: copy co-located media into `public/`
@@ -47,6 +47,17 @@ bun run new-flow
47
47
  bun run new-series "My Series Name"
48
48
  ```
49
49
 
50
+ Series are format-scoped:
51
+
52
+ - Markdown series use `content/series/<slug>/index.mdx` or `index.md`
53
+ - Markdown series may also use `README.mdx` or `README.md`
54
+ - rST series use `content/series/<slug>/index.rst` or `README.rst`
55
+ - Child posts inside the series must use the same format as the index file
56
+ - Mixed Markdown and rST files in one series are treated as build errors
57
+ - For full-fidelity rST rendering, install `docutils` (and `pygments` for code highlighting) in a Python environment available to the project
58
+ - If needed, set `AMYTIS_RST_PYTHON=/absolute/path/to/python` so Amytis uses a specific interpreter
59
+ - Without Python/docutils, Amytis falls back to a lightweight compatibility parser; that keeps loading/building working, but some legacy rST constructs will render with lower fidelity
60
+
50
61
  ### Creating Books
51
62
 
52
63
  Books are manually structured in `content/books/`.
package/eslint.config.mjs CHANGED
@@ -16,6 +16,8 @@ const eslintConfig = defineConfig([
16
16
  "public/pagefind/**",
17
17
  // Package compiled output — not authored code
18
18
  "packages/*/dist/**",
19
+ // Local Python renderer virtualenv
20
+ ".venv-rst/**",
19
21
  ]),
20
22
  ]);
21
23
 
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.15.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",
@@ -48,13 +52,13 @@
48
52
  "github-slugger": "^2.0.0",
49
53
  "gray-matter": "^4.0.3",
50
54
  "image-size": "^2.0.2",
51
- "katex": "^0.16.42",
52
- "mermaid": "^11.13.0",
53
- "next": "16.2.1",
55
+ "katex": "^0.16.45",
56
+ "mermaid": "^11.14.0",
57
+ "next": "16.2.3",
54
58
  "next-image-export-optimizer": "^1.20.1",
55
59
  "next-themes": "^0.4.6",
56
- "react": "19.2.4",
57
- "react-dom": "19.2.4",
60
+ "react": "19.2.5",
61
+ "react-dom": "19.2.5",
58
62
  "react-icons": "^5.6.0",
59
63
  "react-markdown": "^10.1.0",
60
64
  "react-syntax-highlighter": "^16.1.1",
@@ -66,26 +70,28 @@
66
70
  "remark-math": "^6.0.0",
67
71
  "remark-parse": "^11.0.0",
68
72
  "remark-rehype": "^11.1.2",
73
+ "sanitize-html": "^2.17.2",
69
74
  "unified": "^11.0.5",
70
75
  "unist-util-visit": "^5.1.0",
71
76
  "zod": "^4.3.6"
72
77
  },
73
78
  "devDependencies": {
74
- "@playwright/test": "^1.58.2",
79
+ "@playwright/test": "^1.59.1",
75
80
  "@tailwindcss/postcss": "^4.2.2",
76
- "@types/bun": "^1.3.11",
81
+ "@types/bun": "^1.3.12",
77
82
  "@types/d3": "^7.4.3",
78
83
  "@types/hast": "^3.0.4",
79
84
  "@types/image-size": "^0.8.0",
80
85
  "@types/mdast": "^4.0.4",
81
- "@types/node": "^24.12.0",
86
+ "@types/node": "^24.12.2",
82
87
  "@types/react": "^19.2.14",
83
88
  "@types/react-dom": "^19.2.3",
84
89
  "@types/react-syntax-highlighter": "^15.5.13",
90
+ "@types/sanitize-html": "^2.16.1",
85
91
  "babel-plugin-react-compiler": "1.0.0",
86
92
  "eslint": "^9.39.4",
87
- "eslint-config-next": "16.2.1",
88
- "pagefind": "^1.4.0",
93
+ "eslint-config-next": "16.2.3",
94
+ "pagefind": "^1.5.0",
89
95
  "pdf-to-img": "^5.0.0",
90
96
  "tailwindcss": "^4.2.2",
91
97
  "typescript": "^5.9.3"
@@ -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
  }