@demiurgejs/core 0.2.0-beta.4 → 0.2.0-beta.6
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/README.md +37 -0
- package/bin/demiurge.mjs +23 -1
- package/dist/adapter/index.d.ts +1 -1
- package/dist/adapter/index.d.ts.map +1 -1
- package/dist/browser/file-router.d.ts +1 -1
- package/dist/chunks/{document-shell-CpJnFtSC.js → document-shell-C0aPrkhd.js} +2 -2
- package/dist/chunks/{document-shell-CpJnFtSC.js.map → document-shell-C0aPrkhd.js.map} +1 -1
- package/dist/chunks/image-CRerJ5BY.js +145 -0
- package/dist/chunks/image-CRerJ5BY.js.map +1 -0
- package/dist/chunks/{image-codec-BmzWnH6W.js → image-codec-CQ8Cwxwb.js} +2 -2
- package/dist/chunks/{image-codec-BmzWnH6W.js.map → image-codec-CQ8Cwxwb.js.map} +1 -1
- package/dist/chunks/{images-BQ80950B.js → images-QgdmLyQ3.js} +11 -11
- package/dist/chunks/{images-BQ80950B.js.map → images-QgdmLyQ3.js.map} +1 -1
- package/dist/chunks/{index-BVk9zWbe.js → index-LYve1rQp.js} +287 -351
- package/dist/chunks/index-LYve1rQp.js.map +1 -0
- package/dist/chunks/links-iU22QkCE.js +68 -0
- package/dist/chunks/links-iU22QkCE.js.map +1 -0
- package/dist/chunks/plugin-bkP6WwrS.js +2311 -0
- package/dist/chunks/plugin-bkP6WwrS.js.map +1 -0
- package/dist/chunks/runtime-BH519j2t.js +294 -0
- package/dist/chunks/runtime-BH519j2t.js.map +1 -0
- package/dist/chunks/streaming-BlKlC3nw.js +427 -0
- package/dist/chunks/streaming-BlKlC3nw.js.map +1 -0
- package/dist/chunks/{testing-DueXbMo4.js → testing-BEW1nchS.js} +2161 -1562
- package/dist/chunks/testing-BEW1nchS.js.map +1 -0
- package/dist/chunks/{vite-DO70kZA6.js → vite-BADZA8Fl.js} +90 -87
- package/dist/chunks/vite-BADZA8Fl.js.map +1 -0
- package/dist/cli.d.ts +10 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +277 -87
- package/dist/cli.js.map +1 -1
- package/dist/config/index.js +2 -2
- package/dist/config/types.d.ts +14 -2
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/validate.d.ts.map +1 -1
- package/dist/data/cache.d.ts +5 -0
- package/dist/data/cache.d.ts.map +1 -1
- package/dist/data/index.d.ts +1 -1
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/testing.d.ts.map +1 -1
- package/dist/data/testing.js +101 -67
- package/dist/data/testing.js.map +1 -1
- package/dist/data/tiered.d.ts.map +1 -1
- package/dist/edge/index.js +27 -26
- package/dist/edge/index.js.map +1 -1
- package/dist/edge/stores.d.ts.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +158 -135
- package/dist/index.js.map +1 -1
- package/dist/inspect/command.d.ts +28 -0
- package/dist/inspect/command.d.ts.map +1 -0
- package/dist/inspect/index.d.ts +9 -0
- package/dist/inspect/index.d.ts.map +1 -0
- package/dist/inspect/redact.d.ts +35 -0
- package/dist/inspect/redact.d.ts.map +1 -0
- package/dist/inspect/report.d.ts +24 -0
- package/dist/inspect/report.d.ts.map +1 -0
- package/dist/inspect/types.d.ts +102 -0
- package/dist/inspect/types.d.ts.map +1 -0
- package/dist/internal/testing.js +12 -12
- package/dist/kv/index.js +7 -6
- package/dist/kv/index.js.map +1 -1
- package/dist/kv/store.d.ts.map +1 -1
- package/dist/node/http.d.ts +7 -0
- package/dist/node/http.d.ts.map +1 -1
- package/dist/node/index.d.ts +2 -2
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +31 -28
- package/dist/node/index.js.map +1 -1
- package/dist/node/serve.d.ts +4 -0
- package/dist/node/serve.d.ts.map +1 -1
- package/dist/redis/index.js +29 -28
- package/dist/redis/index.js.map +1 -1
- package/dist/redis/store.d.ts.map +1 -1
- package/dist/route/index.d.ts +1 -0
- package/dist/route/index.d.ts.map +1 -1
- package/dist/route/mutation.d.ts.map +1 -1
- package/dist/route/serialization.d.ts +24 -0
- package/dist/route/serialization.d.ts.map +1 -0
- package/dist/route/webhook.d.ts +6 -1
- package/dist/route/webhook.d.ts.map +1 -1
- package/dist/router/manifest.d.ts.map +1 -1
- package/dist/security/audit.d.ts +2 -1
- package/dist/security/audit.d.ts.map +1 -1
- package/dist/security/authorization.d.ts +36 -0
- package/dist/security/authorization.d.ts.map +1 -0
- package/dist/security/csrf.d.ts.map +1 -1
- package/dist/security/env-startup.d.ts +0 -2
- package/dist/security/env-startup.d.ts.map +1 -1
- package/dist/security/env.d.ts +27 -8
- package/dist/security/env.d.ts.map +1 -1
- package/dist/security/exceptions.d.ts +29 -0
- package/dist/security/exceptions.d.ts.map +1 -0
- package/dist/security/finding-codes.d.ts +56 -0
- package/dist/security/finding-codes.d.ts.map +1 -0
- package/dist/security/index.d.ts +5 -2
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/policy.d.ts +2 -5
- package/dist/security/policy.d.ts.map +1 -1
- package/dist/security/report.d.ts +2 -1
- package/dist/security/report.d.ts.map +1 -1
- package/dist/security/request.d.ts +1 -0
- package/dist/security/request.d.ts.map +1 -1
- package/dist/security/types.d.ts +138 -5
- package/dist/security/types.d.ts.map +1 -1
- package/dist/security/upload.d.ts +3 -3
- package/dist/security/upload.d.ts.map +1 -1
- package/dist/security/verification.d.ts.map +1 -1
- package/dist/server/request-handler.d.ts +3 -2
- package/dist/server/request-handler.d.ts.map +1 -1
- package/dist/server-only/browser.d.ts +2 -0
- package/dist/server-only/browser.d.ts.map +1 -0
- package/dist/server-only/browser.js +4 -0
- package/dist/server-only/browser.js.map +1 -0
- package/dist/server-only/index.d.ts +2 -0
- package/dist/server-only/index.d.ts.map +1 -0
- package/dist/server-only/index.js +2 -0
- package/dist/server-only/index.js.map +1 -0
- package/dist/static/index.d.ts.map +1 -1
- package/dist/static/index.js +255 -255
- package/dist/static/index.js.map +1 -1
- package/dist/static/testing.d.ts +26 -0
- package/dist/static/testing.d.ts.map +1 -0
- package/dist/static/testing.js +76 -0
- package/dist/static/testing.js.map +1 -0
- package/dist/testing/index.d.ts +47 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +99 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/vercel/build.d.ts +31 -0
- package/dist/vercel/build.d.ts.map +1 -0
- package/dist/vercel/config.d.ts +15 -0
- package/dist/vercel/config.d.ts.map +1 -0
- package/dist/vercel/index.d.ts +7 -0
- package/dist/vercel/index.d.ts.map +1 -0
- package/dist/vercel/index.js +11 -0
- package/dist/vercel/index.js.map +1 -0
- package/dist/vercel/runtime.d.ts +29 -0
- package/dist/vercel/runtime.d.ts.map +1 -0
- package/dist/vite/index.d.ts +9 -4
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +28 -21
- package/dist/vite/plugin.d.ts +18 -2
- package/dist/vite/plugin.d.ts.map +1 -1
- package/dist/vite/policy-verification.d.ts +33 -2
- package/dist/vite/policy-verification.d.ts.map +1 -1
- package/dist/vite/route-audit.d.ts +14 -2
- package/dist/vite/route-audit.d.ts.map +1 -1
- package/dist/vite/server-only-boundary.d.ts +8 -0
- package/dist/vite/server-only-boundary.d.ts.map +1 -0
- package/package.json +18 -1
- package/dist/chunks/env-startup-DOBYWlZD.js +0 -257
- package/dist/chunks/env-startup-DOBYWlZD.js.map +0 -1
- package/dist/chunks/index-BVk9zWbe.js.map +0 -1
- package/dist/chunks/plugin-BQgMiHxa.js +0 -1827
- package/dist/chunks/plugin-BQgMiHxa.js.map +0 -1
- package/dist/chunks/streaming-BEbPN7Uk.js +0 -469
- package/dist/chunks/streaming-BEbPN7Uk.js.map +0 -1
- package/dist/chunks/testing-DueXbMo4.js.map +0 -1
- package/dist/chunks/vite-DO70kZA6.js.map +0 -1
package/README.md
CHANGED
|
@@ -17,6 +17,18 @@ pnpm add @demiurgejs/core react react-dom
|
|
|
17
17
|
pnpm add -D vite @vitejs/plugin-react typescript @types/node @types/react @types/react-dom
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
+
Declare the root policy for a public page application:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
// src/routes/@policy.ts
|
|
24
|
+
import { security } from "@demiurgejs/core";
|
|
25
|
+
|
|
26
|
+
export const policy = {
|
|
27
|
+
access: { public: true },
|
|
28
|
+
document: security.strict(),
|
|
29
|
+
};
|
|
30
|
+
```
|
|
31
|
+
|
|
20
32
|
Define a route under `src/routes`:
|
|
21
33
|
|
|
22
34
|
```tsx
|
|
@@ -65,6 +77,7 @@ covers host allowlists, proxy trust, timeouts, and graceful shutdown.
|
|
|
65
77
|
|
|
66
78
|
- `@demiurgejs/core` — routes, document APIs, security, caching, and browser runtime
|
|
67
79
|
- `@demiurgejs/core/node` — production Node HTTP, SSR, and static-file adapter
|
|
80
|
+
- `@demiurgejs/core/vercel` — Vercel Node Function deployment adapter
|
|
68
81
|
- `@demiurgejs/core/static` — static-output adapter
|
|
69
82
|
- `@demiurgejs/core/redis` — Redis-backed cache store with cross-instance tag invalidation
|
|
70
83
|
- `@demiurgejs/core/config` — the application configuration contract
|
|
@@ -72,11 +85,35 @@ covers host allowlists, proxy trust, timeouts, and graceful shutdown.
|
|
|
72
85
|
- `@demiurgejs/core/adapter/testing` — adapter capability conformance contract
|
|
73
86
|
- `@demiurgejs/core/deployment/testing` — deployment conformance kit for provider translation and production artifacts
|
|
74
87
|
- `@demiurgejs/core/data/testing` — cache-store conformance contract
|
|
88
|
+
- `@demiurgejs/core/testing` — application route-pipeline test boundary
|
|
75
89
|
- `@demiurgejs/core/internal/testing` — explicitly unstable test helpers
|
|
76
90
|
|
|
77
91
|
Vite is an optional peer dependency with `@vitejs/plugin-react`. A consumer
|
|
78
92
|
that only runs the built output does not install them.
|
|
79
93
|
|
|
94
|
+
## Export prefixes
|
|
95
|
+
|
|
96
|
+
The package uses three levels for an export name. The two prefixes describe
|
|
97
|
+
different properties, so one name can carry both.
|
|
98
|
+
|
|
99
|
+
| Prefix | Promise |
|
|
100
|
+
| --- | --- |
|
|
101
|
+
| none | The name is stable. Demiurge changes it under the semantic version policy of the package. |
|
|
102
|
+
| `unstable_` | The shape can change in any release. A consumer accepts that risk. |
|
|
103
|
+
| `unsafe_` | Use of the export moves a framework guarantee to the application. The shape can still be stable. |
|
|
104
|
+
|
|
105
|
+
A type and the function that returns it carry the same prefix. The function
|
|
106
|
+
`unstable_createRouteAudit` therefore returns `unstable_RouteAudit`.
|
|
107
|
+
|
|
108
|
+
A name leaves the `unstable_` prefix when it meets three conditions. An
|
|
109
|
+
accepted decision record describes the interface. The report of the interface
|
|
110
|
+
states a version. A packed-consumer test covers the interface.
|
|
111
|
+
|
|
112
|
+
The `unsafe_` prefix does not describe maturity, so a name never leaves it.
|
|
113
|
+
[ADR 0019](https://github.com/NorthShoreSoftwareLabs/demiurge/blob/main/architecture/decisions/0019-versioned-inspection-interfaces.md)
|
|
114
|
+
records these levels.
|
|
115
|
+
|
|
116
|
+
|
|
80
117
|
## Support and license
|
|
81
118
|
|
|
82
119
|
Report defects through [GitHub Issues](https://github.com/NorthShoreSoftwareLabs/demiurge/issues).
|
package/bin/demiurge.mjs
CHANGED
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
resolvePreviewOutputDirectory,
|
|
10
10
|
runBuild,
|
|
11
11
|
runDev,
|
|
12
|
+
runInspectCommand,
|
|
13
|
+
runStart,
|
|
12
14
|
} from "../dist/cli.js";
|
|
13
15
|
import { loadDemiurgeConfig } from "../dist/config/index.js";
|
|
14
16
|
import {
|
|
@@ -16,7 +18,22 @@ import {
|
|
|
16
18
|
} from "../dist/static/index.js";
|
|
17
19
|
|
|
18
20
|
async function main() {
|
|
19
|
-
const
|
|
21
|
+
const commandArguments = process.argv.slice(2);
|
|
22
|
+
|
|
23
|
+
// The inspect command owns its exit codes and its problem document, so it
|
|
24
|
+
// runs before the shared argument parser and the shared configuration load.
|
|
25
|
+
if (commandArguments[0] === "inspect") {
|
|
26
|
+
const result = await runInspectCommand({
|
|
27
|
+
arguments: commandArguments.slice(1),
|
|
28
|
+
loadConfig: () => loadDemiurgeConfig(),
|
|
29
|
+
});
|
|
30
|
+
console.error(result.stderr);
|
|
31
|
+
console.log(result.stdout);
|
|
32
|
+
process.exitCode = result.exitCode;
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const options = parseCliArguments(commandArguments, process.env);
|
|
20
37
|
|
|
21
38
|
if (options.command === "help") {
|
|
22
39
|
console.log(helpText);
|
|
@@ -48,6 +65,11 @@ async function main() {
|
|
|
48
65
|
return;
|
|
49
66
|
}
|
|
50
67
|
|
|
68
|
+
if (options.command === "start") {
|
|
69
|
+
await runStart(options, config);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
51
73
|
const outDir = resolvePreviewOutputDirectory(
|
|
52
74
|
config.root,
|
|
53
75
|
options.outDir,
|
package/dist/adapter/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type AdapterCapability = "backgroundLifetime" | "crossOriginIsolationHeaders" | "nonceInjection" | "sharedCache" | "staticOutput" | "streaming" | "webSocket" | "webTransport";
|
|
1
|
+
export type AdapterCapability = "backgroundLifetime" | "crossOriginIsolationHeaders" | "nonceInjection" | "requestTimeoutEnforcement" | "sharedCache" | "staticOutput" | "streaming" | "webSocket" | "webTransport";
|
|
2
2
|
export type AdapterCapabilityMap = Record<AdapterCapability, boolean>;
|
|
3
3
|
export type Adapter = {
|
|
4
4
|
capabilities: AdapterCapabilityMap;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapter/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GACzB,oBAAoB,GACpB,6BAA6B,GAC7B,gBAAgB,GAChB,aAAa,GACb,cAAc,GACd,WAAW,GACX,WAAW,GACX,cAAc,CAAC;AAEnB,MAAM,MAAM,oBAAoB,GAAG,MAAM,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAC;AAEtE,MAAM,MAAM,OAAO,GAAG;IACpB,YAAY,EAAE,oBAAoB,CAAC;IACnC,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,YAAY,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC7C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapter/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GACzB,oBAAoB,GACpB,6BAA6B,GAC7B,gBAAgB,GAChB,2BAA2B,GAC3B,aAAa,GACb,cAAc,GACd,WAAW,GACX,WAAW,GACX,cAAc,CAAC;AAEnB,MAAM,MAAM,oBAAoB,GAAG,MAAM,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAC;AAEtE,MAAM,MAAM,OAAO,GAAG;IACpB,YAAY,EAAE,oBAAoB,CAAC;IACnC,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,YAAY,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC7C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B,CAAC;AAcF,wBAAgB,aAAa,CAAC,UAAU,EAAE,iBAAiB,GAAG,OAAO,CAQpE;AAED,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,SAAS,iBAAiB,EAAE,GACrC,sBAAsB,CAYxB;AAED,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,SAAS,iBAAiB,EAAE,0BAWvC"}
|
|
@@ -112,11 +112,11 @@ export declare function MutationSubmit(props: MutationSubmitProps): import("reac
|
|
|
112
112
|
value?: string | number | readonly string[] | undefined;
|
|
113
113
|
name?: string | undefined | undefined;
|
|
114
114
|
style?: import("react").CSSProperties | undefined;
|
|
115
|
+
security?: string | undefined | undefined;
|
|
115
116
|
title?: string | undefined | undefined;
|
|
116
117
|
type?: "submit" | "reset" | "button" | undefined | undefined;
|
|
117
118
|
nonce?: string | undefined | undefined;
|
|
118
119
|
id?: string | undefined | undefined;
|
|
119
|
-
security?: string | undefined | undefined;
|
|
120
120
|
form?: string | undefined | undefined;
|
|
121
121
|
slot?: string | undefined | undefined;
|
|
122
122
|
children?: ReactNode;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as c } from "./index-
|
|
1
|
+
import { r as c } from "./index-LYve1rQp.js";
|
|
2
2
|
const r = '<template data-demiurge-stream-root=""></template>';
|
|
3
3
|
async function i(t, e) {
|
|
4
4
|
const l = c({
|
|
@@ -33,4 +33,4 @@ async function i(t, e) {
|
|
|
33
33
|
export {
|
|
34
34
|
i as c
|
|
35
35
|
};
|
|
36
|
-
//# sourceMappingURL=document-shell-
|
|
36
|
+
//# sourceMappingURL=document-shell-C0aPrkhd.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"document-shell-
|
|
1
|
+
{"version":3,"file":"document-shell-C0aPrkhd.js","sources":["../../src/server/document-shell.ts"],"sourcesContent":["import { renderDocumentShell } from \"../document/render\";\nimport type { ScriptTag } from \"../document/scripts\";\nimport type { LoadedRouteMatch } from \"../router\";\nimport type { SsrRenderOptions } from \"./ssr\";\n\n// A streaming render sends the document head before the body exists. A\n// transform that rewrites the document therefore needs a placeholder where the\n// streamed body will go, and it has to leave that placeholder in place.\nexport const STREAM_ROOT_MARKER =\n '<template data-demiurge-stream-root=\"\"></template>';\n\nexport type DocumentShell = {\n prefix: string;\n suffix: string;\n};\n\nexport async function createDocumentShell(\n match: LoadedRouteMatch,\n options: SsrRenderOptions & { scripts?: ScriptTag[] },\n): Promise<DocumentShell> {\n const shell = renderDocumentShell({\n body: { data: match.data, locale: options.locale, navigation: options.navigation },\n dir: options.dir,\n entrySrc: options.clientEntry,\n lang: options.lang,\n links: match.links,\n metadata: match.metadata,\n nonce: options.nonce,\n scripts: options.scripts ?? match.scripts,\n styles: options.styles,\n title: options.title,\n });\n\n if (!options.transformDocument) {\n return shell;\n }\n\n const transformed = await options.transformDocument(\n `${shell.prefix}${STREAM_ROOT_MARKER}${shell.suffix}`,\n );\n const markerIndex = transformed.indexOf(STREAM_ROOT_MARKER);\n\n if (\n markerIndex === -1 ||\n transformed.indexOf(\n STREAM_ROOT_MARKER,\n markerIndex + STREAM_ROOT_MARKER.length,\n ) !== -1\n ) {\n throw new Error(\n \"Demiurge streaming document transform must preserve the root marker exactly once.\",\n );\n }\n\n return {\n prefix: transformed.slice(0, markerIndex),\n suffix: transformed.slice(markerIndex + STREAM_ROOT_MARKER.length),\n };\n}\n"],"names":["STREAM_ROOT_MARKER","createDocumentShell","match","options","shell","renderDocumentShell","transformed","markerIndex"],"mappings":";AAQO,MAAMA,IACX;AAOF,eAAsBC,EACpBC,GACAC,GACwB;AACxB,QAAMC,IAAQC,EAAoB;AAAA,IAChC,MAAM,EAAE,MAAMH,EAAM,MAAM,QAAQC,EAAQ,QAAQ,YAAYA,EAAQ,WAAA;AAAA,IACtE,KAAKA,EAAQ;AAAA,IACb,UAAUA,EAAQ;AAAA,IAClB,MAAMA,EAAQ;AAAA,IACd,OAAOD,EAAM;AAAA,IACb,UAAUA,EAAM;AAAA,IAChB,OAAOC,EAAQ;AAAA,IACf,SAASA,EAAQ,WAAWD,EAAM;AAAA,IAClC,QAAQC,EAAQ;AAAA,IAChB,OAAOA,EAAQ;AAAA,EAAA,CAChB;AAED,MAAI,CAACA,EAAQ;AACX,WAAOC;AAGT,QAAME,IAAc,MAAMH,EAAQ;AAAA,IAChC,GAAGC,EAAM,MAAM,GAAGJ,CAAkB,GAAGI,EAAM,MAAM;AAAA,EAAA,GAE/CG,IAAcD,EAAY,QAAQN,CAAkB;AAE1D,MACEO,MAAgB,MAChBD,EAAY;AAAA,IACVN;AAAA,IACAO,IAAcP,EAAmB;AAAA,EAAA,MAC7B;AAEN,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAIJ,SAAO;AAAA,IACL,QAAQM,EAAY,MAAM,GAAGC,CAAW;AAAA,IACxC,QAAQD,EAAY,MAAMC,IAAcP,EAAmB,MAAM;AAAA,EAAA;AAErE;"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { createHash as A } from "node:crypto";
|
|
2
|
+
import { r as R, n as b, t as E } from "./image-codec-CQ8Cwxwb.js";
|
|
3
|
+
import { d as H, a as I, i as T, p as $, u as L } from "./images-QgdmLyQ3.js";
|
|
4
|
+
import { R as w } from "./static-files-ehaD1kE7.js";
|
|
5
|
+
import { readFile as S } from "node:fs/promises";
|
|
6
|
+
import { resolve as v, relative as z, sep as D } from "node:path";
|
|
7
|
+
function B(e) {
|
|
8
|
+
const t = e.basePath ?? H;
|
|
9
|
+
let n;
|
|
10
|
+
return async function(i) {
|
|
11
|
+
const c = new URL(i.url);
|
|
12
|
+
if (!c.pathname.startsWith(`${t}/`))
|
|
13
|
+
return null;
|
|
14
|
+
if (i.method !== "GET" && i.method !== "HEAD")
|
|
15
|
+
return new Response("Method Not Allowed", {
|
|
16
|
+
headers: {
|
|
17
|
+
allow: "GET, HEAD",
|
|
18
|
+
"content-type": "text/plain; charset=utf-8"
|
|
19
|
+
},
|
|
20
|
+
status: 405
|
|
21
|
+
});
|
|
22
|
+
n ??= o();
|
|
23
|
+
let d;
|
|
24
|
+
try {
|
|
25
|
+
d = await n;
|
|
26
|
+
} catch (r) {
|
|
27
|
+
throw n = void 0, r;
|
|
28
|
+
}
|
|
29
|
+
const l = d.get(c.pathname);
|
|
30
|
+
return l ? F(i, l) : null;
|
|
31
|
+
};
|
|
32
|
+
async function o() {
|
|
33
|
+
const s = await R({ ...e, basePath: t });
|
|
34
|
+
return new Map(s.map((i) => [i.url, i]));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function F(e, t) {
|
|
38
|
+
const n = `"${A("sha256").update(t.body).digest("base64url").slice(0, 27)}"`, o = new Headers({
|
|
39
|
+
"cache-control": w,
|
|
40
|
+
"content-type": t.contentType,
|
|
41
|
+
etag: n
|
|
42
|
+
});
|
|
43
|
+
return e.headers.get("if-none-match") === n ? new Response(null, { headers: o, status: 304 }) : (o.set("content-length", String(t.body.byteLength)), new Response(
|
|
44
|
+
e.method === "HEAD" ? null : t.body,
|
|
45
|
+
{ headers: o, status: 200 }
|
|
46
|
+
));
|
|
47
|
+
}
|
|
48
|
+
const x = 64, P = 20 * 1024 * 1024;
|
|
49
|
+
function V(e) {
|
|
50
|
+
const t = v(e.root), n = e.policy ?? {}, o = n.optimizerPath ?? I, s = e.cacheSize ?? x, i = e.fetch ?? ((l, r) => fetch(l, r)), c = /* @__PURE__ */ new Map();
|
|
51
|
+
return async function(r) {
|
|
52
|
+
const u = new URL(r.url), a = u.pathname === o ? void 0 : e.resolveVariant?.(u.pathname);
|
|
53
|
+
if (u.pathname !== o && !a)
|
|
54
|
+
return null;
|
|
55
|
+
if (r.method !== "GET" && r.method !== "HEAD")
|
|
56
|
+
return p(405, "Method Not Allowed", {
|
|
57
|
+
allow: "GET, HEAD"
|
|
58
|
+
});
|
|
59
|
+
if (a && !T(a.src, n))
|
|
60
|
+
return p(403, "The image policy does not allow this image source.");
|
|
61
|
+
const h = a ? { descriptor: a, ok: !0 } : $(u, n);
|
|
62
|
+
if (!h.ok)
|
|
63
|
+
return p(h.rejection.status, h.rejection.reason);
|
|
64
|
+
const y = b(
|
|
65
|
+
h.descriptor,
|
|
66
|
+
a ? null : r.headers.get("accept")
|
|
67
|
+
), f = `${y}|${u.pathname}${u.search}`;
|
|
68
|
+
let m = c.get(f);
|
|
69
|
+
if (m)
|
|
70
|
+
c.delete(f);
|
|
71
|
+
else {
|
|
72
|
+
const g = await d(h.descriptor);
|
|
73
|
+
if (!g)
|
|
74
|
+
return p(404, "The image source does not exist.");
|
|
75
|
+
m = await E(g, {
|
|
76
|
+
format: y,
|
|
77
|
+
quality: h.descriptor.quality,
|
|
78
|
+
width: h.descriptor.width
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
for (c.set(f, m); c.size > s; )
|
|
82
|
+
c.delete(c.keys().next().value);
|
|
83
|
+
return k(
|
|
84
|
+
r,
|
|
85
|
+
m,
|
|
86
|
+
!a && h.descriptor.format === "auto"
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
async function d(l) {
|
|
90
|
+
if (l.sourceKind === "remote") {
|
|
91
|
+
const u = await i(l.src);
|
|
92
|
+
if (!u.ok)
|
|
93
|
+
return;
|
|
94
|
+
const a = new Uint8Array(await u.arrayBuffer());
|
|
95
|
+
return a.byteLength > P ? void 0 : a;
|
|
96
|
+
}
|
|
97
|
+
const r = M(t, l.src);
|
|
98
|
+
if (r)
|
|
99
|
+
try {
|
|
100
|
+
return new Uint8Array(await S(r));
|
|
101
|
+
} catch {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function k(e, t, n) {
|
|
107
|
+
const o = `"${C(t.body)}"`, s = new Headers({
|
|
108
|
+
"cache-control": w,
|
|
109
|
+
"content-type": t.contentType,
|
|
110
|
+
etag: o
|
|
111
|
+
});
|
|
112
|
+
return n && s.set("vary", "accept"), e.headers.get("if-none-match") === o ? new Response(null, { headers: s, status: 304 }) : (s.set("content-length", String(t.body.byteLength)), new Response(
|
|
113
|
+
e.method === "HEAD" ? null : t.body,
|
|
114
|
+
{ headers: s, status: 200 }
|
|
115
|
+
));
|
|
116
|
+
}
|
|
117
|
+
function p(e, t, n = {}) {
|
|
118
|
+
return new Response(t, {
|
|
119
|
+
headers: { "content-type": "text/plain; charset=utf-8", ...n },
|
|
120
|
+
status: e
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
function M(e, t) {
|
|
124
|
+
let n;
|
|
125
|
+
try {
|
|
126
|
+
n = decodeURIComponent(L(t));
|
|
127
|
+
} catch {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (n.split(/[\\/]/).includes(".."))
|
|
131
|
+
return;
|
|
132
|
+
const o = v(e, `.${n}`);
|
|
133
|
+
return z(e, o).split(D).includes("..") ? void 0 : o;
|
|
134
|
+
}
|
|
135
|
+
function C(e) {
|
|
136
|
+
let t = 2166136261, n = 16777619;
|
|
137
|
+
for (const o of e)
|
|
138
|
+
t = Math.imul(t ^ o, 16777619) >>> 0, n = Math.imul(n ^ o, 2246822507) >>> 0;
|
|
139
|
+
return `${t.toString(36)}${n.toString(36)}${e.byteLength.toString(36)}`;
|
|
140
|
+
}
|
|
141
|
+
export {
|
|
142
|
+
V as a,
|
|
143
|
+
B as c
|
|
144
|
+
};
|
|
145
|
+
//# sourceMappingURL=image-CRerJ5BY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-CRerJ5BY.js","sources":["../../src/node/font.ts","../../src/node/image.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport {\n resolveFontAssets,\n type FontAsset,\n type FontAssetOptions,\n} from \"../platform/font-assets\";\nimport { defaultFontPath } from \"../platform/fonts\";\nimport { REVALIDATED_FILE_CACHE_CONTROL } from \"../static-files\";\nimport type { StaticFileHandler } from \"./static\";\n\nexport type FontAssetHandlerOptions = FontAssetOptions;\n\n// The handler answers the self-hosted font URLs that the document declares.\n// It returns null for every other path, so it composes with the static file\n// handler and the route pipeline. The image optimizer composes the same way.\nexport function createFontAssetHandler(\n options: FontAssetHandlerOptions,\n): StaticFileHandler {\n const basePath = options.basePath ?? defaultFontPath;\n let pending: Promise<Map<string, FontAsset>> | undefined;\n\n return async function handleFontRequest(request) {\n const url = new URL(request.url);\n\n if (!url.pathname.startsWith(`${basePath}/`)) {\n return null;\n }\n\n if (request.method !== \"GET\" && request.method !== \"HEAD\") {\n return new Response(\"Method Not Allowed\", {\n headers: {\n allow: \"GET, HEAD\",\n \"content-type\": \"text/plain; charset=utf-8\",\n },\n status: 405,\n });\n }\n\n pending ??= loadAssets();\n\n let assets: Map<string, FontAsset>;\n\n try {\n assets = await pending;\n } catch (error) {\n pending = undefined;\n throw error;\n }\n\n const asset = assets.get(url.pathname);\n\n return asset ? respond(request, asset) : null;\n };\n\n async function loadAssets() {\n const assets = await resolveFontAssets({ ...options, basePath });\n\n return new Map(assets.map((asset) => [asset.url, asset]));\n }\n}\n\n// A font URL names the family, the weight, and the style rather than the\n// content of the file. A strong entity tag keeps a replaced file cheap to\n// revalidate, the same trade the image optimizer makes.\nfunction respond(request: Request, asset: FontAsset) {\n const etag = `\"${createHash(\"sha256\").update(asset.body).digest(\"base64url\").slice(0, 27)}\"`;\n const headers = new Headers({\n \"cache-control\": REVALIDATED_FILE_CACHE_CONTROL,\n \"content-type\": asset.contentType,\n etag,\n });\n\n if (request.headers.get(\"if-none-match\") === etag) {\n return new Response(null, { headers, status: 304 });\n }\n\n headers.set(\"content-length\", String(asset.body.byteLength));\n\n // TYPE-EVIDENCE: the font body is an ArrayBuffer-backed view that the DOM body type accepts.\n return new Response(\n request.method === \"HEAD\" ? null : (asset.body as Uint8Array<ArrayBuffer>),\n { headers, status: 200 },\n );\n}\n","import { readFile } from \"node:fs/promises\";\nimport { relative, resolve, sep } from \"node:path\";\nimport {\n negotiateImageFormat,\n transformImage,\n type EncodedImage,\n} from \"../platform/image-codec\";\nimport { defaultImageOptimizerPath } from \"../platform/image-url\";\nimport type { ImageVariantDescriptor } from \"../platform/image-url\";\nimport {\n isAllowedImageSource,\n parseImageOptimizerRequest,\n} from \"../platform/images\";\nimport type { ImagePolicy } from \"../platform/images\";\nimport { urlPath } from \"../platform/url\";\nimport type { StaticFileHandler } from \"./static\";\nimport { REVALIDATED_FILE_CACHE_CONTROL } from \"../static-files\";\n\nexport type ImageOptimizerFetch = (\n input: string,\n init?: { signal?: AbortSignal },\n) => Promise<Response>;\n\nexport type ImageOptimizerOptions = {\n // The number of encoded variants that stay in memory. The optimizer keeps\n // the most recent request at the front.\n cacheSize?: number;\n fetch?: ImageOptimizerFetch;\n policy?: ImagePolicy;\n // Maps a static loader variant path back to the transform behind it. The\n // development server passes the recorded plans, so a page renders the same\n // URL in development and in the static build.\n resolveVariant?: (pathname: string) => ImageVariantDescriptor | undefined;\n // The directory that holds every local source image, normally the client\n // build output.\n root: string;\n};\n\nconst defaultCacheSize = 64;\nconst maximumRemoteImageBytes = 20 * 1024 * 1024;\n\n// The optimizer answers the same URL that `planImageTransform` writes. It\n// returns null for every other path, so it composes with the static file\n// handler and the route pipeline.\nexport function createImageOptimizer(\n options: ImageOptimizerOptions,\n): StaticFileHandler {\n const root = resolve(options.root);\n const policy = options.policy ?? {};\n const optimizerPath = policy.optimizerPath ?? defaultImageOptimizerPath;\n const cacheSize = options.cacheSize ?? defaultCacheSize;\n const load = options.fetch ?? ((input, init) => fetch(input, init));\n const cache = new Map<string, EncodedImage>();\n\n return async function handleImageRequest(request) {\n const url = new URL(request.url);\n const variant = url.pathname === optimizerPath\n ? undefined\n : options.resolveVariant?.(url.pathname);\n\n if (url.pathname !== optimizerPath && !variant) {\n return null;\n }\n\n if (request.method !== \"GET\" && request.method !== \"HEAD\") {\n return plainText(405, \"Method Not Allowed\", {\n allow: \"GET, HEAD\",\n });\n }\n\n if (variant && !isAllowedImageSource(variant.src, policy)) {\n return plainText(403, \"The image policy does not allow this image source.\");\n }\n\n const parsed = variant\n ? ({ descriptor: variant, ok: true } as const)\n : parseImageOptimizerRequest(url, policy);\n\n if (!parsed.ok) {\n return plainText(parsed.rejection.status, parsed.rejection.reason);\n }\n\n const format = negotiateImageFormat(\n parsed.descriptor,\n variant ? null : request.headers.get(\"accept\"),\n );\n const key = `${format}|${url.pathname}${url.search}`;\n let encoded = cache.get(key);\n\n if (encoded) {\n cache.delete(key);\n } else {\n const source = await readSource(parsed.descriptor);\n\n if (!source) {\n return plainText(404, \"The image source does not exist.\");\n }\n\n encoded = await transformImage(source, {\n format,\n quality: parsed.descriptor.quality,\n width: parsed.descriptor.width,\n });\n }\n\n cache.set(key, encoded);\n\n while (cache.size > cacheSize) {\n // TYPE-EVIDENCE: the loop runs only while the cache holds an entry. The next key therefore exists as a string.\n cache.delete(cache.keys().next().value as string);\n }\n\n return respond(\n request,\n encoded,\n !variant && parsed.descriptor.format === \"auto\",\n );\n };\n\n async function readSource(descriptor: ImageVariantDescriptor) {\n if (descriptor.sourceKind === \"remote\") {\n const response = await load(descriptor.src);\n\n if (!response.ok) {\n return undefined;\n }\n\n const body = new Uint8Array(await response.arrayBuffer());\n\n return body.byteLength > maximumRemoteImageBytes ? undefined : body;\n }\n\n const file = resolveLocalFile(root, descriptor.src);\n\n if (!file) {\n return undefined;\n }\n\n try {\n return new Uint8Array(await readFile(file));\n } catch {\n return undefined;\n }\n }\n}\n\n// The URL carries the source path rather than a content hash, so a long\n// immutable lifetime would outlive a replaced source file. A strong entity\n// tag keeps the revalidation cheap instead.\nfunction respond(request: Request, encoded: EncodedImage, varyOnAccept: boolean) {\n const etag = `\"${weakHash(encoded.body)}\"`;\n const headers = new Headers({\n \"cache-control\": REVALIDATED_FILE_CACHE_CONTROL,\n \"content-type\": encoded.contentType,\n etag,\n });\n\n if (varyOnAccept) {\n headers.set(\"vary\", \"accept\");\n }\n\n if (request.headers.get(\"if-none-match\") === etag) {\n return new Response(null, { headers, status: 304 });\n }\n\n headers.set(\"content-length\", String(encoded.body.byteLength));\n\n // TYPE-EVIDENCE: the encoded body is an ArrayBuffer-backed view that the DOM body type accepts.\n return new Response(\n request.method === \"HEAD\" ? null : (encoded.body as Uint8Array<ArrayBuffer>),\n { headers, status: 200 },\n );\n}\n\nfunction plainText(\n status: number,\n body: string,\n extraHeaders: Record<string, string> = {},\n) {\n return new Response(body, {\n headers: { \"content-type\": \"text/plain; charset=utf-8\", ...extraHeaders },\n status,\n });\n}\n\nfunction resolveLocalFile(root: string, src: string) {\n let decoded: string;\n\n try {\n decoded = decodeURIComponent(urlPath(src));\n } catch {\n return undefined;\n }\n\n if (decoded.split(/[\\\\/]/).includes(\"..\")) {\n return undefined;\n }\n\n const file = resolve(root, `.${decoded}`);\n\n return relative(root, file).split(sep).includes(\"..\") ? undefined : file;\n}\n\nfunction weakHash(body: Uint8Array) {\n let low = 0x811c9dc5;\n let high = 0x01000193;\n\n for (const byte of body) {\n low = Math.imul(low ^ byte, 0x01000193) >>> 0;\n high = Math.imul(high ^ byte, 0x85ebca6b) >>> 0;\n }\n\n return `${low.toString(36)}${high.toString(36)}${body.byteLength.toString(36)}`;\n}\n"],"names":["createFontAssetHandler","options","basePath","defaultFontPath","pending","request","url","loadAssets","assets","error","asset","respond","resolveFontAssets","etag","createHash","headers","REVALIDATED_FILE_CACHE_CONTROL","defaultCacheSize","maximumRemoteImageBytes","createImageOptimizer","root","resolve","policy","optimizerPath","defaultImageOptimizerPath","cacheSize","load","input","init","cache","variant","plainText","isAllowedImageSource","parsed","parseImageOptimizerRequest","format","negotiateImageFormat","key","encoded","source","readSource","transformImage","descriptor","response","body","file","resolveLocalFile","readFile","varyOnAccept","weakHash","status","extraHeaders","src","decoded","urlPath","relative","sep","low","high","byte"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash as d } from "node:crypto";
|
|
2
2
|
import { readFile as h, mkdir as p, writeFile as F } from "node:fs/promises";
|
|
3
3
|
import { resolve as u, join as w, relative as b, sep as $ } from "node:path";
|
|
4
|
-
import { d as v, f as S,
|
|
4
|
+
import { d as v, f as S, b as N, r as E, c as J, e as O, g as j } from "./images-QgdmLyQ3.js";
|
|
5
5
|
const y = 5 * 1024 * 1024;
|
|
6
6
|
async function H(e) {
|
|
7
7
|
const t = e.fonts ?? [];
|
|
@@ -138,4 +138,4 @@ export {
|
|
|
138
138
|
H as r,
|
|
139
139
|
q as t
|
|
140
140
|
};
|
|
141
|
-
//# sourceMappingURL=image-codec-
|
|
141
|
+
//# sourceMappingURL=image-codec-CQ8Cwxwb.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-codec-BmzWnH6W.js","sources":["../../src/platform/font-assets.ts","../../src/platform/image-codec.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { join, relative, resolve, sep } from \"node:path\";\nimport {\n defaultFontPath,\n fontAssetFileName,\n fontFormat,\n fontMediaType,\n fontStylesheetUrl,\n renderFontFaceCss,\n type FontContribution,\n type FontDefinition,\n} from \"./fonts\";\n\nexport type FontAssetFetch = (\n input: string,\n init?: { signal?: AbortSignal },\n) => Promise<Response>;\n\nexport type FontAssetOptions = {\n basePath?: string;\n // Where a downloaded font file stays between builds. A build that already\n // downloaded a font reads it back and makes no network request.\n cacheDir?: string;\n fetch?: FontAssetFetch;\n fonts?: FontContribution;\n // The project directory that every local font source resolves against.\n root: string;\n};\n\nexport type FontAsset = {\n body: Uint8Array;\n contentType: string;\n // The output path, relative to the directory that the build publishes.\n file: string;\n url: string;\n};\n\nconst maximumFontBytes = 5 * 1024 * 1024;\n\n// One font declaration becomes one published file plus one stylesheet. The\n// build writes these files, and the Node handler serves the same set from\n// memory. Both derive every URL from the declaration, so they cannot drift.\nexport async function resolveFontAssets(\n options: FontAssetOptions,\n): Promise<FontAsset[]> {\n const fonts = options.fonts ?? [];\n\n if (fonts.length === 0) {\n return [];\n }\n\n const basePath = options.basePath ?? defaultFontPath;\n const root = resolve(options.root);\n const cacheDir = resolve(\n options.cacheDir ?? join(root, \"node_modules\", \".demiurge\", \"fonts\"),\n );\n const load = options.fetch ?? ((input, init) => fetch(input, init));\n const published = new Map<string, string>();\n const assets: FontAsset[] = [];\n\n for (const definition of fonts) {\n if (!definition.selfHost) {\n continue;\n }\n\n const name = fontAssetFileName(definition);\n const claimed = published.get(name);\n\n if (claimed !== undefined) {\n if (claimed !== definition.src) {\n throw new Error(\n `Font family ${JSON.stringify(definition.family)} declares two sources that publish the same file ${JSON.stringify(name)}. Give one of them a different weight or style.`,\n );\n }\n\n continue;\n }\n\n published.set(name, definition.src);\n assets.push({\n body: await readFontBody(definition, { cacheDir, load, root }),\n contentType: fontMediaType(definition.src),\n file: outputFile(basePath, name),\n url: `${basePath}/${name}`,\n });\n }\n\n const stylesheet = `${renderFontFaceCss(fonts, basePath)}\\n`;\n\n assets.push({\n body: new TextEncoder().encode(stylesheet),\n contentType: \"text/css; charset=utf-8\",\n file: outputFile(basePath, \"fonts.css\"),\n url: fontStylesheetUrl(basePath),\n });\n\n return assets;\n}\n\nasync function readFontBody(\n definition: FontDefinition,\n context: { cacheDir: string; load: FontAssetFetch; root: string },\n) {\n const body = definition.source === \"google\"\n ? await downloadFontFile(definition, context)\n : await readLocalFontFile(definition, context.root);\n\n if (body.byteLength === 0) {\n throw new Error(\n `Font family ${JSON.stringify(definition.family)} resolved to an empty file.`,\n );\n }\n\n if (body.byteLength > maximumFontBytes) {\n throw new Error(\n `Font family ${JSON.stringify(definition.family)} is larger than the ${maximumFontBytes} byte limit.`,\n );\n }\n\n return body;\n}\n\nasync function readLocalFontFile(definition: FontDefinition, root: string) {\n const file = resolve(root, definition.src.replace(/^\\/+/, \"\"));\n\n if (relative(root, file).split(sep).includes(\"..\")) {\n throw new Error(\n `Font source escaped the project directory: ${JSON.stringify(definition.src)}.`,\n );\n }\n\n try {\n return new Uint8Array(await readFile(file));\n } catch (error) {\n throw new Error(\n `Demiurge could not read the font file ${JSON.stringify(definition.src)} for family ${JSON.stringify(definition.family)}.`,\n { cause: error },\n );\n }\n}\n\n// A downloaded font stays in the cache directory. The first build reaches the\n// font host once, and every later build reads the file from disk. A build that\n// runs without network access therefore keeps working.\nasync function downloadFontFile(\n definition: FontDefinition,\n context: { cacheDir: string; load: FontAssetFetch; root: string },\n) {\n const digest = createHash(\"sha256\").update(definition.src).digest(\"hex\");\n const cacheFile = join(\n context.cacheDir,\n `${digest.slice(0, 32)}.${fontFormat(definition.src)}`,\n );\n\n try {\n return new Uint8Array(await readFile(cacheFile));\n } catch {\n // The cache holds no copy yet, so the font host answers this once.\n }\n\n let response: Response;\n\n try {\n response = await context.load(definition.src);\n } catch (error) {\n throw new Error(\n `Demiurge could not download the font for family ${JSON.stringify(definition.family)} from ${JSON.stringify(definition.src)}.`,\n { cause: error },\n );\n }\n\n if (!response.ok) {\n throw new Error(\n `The font host answered ${response.status} for family ${JSON.stringify(definition.family)} at ${JSON.stringify(definition.src)}.`,\n );\n }\n\n const body = new Uint8Array(await response.arrayBuffer());\n\n await mkdir(context.cacheDir, { recursive: true });\n await writeFile(cacheFile, body);\n\n return body;\n}\n\nfunction outputFile(basePath: string, name: string) {\n return `${basePath.replace(/^\\/+/, \"\")}/${name}`;\n}\n","import type { ImageFormat } from \"./images\";\nimport { imageSourceExtension, type ImageVariantDescriptor } from \"./image-url\";\n\nexport type EncodedImage = {\n body: Uint8Array;\n contentType: string;\n};\n\ntype SharpModule = {\n default: (input: Uint8Array) => SharpPipeline;\n};\n\ntype SharpPipeline = {\n avif: (options: { quality: number }) => SharpPipeline;\n jpeg: (options: { quality: number }) => SharpPipeline;\n png: () => SharpPipeline;\n resize: (options: {\n width: number;\n withoutEnlargement: boolean;\n }) => SharpPipeline;\n toBuffer: () => Promise<Buffer>;\n webp: (options: { quality: number }) => SharpPipeline;\n};\n\nconst contentTypes: Record<Exclude<ImageFormat, \"auto\">, string> = {\n avif: \"image/avif\",\n jpeg: \"image/jpeg\",\n png: \"image/png\",\n webp: \"image/webp\",\n};\n\nconst defaultQuality = 75;\n\nexport function imageContentType(format: Exclude<ImageFormat, \"auto\">) {\n return contentTypes[format];\n}\n\n// `auto` lets the browser take the smallest format that it accepts. A client\n// that sends no usable `accept` header keeps the source format.\nexport function negotiateImageFormat(\n descriptor: ImageVariantDescriptor,\n accept: string | null,\n): Exclude<ImageFormat, \"auto\"> {\n if (descriptor.format !== \"auto\") {\n return descriptor.format;\n }\n\n const accepted = (accept ?? \"\").toLowerCase();\n\n if (accepted.includes(\"image/avif\")) {\n return \"avif\";\n }\n\n if (accepted.includes(\"image/webp\")) {\n return \"webp\";\n }\n\n return sourceFormat(descriptor.src);\n}\n\nexport function sourceFormat(src: string): Exclude<ImageFormat, \"auto\"> {\n const extension = imageSourceExtension(src);\n\n if (extension === \"avif\") {\n return \"avif\";\n }\n\n if (extension === \"webp\") {\n return \"webp\";\n }\n\n if (extension === \"png\") {\n return \"png\";\n }\n\n return \"jpeg\";\n}\n\nexport async function transformImage(\n source: Uint8Array,\n options: {\n format: Exclude<ImageFormat, \"auto\">;\n quality?: number;\n width: number;\n },\n): Promise<EncodedImage> {\n const sharp = await loadImageCodec();\n const quality = options.quality ?? defaultQuality;\n const resized = sharp(source).resize({\n width: options.width,\n withoutEnlargement: true,\n });\n\n return {\n body: new Uint8Array(await encode(resized, options.format, quality)),\n contentType: contentTypes[options.format],\n };\n}\n\nfunction encode(\n pipeline: SharpPipeline,\n format: Exclude<ImageFormat, \"auto\">,\n quality: number,\n) {\n if (format === \"png\") {\n return pipeline.png().toBuffer();\n }\n\n if (format === \"jpeg\") {\n return pipeline.jpeg({ quality }).toBuffer();\n }\n\n if (format === \"avif\") {\n return pipeline.avif({ quality }).toBuffer();\n }\n\n return pipeline.webp({ quality }).toBuffer();\n}\n\n// `sharp` is an optional peer dependency. Only an application that optimizes\n// an image installs it, the same rule that ADR 0006 states for a host\n// adapter dependency.\nasync function loadImageCodec() {\n let module: SharpModule;\n\n try {\n // TYPE-EVIDENCE: sharp ships no types. The local type describes the methods the codec calls.\n module = (await import(\"sharp\")) as SharpModule;\n } catch (error) {\n throw new Error(\n \"Demiurge image optimization requires the optional peer dependency sharp. Install sharp in the application.\",\n { cause: error },\n );\n }\n\n return module.default;\n}\n"],"names":["maximumFontBytes","resolveFontAssets","options","fonts","basePath","defaultFontPath","root","resolve","cacheDir","join","load","input","init","published","assets","definition","name","fontAssetFileName","claimed","readFontBody","fontMediaType","outputFile","stylesheet","renderFontFaceCss","fontStylesheetUrl","context","body","downloadFontFile","readLocalFontFile","file","relative","sep","readFile","error","digest","createHash","cacheFile","fontFormat","response","mkdir","writeFile","contentTypes","defaultQuality","negotiateImageFormat","descriptor","accept","accepted","sourceFormat","src","extension","imageSourceExtension","transformImage","source","sharp","loadImageCodec","quality","resized","encode","pipeline","format","module"],"mappings":";;;;AAsCA,MAAMA,IAAmB,IAAI,OAAO;AAKpC,eAAsBC,EACpBC,GACsB;AACtB,QAAMC,IAAQD,EAAQ,SAAS,CAAA;AAE/B,MAAIC,EAAM,WAAW;AACnB,WAAO,CAAA;AAGT,QAAMC,IAAWF,EAAQ,YAAYG,GAC/BC,IAAOC,EAAQL,EAAQ,IAAI,GAC3BM,IAAWD;AAAA,IACfL,EAAQ,YAAYO,EAAKH,GAAM,gBAAgB,aAAa,OAAO;AAAA,EAAA,GAE/DI,IAAOR,EAAQ,UAAU,CAACS,GAAOC,MAAS,MAAMD,GAAOC,CAAI,IAC3DC,wBAAgB,IAAA,GAChBC,IAAsB,CAAA;AAE5B,aAAWC,KAAcZ,GAAO;AAC9B,QAAI,CAACY,EAAW;AACd;AAGF,UAAMC,IAAOC,EAAkBF,CAAU,GACnCG,IAAUL,EAAU,IAAIG,CAAI;AAElC,QAAIE,MAAY,QAAW;AACzB,UAAIA,MAAYH,EAAW;AACzB,cAAM,IAAI;AAAA,UACR,eAAe,KAAK,UAAUA,EAAW,MAAM,CAAC,oDAAoD,KAAK,UAAUC,CAAI,CAAC;AAAA,QAAA;AAI5H;AAAA,IACF;AAEA,IAAAH,EAAU,IAAIG,GAAMD,EAAW,GAAG,GAClCD,EAAO,KAAK;AAAA,MACV,MAAM,MAAMK,EAAaJ,GAAY,EAAE,UAAAP,GAAU,MAAAE,GAAM,MAAAJ,GAAM;AAAA,MAC7D,aAAac,EAAcL,EAAW,GAAG;AAAA,MACzC,MAAMM,EAAWjB,GAAUY,CAAI;AAAA,MAC/B,KAAK,GAAGZ,CAAQ,IAAIY,CAAI;AAAA,IAAA,CACzB;AAAA,EACH;AAEA,QAAMM,IAAa,GAAGC,EAAkBpB,GAAOC,CAAQ,CAAC;AAAA;AAExD,SAAAU,EAAO,KAAK;AAAA,IACV,MAAM,IAAI,cAAc,OAAOQ,CAAU;AAAA,IACzC,aAAa;AAAA,IACb,MAAMD,EAAWjB,GAAU,WAAW;AAAA,IACtC,KAAKoB,EAAkBpB,CAAQ;AAAA,EAAA,CAChC,GAEMU;AACT;AAEA,eAAeK,EACbJ,GACAU,GACA;AACA,QAAMC,IAAOX,EAAW,WAAW,WAC/B,MAAMY,EAAiBZ,GAAYU,CAAO,IAC1C,MAAMG,EAAkBb,GAAYU,EAAQ,IAAI;AAEpD,MAAIC,EAAK,eAAe;AACtB,UAAM,IAAI;AAAA,MACR,eAAe,KAAK,UAAUX,EAAW,MAAM,CAAC;AAAA,IAAA;AAIpD,MAAIW,EAAK,aAAa1B;AACpB,UAAM,IAAI;AAAA,MACR,eAAe,KAAK,UAAUe,EAAW,MAAM,CAAC,uBAAuBf,CAAgB;AAAA,IAAA;AAI3F,SAAO0B;AACT;AAEA,eAAeE,EAAkBb,GAA4BT,GAAc;AACzE,QAAMuB,IAAOtB,EAAQD,GAAMS,EAAW,IAAI,QAAQ,QAAQ,EAAE,CAAC;AAE7D,MAAIe,EAASxB,GAAMuB,CAAI,EAAE,MAAME,CAAG,EAAE,SAAS,IAAI;AAC/C,UAAM,IAAI;AAAA,MACR,8CAA8C,KAAK,UAAUhB,EAAW,GAAG,CAAC;AAAA,IAAA;AAIhF,MAAI;AACF,WAAO,IAAI,WAAW,MAAMiB,EAASH,CAAI,CAAC;AAAA,EAC5C,SAASI,GAAO;AACd,UAAM,IAAI;AAAA,MACR,yCAAyC,KAAK,UAAUlB,EAAW,GAAG,CAAC,eAAe,KAAK,UAAUA,EAAW,MAAM,CAAC;AAAA,MACvH,EAAE,OAAOkB,EAAA;AAAA,IAAM;AAAA,EAEnB;AACF;AAKA,eAAeN,EACbZ,GACAU,GACA;AACA,QAAMS,IAASC,EAAW,QAAQ,EAAE,OAAOpB,EAAW,GAAG,EAAE,OAAO,KAAK,GACjEqB,IAAY3B;AAAA,IAChBgB,EAAQ;AAAA,IACR,GAAGS,EAAO,MAAM,GAAG,EAAE,CAAC,IAAIG,EAAWtB,EAAW,GAAG,CAAC;AAAA,EAAA;AAGtD,MAAI;AACF,WAAO,IAAI,WAAW,MAAMiB,EAASI,CAAS,CAAC;AAAA,EACjD,QAAQ;AAAA,EAER;AAEA,MAAIE;AAEJ,MAAI;AACF,IAAAA,IAAW,MAAMb,EAAQ,KAAKV,EAAW,GAAG;AAAA,EAC9C,SAASkB,GAAO;AACd,UAAM,IAAI;AAAA,MACR,mDAAmD,KAAK,UAAUlB,EAAW,MAAM,CAAC,SAAS,KAAK,UAAUA,EAAW,GAAG,CAAC;AAAA,MAC3H,EAAE,OAAOkB,EAAA;AAAA,IAAM;AAAA,EAEnB;AAEA,MAAI,CAACK,EAAS;AACZ,UAAM,IAAI;AAAA,MACR,0BAA0BA,EAAS,MAAM,eAAe,KAAK,UAAUvB,EAAW,MAAM,CAAC,OAAO,KAAK,UAAUA,EAAW,GAAG,CAAC;AAAA,IAAA;AAIlI,QAAMW,IAAO,IAAI,WAAW,MAAMY,EAAS,aAAa;AAExD,eAAMC,EAAMd,EAAQ,UAAU,EAAE,WAAW,IAAM,GACjD,MAAMe,EAAUJ,GAAWV,CAAI,GAExBA;AACT;AAEA,SAASL,EAAWjB,GAAkBY,GAAc;AAClD,SAAO,GAAGZ,EAAS,QAAQ,QAAQ,EAAE,CAAC,IAAIY,CAAI;AAChD;ACpKA,MAAMyB,IAA6D;AAAA,EACjE,MAAM;AAAA,EACN,MAAM;AAAA,EACN,KAAK;AAAA,EACL,MAAM;AACR,GAEMC,IAAiB;AAQhB,SAASC,EACdC,GACAC,GAC8B;AAC9B,MAAID,EAAW,WAAW;AACxB,WAAOA,EAAW;AAGpB,QAAME,KAAYD,KAAU,IAAI,YAAA;AAEhC,SAAIC,EAAS,SAAS,YAAY,IACzB,SAGLA,EAAS,SAAS,YAAY,IACzB,SAGFC,EAAaH,EAAW,GAAG;AACpC;AAEO,SAASG,EAAaC,GAA2C;AACtE,QAAMC,IAAYC,EAAqBF,CAAG;AAE1C,SAAIC,MAAc,SACT,SAGLA,MAAc,SACT,SAGLA,MAAc,QACT,QAGF;AACT;AAEA,eAAsBE,EACpBC,GACAlD,GAKuB;AACvB,QAAMmD,IAAQ,MAAMC,EAAA,GACdC,IAAUrD,EAAQ,WAAWwC,GAC7Bc,IAAUH,EAAMD,CAAM,EAAE,OAAO;AAAA,IACnC,OAAOlD,EAAQ;AAAA,IACf,oBAAoB;AAAA,EAAA,CACrB;AAED,SAAO;AAAA,IACL,MAAM,IAAI,WAAW,MAAMuD,EAAOD,GAAStD,EAAQ,QAAQqD,CAAO,CAAC;AAAA,IACnE,aAAad,EAAavC,EAAQ,MAAM;AAAA,EAAA;AAE5C;AAEA,SAASuD,EACPC,GACAC,GACAJ,GACA;AACA,SAAII,MAAW,QACND,EAAS,IAAA,EAAM,SAAA,IAGpBC,MAAW,SACND,EAAS,KAAK,EAAE,SAAAH,EAAA,CAAS,EAAE,SAAA,IAGhCI,MAAW,SACND,EAAS,KAAK,EAAE,SAAAH,EAAA,CAAS,EAAE,SAAA,IAG7BG,EAAS,KAAK,EAAE,SAAAH,EAAA,CAAS,EAAE,SAAA;AACpC;AAKA,eAAeD,IAAiB;AAC9B,MAAIM;AAEJ,MAAI;AAEF,IAAAA,IAAU,MAAM,OAAO,OAAO;AAAA,EAChC,SAAS3B,GAAO;AACd,UAAM,IAAI;AAAA,MACR;AAAA,MACA,EAAE,OAAOA,EAAA;AAAA,IAAM;AAAA,EAEnB;AAEA,SAAO2B,EAAO;AAChB;"}
|
|
1
|
+
{"version":3,"file":"image-codec-CQ8Cwxwb.js","sources":["../../src/platform/font-assets.ts","../../src/platform/image-codec.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { join, relative, resolve, sep } from \"node:path\";\nimport {\n defaultFontPath,\n fontAssetFileName,\n fontFormat,\n fontMediaType,\n fontStylesheetUrl,\n renderFontFaceCss,\n type FontContribution,\n type FontDefinition,\n} from \"./fonts\";\n\nexport type FontAssetFetch = (\n input: string,\n init?: { signal?: AbortSignal },\n) => Promise<Response>;\n\nexport type FontAssetOptions = {\n basePath?: string;\n // Where a downloaded font file stays between builds. A build that already\n // downloaded a font reads it back and makes no network request.\n cacheDir?: string;\n fetch?: FontAssetFetch;\n fonts?: FontContribution;\n // The project directory that every local font source resolves against.\n root: string;\n};\n\nexport type FontAsset = {\n body: Uint8Array;\n contentType: string;\n // The output path, relative to the directory that the build publishes.\n file: string;\n url: string;\n};\n\nconst maximumFontBytes = 5 * 1024 * 1024;\n\n// One font declaration becomes one published file plus one stylesheet. The\n// build writes these files, and the Node handler serves the same set from\n// memory. Both derive every URL from the declaration, so they cannot drift.\nexport async function resolveFontAssets(\n options: FontAssetOptions,\n): Promise<FontAsset[]> {\n const fonts = options.fonts ?? [];\n\n if (fonts.length === 0) {\n return [];\n }\n\n const basePath = options.basePath ?? defaultFontPath;\n const root = resolve(options.root);\n const cacheDir = resolve(\n options.cacheDir ?? join(root, \"node_modules\", \".demiurge\", \"fonts\"),\n );\n const load = options.fetch ?? ((input, init) => fetch(input, init));\n const published = new Map<string, string>();\n const assets: FontAsset[] = [];\n\n for (const definition of fonts) {\n if (!definition.selfHost) {\n continue;\n }\n\n const name = fontAssetFileName(definition);\n const claimed = published.get(name);\n\n if (claimed !== undefined) {\n if (claimed !== definition.src) {\n throw new Error(\n `Font family ${JSON.stringify(definition.family)} declares two sources that publish the same file ${JSON.stringify(name)}. Give one of them a different weight or style.`,\n );\n }\n\n continue;\n }\n\n published.set(name, definition.src);\n assets.push({\n body: await readFontBody(definition, { cacheDir, load, root }),\n contentType: fontMediaType(definition.src),\n file: outputFile(basePath, name),\n url: `${basePath}/${name}`,\n });\n }\n\n const stylesheet = `${renderFontFaceCss(fonts, basePath)}\\n`;\n\n assets.push({\n body: new TextEncoder().encode(stylesheet),\n contentType: \"text/css; charset=utf-8\",\n file: outputFile(basePath, \"fonts.css\"),\n url: fontStylesheetUrl(basePath),\n });\n\n return assets;\n}\n\nasync function readFontBody(\n definition: FontDefinition,\n context: { cacheDir: string; load: FontAssetFetch; root: string },\n) {\n const body = definition.source === \"google\"\n ? await downloadFontFile(definition, context)\n : await readLocalFontFile(definition, context.root);\n\n if (body.byteLength === 0) {\n throw new Error(\n `Font family ${JSON.stringify(definition.family)} resolved to an empty file.`,\n );\n }\n\n if (body.byteLength > maximumFontBytes) {\n throw new Error(\n `Font family ${JSON.stringify(definition.family)} is larger than the ${maximumFontBytes} byte limit.`,\n );\n }\n\n return body;\n}\n\nasync function readLocalFontFile(definition: FontDefinition, root: string) {\n const file = resolve(root, definition.src.replace(/^\\/+/, \"\"));\n\n if (relative(root, file).split(sep).includes(\"..\")) {\n throw new Error(\n `Font source escaped the project directory: ${JSON.stringify(definition.src)}.`,\n );\n }\n\n try {\n return new Uint8Array(await readFile(file));\n } catch (error) {\n throw new Error(\n `Demiurge could not read the font file ${JSON.stringify(definition.src)} for family ${JSON.stringify(definition.family)}.`,\n { cause: error },\n );\n }\n}\n\n// A downloaded font stays in the cache directory. The first build reaches the\n// font host once, and every later build reads the file from disk. A build that\n// runs without network access therefore keeps working.\nasync function downloadFontFile(\n definition: FontDefinition,\n context: { cacheDir: string; load: FontAssetFetch; root: string },\n) {\n const digest = createHash(\"sha256\").update(definition.src).digest(\"hex\");\n const cacheFile = join(\n context.cacheDir,\n `${digest.slice(0, 32)}.${fontFormat(definition.src)}`,\n );\n\n try {\n return new Uint8Array(await readFile(cacheFile));\n } catch {\n // The cache holds no copy yet, so the font host answers this once.\n }\n\n let response: Response;\n\n try {\n response = await context.load(definition.src);\n } catch (error) {\n throw new Error(\n `Demiurge could not download the font for family ${JSON.stringify(definition.family)} from ${JSON.stringify(definition.src)}.`,\n { cause: error },\n );\n }\n\n if (!response.ok) {\n throw new Error(\n `The font host answered ${response.status} for family ${JSON.stringify(definition.family)} at ${JSON.stringify(definition.src)}.`,\n );\n }\n\n const body = new Uint8Array(await response.arrayBuffer());\n\n await mkdir(context.cacheDir, { recursive: true });\n await writeFile(cacheFile, body);\n\n return body;\n}\n\nfunction outputFile(basePath: string, name: string) {\n return `${basePath.replace(/^\\/+/, \"\")}/${name}`;\n}\n","import type { ImageFormat } from \"./images\";\nimport { imageSourceExtension, type ImageVariantDescriptor } from \"./image-url\";\n\nexport type EncodedImage = {\n body: Uint8Array;\n contentType: string;\n};\n\ntype SharpModule = {\n default: (input: Uint8Array) => SharpPipeline;\n};\n\ntype SharpPipeline = {\n avif: (options: { quality: number }) => SharpPipeline;\n jpeg: (options: { quality: number }) => SharpPipeline;\n png: () => SharpPipeline;\n resize: (options: {\n width: number;\n withoutEnlargement: boolean;\n }) => SharpPipeline;\n toBuffer: () => Promise<Buffer>;\n webp: (options: { quality: number }) => SharpPipeline;\n};\n\nconst contentTypes: Record<Exclude<ImageFormat, \"auto\">, string> = {\n avif: \"image/avif\",\n jpeg: \"image/jpeg\",\n png: \"image/png\",\n webp: \"image/webp\",\n};\n\nconst defaultQuality = 75;\n\nexport function imageContentType(format: Exclude<ImageFormat, \"auto\">) {\n return contentTypes[format];\n}\n\n// `auto` lets the browser take the smallest format that it accepts. A client\n// that sends no usable `accept` header keeps the source format.\nexport function negotiateImageFormat(\n descriptor: ImageVariantDescriptor,\n accept: string | null,\n): Exclude<ImageFormat, \"auto\"> {\n if (descriptor.format !== \"auto\") {\n return descriptor.format;\n }\n\n const accepted = (accept ?? \"\").toLowerCase();\n\n if (accepted.includes(\"image/avif\")) {\n return \"avif\";\n }\n\n if (accepted.includes(\"image/webp\")) {\n return \"webp\";\n }\n\n return sourceFormat(descriptor.src);\n}\n\nexport function sourceFormat(src: string): Exclude<ImageFormat, \"auto\"> {\n const extension = imageSourceExtension(src);\n\n if (extension === \"avif\") {\n return \"avif\";\n }\n\n if (extension === \"webp\") {\n return \"webp\";\n }\n\n if (extension === \"png\") {\n return \"png\";\n }\n\n return \"jpeg\";\n}\n\nexport async function transformImage(\n source: Uint8Array,\n options: {\n format: Exclude<ImageFormat, \"auto\">;\n quality?: number;\n width: number;\n },\n): Promise<EncodedImage> {\n const sharp = await loadImageCodec();\n const quality = options.quality ?? defaultQuality;\n const resized = sharp(source).resize({\n width: options.width,\n withoutEnlargement: true,\n });\n\n return {\n body: new Uint8Array(await encode(resized, options.format, quality)),\n contentType: contentTypes[options.format],\n };\n}\n\nfunction encode(\n pipeline: SharpPipeline,\n format: Exclude<ImageFormat, \"auto\">,\n quality: number,\n) {\n if (format === \"png\") {\n return pipeline.png().toBuffer();\n }\n\n if (format === \"jpeg\") {\n return pipeline.jpeg({ quality }).toBuffer();\n }\n\n if (format === \"avif\") {\n return pipeline.avif({ quality }).toBuffer();\n }\n\n return pipeline.webp({ quality }).toBuffer();\n}\n\n// `sharp` is an optional peer dependency. Only an application that optimizes\n// an image installs it, the same rule that ADR 0006 states for a host\n// adapter dependency.\nasync function loadImageCodec() {\n let module: SharpModule;\n\n try {\n // TYPE-EVIDENCE: sharp ships no types. The local type describes the methods the codec calls.\n module = (await import(\"sharp\")) as SharpModule;\n } catch (error) {\n throw new Error(\n \"Demiurge image optimization requires the optional peer dependency sharp. Install sharp in the application.\",\n { cause: error },\n );\n }\n\n return module.default;\n}\n"],"names":["maximumFontBytes","resolveFontAssets","options","fonts","basePath","defaultFontPath","root","resolve","cacheDir","join","load","input","init","published","assets","definition","name","fontAssetFileName","claimed","readFontBody","fontMediaType","outputFile","stylesheet","renderFontFaceCss","fontStylesheetUrl","context","body","downloadFontFile","readLocalFontFile","file","relative","sep","readFile","error","digest","createHash","cacheFile","fontFormat","response","mkdir","writeFile","contentTypes","defaultQuality","negotiateImageFormat","descriptor","accept","accepted","sourceFormat","src","extension","imageSourceExtension","transformImage","source","sharp","loadImageCodec","quality","resized","encode","pipeline","format","module"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { p as P } from "./
|
|
1
|
+
import { p as P } from "./links-iU22QkCE.js";
|
|
2
2
|
function j(t) {
|
|
3
3
|
return t.split("?")[0].split("#")[0];
|
|
4
4
|
}
|
|
@@ -322,26 +322,26 @@ function c(t, e) {
|
|
|
322
322
|
return { ok: !1, rejection: { reason: e, status: t } };
|
|
323
323
|
}
|
|
324
324
|
export {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
325
|
+
z as a,
|
|
326
|
+
R as b,
|
|
327
|
+
F as c,
|
|
328
328
|
u as d,
|
|
329
|
-
|
|
329
|
+
m as e,
|
|
330
330
|
N as f,
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
331
|
+
b as g,
|
|
332
|
+
et as h,
|
|
333
|
+
nt as i,
|
|
334
|
+
tt as j,
|
|
335
335
|
at as k,
|
|
336
336
|
B as l,
|
|
337
337
|
rt as m,
|
|
338
338
|
G as n,
|
|
339
339
|
I as o,
|
|
340
|
-
|
|
340
|
+
ot as p,
|
|
341
341
|
X as q,
|
|
342
342
|
Y as r,
|
|
343
343
|
L as s,
|
|
344
344
|
Z as t,
|
|
345
345
|
j as u
|
|
346
346
|
};
|
|
347
|
-
//# sourceMappingURL=images-
|
|
347
|
+
//# sourceMappingURL=images-QgdmLyQ3.js.map
|