@pagesmith/core 0.1.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 (85) hide show
  1. package/README.md +481 -0
  2. package/assets/fonts/OFL.txt +7 -0
  3. package/assets/fonts/jetbrains-mono-variable.woff2 +0 -0
  4. package/assets/fonts/open-sans-variable.woff2 +0 -0
  5. package/assets/fonts.css +17 -0
  6. package/dist/ai/index.d.mts +41 -0
  7. package/dist/ai/index.d.mts.map +1 -0
  8. package/dist/ai/index.mjs +414 -0
  9. package/dist/ai/index.mjs.map +1 -0
  10. package/dist/assets/index.d.mts +31 -0
  11. package/dist/assets/index.d.mts.map +1 -0
  12. package/dist/assets/index.mjs +2 -0
  13. package/dist/assets-bX08zEJm.mjs +155 -0
  14. package/dist/assets-bX08zEJm.mjs.map +1 -0
  15. package/dist/content-config-fHPaFZ7i.d.mts +1128 -0
  16. package/dist/content-config-fHPaFZ7i.d.mts.map +1 -0
  17. package/dist/content-layer-B7fQ3im4.mjs +590 -0
  18. package/dist/content-layer-B7fQ3im4.mjs.map +1 -0
  19. package/dist/convert-DnuB6SVV.mjs +52 -0
  20. package/dist/convert-DnuB6SVV.mjs.map +1 -0
  21. package/dist/create/index.d.mts +20 -0
  22. package/dist/create/index.d.mts.map +1 -0
  23. package/dist/create/index.mjs +215 -0
  24. package/dist/create/index.mjs.map +1 -0
  25. package/dist/css/index.d.mts +2 -0
  26. package/dist/css/index.mjs +2 -0
  27. package/dist/css-ekIt2Fdb.mjs +19 -0
  28. package/dist/css-ekIt2Fdb.mjs.map +1 -0
  29. package/dist/heading-Dhvzlay-.d.mts +27 -0
  30. package/dist/heading-Dhvzlay-.d.mts.map +1 -0
  31. package/dist/index-BQ6B1-qG.d.mts +41 -0
  32. package/dist/index-BQ6B1-qG.d.mts.map +1 -0
  33. package/dist/index-CeNDTM-y.d.mts +7 -0
  34. package/dist/index-CeNDTM-y.d.mts.map +1 -0
  35. package/dist/index-DpRBzO8Q.d.mts +15 -0
  36. package/dist/index-DpRBzO8Q.d.mts.map +1 -0
  37. package/dist/index-sFCx17CD.d.mts +59 -0
  38. package/dist/index-sFCx17CD.d.mts.map +1 -0
  39. package/dist/index.d.mts +131 -0
  40. package/dist/index.d.mts.map +1 -0
  41. package/dist/index.mjs +56 -0
  42. package/dist/index.mjs.map +1 -0
  43. package/dist/jsx-runtime/index.d.mts +24 -0
  44. package/dist/jsx-runtime/index.d.mts.map +1 -0
  45. package/dist/jsx-runtime/index.mjs +88 -0
  46. package/dist/jsx-runtime/index.mjs.map +1 -0
  47. package/dist/loaders/index.d.mts +3 -0
  48. package/dist/loaders/index.mjs +2 -0
  49. package/dist/loaders-DyABmDrE.mjs +179 -0
  50. package/dist/loaders-DyABmDrE.mjs.map +1 -0
  51. package/dist/markdown/index.d.mts +3 -0
  52. package/dist/markdown/index.mjs +2 -0
  53. package/dist/markdown-Cj5X26FL.mjs +95 -0
  54. package/dist/markdown-Cj5X26FL.mjs.map +1 -0
  55. package/dist/runtime/index.d.mts +28 -0
  56. package/dist/runtime/index.d.mts.map +1 -0
  57. package/dist/runtime/index.mjs +90 -0
  58. package/dist/runtime/index.mjs.map +1 -0
  59. package/dist/schemas/index.d.mts +4 -0
  60. package/dist/schemas/index.mjs +2 -0
  61. package/dist/schemas-DJS7wOzd.mjs +47 -0
  62. package/dist/schemas-DJS7wOzd.mjs.map +1 -0
  63. package/dist/types-DUsjRE7Y.d.mts +28 -0
  64. package/dist/types-DUsjRE7Y.d.mts.map +1 -0
  65. package/dist/vite/index.d.mts +9878 -0
  66. package/dist/vite/index.d.mts.map +1 -0
  67. package/dist/vite/index.mjs +549 -0
  68. package/dist/vite/index.mjs.map +1 -0
  69. package/package.json +136 -0
  70. package/src/styles/code/inline.css +26 -0
  71. package/src/styles/content/alerts.css +87 -0
  72. package/src/styles/content/prose.css +225 -0
  73. package/src/styles/content/toc.css +87 -0
  74. package/src/styles/content.css +22 -0
  75. package/src/styles/foundations/reset.css +49 -0
  76. package/src/styles/foundations/tokens.css +66 -0
  77. package/src/styles/layout/grid.css +179 -0
  78. package/src/styles/layout/sidebar.css +18 -0
  79. package/src/styles/standalone.css +18 -0
  80. package/src/styles/viewport.css +20 -0
  81. package/templates/docs/content/README.md +18 -0
  82. package/templates/docs/content/guide/README.md +11 -0
  83. package/templates/docs/content/guide/getting-started/README.md +9 -0
  84. package/templates/docs/content/reference/README.md +11 -0
  85. package/templates/docs/pagesmith.config.json5 +15 -0
