@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.
- package/compiler-assets/nextjs/defaults/app/globals.css +1 -0
- package/compiler-assets/nextjs/defaults/group/index.tsx +50 -0
- package/compiler-assets/nextjs/defaults/group/layout.tsx +7 -3
- package/compiler-assets/nextjs/defaults/group/page.tsx +35 -43
- package/compiler-assets/nextjs/defaults/project/next.config.mjs +22 -11
- package/compiler-assets/nextjs/defaults/project/tsconfig.json.template +1 -4
- package/compiler-assets/nextjs/defaults/root/css/animation.css +131 -0
- package/compiler-assets/nextjs/defaults/root/css/components/dialog.css +4 -1
- package/compiler-assets/nextjs/defaults/root/css/components/menu.css +1 -1
- package/compiler-assets/nextjs/defaults/root/css/components/navigation-menu.css +1 -1
- package/compiler-assets/nextjs/defaults/root/css/components/popover.css +1 -1
- package/compiler-assets/nextjs/defaults/root/css/components/select.css +1 -1
- package/compiler-assets/nextjs/defaults/root/css/components/tooltip.css +1 -1
- package/compiler-assets/nextjs/defaults/root/css/shadow.css +6 -25
- package/compiler-assets/nextjs/defaults/root/css/typography.css +51 -68
- package/compiler-assets/nextjs/defaults/root/globals.css +25 -0
- package/compiler-assets/nextjs/defaults/root/{page.tsx → index.tsx} +6 -2
- package/compiler-assets/nextjs/defaults/root/layout.tsx +7 -1
- package/compiler-assets/nextjs/defaults/root/theme.css +22 -22
- package/compiler-assets/nextjs/shims/generated-group-page.tsx +19 -2
- package/compiler-assets/nextjs/shims/generated-leaf-page.tsx +16 -2
- package/compiler-assets/nextjs/shims/global-layout.tsx +11 -43
- package/compiler-assets/nextjs/shims/proxy.ts +21 -9
- package/compiler-assets/nextjs/shims/publish-os-theme-provider.tsx +76 -15
- package/compiler-assets/nextjs/shims/root-page.tsx +7 -1
- package/compiler-assets/nextjs/shims/site-fenced-code-block.tsx +34 -0
- package/compiler-assets/nextjs/shims/site-heading-level.tsx +14 -0
- package/compiler-assets/nextjs/shims/site-mdx-components.tsx +100 -0
- package/compiler-assets/nextjs/shims/site-mdx.tsx +12 -0
- package/compiler-assets/nextjs/shims/theme-defaults.css +41 -25
- package/dist/build-operation-2JAYBSKF.js +83 -0
- package/dist/button-CtzUJB8u.d.ts +11 -0
- package/dist/{button-CFdx-ohV.d.ts → button-c3WGnxIj.d.ts} +3 -3
- package/dist/chunk-535TM4A5.js +1455 -0
- package/dist/chunk-726GV2NF.js +2344 -0
- package/dist/chunk-7OGTPSKI.js +156 -0
- package/dist/chunk-AI2UULNX.js +9 -0
- package/dist/chunk-B2V22WRT.js +297 -0
- package/dist/chunk-DLDFHU7Y.js +191 -0
- package/dist/chunk-DWA3ZRGW.js +142 -0
- package/dist/chunk-GGKFQ3MA.js +621 -0
- package/dist/chunk-H6XILMZX.js +6154 -0
- package/dist/chunk-NECL5IBJ.js +318 -0
- package/dist/chunk-SVYGXXYR.js +36 -0
- package/dist/chunk-UF6ATARM.js +1100 -0
- package/dist/chunk-VO2I7MTM.js +56 -0
- package/dist/chunk-WDY4IK6Z.js +128 -0
- package/dist/chunk-WKTEO6RS.js +586 -0
- package/dist/chunk-YLTOLYWM.js +92 -0
- package/dist/compiler.d.ts +41 -7
- package/dist/compiler.js +48 -13143
- package/dist/compose-NN23UJGC.js +13 -0
- package/dist/config/createNextConfig.js +13 -0
- package/dist/dev-operation-TZDVJCN7.js +1055 -0
- package/dist/framework-selection-X4U6Y6QC.js +10 -0
- package/dist/ide-support-ZNW7ODFY.js +15 -0
- package/dist/index.d.ts +15 -3
- package/dist/kernel/components/blocks/AgentActions.js +71 -61
- package/dist/kernel/components/blocks/design-system-preview.d.ts +1 -1
- package/dist/kernel/components/blocks/design-system-preview.js +32 -13
- package/dist/kernel/components/blocks/link-playground.js +2 -2
- package/dist/kernel/components/blocks/logo-block.js +2 -2
- package/dist/kernel/components/blocks/nav-breadcrumb.d.ts +10 -2
- package/dist/kernel/components/blocks/nav-breadcrumb.js +20 -5
- package/dist/kernel/components/blocks/navbar.d.ts +2 -2
- package/dist/kernel/components/blocks/navbar.js +401 -60
- package/dist/kernel/components/blocks/navigation-progress-playground.js +4 -4
- package/dist/kernel/components/blocks/navigation-progress.js +4 -1
- package/dist/kernel/components/blocks/next-prev.d.ts +1 -1
- package/dist/kernel/components/blocks/next-prev.js +11 -23
- package/dist/kernel/components/blocks/page-list.d.ts +1 -1
- package/dist/kernel/components/blocks/page-list.js +5 -4
- package/dist/kernel/components/blocks/search.d.ts +83 -58
- package/dist/kernel/components/blocks/search.js +553 -426
- package/dist/kernel/components/blocks/sidebar-node.d.ts +2 -2
- package/dist/kernel/components/blocks/sidebar-node.js +11 -9
- package/dist/kernel/components/blocks/sidebar.d.ts +3 -3
- package/dist/kernel/components/blocks/sidebar.js +18 -11
- package/dist/kernel/components/blocks/theme-toggle.d.ts +2 -2
- package/dist/kernel/components/blocks/theme-toggle.js +48 -2
- package/dist/kernel/components/blocks/toc.js +3 -3
- package/dist/kernel/components/mdx/components/blockquote.js +1 -1
- package/dist/kernel/components/mdx/components/callout.d.ts +5 -7
- package/dist/kernel/components/mdx/components/callout.js +66 -35
- package/dist/kernel/components/mdx/components/code-block.d.ts +5 -2
- package/dist/kernel/components/mdx/components/code-block.js +106 -5
- package/dist/kernel/components/mdx/components/copy-code-button.d.ts +8 -0
- package/dist/kernel/components/mdx/components/copy-code-button.js +60 -0
- package/dist/kernel/components/mdx/components/figure-caption.js +1 -1
- package/dist/kernel/components/mdx/components/figure.js +1 -1
- package/dist/kernel/components/mdx/components/heading-copy-link.js +3 -3
- package/dist/kernel/components/mdx/components/image-zoom.d.ts +2 -2
- package/dist/kernel/components/mdx/components/image-zoom.js +1 -1
- package/dist/kernel/components/mdx/components/image.js +77 -56
- package/dist/kernel/components/mdx/components/inline-code.js +1 -1
- package/dist/kernel/components/mdx/components/kbd.d.ts +2 -2
- package/dist/kernel/components/mdx/components/kbd.js +3 -2
- package/dist/kernel/components/mdx/components/link.d.ts +35 -4
- package/dist/kernel/components/mdx/components/link.js +1 -1
- package/dist/kernel/components/mdx/components/paragraph.js +1 -4
- package/dist/kernel/components/mdx/components/task-list.js +1 -1
- package/dist/kernel/components/mdx/components/video-player.js +2 -2
- package/dist/kernel/components/mdx/components/video.js +1 -1
- package/dist/kernel/components/mdx/error-boundary.d.ts +1 -1
- package/dist/kernel/components/mdx/mdx.d.ts +12 -8
- package/dist/kernel/components/mdx/mdx.js +115 -39
- package/dist/kernel/components/mdx/rehype-heading-ids.js +1 -1
- package/dist/kernel/components/mdx/types.d.ts +1 -1
- package/dist/kernel/components/ui/breadcrumb-trail.d.ts +6 -23
- package/dist/kernel/components/ui/breadcrumb.d.ts +11 -3
- package/dist/kernel/components/ui/breadcrumb.js +6 -4
- package/dist/kernel/components/ui/button-group.d.ts +1 -1
- package/dist/kernel/components/ui/button-group.js +5 -4
- package/dist/kernel/components/ui/button.d.ts +2 -2
- package/dist/kernel/components/ui/button.js +6 -6
- package/dist/kernel/components/ui/card.d.ts +2 -2
- package/dist/kernel/components/ui/drawer.d.ts +3 -1
- package/dist/kernel/components/ui/drawer.js +5 -1
- package/dist/kernel/components/ui/input.d.ts +4 -4
- package/dist/kernel/components/ui/kbd.d.ts +1 -1
- package/dist/kernel/components/ui/kbd.js +9 -16
- package/dist/kernel/components/ui/link.d.ts +5 -3
- package/dist/kernel/components/ui/link.js +10 -9
- package/dist/kernel/components/ui/menu.js +5 -5
- package/dist/kernel/components/ui/mod-key.d.ts +10 -0
- package/dist/kernel/components/ui/mod-key.js +36 -0
- package/dist/kernel/components/ui/navigation-menu.js +2 -2
- package/dist/kernel/components/ui/progress.js +1 -1
- package/dist/kernel/components/ui/scroll-area.d.ts +8 -8
- package/dist/kernel/components/ui/scroll-area.js +4 -4
- package/dist/kernel/components/ui/select.js +7 -10
- package/dist/kernel/components/ui/text.d.ts +2 -2
- package/dist/kernel/components/ui/theme-toggle.d.ts +3 -3
- package/dist/kernel/components/ui/theme-toggle.js +4 -9
- package/dist/kernel/defaults.registry.d.ts +9 -9
- package/dist/kernel/defaults.registry.js +9 -9
- package/dist/kernel/routes/agent/markdown.d.ts +0 -1
- package/dist/kernel/routes/layouts/group.d.ts +1 -1
- package/dist/kernel/routes/metadata/feed.d.ts +1 -1
- package/dist/kernel/routes/metadata/feed.js +13 -9
- package/dist/kernel/routes/metadata/llms.d.ts +2 -1
- package/dist/kernel/routes/metadata/llms.js +4 -4
- package/dist/kernel/routes/metadata/robots.d.ts +1 -1
- package/dist/kernel/routes/metadata/robots.js +2 -2
- package/dist/kernel/routes/metadata/sitemap.d.ts +6 -3
- package/dist/kernel/routes/metadata/sitemap.js +48 -20
- package/dist/kernel/routes/pages/group.d.ts +1 -1
- package/dist/kernel/routes/pages/group.js +20 -12
- package/dist/kernel/routes/pages/leaf.d.ts +2 -2
- package/dist/kernel/routes/pages/leaf.js +20 -12
- package/dist/kernel/routes/pages/root.d.ts +2 -2
- package/dist/kernel/routes/pages/root.js +18 -12
- package/dist/resolvers/manifest-resolver.js +29 -6
- package/dist/search.d.ts +6 -7
- package/dist/search.js +47 -22
- package/dist/{sidebar-types-Bgchdfd6.d.ts → sidebar-types-DKpR27II.d.ts} +1 -1
- package/dist/{types-CIwwieNw.d.ts → types-BzkZty3r.d.ts} +23 -3
- package/dist/{types-DHBm7-j6.d.ts → types-CJWRG05y.d.ts} +20 -1
- package/dist/{types-B_b3C0U6.d.ts → types-CUtrppxt.d.ts} +8 -2
- package/dist/types.d.ts +14 -6
- package/dist/validate-local-source-XRL2Y7XE.js +14 -0
- package/dist/validate-publish-os-source-DAPZ4QGE.js +12 -0
- package/init/seed/minimal/.gitignore +1 -0
- package/init/seed/minimal/publish.os/assets/favicon/favicon.dark.png +0 -0
- package/init/seed/minimal/publish.os/assets/favicon/favicon.png +0 -0
- package/init/seed/minimal/publish.os/docs/content/get-started.mdx +6 -10
- package/init/seed/minimal/publish.os/docs/content/groups.mdx +1 -1
- package/init/seed/minimal/publish.os/docs/content/{theme.mdx → themes.mdx} +14 -12
- package/init/seed/minimal/publish.os/publish.os.config.json +3 -7
- package/init/seed/minimal/publish.os/theme.css +52 -24
- package/init/seed/minimal/publish.os/tsconfig.json +3 -0
- package/init/seed/minimal/seed.json +4 -1
- package/init/seed/scratch/publish.os/docs/content/get-started.mdx +6 -0
- package/init/seed/scratch/publish.os/publish.os.config.json +4 -0
- package/init/seed/scratch/seed.json +5 -0
- package/package.json +12 -26
- package/release/app/phosphor/package.json +3 -3
- package/release/app/phosphor/pnpm-lock.yaml.template +523 -360
- package/release/app/phosphor/pnpm-workspace.yaml +3 -0
- package/release/compiler/package.json +1 -1
- package/release/compiler/pnpm-lock.yaml.template +439 -286
- package/release/compiler/pnpm-workspace.yaml +3 -0
- package/release/release.json +7 -36
- package/compiler-assets/nextjs/defaults/group/leaf.tsx +0 -48
- package/dist/icons.d.ts +0 -1
- package/dist/icons.js +0 -2
- package/dist/kernel/components/mdx/components/pre.d.ts +0 -6
- package/dist/kernel/components/mdx/components/pre.js +0 -18
- package/dist/kernel/components/ui/meta-key.d.ts +0 -17
- package/dist/kernel/components/ui/meta-key.js +0 -9
- package/dist/kernel/framework-icons.d.ts +0 -1
- package/dist/kernel/framework-icons.js +0 -39
- package/dist/link-CYaCRDb6.d.ts +0 -35
- package/dist/route-metadata-og-image-BeKKFHg7.d.ts +0 -58
- package/dist/types-sM2g9nW0.d.ts +0 -12
- package/release/app/lucide/package.json +0 -30
- package/release/app/lucide/pnpm-lock.yaml.template +0 -2905
- package/release/app/lucide/pnpm-workspace.yaml +0 -11
- package/release/app/tabler/package.json +0 -30
- package/release/app/tabler/pnpm-lock.yaml.template +0 -2911
- 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
|
+
};
|