@hutusi/amytis 1.13.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.
- package/.github/workflows/ci.yml +1 -1
- package/.github/workflows/publish.yml +2 -2
- package/CHANGELOG.md +32 -0
- package/GEMINI.md +9 -1
- package/README.md +36 -2
- package/README.zh.md +36 -2
- package/TODO.md +10 -0
- package/bun.lock +123 -91
- package/content/flows/2026/03/05.md +1 -0
- package/content/flows/2026/03/07.md +2 -0
- package/content/series/modern-web-dev/index.mdx +4 -2
- package/content/series/rst-legacy/deeper-notes/images/test.svg +4 -0
- package/content/series/rst-legacy/deeper-notes/index.rst +15 -0
- package/content/series/rst-legacy/getting-started.rst +24 -0
- package/content/series/rst-legacy/index.rst +9 -0
- package/content/series/rst-readme/README.rst +9 -0
- package/content/series/rst-readme/readme-index-post.rst +10 -0
- package/content/series/rst-toctree/first-post.rst +6 -0
- package/content/series/rst-toctree/index.rst +10 -0
- package/content/series/rst-toctree/second-post.rst +6 -0
- package/content/series/rst-toctree-precedence/first-post.rst +6 -0
- package/content/series/rst-toctree-precedence/index.rst +12 -0
- package/content/series/rst-toctree-precedence/second-post.rst +6 -0
- package/docs/ARCHITECTURE.md +30 -4
- package/docs/CONTRIBUTING.md +11 -0
- package/docs/DIGITAL_GARDEN.md +22 -1
- package/eslint.config.mjs +2 -0
- package/next.config.ts +2 -2
- package/package.json +27 -21
- package/packages/create-amytis/package.json +1 -1
- package/packages/create-amytis/src/index.test.ts +43 -1
- package/packages/create-amytis/src/index.ts +64 -8
- package/public/next-image-export-optimizer-hashes.json +14 -73
- package/scripts/build-pagefind.ts +172 -0
- package/scripts/copy-assets.ts +246 -56
- package/scripts/generate-knowledge-graph.ts +2 -1
- package/scripts/new-flow.ts +1 -0
- package/scripts/render-rst.py +719 -0
- package/scripts/run-with-rst-python.ts +42 -0
- package/src/app/[slug]/[postSlug]/page.tsx +20 -10
- package/src/app/[slug]/page/[page]/page.tsx +15 -0
- package/src/app/all.atom/route.ts +7 -0
- package/src/app/all.xml/route.ts +7 -0
- package/src/app/archive/page.tsx +7 -4
- package/src/app/feed.atom/route.ts +2 -57
- package/src/app/feed.xml/route.ts +2 -64
- package/src/app/flows/[year]/[month]/[day]/page.tsx +13 -0
- package/src/app/flows/feed.atom/route.ts +7 -0
- package/src/app/flows/feed.xml/route.ts +7 -0
- package/src/app/globals.css +165 -0
- package/src/app/page.tsx +1 -0
- package/src/app/posts/feed.atom/route.ts +9 -0
- package/src/app/posts/feed.xml/route.ts +9 -0
- package/src/app/series/[slug]/page/[page]/page.tsx +74 -6
- package/src/app/series/[slug]/page.tsx +11 -13
- package/src/app/series/page.tsx +3 -3
- package/src/components/AuthorCard.tsx +25 -16
- package/src/components/CoverImage.tsx +5 -2
- package/src/components/FlowCalendarSidebar.tsx +1 -1
- package/src/components/FlowContent.tsx +2 -1
- package/src/components/FlowTimelineEntry.tsx +7 -1
- package/src/components/Footer.tsx +1 -1
- package/src/components/MarkdownRenderer.test.tsx +22 -0
- package/src/components/MarkdownRenderer.tsx +22 -17
- package/src/components/Navbar.tsx +1 -1
- package/src/components/PostSidebar.tsx +1 -1
- package/src/components/RecentNotesSection.tsx +4 -0
- package/src/components/RstRenderer.test.tsx +93 -0
- package/src/components/RstRenderer.tsx +122 -0
- package/src/layouts/PostLayout.tsx +5 -1
- package/src/layouts/SimpleLayout.tsx +10 -3
- package/src/lib/feed-utils.ts +158 -18
- package/src/lib/image-utils.test.ts +19 -0
- package/src/lib/image-utils.ts +11 -0
- package/src/lib/markdown.test.ts +140 -2
- package/src/lib/markdown.ts +747 -214
- package/src/lib/rehype-image-metadata.ts +2 -2
- package/src/lib/rst-renderer.test.ts +355 -0
- package/src/lib/rst-renderer.ts +617 -0
- package/src/lib/rst.test.ts +140 -0
- package/src/lib/rst.ts +470 -0
- package/src/lib/series-redirects.ts +42 -0
- package/tests/e2e/navigation.test.ts +26 -0
- package/tests/integration/collections.test.ts +17 -2
- package/tests/integration/feed-utils.test.ts +65 -0
- package/tests/integration/flow-title.test.ts +53 -0
- package/tests/integration/reading-time-headings.test.ts +5 -9
- package/tests/integration/series-draft.test.ts +16 -2
- package/tests/integration/series.test.ts +93 -0
- package/tests/tooling/build-pagefind.test.ts +66 -0
- package/tests/unit/static-params.test.ts +140 -0
|
@@ -1,75 +1,16 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
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
|
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import * as pagefind from 'pagefind';
|
|
5
|
+
|
|
6
|
+
interface PagefindManifest {
|
|
7
|
+
version: string;
|
|
8
|
+
sitePath: string;
|
|
9
|
+
outputPath: string;
|
|
10
|
+
files: Record<string, string>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const PAGEFIND_MANIFEST_VERSION = '1';
|
|
14
|
+
const pagefindCacheDir = path.join(process.cwd(), '.cache', 'pagefind');
|
|
15
|
+
|
|
16
|
+
function parseArgs(argv: string[]): { sitePath: string; outputPath: string } {
|
|
17
|
+
let sitePath = 'out';
|
|
18
|
+
let outputPath = path.join('out', 'pagefind');
|
|
19
|
+
|
|
20
|
+
for (let i = 0; i < argv.length; i++) {
|
|
21
|
+
const arg = argv[i];
|
|
22
|
+
if (arg === '--site') {
|
|
23
|
+
sitePath = argv[++i] ?? sitePath;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (arg === '--output-path') {
|
|
27
|
+
outputPath = argv[++i] ?? outputPath;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return { sitePath, outputPath };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function walkHtmlFiles(rootDir: string): string[] {
|
|
36
|
+
const files: string[] = [];
|
|
37
|
+
|
|
38
|
+
const visit = (dir: string) => {
|
|
39
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
const fullPath = path.join(dir, entry.name);
|
|
42
|
+
if (entry.isDirectory()) {
|
|
43
|
+
visit(fullPath);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (entry.isFile() && entry.name.endsWith('.html')) {
|
|
47
|
+
files.push(fullPath);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
if (fs.existsSync(rootDir)) {
|
|
53
|
+
visit(rootDir);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return files.sort();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function collectHtmlFileHashes(sitePath: string): Record<string, string> {
|
|
60
|
+
const absoluteSitePath = path.resolve(sitePath);
|
|
61
|
+
const files = walkHtmlFiles(absoluteSitePath);
|
|
62
|
+
const hashes: Record<string, string> = {};
|
|
63
|
+
|
|
64
|
+
for (const filePath of files) {
|
|
65
|
+
const relativePath = path.relative(absoluteSitePath, filePath).replaceAll(path.sep, '/');
|
|
66
|
+
const content = fs.readFileSync(filePath);
|
|
67
|
+
hashes[relativePath] = createHash('sha1').update(content).digest('hex');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return hashes;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getPagefindManifestPath(sitePath: string, outputPath: string): string {
|
|
74
|
+
const cacheKey = createHash('sha1')
|
|
75
|
+
.update(`${path.resolve(sitePath)}::${path.resolve(outputPath)}`)
|
|
76
|
+
.digest('hex');
|
|
77
|
+
return path.join(pagefindCacheDir, `${cacheKey}.json`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function getPagefindManifestPathForTests(sitePath: string, outputPath: string): string {
|
|
81
|
+
return getPagefindManifestPath(sitePath, outputPath);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function loadManifest(sitePath: string, outputPath: string): PagefindManifest | null {
|
|
85
|
+
const manifestPath = getPagefindManifestPath(sitePath, outputPath);
|
|
86
|
+
if (!fs.existsSync(manifestPath)) return null;
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
return JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as PagefindManifest;
|
|
90
|
+
} catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function writeManifest(sitePath: string, outputPath: string, files: Record<string, string>): void {
|
|
96
|
+
const manifestPath = getPagefindManifestPath(sitePath, outputPath);
|
|
97
|
+
const manifest: PagefindManifest = {
|
|
98
|
+
version: PAGEFIND_MANIFEST_VERSION,
|
|
99
|
+
sitePath: path.resolve(sitePath),
|
|
100
|
+
outputPath: path.resolve(outputPath),
|
|
101
|
+
files,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
|
106
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest), 'utf8');
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.warn(`[pagefind] Failed to persist manifest at ${manifestPath}:`, error);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function shouldSkipPagefindBuild(sitePath: string, outputPath: string, currentFiles: Record<string, string>): boolean {
|
|
113
|
+
const manifest = loadManifest(sitePath, outputPath);
|
|
114
|
+
if (!manifest) return false;
|
|
115
|
+
if (manifest.version !== PAGEFIND_MANIFEST_VERSION) return false;
|
|
116
|
+
if (manifest.sitePath !== path.resolve(sitePath)) return false;
|
|
117
|
+
if (manifest.outputPath !== path.resolve(outputPath)) return false;
|
|
118
|
+
if (!fs.existsSync(outputPath)) return false;
|
|
119
|
+
|
|
120
|
+
const previousEntries = Object.entries(manifest.files).sort(([a], [b]) => a.localeCompare(b));
|
|
121
|
+
const currentEntries = Object.entries(currentFiles).sort(([a], [b]) => a.localeCompare(b));
|
|
122
|
+
if (previousEntries.length !== currentEntries.length) return false;
|
|
123
|
+
|
|
124
|
+
return previousEntries.every(([file, hash], index) => {
|
|
125
|
+
const [currentFile, currentHash] = currentEntries[index];
|
|
126
|
+
return file === currentFile && hash === currentHash;
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function buildPagefind(sitePath: string, outputPath: string): Promise<void> {
|
|
131
|
+
const currentFiles = collectHtmlFileHashes(sitePath);
|
|
132
|
+
|
|
133
|
+
if (shouldSkipPagefindBuild(sitePath, outputPath, currentFiles)) {
|
|
134
|
+
console.log(`[pagefind] Skipping rebuild; no HTML changes detected for ${sitePath}.`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
fs.rmSync(outputPath, { recursive: true, force: true });
|
|
139
|
+
|
|
140
|
+
const { index, errors } = await pagefind.createIndex();
|
|
141
|
+
if (errors.length > 0 || !index) {
|
|
142
|
+
throw new Error(`Failed to create Pagefind index: ${errors.join(', ') || 'unknown error'}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const addResult = await index.addDirectory({ path: sitePath });
|
|
147
|
+
if (addResult.errors.length > 0) {
|
|
148
|
+
throw new Error(`Failed to index ${sitePath}: ${addResult.errors.join(', ')}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const writeResult = await index.writeFiles({ outputPath });
|
|
152
|
+
if (writeResult.errors.length > 0) {
|
|
153
|
+
throw new Error(`Failed to write Pagefind output: ${writeResult.errors.join(', ')}`);
|
|
154
|
+
}
|
|
155
|
+
} finally {
|
|
156
|
+
await pagefind.close();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
writeManifest(sitePath, outputPath, currentFiles);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function main() {
|
|
163
|
+
const { sitePath, outputPath } = parseArgs(Bun.argv.slice(2));
|
|
164
|
+
await buildPagefind(sitePath, outputPath);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (import.meta.main) {
|
|
168
|
+
main().catch((error) => {
|
|
169
|
+
console.error('[pagefind] Build failed:', error);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
});
|
|
172
|
+
}
|
package/scripts/copy-assets.ts
CHANGED
|
@@ -9,45 +9,114 @@ const flowsSrcDir = path.join(process.cwd(), 'content', 'flows');
|
|
|
9
9
|
const destDir = path.join(process.cwd(), 'public', 'posts');
|
|
10
10
|
const booksDestDir = path.join(process.cwd(), 'public', 'books');
|
|
11
11
|
const flowsDestDir = path.join(process.cwd(), 'public', 'flows');
|
|
12
|
+
const optimizerDirName = 'nextImageExportOptimizer';
|
|
13
|
+
const generatedAssetDestinations = new Set<string>();
|
|
14
|
+
|
|
15
|
+
function copyFileOrThrow(srcPath: string, destPath: string, context: string) {
|
|
16
|
+
try {
|
|
17
|
+
fs.copyFileSync(srcPath, destPath);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20
|
+
throw new Error(`[copy-assets] Failed to copy ${context}: ${srcPath} -> ${destPath}: ${message}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function markGeneratedDestination(destPath: string) {
|
|
25
|
+
generatedAssetDestinations.add(path.resolve(destPath));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function shouldPreserveOptimizerDir(optimizerPath: string): boolean {
|
|
29
|
+
const optimizerParentPath = path.resolve(path.dirname(optimizerPath));
|
|
30
|
+
return [...generatedAssetDestinations].some((generatedDestination) =>
|
|
31
|
+
optimizerParentPath === generatedDestination ||
|
|
32
|
+
optimizerParentPath.startsWith(`${generatedDestination}${path.sep}`)
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function pruneOrphanedOptimizerDirs(rootDir: string) {
|
|
37
|
+
if (!fs.existsSync(rootDir)) return;
|
|
38
|
+
|
|
39
|
+
const entries = fs.readdirSync(rootDir, { withFileTypes: true });
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
const entryPath = path.join(rootDir, entry.name);
|
|
42
|
+
|
|
43
|
+
if (!entry.isDirectory()) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (entry.name === optimizerDirName) {
|
|
48
|
+
if (!shouldPreserveOptimizerDir(entryPath)) {
|
|
49
|
+
fs.rmSync(entryPath, { recursive: true, force: true });
|
|
50
|
+
}
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
pruneOrphanedOptimizerDirs(entryPath);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function resetGeneratedAssetDirs() {
|
|
59
|
+
generatedAssetDestinations.clear();
|
|
60
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
61
|
+
fs.mkdirSync(booksDestDir, { recursive: true });
|
|
62
|
+
fs.mkdirSync(flowsDestDir, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function shouldSkipSourceFile(name: string): boolean {
|
|
66
|
+
return name.endsWith('.md') || name.endsWith('.mdx') || name.endsWith('.rst');
|
|
67
|
+
}
|
|
12
68
|
|
|
13
|
-
function
|
|
69
|
+
function shouldCopyBasedOnMtimeAndSize(srcPath: string, destPath: string): boolean {
|
|
70
|
+
if (!fs.existsSync(destPath)) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const srcStat = fs.statSync(srcPath);
|
|
75
|
+
const destStat = fs.statSync(destPath);
|
|
76
|
+
return srcStat.mtimeMs > destStat.mtimeMs || srcStat.size !== destStat.size;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function syncRecursive(src: string, dest: string) {
|
|
14
80
|
if (!fs.existsSync(src)) return;
|
|
15
|
-
|
|
81
|
+
|
|
16
82
|
if (!fs.existsSync(dest)) {
|
|
17
83
|
fs.mkdirSync(dest, { recursive: true });
|
|
18
84
|
}
|
|
19
85
|
|
|
20
|
-
const
|
|
86
|
+
const srcEntries = fs.readdirSync(src, { withFileTypes: true });
|
|
87
|
+
const srcNames = new Set(srcEntries.map((entry) => entry.name));
|
|
21
88
|
|
|
22
|
-
for (const entry of
|
|
89
|
+
for (const entry of srcEntries) {
|
|
23
90
|
const srcPath = path.join(src, entry.name);
|
|
24
91
|
const destPath = path.join(dest, entry.name);
|
|
25
92
|
|
|
26
93
|
if (entry.isDirectory()) {
|
|
27
|
-
|
|
94
|
+
syncRecursive(srcPath, destPath);
|
|
28
95
|
} else {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (fs.existsSync(destPath)) {
|
|
33
|
-
const srcStat = fs.statSync(srcPath);
|
|
34
|
-
const destStat = fs.statSync(destPath);
|
|
35
|
-
if (srcStat.mtimeMs <= destStat.mtimeMs) {
|
|
36
|
-
shouldCopy = false;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
96
|
+
if (shouldSkipSourceFile(entry.name)) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
39
99
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// console.log(`Copied: ${entry.name} -> ${destPath}`);
|
|
43
|
-
}
|
|
100
|
+
if (shouldCopyBasedOnMtimeAndSize(srcPath, destPath)) {
|
|
101
|
+
copyFileOrThrow(srcPath, destPath, 'recursive asset');
|
|
44
102
|
}
|
|
45
103
|
}
|
|
46
104
|
}
|
|
105
|
+
|
|
106
|
+
const destEntries = fs.readdirSync(dest, { withFileTypes: true });
|
|
107
|
+
for (const entry of destEntries) {
|
|
108
|
+
if (entry.name === optimizerDirName) continue;
|
|
109
|
+
|
|
110
|
+
const destPath = path.join(dest, entry.name);
|
|
111
|
+
|
|
112
|
+
if (!srcNames.has(entry.name)) {
|
|
113
|
+
fs.rmSync(destPath, { recursive: true, force: true });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
47
116
|
}
|
|
48
117
|
|
|
49
118
|
function getSlugFromFilename(filename: string): string {
|
|
50
|
-
const nameWithoutExt = filename.replace(/\.mdx
|
|
119
|
+
const nameWithoutExt = filename.replace(/\.(mdx?|rst)$/, '');
|
|
51
120
|
const dateRegex = /^(\d{4}-\d{2}-\d{2})-(.*)$/;
|
|
52
121
|
const match = nameWithoutExt.match(dateRegex);
|
|
53
122
|
|
|
@@ -57,6 +126,124 @@ function getSlugFromFilename(filename: string): string {
|
|
|
57
126
|
return nameWithoutExt;
|
|
58
127
|
}
|
|
59
128
|
|
|
129
|
+
function isLocalAssetReference(rawPath: string): boolean {
|
|
130
|
+
const trimmed = rawPath.trim();
|
|
131
|
+
return Boolean(trimmed) &&
|
|
132
|
+
!trimmed.startsWith('#') &&
|
|
133
|
+
!trimmed.startsWith('/') &&
|
|
134
|
+
!trimmed.startsWith('http://') &&
|
|
135
|
+
!trimmed.startsWith('https://') &&
|
|
136
|
+
!trimmed.startsWith('data:') &&
|
|
137
|
+
!trimmed.startsWith('mailto:') &&
|
|
138
|
+
!trimmed.startsWith('javascript:');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function normalizeReferencedAssetPath(rawPath: string): string | null {
|
|
142
|
+
const trimmed = rawPath.trim().replace(/^['"]|['"]$/g, '');
|
|
143
|
+
const withoutFragment = trimmed.split('#')[0]?.split('?')[0]?.trim();
|
|
144
|
+
if (!withoutFragment || !isLocalAssetReference(withoutFragment)) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return withoutFragment;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function extractReferencedAssetPaths(filePath: string): string[] {
|
|
152
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
153
|
+
const references = new Set<string>();
|
|
154
|
+
const patterns = [
|
|
155
|
+
/\!\[[^\]]*\]\(([^)]+)\)/g,
|
|
156
|
+
/\[[^\]]*\]\(([^)]+)\)/g,
|
|
157
|
+
/\b(?:src|href|poster)=["']([^"']+)["']/g,
|
|
158
|
+
/^\s*\.\.\s+(?:image|figure)::\s+(.+)$/gm,
|
|
159
|
+
/^coverImage:\s*['"]?([^'"\n]+)['"]?$/gm,
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
for (const pattern of patterns) {
|
|
163
|
+
for (const match of content.matchAll(pattern)) {
|
|
164
|
+
const candidate = normalizeReferencedAssetPath(match[1] ?? '');
|
|
165
|
+
if (candidate) {
|
|
166
|
+
references.add(candidate);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return [...references];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function syncReferencedAssets(sourceFile: string, rootDir: string, destPostDir: string) {
|
|
175
|
+
const references = extractReferencedAssetPaths(sourceFile);
|
|
176
|
+
const desiredRelativePaths = new Set<string>();
|
|
177
|
+
|
|
178
|
+
if (!fs.existsSync(destPostDir)) {
|
|
179
|
+
fs.mkdirSync(destPostDir, { recursive: true });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
references.forEach((reference) => {
|
|
183
|
+
const absolutePath = path.resolve(path.dirname(sourceFile), reference);
|
|
184
|
+
const relativeToRoot = path.relative(rootDir, absolutePath);
|
|
185
|
+
|
|
186
|
+
if (relativeToRoot.startsWith('..') || path.isAbsolute(relativeToRoot) || !fs.existsSync(absolutePath)) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const sourceStat = fs.statSync(absolutePath);
|
|
191
|
+
if (sourceStat.isDirectory()) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (absolutePath.endsWith('.md') || absolutePath.endsWith('.mdx') || absolutePath.endsWith('.rst')) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const destPath = path.join(destPostDir, relativeToRoot);
|
|
200
|
+
desiredRelativePaths.add(relativeToRoot.replaceAll(path.sep, '/'));
|
|
201
|
+
if (!fs.existsSync(path.dirname(destPath))) {
|
|
202
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (shouldCopyBasedOnMtimeAndSize(absolutePath, destPath)) {
|
|
206
|
+
copyFileOrThrow(absolutePath, destPath, `referenced asset from ${sourceFile}`);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
function pruneDestDir(currentDir: string, relativeDir = '') {
|
|
211
|
+
if (!fs.existsSync(currentDir)) return;
|
|
212
|
+
|
|
213
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
214
|
+
for (const entry of entries) {
|
|
215
|
+
if (entry.name === optimizerDirName) continue;
|
|
216
|
+
|
|
217
|
+
const relativePath = relativeDir ? path.join(relativeDir, entry.name) : entry.name;
|
|
218
|
+
const normalizedRelativePath = relativePath.replaceAll(path.sep, '/');
|
|
219
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
220
|
+
|
|
221
|
+
if (entry.isDirectory()) {
|
|
222
|
+
const hasDesiredDescendant = [...desiredRelativePaths].some((desiredPath) =>
|
|
223
|
+
desiredPath === normalizedRelativePath || desiredPath.startsWith(`${normalizedRelativePath}/`)
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
if (!hasDesiredDescendant) {
|
|
227
|
+
fs.rmSync(fullPath, { recursive: true, force: true });
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
pruneDestDir(fullPath, relativePath);
|
|
232
|
+
if (fs.existsSync(fullPath) && fs.readdirSync(fullPath).length === 0) {
|
|
233
|
+
fs.rmdirSync(fullPath);
|
|
234
|
+
}
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (!desiredRelativePaths.has(normalizedRelativePath)) {
|
|
239
|
+
fs.rmSync(fullPath, { force: true });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
pruneDestDir(destPostDir);
|
|
245
|
+
}
|
|
246
|
+
|
|
60
247
|
function processPosts() {
|
|
61
248
|
if (fs.existsSync(srcDir)) {
|
|
62
249
|
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
@@ -68,7 +255,19 @@ function processPosts() {
|
|
|
68
255
|
const destPostDir = path.join(destDir, targetName);
|
|
69
256
|
|
|
70
257
|
console.log(`Processing Post: ${entry.name} -> ${targetName}`);
|
|
71
|
-
|
|
258
|
+
markGeneratedDestination(destPostDir);
|
|
259
|
+
syncRecursive(srcPostDir, destPostDir);
|
|
260
|
+
} else if (entry.isFile() && (entry.name.endsWith('.md') || entry.name.endsWith('.mdx') || entry.name.endsWith('.rst'))) {
|
|
261
|
+
const targetName = getSlugFromFilename(entry.name);
|
|
262
|
+
const sourceFile = path.join(srcDir, entry.name);
|
|
263
|
+
const destPostDir = path.join(destDir, targetName);
|
|
264
|
+
|
|
265
|
+
console.log(`Processing Flat Post: ${entry.name} -> ${targetName}`);
|
|
266
|
+
markGeneratedDestination(destPostDir);
|
|
267
|
+
if (!fs.existsSync(destPostDir)) {
|
|
268
|
+
fs.mkdirSync(destPostDir, { recursive: true });
|
|
269
|
+
}
|
|
270
|
+
syncReferencedAssets(sourceFile, srcDir, destPostDir);
|
|
72
271
|
}
|
|
73
272
|
});
|
|
74
273
|
}
|
|
@@ -77,12 +276,8 @@ function processPosts() {
|
|
|
77
276
|
// Check if a directory is a post folder (contains index.md or index.mdx)
|
|
78
277
|
function isPostFolder(dirPath: string): boolean {
|
|
79
278
|
return fs.existsSync(path.join(dirPath, 'index.md')) ||
|
|
80
|
-
fs.existsSync(path.join(dirPath, 'index.mdx'))
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
// Check if a directory is an asset folder (not a post folder)
|
|
84
|
-
function isAssetFolder(dirPath: string): boolean {
|
|
85
|
-
return !isPostFolder(dirPath);
|
|
279
|
+
fs.existsSync(path.join(dirPath, 'index.mdx')) ||
|
|
280
|
+
fs.existsSync(path.join(dirPath, 'index.rst'));
|
|
86
281
|
}
|
|
87
282
|
|
|
88
283
|
function processSeries() {
|
|
@@ -95,37 +290,23 @@ function processSeries() {
|
|
|
95
290
|
const seriesPath = path.join(seriesSrcDir, seriesEntry.name);
|
|
96
291
|
const items = fs.readdirSync(seriesPath, { withFileTypes: true });
|
|
97
292
|
|
|
98
|
-
// First pass: identify shared asset folders at series level (folders that are NOT posts)
|
|
99
|
-
const sharedAssetFolders = items
|
|
100
|
-
.filter(item => item.isDirectory() && isAssetFolder(path.join(seriesPath, item.name)))
|
|
101
|
-
.map(item => item.name);
|
|
102
|
-
|
|
103
293
|
// Process items in series folder
|
|
104
294
|
items.forEach(item => {
|
|
105
|
-
if (item.isFile() && (item.name.endsWith('.md') || item.name.endsWith('.mdx'))) {
|
|
295
|
+
if (item.isFile() && (item.name.endsWith('.md') || item.name.endsWith('.mdx') || item.name.endsWith('.rst'))) {
|
|
106
296
|
// File-based post or series index
|
|
107
|
-
const
|
|
297
|
+
const isSeriesIndex = item.name.startsWith('index.') || item.name.startsWith('README.');
|
|
298
|
+
const targetSlug = isSeriesIndex ? seriesEntry.name : getSlugFromFilename(item.name);
|
|
299
|
+
const sourceFile = path.join(seriesPath, item.name);
|
|
108
300
|
const destPostDir = path.join(destDir, targetSlug);
|
|
109
301
|
|
|
110
302
|
console.log(`Processing Series File: ${item.name} -> ${targetSlug}`);
|
|
303
|
+
markGeneratedDestination(destPostDir);
|
|
111
304
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const destPath = path.join(destPostDir, folderName);
|
|
116
|
-
copyRecursive(srcPath, destPath);
|
|
117
|
-
});
|
|
305
|
+
if (!fs.existsSync(destPostDir)) {
|
|
306
|
+
fs.mkdirSync(destPostDir, { recursive: true });
|
|
307
|
+
}
|
|
118
308
|
|
|
119
|
-
|
|
120
|
-
items.filter(sub => sub.isFile() && !sub.name.endsWith('.md') && !sub.name.endsWith('.mdx'))
|
|
121
|
-
.forEach(sub => {
|
|
122
|
-
const srcPath = path.join(seriesPath, sub.name);
|
|
123
|
-
const destPath = path.join(destPostDir, sub.name);
|
|
124
|
-
if (!fs.existsSync(destPostDir)) {
|
|
125
|
-
fs.mkdirSync(destPostDir, { recursive: true });
|
|
126
|
-
}
|
|
127
|
-
fs.copyFileSync(srcPath, destPath);
|
|
128
|
-
});
|
|
309
|
+
syncReferencedAssets(sourceFile, seriesPath, destPostDir);
|
|
129
310
|
|
|
130
311
|
} else if (item.isDirectory() && isPostFolder(path.join(seriesPath, item.name))) {
|
|
131
312
|
// Folder-based post: copy only its own assets
|
|
@@ -134,6 +315,7 @@ function processSeries() {
|
|
|
134
315
|
const destPostDir = path.join(destDir, targetSlug);
|
|
135
316
|
|
|
136
317
|
console.log(`Processing Series Post Folder: ${item.name} -> ${targetSlug}`);
|
|
318
|
+
markGeneratedDestination(destPostDir);
|
|
137
319
|
|
|
138
320
|
// Copy everything from the post folder EXCEPT markdown files
|
|
139
321
|
const subItems = fs.readdirSync(itemSrcPath, { withFileTypes: true });
|
|
@@ -142,12 +324,14 @@ function processSeries() {
|
|
|
142
324
|
const destPath = path.join(destPostDir, sub.name);
|
|
143
325
|
|
|
144
326
|
if (sub.isDirectory()) {
|
|
145
|
-
|
|
146
|
-
} else if (!
|
|
327
|
+
syncRecursive(srcPath, destPath);
|
|
328
|
+
} else if (!shouldSkipSourceFile(sub.name)) {
|
|
147
329
|
if (!fs.existsSync(destPostDir)) {
|
|
148
330
|
fs.mkdirSync(destPostDir, { recursive: true });
|
|
149
331
|
}
|
|
150
|
-
|
|
332
|
+
if (shouldCopyBasedOnMtimeAndSize(srcPath, destPath)) {
|
|
333
|
+
copyFileOrThrow(srcPath, destPath, `series post asset from ${itemSrcPath}`);
|
|
334
|
+
}
|
|
151
335
|
}
|
|
152
336
|
});
|
|
153
337
|
}
|
|
@@ -167,7 +351,8 @@ function processBooks() {
|
|
|
167
351
|
const destBookDir = path.join(booksDestDir, entry.name);
|
|
168
352
|
|
|
169
353
|
console.log(`Processing Book: ${entry.name}`);
|
|
170
|
-
|
|
354
|
+
markGeneratedDestination(destBookDir);
|
|
355
|
+
syncRecursive(srcBookDir, destBookDir);
|
|
171
356
|
}
|
|
172
357
|
});
|
|
173
358
|
}
|
|
@@ -197,15 +382,20 @@ function processFlows() {
|
|
|
197
382
|
const destFlowDir = path.join(flowsDestDir, yearEntry.name, monthEntry.name, rawName);
|
|
198
383
|
|
|
199
384
|
console.log(`Processing Flow: ${yearEntry.name}/${monthEntry.name}/${rawName}`);
|
|
200
|
-
|
|
385
|
+
markGeneratedDestination(destFlowDir);
|
|
386
|
+
syncRecursive(srcFlowDir, destFlowDir);
|
|
201
387
|
}
|
|
202
388
|
}
|
|
203
389
|
}
|
|
204
390
|
}
|
|
205
391
|
|
|
206
392
|
console.log('Copying assets...');
|
|
393
|
+
resetGeneratedAssetDirs();
|
|
207
394
|
processPosts();
|
|
208
395
|
processSeries();
|
|
209
396
|
processBooks();
|
|
210
397
|
processFlows();
|
|
211
|
-
|
|
398
|
+
pruneOrphanedOptimizerDirs(destDir);
|
|
399
|
+
pruneOrphanedOptimizerDirs(booksDestDir);
|
|
400
|
+
pruneOrphanedOptimizerDirs(flowsDestDir);
|
|
401
|
+
console.log('Assets copied successfully.');
|