@publish.os/nextjs 0.0.1-alpha.8 → 0.0.1-alpha.90

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 (201) hide show
  1. package/compiler-assets/nextjs/defaults/app/globals.css +1 -0
  2. package/compiler-assets/nextjs/defaults/group/index.tsx +50 -0
  3. package/compiler-assets/nextjs/defaults/group/layout.tsx +7 -3
  4. package/compiler-assets/nextjs/defaults/group/page.tsx +35 -43
  5. package/compiler-assets/nextjs/defaults/project/next.config.mjs +22 -11
  6. package/compiler-assets/nextjs/defaults/project/tsconfig.json.template +1 -4
  7. package/compiler-assets/nextjs/defaults/root/css/animation.css +131 -0
  8. package/compiler-assets/nextjs/defaults/root/css/components/dialog.css +4 -1
  9. package/compiler-assets/nextjs/defaults/root/css/components/menu.css +1 -1
  10. package/compiler-assets/nextjs/defaults/root/css/components/navigation-menu.css +1 -1
  11. package/compiler-assets/nextjs/defaults/root/css/components/popover.css +1 -1
  12. package/compiler-assets/nextjs/defaults/root/css/components/select.css +1 -1
  13. package/compiler-assets/nextjs/defaults/root/css/components/tooltip.css +1 -1
  14. package/compiler-assets/nextjs/defaults/root/css/shadow.css +6 -25
  15. package/compiler-assets/nextjs/defaults/root/css/typography.css +51 -68
  16. package/compiler-assets/nextjs/defaults/root/globals.css +25 -0
  17. package/compiler-assets/nextjs/defaults/root/{page.tsx → index.tsx} +6 -2
  18. package/compiler-assets/nextjs/defaults/root/layout.tsx +7 -1
  19. package/compiler-assets/nextjs/defaults/root/theme.css +22 -22
  20. package/compiler-assets/nextjs/shims/generated-group-page.tsx +19 -2
  21. package/compiler-assets/nextjs/shims/generated-leaf-page.tsx +16 -2
  22. package/compiler-assets/nextjs/shims/global-layout.tsx +11 -43
  23. package/compiler-assets/nextjs/shims/proxy.ts +21 -9
  24. package/compiler-assets/nextjs/shims/publish-os-theme-provider.tsx +76 -15
  25. package/compiler-assets/nextjs/shims/root-page.tsx +7 -1
  26. package/compiler-assets/nextjs/shims/site-fenced-code-block.tsx +34 -0
  27. package/compiler-assets/nextjs/shims/site-heading-level.tsx +14 -0
  28. package/compiler-assets/nextjs/shims/site-mdx-components.tsx +100 -0
  29. package/compiler-assets/nextjs/shims/site-mdx.tsx +12 -0
  30. package/compiler-assets/nextjs/shims/theme-defaults.css +41 -25
  31. package/dist/build-operation-2JAYBSKF.js +83 -0
  32. package/dist/button-CtzUJB8u.d.ts +11 -0
  33. package/dist/{button-CFdx-ohV.d.ts → button-c3WGnxIj.d.ts} +3 -3
  34. package/dist/chunk-535TM4A5.js +1455 -0
  35. package/dist/chunk-726GV2NF.js +2344 -0
  36. package/dist/chunk-7OGTPSKI.js +156 -0
  37. package/dist/chunk-AI2UULNX.js +9 -0
  38. package/dist/chunk-B2V22WRT.js +297 -0
  39. package/dist/chunk-DLDFHU7Y.js +191 -0
  40. package/dist/chunk-DWA3ZRGW.js +142 -0
  41. package/dist/chunk-GGKFQ3MA.js +621 -0
  42. package/dist/chunk-H6XILMZX.js +6154 -0
  43. package/dist/chunk-NECL5IBJ.js +318 -0
  44. package/dist/chunk-SVYGXXYR.js +36 -0
  45. package/dist/chunk-UF6ATARM.js +1100 -0
  46. package/dist/chunk-VO2I7MTM.js +56 -0
  47. package/dist/chunk-WDY4IK6Z.js +128 -0
  48. package/dist/chunk-WKTEO6RS.js +586 -0
  49. package/dist/chunk-YLTOLYWM.js +92 -0
  50. package/dist/compiler.d.ts +41 -7
  51. package/dist/compiler.js +48 -13143
  52. package/dist/compose-NN23UJGC.js +13 -0
  53. package/dist/config/createNextConfig.js +13 -0
  54. package/dist/dev-operation-TZDVJCN7.js +1055 -0
  55. package/dist/framework-selection-X4U6Y6QC.js +10 -0
  56. package/dist/ide-support-ZNW7ODFY.js +15 -0
  57. package/dist/index.d.ts +15 -3
  58. package/dist/kernel/components/blocks/AgentActions.js +71 -61
  59. package/dist/kernel/components/blocks/design-system-preview.d.ts +1 -1
  60. package/dist/kernel/components/blocks/design-system-preview.js +32 -13
  61. package/dist/kernel/components/blocks/link-playground.js +2 -2
  62. package/dist/kernel/components/blocks/logo-block.js +2 -2
  63. package/dist/kernel/components/blocks/nav-breadcrumb.d.ts +10 -2
  64. package/dist/kernel/components/blocks/nav-breadcrumb.js +20 -5
  65. package/dist/kernel/components/blocks/navbar.d.ts +2 -2
  66. package/dist/kernel/components/blocks/navbar.js +401 -60
  67. package/dist/kernel/components/blocks/navigation-progress-playground.js +4 -4
  68. package/dist/kernel/components/blocks/navigation-progress.js +4 -1
  69. package/dist/kernel/components/blocks/next-prev.d.ts +1 -1
  70. package/dist/kernel/components/blocks/next-prev.js +11 -23
  71. package/dist/kernel/components/blocks/page-list.d.ts +1 -1
  72. package/dist/kernel/components/blocks/page-list.js +5 -4
  73. package/dist/kernel/components/blocks/search.d.ts +83 -58
  74. package/dist/kernel/components/blocks/search.js +553 -426
  75. package/dist/kernel/components/blocks/sidebar-node.d.ts +2 -2
  76. package/dist/kernel/components/blocks/sidebar-node.js +11 -9
  77. package/dist/kernel/components/blocks/sidebar.d.ts +3 -3
  78. package/dist/kernel/components/blocks/sidebar.js +18 -11
  79. package/dist/kernel/components/blocks/theme-toggle.d.ts +2 -2
  80. package/dist/kernel/components/blocks/theme-toggle.js +48 -2
  81. package/dist/kernel/components/blocks/toc.js +3 -3
  82. package/dist/kernel/components/mdx/components/blockquote.js +1 -1
  83. package/dist/kernel/components/mdx/components/callout.d.ts +5 -7
  84. package/dist/kernel/components/mdx/components/callout.js +66 -35
  85. package/dist/kernel/components/mdx/components/code-block.d.ts +5 -2
  86. package/dist/kernel/components/mdx/components/code-block.js +106 -5
  87. package/dist/kernel/components/mdx/components/copy-code-button.d.ts +8 -0
  88. package/dist/kernel/components/mdx/components/copy-code-button.js +60 -0
  89. package/dist/kernel/components/mdx/components/figure-caption.js +1 -1
  90. package/dist/kernel/components/mdx/components/figure.js +1 -1
  91. package/dist/kernel/components/mdx/components/heading-copy-link.js +3 -3
  92. package/dist/kernel/components/mdx/components/image-zoom.d.ts +2 -2
  93. package/dist/kernel/components/mdx/components/image-zoom.js +1 -1
  94. package/dist/kernel/components/mdx/components/image.js +77 -56
  95. package/dist/kernel/components/mdx/components/inline-code.js +1 -1
  96. package/dist/kernel/components/mdx/components/kbd.d.ts +2 -2
  97. package/dist/kernel/components/mdx/components/kbd.js +3 -2
  98. package/dist/kernel/components/mdx/components/link.d.ts +35 -4
  99. package/dist/kernel/components/mdx/components/link.js +1 -1
  100. package/dist/kernel/components/mdx/components/paragraph.js +1 -4
  101. package/dist/kernel/components/mdx/components/task-list.js +1 -1
  102. package/dist/kernel/components/mdx/components/video-player.js +2 -2
  103. package/dist/kernel/components/mdx/components/video.js +1 -1
  104. package/dist/kernel/components/mdx/error-boundary.d.ts +1 -1
  105. package/dist/kernel/components/mdx/mdx.d.ts +12 -8
  106. package/dist/kernel/components/mdx/mdx.js +115 -39
  107. package/dist/kernel/components/mdx/rehype-heading-ids.js +1 -1
  108. package/dist/kernel/components/mdx/types.d.ts +1 -1
  109. package/dist/kernel/components/ui/breadcrumb-trail.d.ts +6 -23
  110. package/dist/kernel/components/ui/breadcrumb.d.ts +11 -3
  111. package/dist/kernel/components/ui/breadcrumb.js +6 -4
  112. package/dist/kernel/components/ui/button-group.d.ts +1 -1
  113. package/dist/kernel/components/ui/button-group.js +5 -4
  114. package/dist/kernel/components/ui/button.d.ts +2 -2
  115. package/dist/kernel/components/ui/button.js +6 -6
  116. package/dist/kernel/components/ui/card.d.ts +2 -2
  117. package/dist/kernel/components/ui/drawer.d.ts +3 -1
  118. package/dist/kernel/components/ui/drawer.js +5 -1
  119. package/dist/kernel/components/ui/input.d.ts +4 -4
  120. package/dist/kernel/components/ui/kbd.d.ts +1 -1
  121. package/dist/kernel/components/ui/kbd.js +9 -16
  122. package/dist/kernel/components/ui/link.d.ts +5 -3
  123. package/dist/kernel/components/ui/link.js +10 -9
  124. package/dist/kernel/components/ui/menu.js +5 -5
  125. package/dist/kernel/components/ui/mod-key.d.ts +10 -0
  126. package/dist/kernel/components/ui/mod-key.js +36 -0
  127. package/dist/kernel/components/ui/navigation-menu.js +2 -2
  128. package/dist/kernel/components/ui/progress.js +1 -1
  129. package/dist/kernel/components/ui/scroll-area.d.ts +8 -8
  130. package/dist/kernel/components/ui/scroll-area.js +4 -4
  131. package/dist/kernel/components/ui/select.js +7 -10
  132. package/dist/kernel/components/ui/text.d.ts +2 -2
  133. package/dist/kernel/components/ui/theme-toggle.d.ts +3 -3
  134. package/dist/kernel/components/ui/theme-toggle.js +4 -9
  135. package/dist/kernel/defaults.registry.d.ts +9 -9
  136. package/dist/kernel/defaults.registry.js +9 -9
  137. package/dist/kernel/routes/agent/markdown.d.ts +0 -1
  138. package/dist/kernel/routes/layouts/group.d.ts +1 -1
  139. package/dist/kernel/routes/metadata/feed.d.ts +1 -1
  140. package/dist/kernel/routes/metadata/feed.js +13 -9
  141. package/dist/kernel/routes/metadata/llms.d.ts +2 -1
  142. package/dist/kernel/routes/metadata/llms.js +4 -4
  143. package/dist/kernel/routes/metadata/robots.d.ts +1 -1
  144. package/dist/kernel/routes/metadata/robots.js +2 -2
  145. package/dist/kernel/routes/metadata/sitemap.d.ts +6 -3
  146. package/dist/kernel/routes/metadata/sitemap.js +48 -20
  147. package/dist/kernel/routes/pages/group.d.ts +1 -1
  148. package/dist/kernel/routes/pages/group.js +20 -12
  149. package/dist/kernel/routes/pages/leaf.d.ts +2 -2
  150. package/dist/kernel/routes/pages/leaf.js +20 -12
  151. package/dist/kernel/routes/pages/root.d.ts +2 -2
  152. package/dist/kernel/routes/pages/root.js +18 -12
  153. package/dist/resolvers/manifest-resolver.js +29 -6
  154. package/dist/search.d.ts +6 -7
  155. package/dist/search.js +47 -22
  156. package/dist/{sidebar-types-Bgchdfd6.d.ts → sidebar-types-DKpR27II.d.ts} +1 -1
  157. package/dist/{types-CIwwieNw.d.ts → types-BzkZty3r.d.ts} +23 -3
  158. package/dist/{types-DHBm7-j6.d.ts → types-CJWRG05y.d.ts} +20 -1
  159. package/dist/{types-B_b3C0U6.d.ts → types-CUtrppxt.d.ts} +8 -2
  160. package/dist/types.d.ts +14 -6
  161. package/dist/validate-local-source-XRL2Y7XE.js +14 -0
  162. package/dist/validate-publish-os-source-DAPZ4QGE.js +12 -0
  163. package/init/seed/minimal/.gitignore +1 -0
  164. package/init/seed/minimal/publish.os/assets/favicon/favicon.dark.png +0 -0
  165. package/init/seed/minimal/publish.os/assets/favicon/favicon.png +0 -0
  166. package/init/seed/minimal/publish.os/docs/content/get-started.mdx +6 -10
  167. package/init/seed/minimal/publish.os/docs/content/groups.mdx +1 -1
  168. package/init/seed/minimal/publish.os/docs/content/{theme.mdx → themes.mdx} +14 -12
  169. package/init/seed/minimal/publish.os/publish.os.config.json +3 -7
  170. package/init/seed/minimal/publish.os/theme.css +52 -24
  171. package/init/seed/minimal/publish.os/tsconfig.json +3 -0
  172. package/init/seed/minimal/seed.json +4 -1
  173. package/init/seed/scratch/publish.os/docs/content/get-started.mdx +6 -0
  174. package/init/seed/scratch/publish.os/publish.os.config.json +4 -0
  175. package/init/seed/scratch/seed.json +5 -0
  176. package/package.json +12 -26
  177. package/release/app/phosphor/package.json +3 -3
  178. package/release/app/phosphor/pnpm-lock.yaml.template +523 -360
  179. package/release/app/phosphor/pnpm-workspace.yaml +3 -0
  180. package/release/compiler/package.json +1 -1
  181. package/release/compiler/pnpm-lock.yaml.template +439 -286
  182. package/release/compiler/pnpm-workspace.yaml +3 -0
  183. package/release/release.json +7 -36
  184. package/compiler-assets/nextjs/defaults/group/leaf.tsx +0 -48
  185. package/dist/icons.d.ts +0 -1
  186. package/dist/icons.js +0 -2
  187. package/dist/kernel/components/mdx/components/pre.d.ts +0 -6
  188. package/dist/kernel/components/mdx/components/pre.js +0 -18
  189. package/dist/kernel/components/ui/meta-key.d.ts +0 -17
  190. package/dist/kernel/components/ui/meta-key.js +0 -9
  191. package/dist/kernel/framework-icons.d.ts +0 -1
  192. package/dist/kernel/framework-icons.js +0 -39
  193. package/dist/link-CYaCRDb6.d.ts +0 -35
  194. package/dist/route-metadata-og-image-BeKKFHg7.d.ts +0 -58
  195. package/dist/types-sM2g9nW0.d.ts +0 -12
  196. package/release/app/lucide/package.json +0 -30
  197. package/release/app/lucide/pnpm-lock.yaml.template +0 -2905
  198. package/release/app/lucide/pnpm-workspace.yaml +0 -11
  199. package/release/app/tabler/package.json +0 -30
  200. package/release/app/tabler/pnpm-lock.yaml.template +0 -2911
  201. package/release/app/tabler/pnpm-workspace.yaml +0 -11