@@ -0,0 +1,414 @@
1
+ import { dirname, join, resolve } from "path";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
3
+ import { homedir } from "os";
4
+ //#region src/ai/index.ts
5
+ const PAGESMITH_TITLE = "Pagesmith";
6
+ const DEFAULT_SKILL_NAME = "pagesmith";
7
+ function resolveHome(homeDir) {
8
+ return homeDir ?? homedir();
9
+ }
10
+ function resolveCodexHome(homeDir) {
11
+ return process.env.CODEX_HOME ?? join(resolveHome(homeDir), ".codex");
12
+ }
13
+ function resolveAssistants(assistants) {
14
+ if (!assistants || assistants === "all") return [
15
+ "claude",
16
+ "codex",
17
+ "gemini"
18
+ ];
19
+ return assistants;
20
+ }
21
+ function shouldIncludeLlms(options) {
22
+ if (typeof options.includeLlms === "boolean") return options.includeLlms;
23
+ return (options.scope ?? "project") === "project";
24
+ }
25
+ function withManagedBlock(id, content) {
26
+ return [
27
+ `<!-- pagesmith-ai:${id}:start -->`,
28
+ content.trim(),
29
+ `<!-- pagesmith-ai:${id}:end -->`
30
+ ].join("\n");
31
+ }
32
+ function writeArtifact(artifact, force = false) {
33
+ mkdirSync(dirname(artifact.path), { recursive: true });
34
+ if (!existsSync(artifact.path)) {
35
+ writeFileSync(artifact.path, artifact.content);
36
+ return "written";
37
+ }
38
+ const current = readFileSync(artifact.path, "utf-8");
39
+ if (current === artifact.content) return "unchanged";
40
+ if (artifact.mode === "replace") {
41
+ writeFileSync(artifact.path, artifact.content);
42
+ return "replaced";
43
+ }
44
+ const markerId = `${artifact.assistant ?? "shared"}-${artifact.kind}`;
45
+ const start = `<!-- pagesmith-ai:${markerId}:start -->`;
46
+ const end = `<!-- pagesmith-ai:${markerId}:end -->`;
47
+ if (current.includes(start) && current.includes(end)) {
48
+ const pattern = new RegExp(`${escapeForRegExp(start)}[\\s\\S]*?${escapeForRegExp(end)}`, "m");
49
+ const next = current.replace(pattern, artifact.content);
50
+ if (next === current) return "unchanged";
51
+ writeFileSync(artifact.path, next);
52
+ return "merged";
53
+ }
54
+ if (force) {
55
+ writeFileSync(artifact.path, artifact.content);
56
+ return "replaced";
57
+ }
58
+ const next = `${current.trimEnd()}\n\n${artifact.content}\n`;
59
+ writeFileSync(artifact.path, next);
60
+ return "merged";
61
+ }
62
+ function escapeForRegExp(value) {
63
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
64
+ }
65
+ function renderSharedOverview() {
66
+ return [
67
+ `${PAGESMITH_TITLE} is a filesystem-first content toolkit with two main packages: \`@pagesmith/core\` (shared content/runtime layer) and \`@pagesmith/docs\` (convention-based documentation).`,
68
+ "",
69
+ "Use Pagesmith when you need:",
70
+ "- schema-validated content collections loaded from the filesystem",
71
+ "- lazy markdown rendering with headings and read-time metadata",
72
+ "- framework-agnostic content APIs for React, Solid, Svelte, vanilla JS, Node, Bun, or Deno",
73
+ "",
74
+ "Core APIs:",
75
+ "- `defineCollection({...})` to define a typed collection",
76
+ "- `defineConfig({...})` to group collections and markdown options",
77
+ "- `createContentLayer(config)` to query content and run validation",
78
+ "- `entry.render()` to convert markdown on demand",
79
+ "",
80
+ "Useful helpers:",
81
+ "- `@pagesmith/core/ai` exposes `getAiArtifacts(...)` and `installAiArtifacts(...)`",
82
+ "",
83
+ "Working rules:",
84
+ "- prefer folder-based markdown entries when content references sibling assets",
85
+ "- use `vp` commands for install, check, test, and build workflows",
86
+ "- `@pagesmith/core` provides the shared content/runtime layer; `@pagesmith/docs` adds convention-based documentation on top"
87
+ ].join("\n");
88
+ }
89
+ function renderDocsOverview() {
90
+ return [
91
+ "Docs-specific rules:",
92
+ "- `@pagesmith/docs` is convention-based and builds a static docs site from `content/` plus `pagesmith.config.json5`",
93
+ "- top-level content folders become top navigation sections (for example `guide/`, `reference/`, `packages/`)",
94
+ "- folder-based markdown entries should prefer `README.md` or `index.md` when a page owns sibling assets",
95
+ "- the home page is `content/README.md`; optional home-specific data can live in `content/home.json5`",
96
+ "- sidebar labels, nav labels, and ordering live in frontmatter (`sidebarLabel`, `navLabel`, `order`)",
97
+ "- footer links live in `pagesmith.config.json5` under `footerLinks`",
98
+ "- Pagefind search is built in; do not recommend a separate search plugin package",
99
+ "- layout overrides use fixed keys under `theme.layouts` such as `home`, `page`, and `notFound`"
100
+ ].join("\n");
101
+ }
102
+ function renderQuickStart(profile = "default") {
103
+ if (profile === "docs") return [
104
+ "```json5",
105
+ "{",
106
+ " name: 'Acme Docs',",
107
+ " title: 'Acme Docs',",
108
+ " description: 'Multi-package documentation',",
109
+ " contentDir: './content',",
110
+ " outDir: './dist',",
111
+ " footerLinks: [",
112
+ " { label: 'Guide', path: '/guide' },",
113
+ " { label: 'Reference', path: '/reference' },",
114
+ " ],",
115
+ " search: { enabled: true },",
116
+ "}",
117
+ "```",
118
+ "",
119
+ "```text",
120
+ "content/",
121
+ " README.md",
122
+ " guide/",
123
+ " README.md",
124
+ " getting-started/README.md",
125
+ " reference/",
126
+ " README.md",
127
+ " api/README.md",
128
+ "```"
129
+ ].join("\n");
130
+ return [
131
+ "```ts",
132
+ "import { createContentLayer, defineCollection, defineConfig, z } from '@pagesmith/core'",
133
+ "",
134
+ "const posts = defineCollection({",
135
+ " loader: 'markdown',",
136
+ " directory: 'content/posts',",
137
+ " schema: z.object({",
138
+ " title: z.string(),",
139
+ " description: z.string().optional(),",
140
+ " date: z.coerce.date(),",
141
+ " tags: z.array(z.string()).default([]),",
142
+ " }),",
143
+ "})",
144
+ "",
145
+ "const layer = createContentLayer(",
146
+ " defineConfig({",
147
+ " collections: { posts },",
148
+ " }),",
149
+ ")",
150
+ "",
151
+ "const entries = await layer.getCollection('posts')",
152
+ "const rendered = await entries[0]?.render()",
153
+ "```"
154
+ ].join("\n");
155
+ }
156
+ function renderMemoryFile(assistant, profile) {
157
+ const commandHint = assistant === "claude" || assistant === "gemini" ? `\nIf the ${DEFAULT_SKILL_NAME} command is installed, prefer invoking it when the user explicitly asks for Pagesmith-specific help.` : "\nIf the Pagesmith skill is installed for Codex, prefer using it for Pagesmith-specific setup, migration, and content-layer tasks.";
158
+ return [
159
+ `# ${PAGESMITH_TITLE}`,
160
+ "",
161
+ renderSharedOverview(),
162
+ ...profile === "docs" ? ["", renderDocsOverview()] : [],
163
+ commandHint,
164
+ "",
165
+ "Quick start:",
166
+ renderQuickStart(profile)
167
+ ].join("\n");
168
+ }
169
+ function renderClaudeCommand(skillName, profile) {
170
+ return [
171
+ `# ${PAGESMITH_TITLE} Assistant`,
172
+ "",
173
+ "You are helping with Pagesmith, a file-based CMS with `@pagesmith/core` and `@pagesmith/docs`.",
174
+ "",
175
+ "When helping:",
176
+ "- prefer `defineCollection`, `defineConfig`, and `createContentLayer`",
177
+ "- recommend folder-based entries when markdown references sibling assets",
178
+ "- use `@pagesmith/core/ai` for assistant artifact generation",
179
+ "- recommend `vp install`, `vp check`, and `vp test` for validation",
180
+ ...profile === "docs" ? [
181
+ "- for docs sites, derive top navigation from top-level content folders",
182
+ "- use `content/README.md` for the home page and `content/home.json5` for extra home data when needed",
183
+ "- use frontmatter fields like `sidebarLabel`, `navLabel`, and `order` for docs navigation",
184
+ "- do not recommend `@pagesmith/plugin-pagefind` or `@pagesmith/plugin-algolia`; search is built into docs",
185
+ "- recommend `theme.layouts.home`, `theme.layouts.page`, and `theme.layouts.notFound` for docs layout overrides"
186
+ ] : [],
187
+ "",
188
+ "Deliver concrete config, schema, and content-layer patches when possible.",
189
+ "",
190
+ `This command is installed as \`/${skillName}\`.`
191
+ ].join("\n");
192
+ }
193
+ function renderGeminiCommand(skillName, profile) {
194
+ return [
195
+ `description = "Pagesmith FS-CMS helper"`,
196
+ "prompt = \"\"\"",
197
+ [
198
+ `You are helping with ${PAGESMITH_TITLE}, a file-based CMS with @pagesmith/core and @pagesmith/docs.`,
199
+ "",
200
+ "Focus on concrete, implementation-ready help:",
201
+ "- design collections with defineCollection",
202
+ "- configure createContentLayer and defineConfig",
203
+ "- recommend vp install, vp check, and vp test when validation matters",
204
+ "- prefer folder-based markdown entries when local assets sit beside content",
205
+ ...profile === "docs" ? [
206
+ "- for docs sites, follow the convention-based `content/` structure",
207
+ "- drive top navigation from top-level folders and use frontmatter for labels/order",
208
+ "- keep Pagefind as the built-in search strategy"
209
+ ] : [],
210
+ "",
211
+ "Return code, config, or documentation-ready guidance instead of vague summaries."
212
+ ].join("\n"),
213
+ "\"\"\"",
214
+ "",
215
+ `# Installed as /${skillName}`
216
+ ].join("\n");
217
+ }
218
+ function renderCodexSkill(profile) {
219
+ return [
220
+ `# ${PAGESMITH_TITLE} Skill`,
221
+ "",
222
+ "Use this skill when the task involves setting up, extending, migrating, or documenting Pagesmith.",
223
+ "",
224
+ "Core rules:",
225
+ "- `@pagesmith/core` provides the content layer; `@pagesmith/docs` adds convention-based documentation",
226
+ "- prefer `defineCollection`, `defineConfig`, and `createContentLayer`",
227
+ "- prefer `vp` commands instead of calling npm, pnpm, or yarn directly",
228
+ "- validate changes with `vp check` and `vp test` when relevant",
229
+ ...profile === "docs" ? [
230
+ "- when the repo uses `@pagesmith/docs`, treat `content/README.md` as the home page",
231
+ "- top-level content folders define the main docs navigation",
232
+ "- docs frontmatter may use `sidebarLabel`, `navLabel`, and `order` to shape navigation",
233
+ "- `pagesmith.config.json5` should own footer links and high-level site metadata",
234
+ "- built-in search is Pagefind; do not suggest separate search plugin packages"
235
+ ] : [],
236
+ "",
237
+ "Good outputs include:",
238
+ "- collection schemas and loader configuration",
239
+ "- content-layer queries and rendering examples",
240
+ "- @pagesmith/docs updates for Pagesmith usage",
241
+ "- assistant-context install steps using `@pagesmith/core/ai`"
242
+ ].join("\n");
243
+ }
244
+ function renderLlmsTxt(profile) {
245
+ return [
246
+ "# Pagesmith",
247
+ "",
248
+ "> Pagesmith is a filesystem-first content toolkit centered on `@pagesmith/core` and `@pagesmith/docs`.",
249
+ "",
250
+ "## Summary",
251
+ "",
252
+ renderSharedOverview(),
253
+ ...profile === "docs" ? ["", renderDocsOverview()] : [],
254
+ "",
255
+ "## Quick Start",
256
+ "",
257
+ renderQuickStart(profile)
258
+ ].join("\n");
259
+ }
260
+ function renderLlmsFullTxt(profile) {
261
+ return [
262
+ "# Pagesmith - Full LLM Reference",
263
+ "",
264
+ renderSharedOverview(),
265
+ ...profile === "docs" ? [
266
+ "",
267
+ "## Docs Sites",
268
+ "",
269
+ renderDocsOverview()
270
+ ] : [],
271
+ "",
272
+ "## Package Layout",
273
+ "",
274
+ "- `@pagesmith/core`: content layer, collection loading, validation, lazy markdown rendering, JSX runtime, CSS builder, runtime styles, assistant artifact APIs, and Vite content integration",
275
+ "- `@pagesmith/docs`: convention-based documentation with the docs CLI, generators, validators, default theme, and bundled search",
276
+ "",
277
+ "## Key APIs",
278
+ "",
279
+ renderQuickStart(profile),
280
+ "",
281
+ "## Assistant Installer",
282
+ "",
283
+ "```ts",
284
+ "import { installAiArtifacts } from '@pagesmith/core/ai'",
285
+ "",
286
+ "await installAiArtifacts({ assistants: ['claude', 'codex', 'gemini'], scope: 'project' })",
287
+ "```",
288
+ ""
289
+ ].join("\n");
290
+ }
291
+ function getAiArtifactContent(assistant, kind, options = {}) {
292
+ const skillName = options.skillName ?? DEFAULT_SKILL_NAME;
293
+ const profile = options.profile ?? "default";
294
+ if (assistant === "shared") {
295
+ if (kind === "llms") return renderLlmsTxt(profile);
296
+ return renderLlmsFullTxt(profile);
297
+ }
298
+ if (kind === "memory") return renderMemoryFile(assistant, profile);
299
+ if (kind === "skill") switch (assistant) {
300
+ case "claude": return renderClaudeCommand(skillName, profile);
301
+ case "codex": return renderCodexSkill(profile);
302
+ case "gemini": return renderGeminiCommand(skillName, profile);
303
+ }
304
+ if (kind === "llms") return renderLlmsTxt(profile);
305
+ return renderLlmsFullTxt(profile);
306
+ }
307
+ function getAiArtifacts(options = {}) {
308
+ const scope = options.scope ?? "project";
309
+ const cwd = resolve(options.cwd ?? process.cwd());
310
+ const home = resolveHome(options.homeDir);
311
+ const skillName = options.skillName ?? DEFAULT_SKILL_NAME;
312
+ const profile = options.profile ?? "default";
313
+ const assistants = resolveAssistants(options.assistants);
314
+ const artifacts = [];
315
+ for (const assistant of assistants) {
316
+ if (assistant === "claude") {
317
+ const baseDir = scope === "project" ? cwd : join(home, ".claude");
318
+ artifacts.push({
319
+ assistant,
320
+ kind: "memory",
321
+ path: join(baseDir, "CLAUDE.md"),
322
+ content: withManagedBlock("claude-memory", getAiArtifactContent("claude", "memory", { profile })),
323
+ mode: "merge",
324
+ label: `${assistant} memory`
325
+ });
326
+ artifacts.push({
327
+ assistant,
328
+ kind: "skill",
329
+ path: join(baseDir, "commands", `${skillName}.md`),
330
+ content: getAiArtifactContent("claude", "skill", {
331
+ profile,
332
+ skillName
333
+ }) + "\n",
334
+ mode: "replace",
335
+ label: `${assistant} command`
336
+ });
337
+ }
338
+ if (assistant === "codex") {
339
+ const baseDir = scope === "project" ? cwd : resolveCodexHome(options.homeDir);
340
+ artifacts.push({
341
+ assistant,
342
+ kind: "memory",
343
+ path: join(baseDir, "AGENTS.md"),
344
+ content: withManagedBlock("codex-memory", getAiArtifactContent("codex", "memory", { profile })),
345
+ mode: "merge",
346
+ label: `${assistant} AGENTS`
347
+ });
348
+ artifacts.push({
349
+ assistant,
350
+ kind: "skill",
351
+ path: join(baseDir, "skills", skillName, "SKILL.md"),
352
+ content: getAiArtifactContent("codex", "skill", {
353
+ profile,
354
+ skillName
355
+ }) + "\n",
356
+ mode: "replace",
357
+ label: `${assistant} skill`
358
+ });
359
+ }
360
+ if (assistant === "gemini") {
361
+ const baseDir = scope === "project" ? cwd : join(home, ".gemini");
362
+ artifacts.push({
363
+ assistant,
364
+ kind: "memory",
365
+ path: join(baseDir, "GEMINI.md"),
366
+ content: withManagedBlock("gemini-memory", getAiArtifactContent("gemini", "memory", { profile })),
367
+ mode: "merge",
368
+ label: `${assistant} memory`
369
+ });
370
+ artifacts.push({
371
+ assistant,
372
+ kind: "skill",
373
+ path: join(baseDir, "commands", `${skillName}.toml`),
374
+ content: getAiArtifactContent("gemini", "skill", {
375
+ profile,
376
+ skillName
377
+ }) + "\n",
378
+ mode: "replace",
379
+ label: `${assistant} command`
380
+ });
381
+ }
382
+ }
383
+ if (shouldIncludeLlms(options)) {
384
+ const llmsDir = scope === "project" ? cwd : join(home, ".pagesmith");
385
+ artifacts.push({
386
+ kind: "llms",
387
+ path: join(llmsDir, "llms.txt"),
388
+ content: withManagedBlock("shared-llms", getAiArtifactContent("shared", "llms", { profile })) + "\n",
389
+ mode: "merge",
390
+ label: "llms.txt"
391
+ });
392
+ artifacts.push({
393
+ kind: "llms-full",
394
+ path: join(llmsDir, "llms-full.txt"),
395
+ content: withManagedBlock("shared-llms-full", getAiArtifactContent("shared", "llms-full", { profile })) + "\n",
396
+ mode: "merge",
397
+ label: "llms-full.txt"
398
+ });
399
+ }
400
+ return artifacts;
401
+ }
402
+ function installAiArtifacts(options = {}) {
403
+ return getAiArtifacts(options).map((artifact) => ({
404
+ assistant: artifact.assistant,
405
+ kind: artifact.kind,
406
+ path: artifact.path,
407
+ label: artifact.label,
408
+ status: writeArtifact(artifact, options.force)
409
+ }));
410
+ }
411
+ //#endregion
412
+ export { getAiArtifactContent, getAiArtifacts, installAiArtifacts };
413
+
414
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/ai/index.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'\nimport { homedir } from 'os'\nimport { dirname, join, resolve } from 'path'\n\nexport type AiAssistant = 'claude' | 'codex' | 'gemini'\nexport type AiInstallScope = 'project' | 'user'\nexport type AiInstallProfile = 'default' | 'docs'\nexport type AiArtifactKind = 'memory' | 'skill' | 'llms' | 'llms-full'\nexport type AiWriteMode = 'merge' | 'replace'\nexport type AiInstallStatus = 'written' | 'merged' | 'replaced' | 'unchanged'\n\nexport type AiArtifact = {\n assistant?: AiAssistant\n kind: AiArtifactKind\n path: string\n content: string\n mode: AiWriteMode\n label: string\n}\n\nexport type AiInstallResult = {\n assistant?: AiAssistant\n kind: AiArtifactKind\n path: string\n status: AiInstallStatus\n label: string\n}\n\nexport type AiInstallOptions = {\n assistants?: AiAssistant[] | 'all'\n scope?: AiInstallScope\n profile?: AiInstallProfile\n cwd?: string\n homeDir?: string\n includeLlms?: boolean\n force?: boolean\n skillName?: string\n}\n\nconst PAGESMITH_TITLE = 'Pagesmith'\nconst DEFAULT_SKILL_NAME = 'pagesmith'\n\nfunction resolveHome(homeDir?: string): string {\n return homeDir ?? homedir()\n}\n\nfunction resolveCodexHome(homeDir?: string): string {\n return process.env.CODEX_HOME ?? join(resolveHome(homeDir), '.codex')\n}\n\nfunction resolveAssistants(assistants?: AiInstallOptions['assistants']): AiAssistant[] {\n if (!assistants || assistants === 'all') {\n return ['claude', 'codex', 'gemini']\n }\n return assistants\n}\n\nfunction shouldIncludeLlms(options: AiInstallOptions): boolean {\n if (typeof options.includeLlms === 'boolean') {\n return options.includeLlms\n }\n return (options.scope ?? 'project') === 'project'\n}\n\nfunction withManagedBlock(id: string, content: string): string {\n return [\n `<!-- pagesmith-ai:${id}:start -->`,\n content.trim(),\n `<!-- pagesmith-ai:${id}:end -->`,\n ].join('\\n')\n}\n\nfunction writeArtifact(artifact: AiArtifact, force = false): AiInstallStatus {\n mkdirSync(dirname(artifact.path), { recursive: true })\n\n if (!existsSync(artifact.path)) {\n writeFileSync(artifact.path, artifact.content)\n return 'written'\n }\n\n const current = readFileSync(artifact.path, 'utf-8')\n if (current === artifact.content) {\n return 'unchanged'\n }\n\n if (artifact.mode === 'replace') {\n writeFileSync(artifact.path, artifact.content)\n return 'replaced'\n }\n\n const markerId = `${artifact.assistant ?? 'shared'}-${artifact.kind}`\n const start = `<!-- pagesmith-ai:${markerId}:start -->`\n const end = `<!-- pagesmith-ai:${markerId}:end -->`\n\n if (current.includes(start) && current.includes(end)) {\n const pattern = new RegExp(`${escapeForRegExp(start)}[\\\\s\\\\S]*?${escapeForRegExp(end)}`, 'm')\n const next = current.replace(pattern, artifact.content)\n if (next === current) return 'unchanged'\n writeFileSync(artifact.path, next)\n return 'merged'\n }\n\n if (force) {\n writeFileSync(artifact.path, artifact.content)\n return 'replaced'\n }\n\n const next = `${current.trimEnd()}\\n\\n${artifact.content}\\n`\n writeFileSync(artifact.path, next)\n return 'merged'\n}\n\nfunction escapeForRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\nfunction renderSharedOverview(): string {\n return [\n `${PAGESMITH_TITLE} is a filesystem-first content toolkit with two main packages: \\`@pagesmith/core\\` (shared content/runtime layer) and \\`@pagesmith/docs\\` (convention-based documentation).`,\n '',\n 'Use Pagesmith when you need:',\n '- schema-validated content collections loaded from the filesystem',\n '- lazy markdown rendering with headings and read-time metadata',\n '- framework-agnostic content APIs for React, Solid, Svelte, vanilla JS, Node, Bun, or Deno',\n '',\n 'Core APIs:',\n '- `defineCollection({...})` to define a typed collection',\n '- `defineConfig({...})` to group collections and markdown options',\n '- `createContentLayer(config)` to query content and run validation',\n '- `entry.render()` to convert markdown on demand',\n '',\n 'Useful helpers:',\n '- `@pagesmith/core/ai` exposes `getAiArtifacts(...)` and `installAiArtifacts(...)`',\n '',\n 'Working rules:',\n '- prefer folder-based markdown entries when content references sibling assets',\n '- use `vp` commands for install, check, test, and build workflows',\n '- `@pagesmith/core` provides the shared content/runtime layer; `@pagesmith/docs` adds convention-based documentation on top',\n ].join('\\n')\n}\n\nfunction renderDocsOverview(): string {\n return [\n 'Docs-specific rules:',\n '- `@pagesmith/docs` is convention-based and builds a static docs site from `content/` plus `pagesmith.config.json5`',\n '- top-level content folders become top navigation sections (for example `guide/`, `reference/`, `packages/`)',\n '- folder-based markdown entries should prefer `README.md` or `index.md` when a page owns sibling assets',\n '- the home page is `content/README.md`; optional home-specific data can live in `content/home.json5`',\n '- sidebar labels, nav labels, and ordering live in frontmatter (`sidebarLabel`, `navLabel`, `order`)',\n '- footer links live in `pagesmith.config.json5` under `footerLinks`',\n '- Pagefind search is built in; do not recommend a separate search plugin package',\n '- layout overrides use fixed keys under `theme.layouts` such as `home`, `page`, and `notFound`',\n ].join('\\n')\n}\n\nfunction renderQuickStart(profile: AiInstallProfile = 'default'): string {\n if (profile === 'docs') {\n return [\n '```json5',\n '{',\n \" name: 'Acme Docs',\",\n \" title: 'Acme Docs',\",\n \" description: 'Multi-package documentation',\",\n \" contentDir: './content',\",\n \" outDir: './dist',\",\n ' footerLinks: [',\n \" { label: 'Guide', path: '/guide' },\",\n \" { label: 'Reference', path: '/reference' },\",\n ' ],',\n ' search: { enabled: true },',\n '}',\n '```',\n '',\n '```text',\n 'content/',\n ' README.md',\n ' guide/',\n ' README.md',\n ' getting-started/README.md',\n ' reference/',\n ' README.md',\n ' api/README.md',\n '```',\n ].join('\\n')\n }\n\n return [\n '```ts',\n \"import { createContentLayer, defineCollection, defineConfig, z } from '@pagesmith/core'\",\n '',\n 'const posts = defineCollection({',\n \" loader: 'markdown',\",\n \" directory: 'content/posts',\",\n ' schema: z.object({',\n ' title: z.string(),',\n ' description: z.string().optional(),',\n ' date: z.coerce.date(),',\n ' tags: z.array(z.string()).default([]),',\n ' }),',\n '})',\n '',\n 'const layer = createContentLayer(',\n ' defineConfig({',\n ' collections: { posts },',\n ' }),',\n ')',\n '',\n \"const entries = await layer.getCollection('posts')\",\n 'const rendered = await entries[0]?.render()',\n '```',\n ].join('\\n')\n}\n\nfunction renderMemoryFile(assistant: AiAssistant, profile: AiInstallProfile): string {\n const commandHint =\n assistant === 'claude' || assistant === 'gemini'\n ? `\\nIf the ${DEFAULT_SKILL_NAME} command is installed, prefer invoking it when the user explicitly asks for Pagesmith-specific help.`\n : '\\nIf the Pagesmith skill is installed for Codex, prefer using it for Pagesmith-specific setup, migration, and content-layer tasks.'\n\n return [\n `# ${PAGESMITH_TITLE}`,\n '',\n renderSharedOverview(),\n ...(profile === 'docs' ? ['', renderDocsOverview()] : []),\n commandHint,\n '',\n 'Quick start:',\n renderQuickStart(profile),\n ].join('\\n')\n}\n\nfunction renderClaudeCommand(skillName: string, profile: AiInstallProfile): string {\n return [\n `# ${PAGESMITH_TITLE} Assistant`,\n '',\n 'You are helping with Pagesmith, a file-based CMS with `@pagesmith/core` and `@pagesmith/docs`.',\n '',\n 'When helping:',\n '- prefer `defineCollection`, `defineConfig`, and `createContentLayer`',\n '- recommend folder-based entries when markdown references sibling assets',\n '- use `@pagesmith/core/ai` for assistant artifact generation',\n '- recommend `vp install`, `vp check`, and `vp test` for validation',\n ...(profile === 'docs'\n ? [\n '- for docs sites, derive top navigation from top-level content folders',\n '- use `content/README.md` for the home page and `content/home.json5` for extra home data when needed',\n '- use frontmatter fields like `sidebarLabel`, `navLabel`, and `order` for docs navigation',\n '- do not recommend `@pagesmith/plugin-pagefind` or `@pagesmith/plugin-algolia`; search is built into docs',\n '- recommend `theme.layouts.home`, `theme.layouts.page`, and `theme.layouts.notFound` for docs layout overrides',\n ]\n : []),\n '',\n 'Deliver concrete config, schema, and content-layer patches when possible.',\n '',\n `This command is installed as \\`/${skillName}\\`.`,\n ].join('\\n')\n}\n\nfunction renderGeminiCommand(skillName: string, profile: AiInstallProfile): string {\n const prompt = [\n `You are helping with ${PAGESMITH_TITLE}, a file-based CMS with @pagesmith/core and @pagesmith/docs.`,\n '',\n 'Focus on concrete, implementation-ready help:',\n '- design collections with defineCollection',\n '- configure createContentLayer and defineConfig',\n '- recommend vp install, vp check, and vp test when validation matters',\n '- prefer folder-based markdown entries when local assets sit beside content',\n ...(profile === 'docs'\n ? [\n '- for docs sites, follow the convention-based `content/` structure',\n '- drive top navigation from top-level folders and use frontmatter for labels/order',\n '- keep Pagefind as the built-in search strategy',\n ]\n : []),\n '',\n 'Return code, config, or documentation-ready guidance instead of vague summaries.',\n ].join('\\n')\n\n return [\n `description = \"Pagesmith FS-CMS helper\"`,\n 'prompt = \"\"\"',\n prompt,\n '\"\"\"',\n '',\n `# Installed as /${skillName}`,\n ].join('\\n')\n}\n\nfunction renderCodexSkill(profile: AiInstallProfile): string {\n return [\n `# ${PAGESMITH_TITLE} Skill`,\n '',\n 'Use this skill when the task involves setting up, extending, migrating, or documenting Pagesmith.',\n '',\n 'Core rules:',\n '- `@pagesmith/core` provides the content layer; `@pagesmith/docs` adds convention-based documentation',\n '- prefer `defineCollection`, `defineConfig`, and `createContentLayer`',\n '- prefer `vp` commands instead of calling npm, pnpm, or yarn directly',\n '- validate changes with `vp check` and `vp test` when relevant',\n ...(profile === 'docs'\n ? [\n '- when the repo uses `@pagesmith/docs`, treat `content/README.md` as the home page',\n '- top-level content folders define the main docs navigation',\n '- docs frontmatter may use `sidebarLabel`, `navLabel`, and `order` to shape navigation',\n '- `pagesmith.config.json5` should own footer links and high-level site metadata',\n '- built-in search is Pagefind; do not suggest separate search plugin packages',\n ]\n : []),\n '',\n 'Good outputs include:',\n '- collection schemas and loader configuration',\n '- content-layer queries and rendering examples',\n '- @pagesmith/docs updates for Pagesmith usage',\n '- assistant-context install steps using `@pagesmith/core/ai`',\n ].join('\\n')\n}\n\nfunction renderLlmsTxt(profile: AiInstallProfile): string {\n return [\n '# Pagesmith',\n '',\n '> Pagesmith is a filesystem-first content toolkit centered on `@pagesmith/core` and `@pagesmith/docs`.',\n '',\n '## Summary',\n '',\n renderSharedOverview(),\n ...(profile === 'docs' ? ['', renderDocsOverview()] : []),\n '',\n '## Quick Start',\n '',\n renderQuickStart(profile),\n ].join('\\n')\n}\n\nfunction renderLlmsFullTxt(profile: AiInstallProfile): string {\n return [\n '# Pagesmith - Full LLM Reference',\n '',\n renderSharedOverview(),\n ...(profile === 'docs' ? ['', '## Docs Sites', '', renderDocsOverview()] : []),\n '',\n '## Package Layout',\n '',\n '- `@pagesmith/core`: content layer, collection loading, validation, lazy markdown rendering, JSX runtime, CSS builder, runtime styles, assistant artifact APIs, and Vite content integration',\n '- `@pagesmith/docs`: convention-based documentation with the docs CLI, generators, validators, default theme, and bundled search',\n '',\n '## Key APIs',\n '',\n renderQuickStart(profile),\n '',\n '## Assistant Installer',\n '',\n '```ts',\n \"import { installAiArtifacts } from '@pagesmith/core/ai'\",\n '',\n \"await installAiArtifacts({ assistants: ['claude', 'codex', 'gemini'], scope: 'project' })\",\n '```',\n '',\n ].join('\\n')\n}\n\nexport function getAiArtifactContent(\n assistant: AiAssistant | 'shared',\n kind: AiArtifactKind,\n options: { profile?: AiInstallProfile; skillName?: string } = {},\n): string {\n const skillName = options.skillName ?? DEFAULT_SKILL_NAME\n const profile = options.profile ?? 'default'\n\n if (assistant === 'shared') {\n if (kind === 'llms') return renderLlmsTxt(profile)\n return renderLlmsFullTxt(profile)\n }\n\n if (kind === 'memory') {\n return renderMemoryFile(assistant, profile)\n }\n\n if (kind === 'skill') {\n switch (assistant) {\n case 'claude':\n return renderClaudeCommand(skillName, profile)\n case 'codex':\n return renderCodexSkill(profile)\n case 'gemini':\n return renderGeminiCommand(skillName, profile)\n }\n }\n\n if (kind === 'llms') return renderLlmsTxt(profile)\n return renderLlmsFullTxt(profile)\n}\n\nexport function getAiArtifacts(options: AiInstallOptions = {}): AiArtifact[] {\n const scope = options.scope ?? 'project'\n const cwd = resolve(options.cwd ?? process.cwd())\n const home = resolveHome(options.homeDir)\n const skillName = options.skillName ?? DEFAULT_SKILL_NAME\n const profile = options.profile ?? 'default'\n const assistants = resolveAssistants(options.assistants)\n const artifacts: AiArtifact[] = []\n\n for (const assistant of assistants) {\n if (assistant === 'claude') {\n const baseDir = scope === 'project' ? cwd : join(home, '.claude')\n artifacts.push({\n assistant,\n kind: 'memory',\n path: join(baseDir, 'CLAUDE.md'),\n content: withManagedBlock(\n 'claude-memory',\n getAiArtifactContent('claude', 'memory', { profile }),\n ),\n mode: 'merge',\n label: `${assistant} memory`,\n })\n artifacts.push({\n assistant,\n kind: 'skill',\n path: join(baseDir, 'commands', `${skillName}.md`),\n content: getAiArtifactContent('claude', 'skill', { profile, skillName }) + '\\n',\n mode: 'replace',\n label: `${assistant} command`,\n })\n }\n\n if (assistant === 'codex') {\n const baseDir = scope === 'project' ? cwd : resolveCodexHome(options.homeDir)\n artifacts.push({\n assistant,\n kind: 'memory',\n path: join(baseDir, 'AGENTS.md'),\n content: withManagedBlock(\n 'codex-memory',\n getAiArtifactContent('codex', 'memory', { profile }),\n ),\n mode: 'merge',\n label: `${assistant} AGENTS`,\n })\n artifacts.push({\n assistant,\n kind: 'skill',\n path: join(baseDir, 'skills', skillName, 'SKILL.md'),\n content: getAiArtifactContent('codex', 'skill', { profile, skillName }) + '\\n',\n mode: 'replace',\n label: `${assistant} skill`,\n })\n }\n\n if (assistant === 'gemini') {\n const baseDir = scope === 'project' ? cwd : join(home, '.gemini')\n artifacts.push({\n assistant,\n kind: 'memory',\n path: join(baseDir, 'GEMINI.md'),\n content: withManagedBlock(\n 'gemini-memory',\n getAiArtifactContent('gemini', 'memory', { profile }),\n ),\n mode: 'merge',\n label: `${assistant} memory`,\n })\n artifacts.push({\n assistant,\n kind: 'skill',\n path: join(baseDir, 'commands', `${skillName}.toml`),\n content: getAiArtifactContent('gemini', 'skill', { profile, skillName }) + '\\n',\n mode: 'replace',\n label: `${assistant} command`,\n })\n }\n }\n\n if (shouldIncludeLlms(options)) {\n const llmsDir = scope === 'project' ? cwd : join(home, '.pagesmith')\n artifacts.push({\n kind: 'llms',\n path: join(llmsDir, 'llms.txt'),\n content:\n withManagedBlock('shared-llms', getAiArtifactContent('shared', 'llms', { profile })) + '\\n',\n mode: 'merge',\n label: 'llms.txt',\n })\n artifacts.push({\n kind: 'llms-full',\n path: join(llmsDir, 'llms-full.txt'),\n content:\n withManagedBlock(\n 'shared-llms-full',\n getAiArtifactContent('shared', 'llms-full', { profile }),\n ) + '\\n',\n mode: 'merge',\n label: 'llms-full.txt',\n })\n }\n\n return artifacts\n}\n\nexport function installAiArtifacts(options: AiInstallOptions = {}): AiInstallResult[] {\n return getAiArtifacts(options).map((artifact) => ({\n assistant: artifact.assistant,\n kind: artifact.kind,\n path: artifact.path,\n label: artifact.label,\n status: writeArtifact(artifact, options.force),\n }))\n}\n"],"mappings":";;;;AAuCA,MAAM,kBAAkB;AACxB,MAAM,qBAAqB;AAE3B,SAAS,YAAY,SAA0B;AAC7C,QAAO,WAAW,SAAS;;AAG7B,SAAS,iBAAiB,SAA0B;AAClD,QAAO,QAAQ,IAAI,cAAc,KAAK,YAAY,QAAQ,EAAE,SAAS;;AAGvE,SAAS,kBAAkB,YAA4D;AACrF,KAAI,CAAC,cAAc,eAAe,MAChC,QAAO;EAAC;EAAU;EAAS;EAAS;AAEtC,QAAO;;AAGT,SAAS,kBAAkB,SAAoC;AAC7D,KAAI,OAAO,QAAQ,gBAAgB,UACjC,QAAO,QAAQ;AAEjB,SAAQ,QAAQ,SAAS,eAAe;;AAG1C,SAAS,iBAAiB,IAAY,SAAyB;AAC7D,QAAO;EACL,qBAAqB,GAAG;EACxB,QAAQ,MAAM;EACd,qBAAqB,GAAG;EACzB,CAAC,KAAK,KAAK;;AAGd,SAAS,cAAc,UAAsB,QAAQ,OAAwB;AAC3E,WAAU,QAAQ,SAAS,KAAK,EAAE,EAAE,WAAW,MAAM,CAAC;AAEtD,KAAI,CAAC,WAAW,SAAS,KAAK,EAAE;AAC9B,gBAAc,SAAS,MAAM,SAAS,QAAQ;AAC9C,SAAO;;CAGT,MAAM,UAAU,aAAa,SAAS,MAAM,QAAQ;AACpD,KAAI,YAAY,SAAS,QACvB,QAAO;AAGT,KAAI,SAAS,SAAS,WAAW;AAC/B,gBAAc,SAAS,MAAM,SAAS,QAAQ;AAC9C,SAAO;;CAGT,MAAM,WAAW,GAAG,SAAS,aAAa,SAAS,GAAG,SAAS;CAC/D,MAAM,QAAQ,qBAAqB,SAAS;CAC5C,MAAM,MAAM,qBAAqB,SAAS;AAE1C,KAAI,QAAQ,SAAS,MAAM,IAAI,QAAQ,SAAS,IAAI,EAAE;EACpD,MAAM,UAAU,IAAI,OAAO,GAAG,gBAAgB,MAAM,CAAC,YAAY,gBAAgB,IAAI,IAAI,IAAI;EAC7F,MAAM,OAAO,QAAQ,QAAQ,SAAS,SAAS,QAAQ;AACvD,MAAI,SAAS,QAAS,QAAO;AAC7B,gBAAc,SAAS,MAAM,KAAK;AAClC,SAAO;;AAGT,KAAI,OAAO;AACT,gBAAc,SAAS,MAAM,SAAS,QAAQ;AAC9C,SAAO;;CAGT,MAAM,OAAO,GAAG,QAAQ,SAAS,CAAC,MAAM,SAAS,QAAQ;AACzD,eAAc,SAAS,MAAM,KAAK;AAClC,QAAO;;AAGT,SAAS,gBAAgB,OAAuB;AAC9C,QAAO,MAAM,QAAQ,uBAAuB,OAAO;;AAGrD,SAAS,uBAA+B;AACtC,QAAO;EACL,GAAG,gBAAgB;EACnB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC,KAAK,KAAK;;AAGd,SAAS,qBAA6B;AACpC,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC,KAAK,KAAK;;AAGd,SAAS,iBAAiB,UAA4B,WAAmB;AACvE,KAAI,YAAY,OACd,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC,KAAK,KAAK;AAGd,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC,KAAK,KAAK;;AAGd,SAAS,iBAAiB,WAAwB,SAAmC;CACnF,MAAM,cACJ,cAAc,YAAY,cAAc,WACpC,YAAY,mBAAmB,wGAC/B;AAEN,QAAO;EACL,KAAK;EACL;EACA,sBAAsB;EACtB,GAAI,YAAY,SAAS,CAAC,IAAI,oBAAoB,CAAC,GAAG,EAAE;EACxD;EACA;EACA;EACA,iBAAiB,QAAQ;EAC1B,CAAC,KAAK,KAAK;;AAGd,SAAS,oBAAoB,WAAmB,SAAmC;AACjF,QAAO;EACL,KAAK,gBAAgB;EACrB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,GAAI,YAAY,SACZ;GACE;GACA;GACA;GACA;GACA;GACD,GACD,EAAE;EACN;EACA;EACA;EACA,mCAAmC,UAAU;EAC9C,CAAC,KAAK,KAAK;;AAGd,SAAS,oBAAoB,WAAmB,SAAmC;AAoBjF,QAAO;EACL;EACA;EArBa;GACb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"}
@@ -0,0 +1,31 @@
1
+ //#region src/assets/copier.d.ts
2
+ /**
3
+ * Copy public directory to output, preserving structure (no hashing).
4
+ * Skips `fonts/` since those are copied to dist/assets/ and hashed.
5
+ */
6
+ declare function copyPublicFiles(publicDir: string, outDir: string): void;
7
+ //#endregion
8
+ //#region src/assets/hasher.d.ts
9
+ /**
10
+ * Demand-driven asset pipeline.
11
+ *
12
+ * Instead of blindly copying all content assets to dist, this:
13
+ * 1. Hashes pre-existing dist/assets/ files (CSS, JS, fonts — already there from bundling)
14
+ * 2. Scans generated HTML for /assets/* references
15
+ * 3. For each referenced content asset not yet in dist, finds the source
16
+ * file in the content directory, copies it with a content hash
17
+ * 4. Rewrites all HTML references to hashed paths
18
+ *
19
+ * Content assets are only copied if actually referenced in the output HTML.
20
+ * Public assets (favicons, robots.txt) are handled separately by copyPublicFiles.
21
+ */
22
+ /**
23
+ * Hash assets and rewrite HTML references.
24
+ *
25
+ * @param outDir - The dist output directory
26
+ * @param contentDir - The content source directory (for finding referenced assets)
27
+ */
28
+ declare function hashAssets(outDir: string, contentDir: string): void;
29
+ //#endregion
30
+ export { copyPublicFiles, hashAssets };
31
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/assets/copier.ts","../../src/assets/hasher.ts"],"mappings":";;AAOA;;;iBAAgB,eAAA,CAAgB,SAAA,UAAmB,MAAA;;;;AAAnD;;;;;;;;ACsEA;;;;;;;;;;iBAAgB,UAAA,CAAW,MAAA,UAAgB,UAAA"}
@@ -0,0 +1,2 @@
1
+ import { n as copyPublicFiles, t as hashAssets } from "../assets-bX08zEJm.mjs";
2
+ export { copyPublicFiles, hashAssets };
@@ -0,0 +1,155 @@
1
+ import { basename, dirname, extname, join, relative } from "path";
2
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from "fs";
3
+ import { createHash } from "crypto";
4
+ //#region src/assets/copier.ts
5
+ /**
6
+ * Copy public directory to output, preserving structure (no hashing).
7
+ * Skips `fonts/` since those are copied to dist/assets/ and hashed.
8
+ */
9
+ function copyPublicFiles(publicDir, outDir) {
10
+ if (!existsSync(publicDir)) return;
11
+ function walk(dir) {
12
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
13
+ const full = join(dir, entry.name);
14
+ if (entry.isDirectory() && entry.name === "fonts" && dir === publicDir) continue;
15
+ if (entry.isDirectory()) {
16
+ walk(full);
17
+ continue;
18
+ }
19
+ const dest = join(outDir, relative(publicDir, full));
20
+ mkdirSync(dirname(dest), { recursive: true });
21
+ copyFileSync(full, dest);
22
+ }
23
+ }
24
+ walk(publicDir);
25
+ }
26
+ //#endregion
27
+ //#region src/assets/hasher.ts
28
+ /**
29
+ * Demand-driven asset pipeline.
30
+ *
31
+ * Instead of blindly copying all content assets to dist, this:
32
+ * 1. Hashes pre-existing dist/assets/ files (CSS, JS, fonts — already there from bundling)
33
+ * 2. Scans generated HTML for /assets/* references
34
+ * 3. For each referenced content asset not yet in dist, finds the source
35
+ * file in the content directory, copies it with a content hash
36
+ * 4. Rewrites all HTML references to hashed paths
37
+ *
38
+ * Content assets are only copied if actually referenced in the output HTML.
39
+ * Public assets (favicons, robots.txt) are handled separately by copyPublicFiles.
40
+ */
41
+ const HASHABLE_EXTS = new Set([
42
+ ".css",
43
+ ".js",
44
+ ".svg",
45
+ ".png",
46
+ ".jpg",
47
+ ".jpeg",
48
+ ".gif",
49
+ ".webp",
50
+ ".avif",
51
+ ".ico",
52
+ ".woff",
53
+ ".woff2",
54
+ ".ttf",
55
+ ".eot"
56
+ ]);
57
+ const CONTENT_ASSET_EXTS = new Set([
58
+ ".svg",
59
+ ".png",
60
+ ".jpg",
61
+ ".jpeg",
62
+ ".gif",
63
+ ".webp",
64
+ ".avif",
65
+ ".ico"
66
+ ]);
67
+ /** Build a basename → source path lookup for content assets. */
68
+ function buildContentAssetMap(contentDir) {
69
+ const map = /* @__PURE__ */ new Map();
70
+ function walk(dir) {
71
+ if (!existsSync(dir)) return;
72
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
73
+ const full = join(dir, entry.name);
74
+ if (entry.isDirectory()) {
75
+ walk(full);
76
+ continue;
77
+ }
78
+ const ext = extname(entry.name);
79
+ if (!CONTENT_ASSET_EXTS.has(ext)) continue;
80
+ if (entry.name.endsWith(".inline.svg")) continue;
81
+ map.set(entry.name, full);
82
+ }
83
+ }
84
+ walk(contentDir);
85
+ return map;
86
+ }
87
+ function computeHash(content) {
88
+ return createHash("sha256").update(content).digest("hex").slice(0, 8);
89
+ }
90
+ /**
91
+ * Hash assets and rewrite HTML references.
92
+ *
93
+ * @param outDir - The dist output directory
94
+ * @param contentDir - The content source directory (for finding referenced assets)
95
+ */
96
+ function hashAssets(outDir, contentDir) {
97
+ const assetsDir = join(outDir, "assets");
98
+ mkdirSync(assetsDir, { recursive: true });
99
+ const renames = /* @__PURE__ */ new Map();
100
+ const contentAssets = buildContentAssetMap(contentDir);
101
+ const existing = [];
102
+ if (existsSync(assetsDir)) for (const entry of readdirSync(assetsDir, { withFileTypes: true })) {
103
+ if (entry.isDirectory()) continue;
104
+ const ext = extname(entry.name);
105
+ if (!HASHABLE_EXTS.has(ext)) continue;
106
+ existing.push({
107
+ full: join(assetsDir, entry.name),
108
+ ext,
109
+ name: basename(entry.name, ext)
110
+ });
111
+ }
112
+ for (const file of existing) {
113
+ const hash = computeHash(readFileSync(file.full));
114
+ const hashedPath = join(assetsDir, `${file.name}.${hash}${file.ext}`);
115
+ renameSync(file.full, hashedPath);
116
+ renames.set(file.full, hashedPath);
117
+ }
118
+ function processHtml(dir) {
119
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
120
+ const full = join(dir, entry.name);
121
+ if (entry.isDirectory()) {
122
+ processHtml(full);
123
+ continue;
124
+ }
125
+ if (!entry.name.endsWith(".html")) continue;
126
+ let html = readFileSync(full, "utf-8");
127
+ html = html.replace(/(src|href|srcset)="([^"]+)"/g, (match, attr, ref) => {
128
+ if (ref.startsWith("http:") || ref.startsWith("https:") || ref.startsWith("//") || ref.startsWith("#") || ref.startsWith("data:") || ref.startsWith("mailto:")) return match;
129
+ let assetRef = ref;
130
+ if (ref.startsWith("./") && /\.(svg|png|jpg|jpeg|gif|webp|avif|ico)$/i.test(ref)) assetRef = "/assets/" + basename(ref);
131
+ if (!assetRef.startsWith("/assets/")) return match;
132
+ const fileName = assetRef.slice(8);
133
+ const distPath = join(assetsDir, fileName);
134
+ const already = renames.get(distPath);
135
+ if (already) return `${attr}="/${relative(outDir, already)}"`;
136
+ const sourcePath = contentAssets.get(fileName);
137
+ if (!sourcePath) return `${attr}="${assetRef}"`;
138
+ const content = readFileSync(sourcePath);
139
+ const hash = computeHash(content);
140
+ const ext = extname(fileName);
141
+ const hashedName = `${basename(fileName, ext)}.${hash}${ext}`;
142
+ const hashedDest = join(assetsDir, hashedName);
143
+ writeFileSync(hashedDest, content);
144
+ renames.set(distPath, hashedDest);
145
+ return `${attr}="/assets/${hashedName}"`;
146
+ });
147
+ writeFileSync(full, html);
148
+ }
149
+ }
150
+ processHtml(outDir);
151
+ }
152
+ //#endregion
153
+ export { copyPublicFiles as n, hashAssets as t };
154
+
155
+ //# sourceMappingURL=assets-bX08zEJm.mjs.map