@@ -0,0 +1,1455 @@
1
+ import {
2
+ ASSET_PREFIX,
3
+ PUBLIC_ASSET_DIRECTORY,
4
+ isExcludedSourcePath,
5
+ isSameOrDescendant
6
+ } from "./chunk-YLTOLYWM.js";
7
+ import {
8
+ AppError
9
+ } from "./chunk-WDY4IK6Z.js";
10
+
11
+ // src/lib/compiler/source/config/root-content-group.ts
12
+ var ROOT_CONTENT_GROUP_KEY = "__root", ROOT_CONTENT_GROUP_TITLE = "Root";
13
+
14
+ // src/lib/compiler/source/assert-source-dependency.ts
15
+ import { lstat } from "fs/promises";
16
+ import { isAbsolute, relative, resolve, sep } from "path";
17
+ async function assertSourceDependency({
18
+ publishOsDir,
19
+ path,
20
+ sourceFile
21
+ }) {
22
+ let root = resolve(publishOsDir), target = resolve(path), targetRelativePath = relative(root, target), details = {
23
+ file: (isAbsolute(sourceFile) ? relative(root, sourceFile) : sourceFile).split(sep).join("/"),
24
+ target: targetRelativePath.split(sep).join("/")
25
+ };
26
+ if (!isSameOrDescendant(root, target))
27
+ throw new AppError({
28
+ code: "FRAMEWORK_SOURCE_DEPENDENCY_ESCAPE",
29
+ message: "Source dependencies must stay inside publish.os.",
30
+ details
31
+ });
32
+ if (isExcludedSourcePath(details.target))
33
+ throw new AppError({
34
+ code: "FRAMEWORK_SOURCE_DEPENDENCY_EXCLUDED",
35
+ message: "Source dependencies cannot use nested sites, generated output, or root IDE metadata.",
36
+ details
37
+ });
38
+ let segments = targetRelativePath ? targetRelativePath.split(sep) : [], current = root;
39
+ for (let segment of ["", ...segments]) {
40
+ current = resolve(current, segment);
41
+ let entry = await lstat(current).catch((error) => {
42
+ throw error.code !== "ENOENT" && error.code !== "ENOTDIR" ? error : new AppError({
43
+ code: "FRAMEWORK_SOURCE_DEPENDENCY_MISSING",
44
+ message: "A source dependency does not exist.",
45
+ details
46
+ });
47
+ });
48
+ if (entry.isSymbolicLink())
49
+ throw new AppError({
50
+ code: "FRAMEWORK_SOURCE_DEPENDENCY_SYMLINK",
51
+ message: "Source dependencies cannot use symbolic links.",
52
+ details
53
+ });
54
+ if (current === target ? !entry.isFile() : !entry.isDirectory())
55
+ throw new AppError({
56
+ code: "FRAMEWORK_SOURCE_DEPENDENCY_NOT_FILE",
57
+ message: "A source dependency must resolve to a regular file.",
58
+ details
59
+ });
60
+ }
61
+ }
62
+
63
+ // src/lib/compiler/source/css-imports.ts
64
+ function skipString(css, start) {
65
+ let quote = css[start];
66
+ for (let index = start + 1; index < css.length; index += 1)
67
+ if (css[index] === "\\") index += 1;
68
+ else if (css[index] === quote) return index + 1;
69
+ return css.length;
70
+ }
71
+ function maskComments(css) {
72
+ let result = "";
73
+ for (let index = 0; index < css.length; )
74
+ if (css[index] === '"' || css[index] === "'") {
75
+ let end = skipString(css, index);
76
+ result += css.slice(index, end), index = end;
77
+ } else if (css.startsWith("/*", index)) {
78
+ let closing = css.indexOf("*/", index + 2), end = closing < 0 ? css.length : closing + 2;
79
+ result += css.slice(index, end).replace(/[^\r\n]/g, " "), index = end;
80
+ } else
81
+ result += css[index], index += 1;
82
+ return result;
83
+ }
84
+ function decodeCssPath(value) {
85
+ return value.replace(/\\(?:\r\n|[\r\n\f])/g, "").replace(
86
+ /\\([\da-f]{1,6})[\t\n\f\r ]?|\\(.)/gi,
87
+ (_match, hex, escaped) => {
88
+ if (!hex) return escaped ?? "";
89
+ let codePoint = Number.parseInt(hex, 16);
90
+ return codePoint > 0 && codePoint <= 1114111 && !(codePoint >= 55296 && codePoint <= 57343) ? String.fromCodePoint(codePoint) : "\uFFFD";
91
+ }
92
+ );
93
+ }
94
+ function statementEnd(scan, start) {
95
+ let depth = 0;
96
+ for (let index = start; index < scan.length; index += 1)
97
+ if (scan[index] === '"' || scan[index] === "'")
98
+ index = skipString(scan, index) - 1;
99
+ else if (scan[index] === "\\")
100
+ index += 1;
101
+ else if (scan[index] === "(")
102
+ depth += 1;
103
+ else if (scan[index] === ")")
104
+ depth -= 1;
105
+ else {
106
+ if (scan[index] === ";" && depth === 0)
107
+ return index + 1;
108
+ if ((scan[index] === "{" || scan[index] === "}") && depth === 0)
109
+ return index;
110
+ }
111
+ return scan.length;
112
+ }
113
+ function closingParenthesis(scan, start, end) {
114
+ let depth = 0;
115
+ for (let index = start; index < end; index += 1)
116
+ if (scan[index] === '"' || scan[index] === "'")
117
+ index = skipString(scan, index) - 1;
118
+ else if (scan[index] === "\\")
119
+ index += 1;
120
+ else if (scan[index] === "(")
121
+ depth += 1;
122
+ else if (scan[index] === ")" && --depth === 0)
123
+ return index;
124
+ return end;
125
+ }
126
+ function importConditions({
127
+ css,
128
+ scan,
129
+ start,
130
+ end
131
+ }) {
132
+ let result = {}, tailEnd = scan[end - 1] === ";" ? end - 1 : end, cursor = start, skipWhitespace = () => {
133
+ for (; cursor < tailEnd && /\s/.test(scan[cursor] ?? ""); ) cursor += 1;
134
+ };
135
+ skipWhitespace();
136
+ let layer = /^layer(?=\(|\s|$)/i.exec(scan.slice(cursor, tailEnd));
137
+ if (layer) {
138
+ if (cursor += layer[0].length, scan[cursor] === "(") {
139
+ let closing = closingParenthesis(scan, cursor, tailEnd);
140
+ result.layer = css.slice(cursor + 1, closing).trim(), cursor = closing + 1;
141
+ } else
142
+ result.layer = !0;
143
+ skipWhitespace();
144
+ }
145
+ let supports = /^supports\(/i.exec(scan.slice(cursor, tailEnd));
146
+ if (supports) {
147
+ let opening = cursor + supports[0].length - 1, closing = closingParenthesis(scan, opening, tailEnd);
148
+ result.supports = css.slice(opening + 1, closing).trim(), cursor = closing + 1, skipWhitespace();
149
+ }
150
+ return scan.slice(cursor, tailEnd).trim() && (result.media = css.slice(cursor, tailEnd).trim()), result;
151
+ }
152
+ function cssImports({ css }) {
153
+ let scan = maskComments(css), pattern = /@import\s+(?:"((?:\\.|[^"\\])*)"|'((?:\\.|[^'\\])*)'|url\(\s*(?:"((?:\\.|[^"\\])*)"|'((?:\\.|[^'\\])*)'|([^\s)]+))\s*\))/giy, imports = [];
154
+ for (let index = 0; index < scan.length; ) {
155
+ if (scan[index] === '"' || scan[index] === "'") {
156
+ index = skipString(scan, index);
157
+ continue;
158
+ }
159
+ if (scan[index] === "\\") {
160
+ index += 2;
161
+ continue;
162
+ }
163
+ pattern.lastIndex = index;
164
+ let match = scan[index] === "@" ? pattern.exec(scan) : null;
165
+ if (!match) {
166
+ index += 1;
167
+ continue;
168
+ }
169
+ let path = decodeCssPath(
170
+ match.slice(1).find((value) => value !== void 0) ?? ""
171
+ ), end = statementEnd(scan, pattern.lastIndex);
172
+ imports.push({
173
+ path,
174
+ start: index,
175
+ end,
176
+ ...importConditions({ css, scan, start: pattern.lastIndex, end })
177
+ }), index = end;
178
+ }
179
+ return imports;
180
+ }
181
+ function localCssImports({ css }) {
182
+ return cssImports({ css }).filter(({ path }) => path.startsWith("."));
183
+ }
184
+
185
+ // src/lib/compiler/source/assets/favicons-and-logos.ts
186
+ import { createHash } from "crypto";
187
+ import {
188
+ lstat as lstat2,
189
+ mkdir,
190
+ readdir,
191
+ readFile,
192
+ rm,
193
+ stat,
194
+ writeFile
195
+ } from "fs/promises";
196
+ import { dirname, extname, join, resolve as resolve2 } from "path";
197
+
198
+ // src/lib/compiler/source/assets/image-admission.ts
199
+ import sharp from "sharp";
200
+ function hasImageSignature(options) {
201
+ let { contents, format } = options;
202
+ if (format === "png")
203
+ return contents.length >= 8 && contents.readUInt32BE(0) === 2303741511;
204
+ if (format === "jpeg")
205
+ return contents.length >= 2 && contents[0] === 255 && contents[1] === 216;
206
+ if (format === "gif") {
207
+ let header = contents.subarray(0, 6).toString("ascii");
208
+ return header === "GIF87a" || header === "GIF89a";
209
+ }
210
+ return format === "webp" ? contents.subarray(0, 4).toString("ascii") === "RIFF" && contents.subarray(8, 12).toString("ascii") === "WEBP" : format === "avif" ? contents.subarray(4, 12).toString("ascii").includes("ftyp") : contents.length >= 6 && contents[0] === 0 && contents[1] === 0 && contents[2] === 1 && contents[3] === 0 && contents.readUInt16LE(4) > 0;
211
+ }
212
+ async function readRasterMetadata(options) {
213
+ let metadata;
214
+ try {
215
+ metadata = await sharp(options.contents, { animated: !0 }).metadata();
216
+ } catch (error) {
217
+ throw new AppError({
218
+ code: `${options.errorPrefix}_DIMENSIONS_UNREADABLE`,
219
+ message: `${options.path} has image metadata that could not be read`,
220
+ details: {
221
+ path: options.path,
222
+ cause: error instanceof Error ? error.message : String(error)
223
+ }
224
+ });
225
+ }
226
+ let rawWidth = metadata.width, rawHeight = metadata.pageHeight ?? metadata.height;
227
+ if (!rawWidth || !rawHeight)
228
+ throw new AppError({
229
+ code: `${options.errorPrefix}_DIMENSIONS_UNREADABLE`,
230
+ message: `${options.path} is missing readable raster image dimensions`,
231
+ details: { path: options.path }
232
+ });
233
+ let orientation = metadata.orientation ?? 1, rotated = orientation >= 5 && orientation <= 8, width = rotated ? rawHeight : rawWidth, height = rotated ? rawWidth : rawHeight, { maxRasterMegapixels, maxRasterSidePixels } = options.policy;
234
+ if (width > maxRasterSidePixels || height > maxRasterSidePixels || width * height > maxRasterMegapixels * 1e6)
235
+ throw new AppError({
236
+ code: `${options.errorPrefix}_DIMENSIONS_TOO_LARGE`,
237
+ message: `${options.path} is ${width}x${height}. Raster images must be no larger than ${maxRasterSidePixels}px on either side and ${maxRasterMegapixels} megapixels.`,
238
+ details: {
239
+ path: options.path,
240
+ width,
241
+ height,
242
+ maxSidePixels: maxRasterSidePixels,
243
+ maxMegapixels: maxRasterMegapixels
244
+ }
245
+ });
246
+ return {
247
+ width,
248
+ height,
249
+ animated: (metadata.pages ?? 1) > 1
250
+ };
251
+ }
252
+ function sanitizeSvg(options) {
253
+ if (options.contents.byteLength > options.policy.maxSvgBytes)
254
+ throw new AppError({
255
+ code: `${options.errorPrefix}_SVG_TOO_LARGE`,
256
+ message: `${options.path} is larger than the ${Math.round(options.policy.maxSvgBytes / 1024)} KB SVG limit`,
257
+ details: { path: options.path, bytes: options.contents.byteLength }
258
+ });
259
+ let source = options.contents.toString("utf-8");
260
+ if (!source.toLowerCase().includes("<svg"))
261
+ throw new AppError({
262
+ code: `${options.errorPrefix}_SVG_INVALID`,
263
+ message: `${options.path} is not a valid SVG document`,
264
+ details: { path: options.path }
265
+ });
266
+ let blocked = [
267
+ /<!doctype/i,
268
+ /<!entity/i,
269
+ /<script[\s>]/i,
270
+ /<foreignobject[\s>]/i,
271
+ /<iframe[\s>]/i,
272
+ /<object[\s>]/i,
273
+ /<embed[\s>]/i,
274
+ /<audio[\s>]/i,
275
+ /<video[\s>]/i,
276
+ /<animate(?:transform|motion)?[\s>]/i,
277
+ /\son[a-z]+\s*=/i,
278
+ /javascript\s*:/i,
279
+ /\b(?:href|xlink:href|src)\s*=\s*["'](?:https?:|data:|\/\/)/i,
280
+ /url\(\s*["']?(?:https?:|data:|\/\/)/i
281
+ ].find((pattern) => pattern.test(source));
282
+ if (blocked)
283
+ throw new AppError({
284
+ code: `${options.errorPrefix}_SVG_UNSAFE`,
285
+ message: `${options.path} contains unsafe SVG content`,
286
+ details: { path: options.path, pattern: String(blocked) }
287
+ });
288
+ return source = source.replace(/<!--[\s\S]*?-->/g, "").replace(/<\?xml[\s\S]*?\?>/gi, "").trim(), Buffer.from(source, "utf-8");
289
+ }
290
+
291
+ // src/lib/compiler/source/assets/favicons-and-logos.ts
292
+ var FAVICONS_AND_LOGOS_PUBLIC_PREFIX = `${ASSET_PREFIX}/_publishos`, MAX_FAVICON_BYTES = 1 * 1024 * 1024, MAX_LOGO_BYTES = 2 * 1024 * 1024, FAVICON_IMAGE_ADMISSION_POLICY = {
293
+ maxRasterMegapixels: 40,
294
+ maxRasterSidePixels: 8192,
295
+ maxSvgBytes: 256 * 1024
296
+ }, FAVICON_ASSET_EXTENSIONS = ["ico", "svg", "png", "jpg", "jpeg"], FAVICON_ASSET_STEMS = [
297
+ "favicon",
298
+ "favicon.light",
299
+ "favicon.dark",
300
+ "apple-icon"
301
+ ], FAVICON_ASSET_EXAMPLES = [
302
+ "favicon.ico",
303
+ "favicon.svg",
304
+ "favicon.light.svg",
305
+ "favicon.dark.svg",
306
+ "apple-icon.png",
307
+ "acme.favicon.light.svg",
308
+ "acme-favicon.dark.png"
309
+ ], LOGO_ASSET_EXTENSIONS = ["svg", "png", "jpg", "jpeg", "webp", "avif"], LOGO_ASSET_STEMS = [
310
+ "logo",
311
+ "logo.light",
312
+ "logo.dark",
313
+ "mark",
314
+ "mark.light",
315
+ "mark.dark",
316
+ "wordmark",
317
+ "wordmark.light",
318
+ "wordmark.dark"
319
+ ], LOGO_ASSET_EXAMPLES = LOGO_ASSET_STEMS.map((stem) => `${stem}.svg`), LOGO_ROLE_SUFFIXES = [
320
+ { suffix: "logo", kind: "logo-light", role: "light" },
321
+ { suffix: "logo.light", kind: "logo-light", role: "light" },
322
+ { suffix: "logo.dark", kind: "logo-dark", role: "dark" }
323
+ ];
324
+ function hash(contents) {
325
+ return `sha256-${createHash("sha256").update(contents).digest("hex").slice(0, 16)}`;
326
+ }
327
+ async function directoryExists(path) {
328
+ return await stat(path).then((entry) => entry.isDirectory()).catch(() => !1);
329
+ }
330
+ async function immediateFiles(dir) {
331
+ let entries = await readdir(dir, { withFileTypes: !0 }).catch(() => []), files = [];
332
+ for (let entry of entries) {
333
+ if (isExcludedSourcePath(`assets/${entry.name}`)) continue;
334
+ let absolutePath = join(dir, entry.name), info = await lstat2(absolutePath);
335
+ if (info.isSymbolicLink())
336
+ throw new AppError({
337
+ code: "FRAMEWORK_FAVICONS_LOGOS_SYMLINK_REJECTED",
338
+ message: `Favicon/logo asset "${entry.name}" is a symlink. Symlinks are not supported in publish.os/assets/favicon or publish.os/assets/logo.`,
339
+ details: { path: absolutePath }
340
+ });
341
+ if (info.isDirectory())
342
+ throw new AppError({
343
+ code: "FRAMEWORK_FAVICONS_LOGOS_NESTED_PATH_REJECTED",
344
+ message: `Favicon/logo asset folder "${entry.name}" is not supported. Favicon and logo files must be direct children of their folder.`,
345
+ details: { path: absolutePath }
346
+ });
347
+ info.isFile() && files.push(entry.name);
348
+ }
349
+ return files.sort();
350
+ }
351
+ async function assertNoRootFaviconOrLogoFiles(publishOsDir) {
352
+ let entries = await readdir(publishOsDir, { withFileTypes: !0 }).catch(
353
+ () => []
354
+ );
355
+ for (let entry of entries) {
356
+ if (!entry.isFile()) continue;
357
+ let file = entry.name;
358
+ if (!/^(?:favicon(?:\.(?:light|dark))?|apple-icon|logo(?:\.(?:light|dark))?|mark(?:\.(?:light|dark))?|wordmark(?:\.(?:light|dark))?)\.[a-z0-9]+$/i.test(
359
+ file
360
+ ))
361
+ continue;
362
+ let target = file.startsWith("apple-icon") ? `assets/favicon/${file}` : file.startsWith("favicon") ? `assets/favicon/${file}` : `assets/logo/${file}`;
363
+ throw new AppError({
364
+ code: "FRAMEWORK_FAVICONS_LOGOS_ROOT_PATH_REJECTED",
365
+ message: `publish.os/${file} is not a supported favicon/logo path. Move it to publish.os/${target}.`,
366
+ details: { path: `publish.os/${file}`, target: `publish.os/${target}` }
367
+ });
368
+ }
369
+ }
370
+ async function classifyFaviconOrLogoAsset(options) {
371
+ let ext = extname(options.file).slice(1).toLowerCase(), relativePath = `assets/${options.section}/${options.file}`;
372
+ if (ext === "ico") {
373
+ if (!hasImageSignature({ contents: options.contents, format: "ico" }))
374
+ throw new AppError({
375
+ code: "FRAMEWORK_FAVICONS_LOGOS_ICO_INVALID",
376
+ message: `${relativePath} is not a valid ICO file`,
377
+ details: { path: relativePath }
378
+ });
379
+ return {
380
+ mimeType: "image/x-icon",
381
+ outputContents: options.contents
382
+ };
383
+ }
384
+ if (ext === "png" && hasImageSignature({ contents: options.contents, format: "png" }))
385
+ return {
386
+ mimeType: "image/png",
387
+ outputContents: options.contents,
388
+ ...await readRasterMetadata({
389
+ contents: options.contents,
390
+ errorPrefix: "FRAMEWORK_FAVICONS_LOGOS",
391
+ path: relativePath,
392
+ policy: FAVICON_IMAGE_ADMISSION_POLICY
393
+ })
394
+ };
395
+ if ((ext === "jpg" || ext === "jpeg") && hasImageSignature({ contents: options.contents, format: "jpeg" }))
396
+ return {
397
+ mimeType: "image/jpeg",
398
+ outputContents: options.contents,
399
+ ...await readRasterMetadata({
400
+ contents: options.contents,
401
+ errorPrefix: "FRAMEWORK_FAVICONS_LOGOS",
402
+ path: relativePath,
403
+ policy: FAVICON_IMAGE_ADMISSION_POLICY
404
+ })
405
+ };
406
+ if (options.section === "logo" && ext === "webp" && hasImageSignature({ contents: options.contents, format: "webp" }))
407
+ return {
408
+ mimeType: "image/webp",
409
+ outputContents: options.contents,
410
+ ...await readRasterMetadata({
411
+ contents: options.contents,
412
+ errorPrefix: "FRAMEWORK_FAVICONS_LOGOS",
413
+ path: relativePath,
414
+ policy: FAVICON_IMAGE_ADMISSION_POLICY
415
+ })
416
+ };
417
+ if (options.section === "logo" && ext === "avif" && hasImageSignature({ contents: options.contents, format: "avif" }))
418
+ return {
419
+ mimeType: "image/avif",
420
+ outputContents: options.contents,
421
+ ...await readRasterMetadata({
422
+ contents: options.contents,
423
+ errorPrefix: "FRAMEWORK_FAVICONS_LOGOS",
424
+ path: relativePath,
425
+ policy: FAVICON_IMAGE_ADMISSION_POLICY
426
+ })
427
+ };
428
+ if (ext === "svg")
429
+ return {
430
+ mimeType: "image/svg+xml",
431
+ outputContents: sanitizeSvg({
432
+ contents: options.contents,
433
+ errorPrefix: "FRAMEWORK_FAVICONS_LOGOS",
434
+ path: relativePath,
435
+ policy: FAVICON_IMAGE_ADMISSION_POLICY
436
+ })
437
+ };
438
+ throw new AppError({
439
+ code: "FRAMEWORK_FAVICONS_LOGOS_TYPE_REJECTED",
440
+ message: `${relativePath} is not an allowed favicon/logo asset type`,
441
+ details: { path: relativePath }
442
+ });
443
+ }
444
+ function matchesFaviconStem(stem, role) {
445
+ return stem === role ? !0 : [".", "-"].some(
446
+ (separator) => stem.length > role.length + separator.length && stem.endsWith(`${separator}${role}`)
447
+ );
448
+ }
449
+ function faviconCandidate(file) {
450
+ let ext = extname(file).slice(1).toLowerCase(), stem = file.slice(0, -(ext.length + 1));
451
+ if (!FAVICON_ASSET_EXTENSIONS.includes(ext))
452
+ throw new AppError({
453
+ code: "FRAMEWORK_FAVICONS_LOGOS_TYPE_REJECTED",
454
+ message: `assets/favicon/${file} is not an allowed favicon asset type`,
455
+ details: { path: `assets/favicon/${file}` }
456
+ });
457
+ if (matchesFaviconStem(stem, "favicon") && ext === "ico")
458
+ return {
459
+ file,
460
+ kind: "favicon-fallback",
461
+ role: "fallback",
462
+ section: "favicon"
463
+ };
464
+ if (matchesFaviconStem(stem, "favicon") && ["svg", "png", "jpg", "jpeg"].includes(ext))
465
+ return {
466
+ file,
467
+ kind: "favicon-light",
468
+ role: "light",
469
+ section: "favicon"
470
+ };
471
+ if (matchesFaviconStem(stem, "favicon.light") && ["ico", "svg", "png", "jpg", "jpeg"].includes(ext))
472
+ return {
473
+ file,
474
+ kind: "favicon-light",
475
+ role: "light",
476
+ section: "favicon"
477
+ };
478
+ if (matchesFaviconStem(stem, "favicon.dark") && ["ico", "svg", "png", "jpg", "jpeg"].includes(ext))
479
+ return {
480
+ file,
481
+ kind: "favicon-dark",
482
+ role: "dark",
483
+ section: "favicon"
484
+ };
485
+ if (matchesFaviconStem(stem, "apple-icon") && ext === "svg")
486
+ throw new AppError({
487
+ code: "FRAMEWORK_FAVICONS_LOGOS_TYPE_REJECTED",
488
+ message: `assets/favicon/${file} is not supported. Use a PNG or JPEG Apple icon.`,
489
+ details: { path: `assets/favicon/${file}` }
490
+ });
491
+ if (matchesFaviconStem(stem, "apple-icon") && ["png", "jpg", "jpeg"].includes(ext))
492
+ return {
493
+ file,
494
+ kind: "apple-touch",
495
+ role: "apple",
496
+ section: "favicon"
497
+ };
498
+ throw new AssetNameRejectedWarning({
499
+ code: "FRAMEWORK_FAVICONS_LOGOS_NAME_REJECTED",
500
+ message: `assets/favicon/${file} is not a supported favicon asset name. Supported favicon files include ${FAVICON_ASSET_EXAMPLES.join(", ")}.`,
501
+ details: {
502
+ acceptedExtensions: FAVICON_ASSET_EXTENSIONS.map((extension) => `.${extension}`),
503
+ acceptedNames: FAVICON_ASSET_EXAMPLES,
504
+ acceptedStems: FAVICON_ASSET_STEMS,
505
+ file: `assets/favicon/${file}`,
506
+ path: `assets/favicon/${file}`
507
+ }
508
+ });
509
+ }
510
+ function logoCandidate(file) {
511
+ let ext = extname(file).slice(1).toLowerCase(), stem = file.slice(0, -(ext.length + 1));
512
+ if (!LOGO_ASSET_EXTENSIONS.includes(ext))
513
+ throw new AppError({
514
+ code: "FRAMEWORK_FAVICONS_LOGOS_TYPE_REJECTED",
515
+ message: `assets/logo/${file} is not an allowed logo asset type`,
516
+ details: { path: `assets/logo/${file}` }
517
+ });
518
+ let flexibleLogoRole = {
519
+ logo: { kind: "logo-light", role: "light" },
520
+ "logo.light": { kind: "logo-light", role: "light" },
521
+ "logo.dark": { kind: "logo-dark", role: "dark" },
522
+ mark: { kind: "mark-light", role: "markLight" },
523
+ "mark.light": { kind: "mark-light", role: "markLight" },
524
+ "mark.dark": { kind: "mark-dark", role: "markDark" },
525
+ wordmark: { kind: "wordmark-light", role: "wordmarkLight" },
526
+ "wordmark.light": { kind: "wordmark-light", role: "wordmarkLight" },
527
+ "wordmark.dark": { kind: "wordmark-dark", role: "wordmarkDark" }
528
+ }[stem] ?? LOGO_ROLE_SUFFIXES.find((entry) => stem === entry.suffix || stem.endsWith(`.${entry.suffix}`) || stem.endsWith(`-${entry.suffix}`));
529
+ if (!flexibleLogoRole)
530
+ throw new AssetNameRejectedWarning({
531
+ code: "FRAMEWORK_FAVICONS_LOGOS_NAME_REJECTED",
532
+ message: [
533
+ `assets/logo/${file} is not a supported logo name.`,
534
+ "Supported file types: svg, png, jpg, jpeg, webp, avif.",
535
+ "Naming: *logo, *logo.light, *logo.dark.",
536
+ "Example:",
537
+ " assets/logo/acme.logo.light.svg",
538
+ " assets/logo/acme.logo.dark.svg"
539
+ ].join(`
540
+ `),
541
+ details: {
542
+ acceptedExtensions: LOGO_ASSET_EXTENSIONS.map((extension) => `.${extension}`),
543
+ acceptedNames: [
544
+ ...LOGO_ASSET_EXAMPLES,
545
+ "brand.logo.svg",
546
+ "brand.logo.light.svg",
547
+ "brand.logo.dark.svg"
548
+ ],
549
+ acceptedStems: [
550
+ ...LOGO_ASSET_STEMS,
551
+ "*.logo",
552
+ "*-logo",
553
+ "*.logo.light",
554
+ "*-logo.light",
555
+ "*.logo.dark",
556
+ "*-logo.dark"
557
+ ],
558
+ file: `assets/logo/${file}`,
559
+ path: `assets/logo/${file}`
560
+ }
561
+ });
562
+ return {
563
+ file,
564
+ kind: flexibleLogoRole.kind,
565
+ role: flexibleLogoRole.role,
566
+ section: "logo"
567
+ };
568
+ }
569
+ var AssetNameRejectedWarning = class extends Error {
570
+ code;
571
+ details;
572
+ constructor(warning) {
573
+ super(warning.message), this.name = "AssetNameRejectedWarning", this.code = warning.code, this.details = warning.details;
574
+ }
575
+ };
576
+ function collectFaviconCandidates(files) {
577
+ let candidates = [], warnings = [];
578
+ for (let file of files)
579
+ try {
580
+ candidates.push(faviconCandidate(file));
581
+ } catch (error) {
582
+ if (error instanceof AssetNameRejectedWarning) {
583
+ warnings.push({
584
+ code: error.code,
585
+ message: error.message,
586
+ details: error.details
587
+ });
588
+ continue;
589
+ }
590
+ throw error;
591
+ }
592
+ return { candidates, warnings };
593
+ }
594
+ function collectLogoCandidates(files) {
595
+ let candidates = [], warnings = [];
596
+ for (let file of files)
597
+ try {
598
+ candidates.push(logoCandidate(file));
599
+ } catch (error) {
600
+ if (error instanceof AssetNameRejectedWarning) {
601
+ warnings.push({
602
+ code: error.code,
603
+ message: error.message,
604
+ details: error.details
605
+ });
606
+ continue;
607
+ }
608
+ throw error;
609
+ }
610
+ return { candidates, warnings };
611
+ }
612
+ function assertUniqueRole(candidates, section) {
613
+ let seen = /* @__PURE__ */ new Map();
614
+ for (let candidate of candidates) {
615
+ let key = `${candidate.section}:${candidate.role}`, existing = seen.get(key);
616
+ if (!existing) {
617
+ seen.set(key, candidate.file);
618
+ continue;
619
+ }
620
+ throw new AppError({
621
+ code: "FRAMEWORK_FAVICONS_LOGOS_DUPLICATE_ROLE",
622
+ message: `assets/${section}/${existing} and assets/${section}/${candidate.file} both define the same ${section} role`,
623
+ details: {
624
+ role: candidate.role,
625
+ first: `assets/${section}/${existing}`,
626
+ second: `assets/${section}/${candidate.file}`
627
+ }
628
+ });
629
+ }
630
+ }
631
+ function buildEntry(options) {
632
+ let contentHash = hash(options.asset.outputContents), hashPath = contentHash.replace(/^sha256-/, ""), publicPath = `${ASSET_PREFIX}/assets/${options.candidate.section}/${options.candidate.file}`, outputPath = `${FAVICONS_AND_LOGOS_PUBLIC_PREFIX}/${options.candidate.section}/${hashPath}/${options.candidate.file}`, sizes = options.asset.width && options.asset.height ? `${options.asset.width}x${options.asset.height}` : options.asset.mimeType === "image/svg+xml" || options.asset.mimeType === "image/x-icon" ? "any" : void 0;
633
+ return {
634
+ entry: {
635
+ kind: options.candidate.kind,
636
+ sourcePath: options.sourcePath,
637
+ publicPath,
638
+ outputPath,
639
+ contentHash,
640
+ mimeType: options.asset.mimeType,
641
+ byteSize: options.asset.outputContents.byteLength,
642
+ width: options.asset.width,
643
+ height: options.asset.height,
644
+ sizes
645
+ },
646
+ outputs: [
647
+ { contents: options.asset.outputContents, outputPath },
648
+ { contents: options.asset.outputContents, outputPath: publicPath }
649
+ ]
650
+ };
651
+ }
652
+ function assertFaviconShape(options) {
653
+ if (options.candidate.section === "favicon" && options.candidate.role !== "apple" && !(!options.asset.width || !options.asset.height) && options.asset.width !== options.asset.height)
654
+ throw new AppError({
655
+ code: "FRAMEWORK_FAVICONS_LOGOS_FAVICON_NOT_SQUARE",
656
+ message: `assets/favicon/${options.candidate.file} must be square`,
657
+ details: {
658
+ height: options.asset.height,
659
+ path: `assets/favicon/${options.candidate.file}`,
660
+ width: options.asset.width
661
+ }
662
+ });
663
+ }
664
+ function assertAppleIconShape(options) {
665
+ if (options.candidate.section === "favicon" && options.candidate.role === "apple" && !(!options.asset.width || !options.asset.height) && options.asset.width !== options.asset.height)
666
+ throw new AppError({
667
+ code: "FRAMEWORK_FAVICONS_LOGOS_APPLE_ICON_NOT_SQUARE",
668
+ message: `assets/favicon/${options.candidate.file} must be square`,
669
+ details: {
670
+ height: options.asset.height,
671
+ path: `assets/favicon/${options.candidate.file}`,
672
+ width: options.asset.width
673
+ }
674
+ });
675
+ }
676
+ function metadataIcons(manifest) {
677
+ let icon = [], light = manifest.light && manifest.light !== manifest.fallback ? manifest.light : void 0;
678
+ light && manifest.dark ? (icon.push({
679
+ url: light.outputPath,
680
+ media: "(prefers-color-scheme: light)",
681
+ type: light.mimeType,
682
+ sizes: light.sizes
683
+ }), icon.push({
684
+ url: manifest.dark.outputPath,
685
+ media: "(prefers-color-scheme: dark)",
686
+ type: manifest.dark.mimeType,
687
+ sizes: manifest.dark.sizes
688
+ })) : light ? icon.push({
689
+ url: light.outputPath,
690
+ type: light.mimeType,
691
+ sizes: light.sizes
692
+ }) : manifest.dark && icon.push({
693
+ url: manifest.dark.outputPath,
694
+ media: "(prefers-color-scheme: dark)",
695
+ type: manifest.dark.mimeType,
696
+ sizes: manifest.dark.sizes
697
+ }), manifest.fallback && icon.push({
698
+ url: manifest.fallback.outputPath,
699
+ type: manifest.fallback.mimeType,
700
+ sizes: manifest.fallback.sizes
701
+ });
702
+ let apple = manifest.apple ? [
703
+ {
704
+ url: manifest.apple.outputPath,
705
+ type: manifest.apple.mimeType,
706
+ sizes: manifest.apple.sizes
707
+ }
708
+ ] : void 0;
709
+ return {
710
+ ...icon.length > 0 ? { icon } : {},
711
+ ...apple ? { apple } : {}
712
+ };
713
+ }
714
+ function assignEntry(manifest, candidate, entry) {
715
+ if (candidate.section === "favicon") {
716
+ candidate.role === "fallback" ? manifest.favicon.fallback = entry : candidate.role === "light" ? manifest.favicon.light = entry : candidate.role === "dark" ? manifest.favicon.dark = entry : candidate.role === "apple" && (manifest.favicon.apple = entry);
717
+ return;
718
+ }
719
+ candidate.role === "light" ? manifest.logo.light = entry : candidate.role === "dark" ? manifest.logo.dark = entry : candidate.role === "markLight" ? manifest.logo.markLight = entry : candidate.role === "markDark" ? manifest.logo.markDark = entry : candidate.role === "wordmarkLight" ? manifest.logo.wordmarkLight = entry : candidate.role === "wordmarkDark" && (manifest.logo.wordmarkDark = entry);
720
+ }
721
+ async function buildFaviconsAndLogos(options) {
722
+ await assertNoRootFaviconOrLogoFiles(options.publishOsDir);
723
+ let faviconDir = resolve2(options.publishOsDir, "assets", "favicon"), logoDir = resolve2(options.publishOsDir, "assets", "logo"), faviconFiles = await directoryExists(faviconDir) ? await immediateFiles(faviconDir) : [], logoFiles = await directoryExists(logoDir) ? await immediateFiles(logoDir) : [], faviconCandidateResult = collectFaviconCandidates(faviconFiles), faviconCandidates = faviconCandidateResult.candidates, logoCandidateResult = collectLogoCandidates(logoFiles), logoCandidates = logoCandidateResult.candidates, warnings = [
724
+ ...faviconCandidateResult.warnings,
725
+ ...logoCandidateResult.warnings
726
+ ];
727
+ assertUniqueRole(faviconCandidates, "favicon"), assertUniqueRole(logoCandidates, "logo");
728
+ let manifest = {
729
+ assets: [],
730
+ favicon: {},
731
+ logo: {},
732
+ metadataIcons: {}
733
+ }, outputs = [];
734
+ for (let candidate of [...faviconCandidates, ...logoCandidates]) {
735
+ let baseDir = candidate.section === "favicon" ? faviconDir : logoDir, sourcePath = resolve2(baseDir, candidate.file);
736
+ if (!isSameOrDescendant(baseDir, sourcePath))
737
+ throw new AppError({
738
+ code: "FRAMEWORK_FAVICONS_LOGOS_PATH_REJECTED",
739
+ message: `${candidate.file} escapes publish.os/assets/${candidate.section}`,
740
+ details: { path: candidate.file }
741
+ });
742
+ let contents = await readFile(sourcePath), asset = await classifyFaviconOrLogoAsset({
743
+ contents,
744
+ file: candidate.file,
745
+ section: candidate.section
746
+ });
747
+ assertFaviconShape({ asset, candidate }), assertAppleIconShape({ asset, candidate });
748
+ let limit = candidate.section === "favicon" ? MAX_FAVICON_BYTES : MAX_LOGO_BYTES;
749
+ if (asset.outputContents.byteLength > limit)
750
+ throw new AppError({
751
+ code: "FRAMEWORK_FAVICONS_LOGOS_FILE_TOO_LARGE",
752
+ message: `assets/${candidate.section}/${candidate.file} is ${asset.outputContents.byteLength} bytes. Favicon/logo assets in this folder must be less than ${Math.round(limit / 1024 / 1024)} MB.`,
753
+ details: {
754
+ path: `assets/${candidate.section}/${candidate.file}`,
755
+ bytes: asset.outputContents.byteLength,
756
+ limit
757
+ }
758
+ });
759
+ let { entry, outputs: entryOutputs } = buildEntry({
760
+ asset,
761
+ candidate,
762
+ sourcePath: `publish.os/assets/${candidate.section}/${candidate.file}`
763
+ });
764
+ manifest.assets.push(entry), assignEntry(manifest, candidate, entry), outputs.push(...entryOutputs);
765
+ }
766
+ return !manifest.favicon.light && manifest.favicon.fallback && (manifest.favicon.light = manifest.favicon.fallback), !manifest.logo.dark && manifest.logo.light && (manifest.logo.dark = manifest.logo.light), !manifest.logo.markDark && manifest.logo.markLight && (manifest.logo.markDark = manifest.logo.markLight), !manifest.logo.wordmarkDark && manifest.logo.wordmarkLight && (manifest.logo.wordmarkDark = manifest.logo.wordmarkLight), manifest.metadataIcons = metadataIcons(manifest.favicon), { manifest, outputs, warnings };
767
+ }
768
+ async function writeFaviconsAndLogosOutputs(options) {
769
+ await rm(
770
+ resolve2(options.outputDir, "public", "_publishos", "favicons-and-logos"),
771
+ {
772
+ force: !0,
773
+ recursive: !0
774
+ }
775
+ ), await rm(resolve2(options.outputDir, "public", "_publishos", "identity"), {
776
+ force: !0,
777
+ recursive: !0
778
+ }), await rm(resolve2(options.outputDir, "public", "_publishos", "favicon"), {
779
+ force: !0,
780
+ recursive: !0
781
+ }), await rm(resolve2(options.outputDir, "public", "_publishos", "logo"), {
782
+ force: !0,
783
+ recursive: !0
784
+ }), await rm(
785
+ resolve2(
786
+ options.outputDir,
787
+ "public",
788
+ ...PUBLIC_ASSET_DIRECTORY.split("/"),
789
+ "_publishos",
790
+ "favicon"
791
+ ),
792
+ { force: !0, recursive: !0 }
793
+ ), await rm(
794
+ resolve2(
795
+ options.outputDir,
796
+ "public",
797
+ ...PUBLIC_ASSET_DIRECTORY.split("/"),
798
+ "_publishos",
799
+ "logo"
800
+ ),
801
+ { force: !0, recursive: !0 }
802
+ ), await rm(
803
+ resolve2(
804
+ options.outputDir,
805
+ "public",
806
+ ...PUBLIC_ASSET_DIRECTORY.split("/"),
807
+ "assets",
808
+ "favicon"
809
+ ),
810
+ { force: !0, recursive: !0 }
811
+ ), await rm(
812
+ resolve2(
813
+ options.outputDir,
814
+ "public",
815
+ ...PUBLIC_ASSET_DIRECTORY.split("/"),
816
+ "assets",
817
+ "logo"
818
+ ),
819
+ { force: !0, recursive: !0 }
820
+ ), await rm(resolve2(options.outputDir, "public", "favicon.ico"), {
821
+ force: !0
822
+ }), await rm(resolve2(options.outputDir, "public", "apple-touch-icon.png"), {
823
+ force: !0
824
+ }), await rm(
825
+ resolve2(options.outputDir, "public", "apple-touch-icon-precomposed.png"),
826
+ {
827
+ force: !0
828
+ }
829
+ );
830
+ for (let output of options.faviconsAndLogos.outputs) {
831
+ let relativeOutput = output.outputPath.replace(/^\/+/, ""), dest = resolve2(options.outputDir, "public", relativeOutput);
832
+ await mkdir(dirname(dest), { recursive: !0 }), await writeFile(dest, output.contents);
833
+ }
834
+ }
835
+ function faviconsAndLogosManifestModule(manifest) {
836
+ return `export const faviconsAndLogos = ${JSON.stringify(manifest, null, 2)} as const;
837
+
838
+ export type FaviconsAndLogosManifest = typeof faviconsAndLogos;
839
+ export type FaviconAndLogoAssetManifestEntry = FaviconsAndLogosManifest["assets"][number];
840
+ `;
841
+ }
842
+
843
+ // src/lib/compiler/source/routing/slug-path.ts
844
+ var SLUG_SEGMENT_RE = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;
845
+ function issue(code, message, details) {
846
+ return details ? { code, message, details } : { code, message };
847
+ }
848
+ function isLowerAlpha(code) {
849
+ return code >= 97 && code <= 122;
850
+ }
851
+ function isUpperAlpha(code) {
852
+ return code >= 65 && code <= 90;
853
+ }
854
+ function isDigit(code) {
855
+ return code >= 48 && code <= 57;
856
+ }
857
+ function isAsciiAlphaNumeric(code) {
858
+ return isLowerAlpha(code) || isUpperAlpha(code) || isDigit(code);
859
+ }
860
+ function isWhitespace(code) {
861
+ return code === 32 || code === 9 || code === 10 || code === 11 || code === 12 || code === 13 || code === 160;
862
+ }
863
+ function isUnsafeInvisible(code) {
864
+ return code <= 31 || code >= 127 && code <= 159 || code >= 8203 && code <= 8207 || code >= 8232 && code <= 8239 || code >= 8288 && code <= 8303 || code === 65279;
865
+ }
866
+ function isCombiningMark(code) {
867
+ return code >= 768 && code <= 879 || code >= 6832 && code <= 6911 || code >= 7616 && code <= 7679 || code >= 8400 && code <= 8447 || code >= 65056 && code <= 65071;
868
+ }
869
+ function hasPercentEscape(value) {
870
+ return /%[0-9a-fA-F]{2}/.test(value);
871
+ }
872
+ function appendNormalizedChar(output, char, pendingSeparator) {
873
+ pendingSeparator && output.length > 0 && output.push(pendingSeparator), output.push(char);
874
+ }
875
+ function queueSeparator(current, next) {
876
+ return current === "." || next === "." ? "." : "-";
877
+ }
878
+ function normalizeSegmentValue(input) {
879
+ let output = [], pendingSeparator, sanitized = !1;
880
+ for (let char of input.normalize("NFKD")) {
881
+ let code = char.codePointAt(0) ?? 0;
882
+ if (isCombiningMark(code)) {
883
+ sanitized = !0;
884
+ continue;
885
+ }
886
+ if (isAsciiAlphaNumeric(code)) {
887
+ let lower = isUpperAlpha(code) ? char.toLowerCase() : char;
888
+ pendingSeparator = appendNormalizedChar(output, lower, pendingSeparator), sanitized = sanitized || lower !== char;
889
+ continue;
890
+ }
891
+ if (char === "." || char === "-") {
892
+ pendingSeparator = queueSeparator(pendingSeparator, char);
893
+ continue;
894
+ }
895
+ if (isWhitespace(code) || char === "_" || isUnsafeInvisible(code)) {
896
+ pendingSeparator = queueSeparator(pendingSeparator, "-"), sanitized = !0;
897
+ continue;
898
+ }
899
+ pendingSeparator = queueSeparator(pendingSeparator, "-"), sanitized = !0;
900
+ }
901
+ return { value: output.join(""), sanitized };
902
+ }
903
+ function validateSegment(slug, options) {
904
+ return slug ? slug.length > options.maxSegmentLength ? {
905
+ code: "SLUG_SEGMENT_TOO_LONG",
906
+ error: `URL path must be ${options.maxSegmentLength} characters or less`,
907
+ isValid: !1
908
+ } : slug !== slug.toLowerCase() ? {
909
+ code: "SLUG_UPPERCASE_INVALID",
910
+ error: "URL path must be lowercase",
911
+ isValid: !1
912
+ } : SLUG_SEGMENT_RE.test(slug) ? { isValid: !0 } : {
913
+ code: "SLUG_PATTERN_INVALID",
914
+ error: 'URL path can only include lowercase letters, numbers, and single "." or "-" separators',
915
+ isValid: !1
916
+ } : {
917
+ code: "SLUG_EMPTY",
918
+ error: "URL path is required",
919
+ isValid: !1
920
+ };
921
+ }
922
+ function normalizeSlugSegmentInternal(input, options = {}) {
923
+ let maxSegmentLength = options.maxSegmentLength ?? 100, warnings = [], errors = [], trimmed = input.normalize("NFKC").trim(), rawLower = trimmed.toLowerCase();
924
+ hasPercentEscape(trimmed) && errors.push(
925
+ issue(
926
+ "PATH_PERCENT_ESCAPE_INVALID",
927
+ "Path input must not contain percent escapes",
928
+ {
929
+ value: input
930
+ }
931
+ )
932
+ ), (rawLower === "." || rawLower === "..") && errors.push(
933
+ issue(
934
+ "PATH_DOT_SEGMENT_INVALID",
935
+ "Path segments must not be dot traversal segments",
936
+ {
937
+ segment: input
938
+ }
939
+ )
940
+ );
941
+ let normalized = normalizeSegmentValue(trimmed), value = normalized.value;
942
+ value.length > maxSegmentLength && (errors.push(
943
+ issue(
944
+ "SLUG_SEGMENT_TOO_LONG",
945
+ `Path segments must be ${maxSegmentLength} characters or less`,
946
+ {
947
+ max: maxSegmentLength,
948
+ segment: input
949
+ }
950
+ )
951
+ ), value = value.slice(0, maxSegmentLength)), (normalized.sanitized || value !== trimmed) && warnings.push(
952
+ issue("PATH_SEGMENT_NORMALIZED", "Path segment was normalized", {
953
+ from: input,
954
+ to: value
955
+ })
956
+ ), !value && options.allowEmpty !== !0 && errors.push(
957
+ issue("PATH_SEGMENT_EMPTY", "Path segment normalized to an empty value", {
958
+ segment: input
959
+ })
960
+ );
961
+ let validation = value ? validateSegment(value, {
962
+ maxSegmentLength
963
+ }) : { isValid: !0 };
964
+ return validation.isValid || errors.push(issue(validation.code, validation.error, { segment: input })), {
965
+ changed: value !== input,
966
+ errors,
967
+ value,
968
+ warnings
969
+ };
970
+ }
971
+ function normalizePathParts(input, options = {}) {
972
+ let warnings = [], errors = [], maxPathLength = options.maxPathLength ?? 512, trimmed = input.normalize("NFKC").trim();
973
+ (/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(trimmed) || /^\/\/[^/]/.test(trimmed)) && errors.push(
974
+ issue(
975
+ "PATH_PROTOCOL_INVALID",
976
+ "Path input must not be a URL or protocol reference",
977
+ {
978
+ value: input
979
+ }
980
+ )
981
+ ), /[?#]/.test(trimmed) && errors.push(
982
+ issue(
983
+ "PATH_QUERY_HASH_INVALID",
984
+ "Path input must not include a query string or hash",
985
+ {
986
+ value: input
987
+ }
988
+ )
989
+ ), trimmed.includes("\\") && warnings.push(
990
+ issue(
991
+ "PATH_BACKSLASH_NORMALIZED",
992
+ "Backslashes were normalized to slashes",
993
+ {
994
+ value: input
995
+ }
996
+ )
997
+ );
998
+ let rawSegments = trimmed.replace(/\\/g, "/").split("/"), segments = [];
999
+ for (let rawSegment of rawSegments) {
1000
+ if (!rawSegment) continue;
1001
+ let segment = normalizeSlugSegmentInternal(rawSegment, options);
1002
+ warnings.push(...segment.warnings), errors.push(...segment.errors), segment.value && segments.push(segment.value);
1003
+ }
1004
+ segments.length === 0 && options.allowEmpty !== !0 && errors.push(
1005
+ issue("PATH_EMPTY", "Path input normalized to an empty value", {
1006
+ value: input
1007
+ })
1008
+ );
1009
+ let value = segments.join("/");
1010
+ return value.length > maxPathLength && errors.push(
1011
+ issue(
1012
+ "SLUG_PATH_TOO_LONG",
1013
+ `Path must be ${maxPathLength} characters or less`,
1014
+ {
1015
+ max: maxPathLength,
1016
+ value: input
1017
+ }
1018
+ )
1019
+ ), { errors, value, warnings };
1020
+ }
1021
+ function normalizeSlugPath(input, options = {}) {
1022
+ let normalized = normalizePathParts(input, options);
1023
+ return {
1024
+ ...normalized,
1025
+ changed: normalized.value !== input
1026
+ };
1027
+ }
1028
+ function normalizeUrlPath(input, options = {}) {
1029
+ let normalized = normalizePathParts(input, options), value = normalized.value ? `/${normalized.value}` : "/";
1030
+ return {
1031
+ ...normalized,
1032
+ value,
1033
+ changed: value !== input
1034
+ };
1035
+ }
1036
+ function joinUrlPath(parentPath, childPath) {
1037
+ if (childPath.startsWith("/"))
1038
+ return normalizeUrlPath(childPath).value;
1039
+ let parent = parentPath === "/" ? "" : parentPath.replace(/\/+$/, "");
1040
+ return normalizeUrlPath(`${parent}/${childPath}`).value;
1041
+ }
1042
+
1043
+ // src/lib/compiler/source/assets/media-assets.ts
1044
+ import { createHash as createHash2 } from "crypto";
1045
+ import {
1046
+ lstat as lstat3,
1047
+ mkdir as mkdir2,
1048
+ readdir as readdir2,
1049
+ readFile as readFile2,
1050
+ rm as rm2,
1051
+ writeFile as writeFile2
1052
+ } from "fs/promises";
1053
+ import { dirname as dirname2, join as join2, relative as relative2, resolve as resolve3, sep as sep2 } from "path";
1054
+ var DEFAULT_MAX_IMAGE_BYTES = 20 * 1024 * 1024, DEFAULT_MAX_VIDEO_BYTES = 20 * 1024 * 1024, DEFAULT_MAX_DOCUMENT_BYTES = 20 * 1024 * 1024, DEFAULT_MAX_OTHER_BYTES = 5 * 1024 * 1024, DEFAULT_MAX_TOTAL_BYTES = 100 * 1024 * 1024, DEFAULT_MAX_FILE_COUNT = 500, MEDIA_IMAGE_ADMISSION_POLICY = {
1055
+ maxRasterMegapixels: 40,
1056
+ maxRasterSidePixels: 8192,
1057
+ maxSvgBytes: 256 * 1024
1058
+ }, MEDIA_PUBLIC_PREFIX = `${ASSET_PREFIX}/assets`, GENERATED_MEDIA_PUBLIC_PREFIX = `${ASSET_PREFIX}/_publishos/assets`, RESERVED_FAVICON_AND_LOGO_ASSET_DIRS = /* @__PURE__ */ new Set(["favicon", "logo"]);
1059
+ function toPosixPath(path) {
1060
+ return path.split(sep2).join("/");
1061
+ }
1062
+ function normalizeAssetPath(relativePath) {
1063
+ if (relativePath.split("/").some(
1064
+ (segment) => segment === "" || segment === "." || segment === ".." || segment.startsWith(".")
1065
+ ))
1066
+ throw new AppError({
1067
+ code: "FRAMEWORK_MEDIA_PATH_REJECTED",
1068
+ message: `Media asset path "${relativePath}" is not allowed`,
1069
+ details: { path: relativePath }
1070
+ });
1071
+ let normalized = normalizeSlugPath(relativePath);
1072
+ if (normalized.errors.length > 0)
1073
+ throw new AppError({
1074
+ code: "FRAMEWORK_MEDIA_PATH_REJECTED",
1075
+ message: `Media asset path "${relativePath}" does not produce a safe URL path`,
1076
+ details: { errors: normalized.errors, path: relativePath }
1077
+ });
1078
+ return normalized.value;
1079
+ }
1080
+ function ignoredMediaPathWarning(relativePath) {
1081
+ return {
1082
+ code: "FRAMEWORK_MEDIA_PATH_IGNORED",
1083
+ message: "Hidden media file ignored.",
1084
+ details: { file: relativePath, path: relativePath }
1085
+ };
1086
+ }
1087
+ async function walkAssetFiles(dir, root = dir) {
1088
+ let entries = await readdir2(dir, { withFileTypes: !0 }).catch(() => []), files = [], warnings = [];
1089
+ for (let entry of entries) {
1090
+ let absolutePath = join2(dir, entry.name), relativePath = toPosixPath(relative2(root, absolutePath));
1091
+ if (isExcludedSourcePath(`assets/${relativePath}`)) continue;
1092
+ if (entry.name.startsWith(".")) {
1093
+ warnings.push(ignoredMediaPathWarning(relativePath));
1094
+ continue;
1095
+ }
1096
+ let info = await lstat3(absolutePath);
1097
+ if (info.isSymbolicLink())
1098
+ throw new AppError({
1099
+ code: "FRAMEWORK_MEDIA_SYMLINK_REJECTED",
1100
+ message: `Media asset "${relativePath}" is a symlink. Symlinks are not supported in publish.os/assets.`,
1101
+ details: { path: absolutePath }
1102
+ });
1103
+ if (info.isDirectory()) {
1104
+ let nested = await walkAssetFiles(absolutePath, root);
1105
+ files.push(...nested.files), warnings.push(...nested.warnings);
1106
+ continue;
1107
+ }
1108
+ info.isFile() && files.push(relativePath);
1109
+ }
1110
+ return { files: files.sort(), warnings };
1111
+ }
1112
+ function hash2(contents) {
1113
+ return `sha256-${createHash2("sha256").update(contents).digest("hex").slice(0, 16)}`;
1114
+ }
1115
+ function startsWithAscii(contents, value) {
1116
+ return contents.subarray(0, value.length).toString("ascii") === value;
1117
+ }
1118
+ function hasNullByte(contents) {
1119
+ return contents.includes(0);
1120
+ }
1121
+ async function classifyAsset(relativePath, contents) {
1122
+ let ext = relativePath.split(".").at(-1)?.toLowerCase() ?? "";
1123
+ if (ext === "png" && hasImageSignature({ contents, format: "png" })) {
1124
+ let metadata = await readRasterMetadata({
1125
+ contents,
1126
+ errorPrefix: "FRAMEWORK_MEDIA",
1127
+ path: relativePath,
1128
+ policy: MEDIA_IMAGE_ADMISSION_POLICY
1129
+ });
1130
+ return {
1131
+ contents,
1132
+ mediaType: "image",
1133
+ mimeType: "image/png",
1134
+ outputContents: contents,
1135
+ ...metadata
1136
+ };
1137
+ }
1138
+ if ((ext === "jpg" || ext === "jpeg") && hasImageSignature({ contents, format: "jpeg" })) {
1139
+ let metadata = await readRasterMetadata({
1140
+ contents,
1141
+ errorPrefix: "FRAMEWORK_MEDIA",
1142
+ path: relativePath,
1143
+ policy: MEDIA_IMAGE_ADMISSION_POLICY
1144
+ });
1145
+ return {
1146
+ contents,
1147
+ mediaType: "image",
1148
+ mimeType: "image/jpeg",
1149
+ outputContents: contents,
1150
+ ...metadata
1151
+ };
1152
+ }
1153
+ if (ext === "gif" && hasImageSignature({ contents, format: "gif" })) {
1154
+ let metadata = await readRasterMetadata({
1155
+ contents,
1156
+ errorPrefix: "FRAMEWORK_MEDIA",
1157
+ path: relativePath,
1158
+ policy: MEDIA_IMAGE_ADMISSION_POLICY
1159
+ });
1160
+ return {
1161
+ contents,
1162
+ mediaType: "image",
1163
+ mimeType: "image/gif",
1164
+ outputContents: contents,
1165
+ ...metadata
1166
+ };
1167
+ }
1168
+ if (ext === "webp" && hasImageSignature({ contents, format: "webp" })) {
1169
+ let metadata = await readRasterMetadata({
1170
+ contents,
1171
+ errorPrefix: "FRAMEWORK_MEDIA",
1172
+ path: relativePath,
1173
+ policy: MEDIA_IMAGE_ADMISSION_POLICY
1174
+ });
1175
+ return {
1176
+ contents,
1177
+ mediaType: "image",
1178
+ mimeType: "image/webp",
1179
+ outputContents: contents,
1180
+ ...metadata
1181
+ };
1182
+ }
1183
+ if (ext === "avif" && hasImageSignature({ contents, format: "avif" })) {
1184
+ let metadata = await readRasterMetadata({
1185
+ contents,
1186
+ errorPrefix: "FRAMEWORK_MEDIA",
1187
+ path: relativePath,
1188
+ policy: MEDIA_IMAGE_ADMISSION_POLICY
1189
+ });
1190
+ return {
1191
+ contents,
1192
+ mediaType: "image",
1193
+ mimeType: "image/avif",
1194
+ outputContents: contents,
1195
+ ...metadata
1196
+ };
1197
+ }
1198
+ if (ext === "svg") {
1199
+ if (relativePath.startsWith("images/"))
1200
+ throw new AppError({
1201
+ code: "FRAMEWORK_MEDIA_SVG_IN_OPTIMIZED_IMAGES",
1202
+ message: `${relativePath} is an SVG. SVG files are not allowed under publish.os/assets/images because that folder is reserved for optimized raster images.`,
1203
+ details: { path: relativePath }
1204
+ });
1205
+ let outputContents = sanitizeSvg({
1206
+ contents,
1207
+ errorPrefix: "FRAMEWORK_MEDIA",
1208
+ path: relativePath,
1209
+ policy: MEDIA_IMAGE_ADMISSION_POLICY
1210
+ });
1211
+ return {
1212
+ contents,
1213
+ mediaType: "image",
1214
+ mimeType: "image/svg+xml",
1215
+ outputContents
1216
+ };
1217
+ }
1218
+ if (ext === "mp4" && contents.subarray(4, 8).toString("ascii") === "ftyp")
1219
+ return {
1220
+ contents,
1221
+ mediaType: "video",
1222
+ mimeType: "video/mp4",
1223
+ outputContents: contents
1224
+ };
1225
+ if (ext === "webm" && contents[0] === 26 && contents[1] === 69 && contents[2] === 223 && contents[3] === 163)
1226
+ return {
1227
+ contents,
1228
+ mediaType: "video",
1229
+ mimeType: "video/webm",
1230
+ outputContents: contents
1231
+ };
1232
+ if (ext === "pdf" && startsWithAscii(contents, "%PDF"))
1233
+ return {
1234
+ contents,
1235
+ mediaType: "document",
1236
+ mimeType: "application/pdf",
1237
+ outputContents: contents
1238
+ };
1239
+ if (ext === "json")
1240
+ return JSON.parse(contents.toString("utf-8")), {
1241
+ contents,
1242
+ mediaType: "file",
1243
+ mimeType: "application/json",
1244
+ outputContents: contents
1245
+ };
1246
+ if ((ext === "txt" || ext === "csv") && !hasNullByte(contents))
1247
+ return {
1248
+ contents,
1249
+ mediaType: "file",
1250
+ mimeType: ext === "csv" ? "text/csv" : "text/plain",
1251
+ outputContents: contents
1252
+ };
1253
+ throw new AppError({
1254
+ code: "FRAMEWORK_MEDIA_TYPE_REJECTED",
1255
+ message: `${relativePath} is not an allowed media type`,
1256
+ details: { path: relativePath }
1257
+ });
1258
+ }
1259
+ function assertAssetSize(options) {
1260
+ let limits = options.config.media?.assets, limit = options.asset.mediaType === "image" ? limits?.maxImageBytes ?? DEFAULT_MAX_IMAGE_BYTES : options.asset.mediaType === "video" ? limits?.maxVideoBytes ?? DEFAULT_MAX_VIDEO_BYTES : options.asset.mediaType === "document" ? DEFAULT_MAX_DOCUMENT_BYTES : DEFAULT_MAX_OTHER_BYTES;
1261
+ if (options.asset.contents.byteLength <= limit) return;
1262
+ let label = options.asset.mediaType === "image" ? "Image files" : options.asset.mediaType === "video" ? "Video files" : "Media files";
1263
+ throw new AppError({
1264
+ code: "FRAMEWORK_MEDIA_FILE_TOO_LARGE",
1265
+ message: `${options.relativePath} is ${options.asset.contents.byteLength} bytes. ${label} must be less than ${Math.round(limit / 1024 / 1024)} MB.`,
1266
+ details: {
1267
+ path: options.relativePath,
1268
+ bytes: options.asset.contents.byteLength,
1269
+ limit
1270
+ }
1271
+ });
1272
+ }
1273
+ function isOptimizableLocalImage(asset) {
1274
+ return asset.mediaType === "image" && !!(asset.width && asset.height) && !asset.animated && asset.mimeType !== "image/svg+xml" && asset.mimeType !== "image/gif";
1275
+ }
1276
+ async function buildMediaAssets(options) {
1277
+ let assetsDir = resolve3(options.publishOsDir, "assets"), mediaFiles = await walkAssetFiles(assetsDir), files = mediaFiles.files.filter(
1278
+ (file) => !RESERVED_FAVICON_AND_LOGO_ASSET_DIRS.has(file.split("/")[0] ?? "")
1279
+ ), warnings = mediaFiles.warnings, maxFileCount = options.config.media?.assets?.maxFileCount ?? DEFAULT_MAX_FILE_COUNT;
1280
+ if (files.length > maxFileCount)
1281
+ throw new AppError({
1282
+ code: "FRAMEWORK_MEDIA_FILE_COUNT_EXCEEDED",
1283
+ message: `publish.os/assets contains ${files.length} files, exceeding the limit of ${maxFileCount}`,
1284
+ details: { count: files.length, limit: maxFileCount }
1285
+ });
1286
+ let manifest = {
1287
+ assets: {},
1288
+ remote: {
1289
+ images: options.config.media?.remote?.images ?? [],
1290
+ videos: options.config.media?.remote?.videos ?? [],
1291
+ embeds: options.config.media?.remote?.embeds ?? []
1292
+ },
1293
+ totals: {
1294
+ bytes: 0,
1295
+ count: 0,
1296
+ images: 0,
1297
+ videos: 0,
1298
+ documents: 0,
1299
+ files: 0
1300
+ }
1301
+ }, outputs = [], seenPublicPaths = /* @__PURE__ */ new Map();
1302
+ for (let file of files) {
1303
+ let sourcePath = resolve3(assetsDir, file);
1304
+ if (!isSameOrDescendant(assetsDir, sourcePath))
1305
+ throw new AppError({
1306
+ code: "FRAMEWORK_MEDIA_PATH_REJECTED",
1307
+ message: `${file} escapes publish.os/assets`,
1308
+ details: { path: file }
1309
+ });
1310
+ let normalized = normalizeAssetPath(file), sourcePublicPath = `/assets/${normalized}`, publicPath = `${MEDIA_PUBLIC_PREFIX}/${normalized}`, existing = seenPublicPaths.get(publicPath);
1311
+ if (existing)
1312
+ throw new AppError({
1313
+ code: "FRAMEWORK_MEDIA_PATH_COLLISION",
1314
+ message: `${file} and ${existing} both resolve to ${publicPath}`,
1315
+ details: { path: file, existing, publicPath }
1316
+ });
1317
+ seenPublicPaths.set(publicPath, file);
1318
+ let contents = await readFile2(sourcePath), asset = await classifyAsset(file, contents);
1319
+ assertAssetSize({ asset, config: options.config, relativePath: file });
1320
+ let contentHash = hash2(asset.outputContents), outputPath = `${GENERATED_MEDIA_PUBLIC_PREFIX}/${contentHash}/${normalized}`, optimized = isOptimizableLocalImage(asset), aspect = asset.width && asset.height ? asset.width / asset.height : void 0;
1321
+ manifest.assets[sourcePublicPath] = {
1322
+ sourcePath: `publish.os/assets/${file}`,
1323
+ publicPath,
1324
+ outputPath,
1325
+ contentHash,
1326
+ mediaType: asset.mediaType,
1327
+ mimeType: asset.mimeType,
1328
+ byteSize: asset.outputContents.byteLength,
1329
+ intrinsicWidth: asset.width,
1330
+ intrinsicHeight: asset.height,
1331
+ width: asset.width,
1332
+ height: asset.height,
1333
+ aspect,
1334
+ animated: asset.animated,
1335
+ optimized,
1336
+ optimizationReason: optimized ? void 0 : asset.mediaType === "image" ? asset.mimeType === "image/svg+xml" ? "svg" : asset.animated ? "animated" : asset.mimeType === "image/gif" ? "gif" : "dimensions-unavailable" : void 0
1337
+ }, manifest.totals.bytes += asset.outputContents.byteLength, manifest.totals.count += 1, asset.mediaType === "image" ? manifest.totals.images += 1 : asset.mediaType === "video" ? manifest.totals.videos += 1 : asset.mediaType === "document" ? manifest.totals.documents += 1 : manifest.totals.files += 1, outputs.push({ contents: asset.outputContents, outputPath }), outputs.push({ contents: asset.outputContents, outputPath: publicPath });
1338
+ }
1339
+ let maxTotalBytes = options.config.media?.assets?.maxTotalBytes ?? DEFAULT_MAX_TOTAL_BYTES;
1340
+ if (manifest.totals.bytes > maxTotalBytes)
1341
+ throw new AppError({
1342
+ code: "FRAMEWORK_MEDIA_TOTAL_TOO_LARGE",
1343
+ message: `publish.os/assets is ${manifest.totals.bytes} bytes, exceeding the limit of ${maxTotalBytes}`,
1344
+ details: { bytes: manifest.totals.bytes, limit: maxTotalBytes }
1345
+ });
1346
+ return { manifest, outputs, warnings };
1347
+ }
1348
+ async function writeMediaOutputs(options) {
1349
+ await rm2(resolve3(options.outputDir, "public", "_publishos", "assets"), {
1350
+ force: !0,
1351
+ recursive: !0
1352
+ }), await rm2(resolve3(options.outputDir, "public", "_publishos", "media"), {
1353
+ force: !0,
1354
+ recursive: !0
1355
+ }), await rm2(resolve3(options.outputDir, "public", "assets"), {
1356
+ force: !0,
1357
+ recursive: !0
1358
+ });
1359
+ for (let output of options.media.outputs) {
1360
+ let relativeOutput = output.outputPath.replace(/^\/+/, ""), dest = resolve3(options.outputDir, "public", relativeOutput);
1361
+ await mkdir2(dirname2(dest), { recursive: !0 }), await writeFile2(dest, output.contents);
1362
+ }
1363
+ }
1364
+ function mediaManifestModule(manifest) {
1365
+ return `export const mediaManifest = ${JSON.stringify(manifest, null, 2)} as const;
1366
+
1367
+ export type MediaManifest = typeof mediaManifest;
1368
+ export type MediaAssetManifestEntry = MediaManifest["assets"][keyof MediaManifest["assets"]];
1369
+ `;
1370
+ }
1371
+
1372
+ // src/lib/compiler/source/mdx-component-contract.ts
1373
+ import ts from "typescript";
1374
+ var groupedMdxExports = {
1375
+ figure: { FigureCaption: "figcaption" },
1376
+ table: {
1377
+ TableHeadGroup: "thead",
1378
+ TableBodyGroup: "tbody",
1379
+ TableRowGroup: "tr",
1380
+ TableHeaderCell: "th",
1381
+ TableDataCell: "td"
1382
+ }
1383
+ }, replacedMdxSourceFiles = {
1384
+ a: "link",
1385
+ "figure-caption": "figure",
1386
+ h1: "heading",
1387
+ h2: "heading",
1388
+ h3: "heading",
1389
+ h4: "heading",
1390
+ h5: "heading",
1391
+ h6: "heading",
1392
+ img: "image",
1393
+ pre: "code-block",
1394
+ "table-body-group": "table",
1395
+ "table-data-cell": "table",
1396
+ "table-header-cell": "table",
1397
+ "table-head-group": "table",
1398
+ "table-row-group": "table"
1399
+ };
1400
+ function mdxComponentExportName(stem) {
1401
+ return stem.split("-").map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join("");
1402
+ }
1403
+ function collectBindingNames(name) {
1404
+ return ts.isIdentifier(name) ? [name.text] : name.elements.flatMap(
1405
+ (element) => ts.isOmittedExpression(element) ? [] : collectBindingNames(element.name)
1406
+ );
1407
+ }
1408
+ function namedValueExports(sourceFile) {
1409
+ let names = /* @__PURE__ */ new Set();
1410
+ for (let statement of sourceFile.statements) {
1411
+ if (ts.isExportDeclaration(statement) && !statement.isTypeOnly && statement.exportClause && ts.isNamedExports(statement.exportClause)) {
1412
+ for (let element of statement.exportClause.elements)
1413
+ element.isTypeOnly || names.add(element.name.text);
1414
+ continue;
1415
+ }
1416
+ if (!ts.canHaveModifiers(statement)) continue;
1417
+ let modifiers = ts.getModifiers(statement);
1418
+ if (!(!modifiers?.some(
1419
+ (modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword
1420
+ ) || modifiers.some(
1421
+ (modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword
1422
+ ))) {
1423
+ if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name)
1424
+ names.add(statement.name.text);
1425
+ else if (ts.isVariableStatement(statement))
1426
+ for (let declaration of statement.declarationList.declarations)
1427
+ for (let name of collectBindingNames(declaration.name))
1428
+ names.add(name);
1429
+ }
1430
+ }
1431
+ return names;
1432
+ }
1433
+
1434
+ export {
1435
+ ROOT_CONTENT_GROUP_KEY,
1436
+ ROOT_CONTENT_GROUP_TITLE,
1437
+ assertSourceDependency,
1438
+ decodeCssPath,
1439
+ cssImports,
1440
+ localCssImports,
1441
+ buildFaviconsAndLogos,
1442
+ writeFaviconsAndLogosOutputs,
1443
+ faviconsAndLogosManifestModule,
1444
+ normalizeSlugPath,
1445
+ normalizeUrlPath,
1446
+ joinUrlPath,
1447
+ buildMediaAssets,
1448
+ writeMediaOutputs,
1449
+ mediaManifestModule,
1450
+ groupedMdxExports,
1451
+ replacedMdxSourceFiles,
1452
+ mdxComponentExportName,
1453
+ collectBindingNames,
1454
+ namedValueExports
1455
+ };