smoodly 0.0.51 → 0.0.52
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 +14 -13
- package/dist/admin/auth-config.d.ts +6 -5
- package/dist/admin/auth.d.ts +3 -11
- package/dist/admin/auth.js +1 -30
- package/dist/admin/browser/fetch-op.js +3 -2
- package/dist/admin/browser/main.js +6 -2
- package/dist/admin/client-ops.d.ts +5 -4
- package/dist/admin/client-ops.js +14 -11
- package/dist/admin/delivery-mode.d.ts +1 -1
- package/dist/admin/delivery-mode.js +2 -2
- package/dist/admin/drift.d.ts +0 -2
- package/dist/admin/drift.js +1 -1
- package/dist/admin/editor/EditorView.d.ts +0 -3
- package/dist/admin/editor/EditorView.js +11 -16
- package/dist/admin/editor/FloatingToolbar.d.ts +0 -1
- package/dist/admin/editor/FloatingToolbar.js +1 -1
- package/dist/admin/editor/SeoSettings.d.ts +0 -2
- package/dist/admin/editor/SeoSettings.js +2 -2
- package/dist/admin/editor/canvas-address.d.ts +8 -0
- package/dist/admin/editor/canvas-address.js +18 -2
- package/dist/admin/editor/canvas-diagnosis.d.ts +2 -0
- package/dist/admin/editor/canvas-diagnosis.js +3 -0
- package/dist/admin/editor/inline-dom.d.ts +0 -4
- package/dist/admin/editor/inline-dom.js +1 -1
- package/dist/admin/editor/preview-grant.d.ts +6 -0
- package/dist/admin/editor/preview-grant.js +25 -0
- package/dist/admin/editor/useCanvasDiagnosis.js +2 -2
- package/dist/admin/files.generated.js +1 -1
- package/dist/admin/fixed-nodes.d.ts +0 -6
- package/dist/admin/fixed-nodes.js +1 -81
- package/dist/admin/forms/CropEditor.d.ts +4 -2
- package/dist/admin/forms/CropEditor.js +58 -5
- package/dist/admin/forms/ImagePreview.d.ts +3 -1
- package/dist/admin/forms/ImagePreview.js +9 -3
- package/dist/admin/forms/MediaGrid.js +2 -1
- package/dist/admin/forms/MediaPicker.js +9 -6
- package/dist/admin/forms/MediaWidget.js +11 -9
- package/dist/admin/forms/assets-context.d.ts +7 -3
- package/dist/admin/forms/assets-context.js +5 -3
- package/dist/admin/forms/crop-editor.d.ts +24 -2
- package/dist/admin/forms/crop-editor.js +36 -2
- package/dist/admin/forms/date-picker.d.ts +0 -1
- package/dist/admin/forms/date-picker.js +1 -1
- package/dist/admin/forms/tabs.d.ts +0 -1
- package/dist/admin/forms/tabs.js +1 -1
- package/dist/admin/forms/upload.d.ts +18 -11
- package/dist/admin/forms/upload.js +62 -24
- package/dist/admin/global-items.d.ts +0 -3
- package/dist/admin/global-items.js +0 -54
- package/dist/admin/handler.d.ts +22 -28
- package/dist/admin/handler.js +146 -47
- package/dist/admin/health.d.ts +0 -4
- package/dist/admin/index.d.ts +5 -8
- package/dist/admin/index.js +3 -6
- package/dist/admin/ops.d.ts +36 -22
- package/dist/admin/page-data.d.ts +14 -6
- package/dist/admin/page-data.js +1 -1
- package/dist/admin/session-flow.d.ts +43 -0
- package/dist/admin/session-flow.js +94 -0
- package/dist/admin/shell/AdminApp.d.ts +8 -3
- package/dist/admin/shell/AdminApp.js +37 -58
- package/dist/admin/shell/ContentHealthPage.js +1 -3
- package/dist/admin/shell/GlobalFields.d.ts +0 -3
- package/dist/admin/shell/GlobalFields.js +1 -1
- package/dist/admin/shell/IconRail.js +5 -4
- package/dist/admin/shell/MediaScreen.js +9 -6
- package/dist/admin/shell/MembersPage.d.ts +1 -2
- package/dist/admin/shell/MembersPage.js +19 -90
- package/dist/admin/shell/SettingsView.d.ts +1 -2
- package/dist/admin/shell/SettingsView.js +2 -2
- package/dist/admin/shell/SignedOutView.d.ts +5 -0
- package/dist/admin/shell/SignedOutView.js +9 -0
- package/dist/admin/shell/console-links.d.ts +5 -0
- package/dist/admin/shell/console-links.js +11 -0
- package/dist/admin/shell/members-list.d.ts +1 -14
- package/dist/admin/shell/members-list.js +2 -23
- package/dist/admin/shell/pages-tree.d.ts +0 -2
- package/dist/admin/shell/pages-tree.js +1 -1
- package/dist/admin/shell/presence-socket.d.ts +10 -0
- package/dist/admin/shell/presence-socket.js +191 -0
- package/dist/admin/shell/presence.d.ts +6 -18
- package/dist/admin/shell/presence.js +4 -11
- package/dist/admin/shell/session.d.ts +20 -12
- package/dist/admin/shell/session.js +113 -47
- package/dist/admin/ui/LocaleSwitcher.d.ts +0 -4
- package/dist/admin/ui/LocaleSwitcher.js +2 -2
- package/dist/admin/ui/primitives.d.ts +4 -14
- package/dist/admin/ui/primitives.js +5 -8
- package/dist/admin/ui/theme.js +13 -2
- package/dist/admin/validate.js +10 -1
- package/dist/api-client.d.ts +26 -0
- package/dist/api-client.js +107 -0
- package/dist/assemble.d.ts +4 -41
- package/dist/assemble.js +4 -302
- package/dist/asset-store.d.ts +28 -30
- package/dist/asset-store.js +26 -121
- package/dist/astro/components/Head.astro +4 -1
- package/dist/astro/components/Image.astro +12 -17
- package/dist/astro/components/Video.astro +5 -2
- package/dist/astro/head.d.ts +3 -0
- package/dist/astro/head.js +24 -0
- package/dist/astro/index.js +54 -25
- package/dist/astro/locale-paths.d.ts +5 -2
- package/dist/astro/locale-paths.js +10 -2
- package/dist/astro/routes/admin.js +9 -7
- package/dist/astro/runtime.d.ts +2 -1
- package/dist/astro/runtime.js +4 -3
- package/dist/astro/server.d.ts +7 -11
- package/dist/astro/server.js +22 -28
- package/dist/astro/shell.d.ts +2 -2
- package/dist/astro/static-media-node.d.ts +4 -0
- package/dist/astro/static-media-node.js +25 -0
- package/dist/astro/static-media.d.ts +17 -0
- package/dist/astro/static-media.js +55 -0
- package/dist/astro/static.d.ts +5 -10
- package/dist/astro/static.js +13 -29
- package/dist/canvas-marker.d.ts +14 -0
- package/dist/canvas-marker.js +36 -0
- package/dist/cli/api.d.ts +14 -0
- package/dist/cli/api.js +48 -0
- package/dist/cli/app-env.d.ts +2 -0
- package/dist/cli/app-env.js +20 -0
- package/dist/cli/export.d.ts +16 -2
- package/dist/cli/export.js +52 -35
- package/dist/cli/import.d.ts +7 -11
- package/dist/cli/import.js +50 -54
- package/dist/cli/load-config.d.ts +0 -1
- package/dist/cli/load-config.js +1 -1
- package/dist/cli/main.d.ts +4 -12
- package/dist/cli/main.js +26 -72
- package/dist/cli/push.d.ts +6 -0
- package/dist/cli/push.js +31 -0
- package/dist/collections.d.ts +0 -1
- package/dist/collections.js +1 -1
- package/dist/config.d.ts +20 -5
- package/dist/config.js +18 -1
- package/dist/content/content-api.d.ts +22 -23
- package/dist/content/content-api.js +46 -56
- package/dist/content/mime.d.ts +1 -0
- package/dist/content/mime.js +6 -0
- package/dist/deploy-store.d.ts +19 -0
- package/dist/deploy-store.js +1 -0
- package/dist/entry-store.d.ts +3 -89
- package/dist/entry-store.js +2 -550
- package/dist/env.d.ts +27 -20
- package/dist/env.js +38 -50
- package/dist/fields.d.ts +11 -3
- package/dist/fields.js +33 -2
- package/dist/global-id.d.ts +2 -0
- package/dist/global-id.js +4 -2
- package/dist/image/SmoodlyImage.d.ts +9 -5
- package/dist/image/SmoodlyImage.js +29 -15
- package/dist/image/SmoodlyVideo.d.ts +1 -1
- package/dist/image/SmoodlyVideo.js +3 -2
- package/dist/image/index.d.ts +2 -3
- package/dist/image/index.js +0 -2
- package/dist/image/markup.d.ts +53 -0
- package/dist/image/markup.js +111 -0
- package/dist/index.d.ts +12 -22
- package/dist/index.js +7 -20
- package/dist/media-cut.d.ts +44 -0
- package/dist/media-cut.js +70 -0
- package/dist/media.d.ts +57 -3
- package/dist/media.js +125 -15
- package/dist/member-store.d.ts +3 -22
- package/dist/member-store.js +1 -53
- package/dist/next/admin-route.d.ts +1 -1
- package/dist/next/admin-route.js +5 -3
- package/dist/next/meta.js +2 -3
- package/dist/next/page-renderer.d.ts +2 -2
- package/dist/next/page-renderer.js +22 -11
- package/dist/next/queries.d.ts +2 -2
- package/dist/path-index.d.ts +0 -24
- package/dist/path-index.js +0 -98
- package/dist/push.d.ts +30 -0
- package/dist/push.js +52 -0
- package/dist/read-counter.d.ts +2 -0
- package/dist/read-counter.js +6 -0
- package/dist/reads.d.ts +7 -7
- package/dist/reads.js +3 -0
- package/dist/registry-json.d.ts +11 -0
- package/dist/registry-json.js +81 -0
- package/dist/render.d.ts +0 -1
- package/dist/render.js +1 -1
- package/dist/resolve.d.ts +12 -6
- package/dist/resolve.js +11 -8
- package/dist/revalidate.d.ts +8 -1
- package/dist/revalidate.js +8 -1
- package/dist/richtext/index.d.ts +1 -1
- package/dist/section-wrapper.d.ts +1 -1
- package/dist/section-wrapper.js +0 -9
- package/dist/signed-message.d.ts +26 -0
- package/dist/signed-message.js +103 -0
- package/dist/site.d.ts +59 -18
- package/dist/site.js +21 -252
- package/dist/space-transfer.d.ts +5 -2
- package/dist/space-transfer.js +17 -5
- package/dist/store.d.ts +2 -80
- package/dist/store.js +3 -489
- package/dist/toolset.d.ts +2 -1
- package/dist/toolset.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -15
- package/dist/admin/ops-impl.d.ts +0 -51
- package/dist/admin/ops-impl.js +0 -1403
- package/dist/admin/shell/InviteModal.d.ts +0 -6
- package/dist/admin/shell/InviteModal.js +0 -28
- package/dist/admin/shell/LoginView.d.ts +0 -5
- package/dist/admin/shell/LoginView.js +0 -80
- package/dist/admin/shell/SchemaNotice.d.ts +0 -4
- package/dist/admin/shell/SchemaNotice.js +0 -4
- package/dist/admin/shell/SetPasswordView.d.ts +0 -7
- package/dist/admin/shell/SetPasswordView.js +0 -41
- package/dist/admin/shell/presence-realtime.d.ts +0 -3
- package/dist/admin/shell/presence-realtime.js +0 -32
- package/dist/admin/supabase-auth.d.ts +0 -7
- package/dist/admin/supabase-auth.js +0 -30
- package/dist/astro/deploy-deps.d.ts +0 -10
- package/dist/astro/deploy-deps.js +0 -8
- package/dist/astro/image.d.ts +0 -27
- package/dist/astro/image.js +0 -29
- package/dist/bind.d.ts +0 -19
- package/dist/bind.js +0 -34
- package/dist/cli/add-admin.d.ts +0 -11
- package/dist/cli/add-admin.js +0 -25
- package/dist/cli/load-collections.d.ts +0 -2
- package/dist/cli/load-collections.js +0 -27
- package/dist/cli/migrate.d.ts +0 -43
- package/dist/cli/migrate.js +0 -138
- package/dist/cli/rebuild.d.ts +0 -23
- package/dist/cli/rebuild.js +0 -22
- package/dist/cli/space-io.d.ts +0 -41
- package/dist/cli/space-io.js +0 -84
- package/dist/client.d.ts +0 -11
- package/dist/client.js +0 -54
- package/dist/current-space.d.ts +0 -4
- package/dist/current-space.js +0 -9
- package/dist/document-stamp.d.ts +0 -7
- package/dist/document-stamp.js +0 -48
- package/dist/image/loaders.d.ts +0 -15
- package/dist/image/loaders.js +0 -5
- package/dist/image/next-config.d.ts +0 -18
- package/dist/image/next-config.js +0 -32
- package/dist/image/remote-pattern.d.ts +0 -1
- package/dist/image/remote-pattern.js +0 -5
- package/dist/migrations.d.ts +0 -37
- package/dist/migrations.js +0 -73
- package/dist/project.d.ts +0 -88
- package/dist/project.js +0 -231
- package/dist/published.d.ts +0 -142
- package/dist/published.js +0 -164
- package/dist/ref-index.d.ts +0 -19
- package/dist/ref-index.js +0 -0
- package/dist/schema-read.d.ts +0 -23
- package/dist/schema-read.js +0 -50
- package/dist/schema-version.d.ts +0 -19
- package/dist/schema-version.js +0 -34
- package/dist/sql-space.d.ts +0 -44
- package/dist/sql-space.js +0 -289
- package/dist/sql.d.ts +0 -15
- package/dist/sql.js +0 -556
- package/dist/supabase-asset-store.d.ts +0 -21
- package/dist/supabase-asset-store.js +0 -170
- package/dist/supabase-entry-store.d.ts +0 -92
- package/dist/supabase-entry-store.js +0 -824
- package/dist/supabase-member-store.d.ts +0 -21
- package/dist/supabase-member-store.js +0 -96
- package/dist/supabase-path-index.d.ts +0 -30
- package/dist/supabase-path-index.js +0 -138
- package/dist/supabase-published-store.d.ts +0 -56
- package/dist/supabase-published-store.js +0 -314
- package/dist/supabase-store.d.ts +0 -103
- package/dist/supabase-store.js +0 -678
- package/migrations/20260926120000_smoodly_v1.sql +0 -535
- package/migrations/20260926130000_smoodly_v2.sql +0 -26
- package/migrations/20260926140000_smoodly_v3.sql +0 -38
- package/migrations/20260926150000_smoodly_v4.sql +0 -41
- package/migrations/20260929120000_smoodly_v5.sql +0 -56
- package/migrations/20260929135523_smoodly_v6.sql +0 -200
- package/migrations/20260930040956_smoodly_v7.sql +0 -84
- package/migrations/20261007120000_smoodly_v8.sql +0 -154
- package/migrations/20261007200000_smoodly_v9.sql +0 -30
- package/migrations/20261008120000_smoodly_v10.sql +0 -47
- package/migrations/20261009120000_smoodly_v11.sql +0 -49
package/dist/astro/head.js
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
import { isResolvedImage } from "../richtext-href.js";
|
|
2
|
+
/** The origin of the first resolved image in a document (its cut's, else its original's), depth first. */
|
|
3
|
+
function firstImageOrigin(value, depth = 0) {
|
|
4
|
+
if (depth > 32 || value === null || typeof value !== "object")
|
|
5
|
+
return undefined;
|
|
6
|
+
if (isResolvedImage(value) && typeof value.mime === "string" && value.mime.startsWith("image/")) {
|
|
7
|
+
try {
|
|
8
|
+
return new URL(value.cut ?? value.url).origin;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
for (const v of Array.isArray(value) ? value : Object.values(value)) {
|
|
15
|
+
const found = firstImageOrigin(v, depth + 1);
|
|
16
|
+
if (found)
|
|
17
|
+
return found;
|
|
18
|
+
}
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
1
21
|
export function headOf(hit, locale, href, locales) {
|
|
2
22
|
// A page type without seo: true still titles the document with its page title; an entry without seo leaves the title to the layout.
|
|
3
23
|
const seo = hit.seo ?? (hit.kind === "page" ? { title: hit.title } : {});
|
|
@@ -8,6 +28,9 @@ export function headOf(hit, locale, href, locales) {
|
|
|
8
28
|
if (alt?.path)
|
|
9
29
|
languages[l] = href(alt.path, l);
|
|
10
30
|
}
|
|
31
|
+
// What the page shows: a page's tree and its own fields, an entry's fields; never the social image alone.
|
|
32
|
+
const shown = hit.kind === "page" ? [hit.tree, hit.fields] : [hit.entry.fields];
|
|
33
|
+
const mediaOrigin = firstImageOrigin(shown);
|
|
11
34
|
return {
|
|
12
35
|
...(seo.title ? { title: seo.title } : {}),
|
|
13
36
|
...(seo.description ? { description: seo.description } : {}),
|
|
@@ -15,5 +38,6 @@ export function headOf(hit, locale, href, locales) {
|
|
|
15
38
|
noindex: seo.noindex === true,
|
|
16
39
|
...(own ? { canonical: href(own, locale) } : {}),
|
|
17
40
|
languages,
|
|
41
|
+
...(mediaOrigin ? { mediaOrigin } : {}),
|
|
18
42
|
};
|
|
19
43
|
}
|
package/dist/astro/index.js
CHANGED
|
@@ -6,18 +6,18 @@ import { mkdir, writeFile } from "node:fs/promises";
|
|
|
6
6
|
import { dirname, join, relative } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { DEFAULT_ADMIN_PATH } from "../admin-path.js";
|
|
9
|
-
import { createSupabaseStores, siteOptions } from "../bind.js";
|
|
10
9
|
import { loadConfig, loadConfigFile } from "../cli/load-config.js";
|
|
11
|
-
import {
|
|
12
|
-
import { createSmoodlyContent } from "../content/content-api.js";
|
|
10
|
+
import { apiTransport, createSmoodlyContent } from "../content/content-api.js";
|
|
13
11
|
import { markerJson, MARKER_PATH } from "../deploy-state.js";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
12
|
+
import { DEFAULT_API_URL, apiEnv, siteUrl } from "../env.js";
|
|
13
|
+
import { createSmoodlyApi } from "../api-client.js";
|
|
14
|
+
import { pushBody, pushSchema } from "../push.js";
|
|
16
15
|
import { loadSmoodlyEnv } from "./env-files.js";
|
|
17
16
|
import { astroRouting, localeHref } from "./locale-paths.js";
|
|
18
17
|
import { PREVIEW_BASE } from "./preview.js";
|
|
19
18
|
import { hostRedirects, redirectEntries, rootRedirect } from "./static.js";
|
|
20
|
-
import {
|
|
19
|
+
import { collectStaticMedia, STATIC_MEDIA_DIR } from "./static-media.js";
|
|
20
|
+
import { downloadStaticMedia } from "./static-media-node.js";
|
|
21
21
|
// In the repository this file is TypeScript; in the published package tsc made it JavaScript, and the
|
|
22
22
|
// .astro files were copied beside it unchanged.
|
|
23
23
|
const ext = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
|
|
@@ -75,10 +75,25 @@ export default function smoodly(options = {}) {
|
|
|
75
75
|
let output = "server";
|
|
76
76
|
let root = process.cwd();
|
|
77
77
|
let watermark = null;
|
|
78
|
-
/** The
|
|
79
|
-
|
|
78
|
+
/** The files the prerendered pages registered (static output). */
|
|
79
|
+
let staticMedia = new Map();
|
|
80
|
+
/** astro.config's `i18n`, which the build holds the config's `routing` against. */
|
|
81
|
+
let i18n;
|
|
82
|
+
/** The project's API client at build, from its config file (the one `options.config` names) and the env the setup hook loaded. */
|
|
83
|
+
const buildApi = async () => {
|
|
80
84
|
const loaded = options.config ? await loadConfigFile(join(root, options.config), root) : await loadConfig(root);
|
|
81
|
-
return { loaded, ...
|
|
85
|
+
return { loaded, api: createSmoodlyApi({ ...apiEnv(process.env), config: loaded }) };
|
|
86
|
+
};
|
|
87
|
+
/** A build read that fails names the service's address and stops the build before any page renders (a static build
|
|
88
|
+
* with the Worker down would otherwise fail page by page). */
|
|
89
|
+
const fromApi = async (read) => {
|
|
90
|
+
try {
|
|
91
|
+
return await read();
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
const address = process.env.SMOODLY_API_URL || DEFAULT_API_URL;
|
|
95
|
+
throw new Error(`smoodly: the static build needs Smoodly's API — SMOODLY_API_URL (${address}) did not answer: ${e instanceof Error ? e.message : String(e)}. Start the service (npm run dev:service) or set the address.`);
|
|
96
|
+
}
|
|
82
97
|
};
|
|
83
98
|
return {
|
|
84
99
|
name: "smoodly",
|
|
@@ -86,16 +101,14 @@ export default function smoodly(options = {}) {
|
|
|
86
101
|
"astro:config:setup": async ({ config, command, updateConfig, injectRoute, addMiddleware, logger }) => {
|
|
87
102
|
root = fileURLToPath(config.root);
|
|
88
103
|
output = config.output === "static" ? "static" : "server";
|
|
104
|
+
i18n = config.i18n;
|
|
89
105
|
// Root-relative with a leading slash, as Vite's import.meta.glob reads it; a source dir that is the root itself is "".
|
|
90
106
|
const rel = relative(root, fileURLToPath(config.srcDir)).split("\\").join("/");
|
|
91
107
|
const srcDir = rel === "" ? "" : "/" + rel;
|
|
92
108
|
loadSmoodlyEnv(root, command === "dev" ? "development" : "production");
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
if (!mediaUrl)
|
|
96
|
-
logger.warn("smoodly: SMOODLY_URL is not set at build — every image will fail: Astro's /_image serves only hosts in image.remotePatterns, and the media host is taken from SMOODLY_URL here. Set it where `astro build` runs.");
|
|
109
|
+
// No image service config: <Image> is plain markup over the cuts from Smoodly's media host (spec 2026-10-18 media
|
|
110
|
+
// on R2 and Images §7), so no remote pattern and no /_image.
|
|
97
111
|
updateConfig({
|
|
98
|
-
image: { remotePatterns: mediaUrl ? [mediaRemotePattern(mediaUrl)] : [] },
|
|
99
112
|
vite: { plugins: [virtualModules(root, options.config ?? "smoodly.config.ts", srcDir, output)] },
|
|
100
113
|
});
|
|
101
114
|
for (const route of routes(adminPath, output))
|
|
@@ -122,10 +135,10 @@ export default function smoodly(options = {}) {
|
|
|
122
135
|
// Static output (spec 2026-10-09 static mode §3): the redirect rows and the prefix-always root become the
|
|
123
136
|
// host's redirects, answered without a function. Read at build only; `astro dev` serves them on demand.
|
|
124
137
|
if (output === "static" && command === "build") {
|
|
125
|
-
const { loaded,
|
|
126
|
-
const routing = astroRouting(config.i18n, loaded.locales);
|
|
138
|
+
const { loaded, api } = await buildApi();
|
|
139
|
+
const routing = astroRouting(config.i18n, loaded.locales, loaded.routing);
|
|
127
140
|
const href = localeHref(loaded.locales, routing);
|
|
128
|
-
const redirects = { ...(await redirectEntries({
|
|
141
|
+
const redirects = { ...(await fromApi(() => redirectEntries({ delivery: api, config: loaded, href }))), ...rootRedirect(routing, href, loaded.locales) };
|
|
129
142
|
updateConfig({ redirects: hostRedirects(config.redirects, redirects) });
|
|
130
143
|
logger.info(`smoodly: static output — ${Object.keys(redirects).length} redirects handed to the host.`);
|
|
131
144
|
}
|
|
@@ -133,15 +146,33 @@ export default function smoodly(options = {}) {
|
|
|
133
146
|
// The marker's watermark (spec 2026-10-09 static mode §5) is read BEFORE any page renders: a publish that lands
|
|
134
147
|
// while the build runs is then newer than it and counts as waiting, which it is — the build may not hold it. A
|
|
135
148
|
// row the build itself rebuilds keeps its publishedAt (a heal is not a publish, project.ts), so it never counts.
|
|
136
|
-
|
|
149
|
+
// The schema push (spec 2026-10-16 the control plane §4) comes first, in either output: the space then holds the
|
|
150
|
+
// registry this build renders with. A failed push never fails the build; the site runs against the schema the space has.
|
|
151
|
+
"astro:build:start": async ({ logger }) => {
|
|
152
|
+
const loaded = options.config ? await loadConfigFile(join(root, options.config), root) : await loadConfig(root);
|
|
153
|
+
// The runtime's startup check, at build in either output: a deploy that builds must not refuse every request.
|
|
154
|
+
astroRouting(i18n, loaded.locales, loaded.routing);
|
|
155
|
+
try {
|
|
156
|
+
const env = apiEnv(process.env);
|
|
157
|
+
const { version, changed } = await pushSchema(env, pushBody(loaded, process.env, output === "static" ? "static" : "live"));
|
|
158
|
+
logger.info(changed ? `smoodly: pushed schema version ${version} to ${env.spaceId}.` : `smoodly: schema unchanged (version ${version}).`);
|
|
159
|
+
}
|
|
160
|
+
catch (e) {
|
|
161
|
+
logger.warn(`smoodly: the schema push failed — ${e instanceof Error ? e.message : String(e)}; the site runs against the schema the space has.`);
|
|
162
|
+
}
|
|
137
163
|
if (output !== "static")
|
|
138
164
|
return;
|
|
139
|
-
|
|
165
|
+
// The media every prerendered page registers (static-media.ts), collected in this process for the build's end.
|
|
166
|
+
staticMedia = collectStaticMedia();
|
|
167
|
+
const { api } = await buildApi();
|
|
168
|
+
watermark = await fromApi(() => api.newestPublishedAt());
|
|
140
169
|
},
|
|
141
170
|
// The build marker: beside the pages, the only trace of Smoodly in a static build.
|
|
142
171
|
"astro:build:done": async ({ dir, logger }) => {
|
|
143
172
|
if (output !== "static")
|
|
144
173
|
return;
|
|
174
|
+
// Every image the pages show, into the output beside them (spec 2026-10-18 media on R2 and Images §8).
|
|
175
|
+
logger.info(`smoodly: wrote ${await downloadStaticMedia(staticMedia.values(), fileURLToPath(dir))} media files into ${STATIC_MEDIA_DIR}.`);
|
|
145
176
|
const marker = markerJson({ builtAt: Date.now(), publishedThrough: watermark });
|
|
146
177
|
const file = join(fileURLToPath(dir), MARKER_PATH);
|
|
147
178
|
await mkdir(dirname(file), { recursive: true });
|
|
@@ -162,10 +193,8 @@ export function createContent(config, options = {}) {
|
|
|
162
193
|
loadSmoodlyEnv(options.root ?? process.cwd(), process.env.NODE_ENV === "production" ? "production" : "development");
|
|
163
194
|
env = process.env;
|
|
164
195
|
}
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const { projector } = createSmoodlySite(siteOptions(config, () => ({ pages, entries, assets, published })));
|
|
170
|
-
return { content: createSmoodlyContent({ config, pages, entries, assets, members, href, projector }), href };
|
|
196
|
+
const href = localeHref(config.locales, astroRouting(options.i18n, config.locales, config.routing));
|
|
197
|
+
// Over Smoodly's API with the secret key (spec 2026-10-14 §1): the service runs the operations, so a publish from a
|
|
198
|
+
// script writes the delivery rows and expires the site's tags as the admin's does.
|
|
199
|
+
return { content: createSmoodlyContent({ config, href, transport: apiTransport(apiEnv(env)) }), href };
|
|
171
200
|
}
|
|
@@ -26,5 +26,8 @@ export declare function splitLocale(pathname: string, locales: LocaleSet, routin
|
|
|
26
26
|
} | null;
|
|
27
27
|
/** Locale-relative CMS path → site URL: what the operations and the `page` prop link with. */
|
|
28
28
|
export declare const localeHref: (locales: LocaleSet, routing?: LocaleRouting) => (path: string, locale: string) => string;
|
|
29
|
-
/** The rule astro.config's i18n asks for; its locales must be the config's,
|
|
30
|
-
|
|
29
|
+
/** The rule astro.config's i18n asks for; its locales must be the config's, and its prefixDefaultLocale the config's
|
|
30
|
+
* `routing` (which the pushed registry carries to the space's links), or links and reads would disagree. */
|
|
31
|
+
export declare function astroRouting(i18n: AstroI18n | undefined, locales: LocaleSet, configRouting?: {
|
|
32
|
+
prefixDefaultLocale: boolean;
|
|
33
|
+
}): LocaleRouting;
|
|
@@ -21,8 +21,16 @@ export function splitLocale(pathname, locales, routing = DEFAULT_ROUTING) {
|
|
|
21
21
|
}
|
|
22
22
|
/** Locale-relative CMS path → site URL: what the operations and the `page` prop link with. */
|
|
23
23
|
export const localeHref = (locales, routing = DEFAULT_ROUTING) => (path, locale) => locale === locales.default && !routing.prefixDefault ? path : path === "/" ? `/${locale}` : `/${locale}${path}`;
|
|
24
|
-
/** The rule astro.config's i18n asks for; its locales must be the config's,
|
|
25
|
-
|
|
24
|
+
/** The rule astro.config's i18n asks for; its locales must be the config's, and its prefixDefaultLocale the config's
|
|
25
|
+
* `routing` (which the pushed registry carries to the space's links), or links and reads would disagree. */
|
|
26
|
+
export function astroRouting(i18n, locales, configRouting) {
|
|
27
|
+
const routing = readAstroRouting(i18n, locales);
|
|
28
|
+
if (configRouting && configRouting.prefixDefaultLocale !== routing.prefixDefault) {
|
|
29
|
+
throw new Error(`smoodly: astro.config's i18n.routing.prefixDefaultLocale is ${routing.prefixDefault} but smoodly.config's routing.prefixDefaultLocale is ${configRouting.prefixDefaultLocale} — make them agree.`);
|
|
30
|
+
}
|
|
31
|
+
return routing;
|
|
32
|
+
}
|
|
33
|
+
function readAstroRouting(i18n, locales) {
|
|
26
34
|
if (!i18n)
|
|
27
35
|
return DEFAULT_ROUTING;
|
|
28
36
|
if (i18n.domains && Object.keys(i18n.domains).length > 0) {
|
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
// The admin in an Astro project (spec 2026-10-02 astro cloud fit §4), injected by the integration at the
|
|
2
|
-
// admin path: the page, the package's files and the
|
|
3
|
-
// (admin/handler.ts), made once per server. This file adds only what is Astro's:
|
|
4
|
-
//
|
|
5
|
-
// address. The request's own effects ride an AsyncLocalStorage, so one handler serves requests at once.
|
|
2
|
+
// admin path: the page, the package's files, the revalidate route and the preview route are the
|
|
3
|
+
// framework-free handler's (admin/handler.ts), made once per server. This file adds only what is Astro's:
|
|
4
|
+
// the tags the service's revalidate call names go to the response cache together, and draft mode is the
|
|
5
|
+
// draft cookie, scoped to the preview address. The request's own effects ride an AsyncLocalStorage, so one handler serves requests at once.
|
|
6
6
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
7
7
|
import { createAdminHandler } from "../../admin/handler.js";
|
|
8
8
|
import { expire } from "../cache.js";
|
|
9
9
|
import { DRAFT_COOKIE, PREVIEW_BASE, draftCookieValue } from "../preview.js";
|
|
10
|
-
import {
|
|
10
|
+
import { output } from "smoodly:build";
|
|
11
|
+
import { adminDeps, secret } from "../server.js";
|
|
11
12
|
export const prerender = false;
|
|
12
13
|
const current = new AsyncLocalStorage();
|
|
13
14
|
const effects = () => {
|
|
14
15
|
const got = current.getStore();
|
|
15
16
|
if (!got)
|
|
16
|
-
throw new Error("smoodly: an admin
|
|
17
|
+
throw new Error("smoodly: an admin route ran outside its request.");
|
|
17
18
|
return got;
|
|
18
19
|
};
|
|
19
20
|
const secure = (context) => context.url.protocol === "https:" || context.request.headers.get("x-forwarded-proto")?.split(",")[0]?.trim() === "https";
|
|
20
21
|
const handle = createAdminHandler(() => ({
|
|
21
22
|
...adminDeps(),
|
|
22
23
|
previewBase: PREVIEW_BASE,
|
|
23
|
-
deploy
|
|
24
|
+
// The Deploy tile's mode is the build's output; the deploy operations themselves run in the service.
|
|
25
|
+
delivery: output === "static" ? "static" : "live",
|
|
24
26
|
effects: {
|
|
25
27
|
revalidate: (tag) => void effects().expired.add(tag),
|
|
26
28
|
draft: {
|
package/dist/astro/runtime.d.ts
CHANGED
|
@@ -8,7 +8,8 @@ export { crashMessage } from "../crash-message.ts";
|
|
|
8
8
|
export { styleAttribute } from "./style.ts";
|
|
9
9
|
export { richtextChunks, type RichTextChunk } from "./richtext.ts";
|
|
10
10
|
export { CUSTOM_NODE_TYPE } from "../custom/schema.ts";
|
|
11
|
-
export {
|
|
11
|
+
export { imageHtml, imageMarkup, posterUrl } from "../image/markup.ts";
|
|
12
|
+
export { staticImage } from "./static-media.ts";
|
|
12
13
|
export { previewPath, previewTarget } from "./preview.ts";
|
|
13
14
|
export { href } from "./server.ts";
|
|
14
15
|
export type { Shell, SmoodlyLocals } from "./shell.ts";
|
package/dist/astro/runtime.js
CHANGED
|
@@ -11,14 +11,15 @@ export { crashMessage } from "../crash-message.js";
|
|
|
11
11
|
export { styleAttribute } from "./style.js";
|
|
12
12
|
export { richtextChunks } from "./richtext.js";
|
|
13
13
|
export { CUSTOM_NODE_TYPE } from "../custom/schema.js";
|
|
14
|
-
export {
|
|
14
|
+
export { imageHtml, imageMarkup, posterUrl } from "../image/markup.js";
|
|
15
|
+
export { staticImage } from "./static-media.js";
|
|
15
16
|
export { previewPath, previewTarget } from "./preview.js";
|
|
16
17
|
export { href } from "./server.js";
|
|
17
18
|
import { customLayout } from "./custom.js";
|
|
18
19
|
import { registryInfo } from "./defaults.js";
|
|
19
20
|
import { lifetime } from "./cache.js";
|
|
20
21
|
import { astroReads, preload } from "./scope.js";
|
|
21
|
-
import { config,
|
|
22
|
+
import { config, delivery, href, routing, site } from "./server.js";
|
|
22
23
|
import { staticPaths as listStatic } from "./static.js";
|
|
23
24
|
import { missingViews } from "./views-check.js";
|
|
24
25
|
import { resolveShell } from "./shell.js";
|
|
@@ -40,7 +41,7 @@ export async function shell(pathname, search, state, views, cache) {
|
|
|
40
41
|
return result;
|
|
41
42
|
}
|
|
42
43
|
/** getStaticPaths for the static page shell (spec 2026-10-09 static mode §3): every published address of the bound site. */
|
|
43
|
-
export const staticPaths = () => listStatic({
|
|
44
|
+
export const staticPaths = () => listStatic({ delivery: delivery(), config, href });
|
|
44
45
|
/** The registry's titles and settings defaults, for the zone and the richtext renderer. */
|
|
45
46
|
export const info = registryInfo(config.registry);
|
|
46
47
|
/** The custom section's rows and layout css, at the project's (already resolved) stacking breakpoint. */
|
package/dist/astro/server.d.ts
CHANGED
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
import type { AdminHandlerDeps } from "../admin/handler.ts";
|
|
2
2
|
import type { ResolvedConfig } from "../config.ts";
|
|
3
|
+
import type { ContentReads, DeliveryReads } from "../site.ts";
|
|
3
4
|
export declare const config: ResolvedConfig;
|
|
4
5
|
/** The address rule: astro.config's i18n when it is set, else the default locale at the root. */
|
|
5
6
|
export declare const routing: import("./locale-paths.ts").LocaleRouting;
|
|
6
7
|
/** Locale-relative CMS path → site URL, for the operations and the `page` prop. */
|
|
7
8
|
export declare const href: (path: string, locale: string) => string;
|
|
8
|
-
export declare const site: () =>
|
|
9
|
-
/** The delivery
|
|
10
|
-
export declare const
|
|
11
|
-
/** The page store, for the paths index the static shell lists entry addresses from. */
|
|
12
|
-
export declare const pages: () => import("../supabase-store.ts").SupabasePageStore;
|
|
13
|
-
/** The deploy operations' deps in static output (spec 2026-10-09 static mode §7); undefined on a live site. */
|
|
14
|
-
export declare const deployDeps: () => (import("../admin/ops-impl.ts").DeployDeps & {
|
|
15
|
-
mode: "live" | "static";
|
|
16
|
-
}) | undefined;
|
|
9
|
+
export declare const site: () => ContentReads;
|
|
10
|
+
/** The delivery reads of the static shell and the build: the addresses and the watermark, over the API. */
|
|
11
|
+
export declare const delivery: () => DeliveryReads;
|
|
17
12
|
/** The draft cookie's key: the server-only key every Smoodly site already holds. */
|
|
18
13
|
export declare const secret: () => string;
|
|
19
|
-
type AdminDeps = Omit<AdminHandlerDeps, "effects" | "previewBase">;
|
|
20
|
-
/** The admin's wiring
|
|
14
|
+
type AdminDeps = Omit<AdminHandlerDeps, "effects" | "previewBase" | "delivery">;
|
|
15
|
+
/** The admin's wiring: the page, the revalidate route the service calls and the preview route the browser
|
|
16
|
+
* posts the grant to. Operations run in the service, which also tells the page where editors sign in. */
|
|
21
17
|
export declare function adminDeps(): AdminDeps;
|
|
22
18
|
export {};
|
package/dist/astro/server.js
CHANGED
|
@@ -1,52 +1,46 @@
|
|
|
1
|
-
// The bound
|
|
2
|
-
// smoodly.config.ts through the virtual module: what the Next starter's server.ts glue does, with no file
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import {
|
|
1
|
+
// The bound reads of an Astro project (spec 2026-10-02 astro cloud fit §1; spec 2026-10-14 §1), built from its
|
|
2
|
+
// smoodly.config.ts through the virtual module: what the Next starter's server.ts glue does, with no file in the
|
|
3
|
+
// app. Every read goes over Smoodly's content API; the site opens no store. Built on first use, so a build that
|
|
4
|
+
// renders nothing needs no keys.
|
|
5
|
+
import { i18n } from "astro:config/server";
|
|
6
6
|
import { config as loaded } from "smoodly:config";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { presenceConfig, smoodlyEnv } from "../env.js";
|
|
12
|
-
import { createSmoodlySite } from "../site.js";
|
|
7
|
+
import { output } from "smoodly:build";
|
|
8
|
+
import { createSmoodlyApi } from "../api-client.js";
|
|
9
|
+
import { pushBody } from "../push.js";
|
|
10
|
+
import { apiEnv, presenceConfig, smoodlyEnv } from "../env.js";
|
|
13
11
|
import { astroRouting, localeHref } from "./locale-paths.js";
|
|
14
12
|
export const config = loaded;
|
|
15
13
|
if (!config?.registry) {
|
|
16
14
|
throw new Error("smoodly: smoodly.config.ts must export `config`, the result of defineConfig(…).");
|
|
17
15
|
}
|
|
18
16
|
/** The address rule: astro.config's i18n when it is set, else the default locale at the root. */
|
|
19
|
-
export const routing = astroRouting(i18n, config.locales);
|
|
17
|
+
export const routing = astroRouting(i18n, config.locales, config.routing);
|
|
20
18
|
/** Locale-relative CMS path → site URL, for the operations and the `page` prop. */
|
|
21
19
|
export const href = localeHref(config.locales, routing);
|
|
22
20
|
function build() {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
21
|
+
// The site's reads go over Smoodly's API (SMOODLY_API_URL, SMOODLY_SPACE_ID, SMOODLY_SECRET_KEY). In `astro dev` the
|
|
22
|
+
// client pushes the schema first, in the mode the build would (spec 2026-10-16 the control plane §4).
|
|
23
|
+
const dev = process.env.NODE_ENV === "development";
|
|
24
|
+
const api = createSmoodlyApi({ ...apiEnv(), config, push: dev ? pushBody(config, process.env, output === "static" ? "static" : "live") : false });
|
|
25
|
+
return { api };
|
|
27
26
|
}
|
|
28
27
|
let cached = null;
|
|
29
28
|
const bound = () => (cached ?? (cached = build()));
|
|
30
|
-
export const site = () => bound().
|
|
31
|
-
/** The delivery
|
|
32
|
-
export const
|
|
33
|
-
/** The page store, for the paths index the static shell lists entry addresses from. */
|
|
34
|
-
export const pages = () => bound().pages;
|
|
35
|
-
/** The deploy operations' deps in static output (spec 2026-10-09 static mode §7); undefined on a live site. */
|
|
36
|
-
export const deployDeps = () => deployDepsOf({ output, site: configuredSite, env: process.env, store: published() });
|
|
29
|
+
export const site = () => bound().api;
|
|
30
|
+
/** The delivery reads of the static shell and the build: the addresses and the watermark, over the API. */
|
|
31
|
+
export const delivery = () => bound().api;
|
|
37
32
|
/** The draft cookie's key: the server-only key every Smoodly site already holds. */
|
|
38
33
|
export const secret = () => smoodlyEnv().secretKey;
|
|
39
34
|
let admin = null;
|
|
40
|
-
/** The admin's wiring
|
|
35
|
+
/** The admin's wiring: the page, the revalidate route the service calls and the preview route the browser
|
|
36
|
+
* posts the grant to. Operations run in the service, which also tells the page where editors sign in. */
|
|
41
37
|
export function adminDeps() {
|
|
42
38
|
if (admin)
|
|
43
39
|
return admin;
|
|
44
40
|
const env = smoodlyEnv("admin");
|
|
45
|
-
const { db, pages, entries, assets, members, site } = bound();
|
|
46
41
|
admin = {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
signIn: { url: env.authUrl, key: env.authKey },
|
|
42
|
+
// Where editors sign in comes from the service (GET /v1/auth, spec 2026-10-16 sign-in and the dashboard §4).
|
|
43
|
+
config, apiUrl: env.apiUrl, spaceId: env.spaceId,
|
|
50
44
|
presence: () => presenceConfig(env),
|
|
51
45
|
};
|
|
52
46
|
return admin;
|
package/dist/astro/shell.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ResolvedConfig } from "../config.ts";
|
|
|
2
2
|
import { type ReadAdapter } from "../reads.ts";
|
|
3
3
|
import { type EntryItem } from "../read-shapes.ts";
|
|
4
4
|
import type { PageContext } from "../page-context.ts";
|
|
5
|
-
import { type
|
|
5
|
+
import { type ContentReads } from "../site.ts";
|
|
6
6
|
import { type PageHead } from "./head.ts";
|
|
7
7
|
import { type LocaleRouting } from "./locale-paths.ts";
|
|
8
8
|
import type { ZoneContent } from "./zone.ts";
|
|
@@ -29,7 +29,7 @@ declare global {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
export type ShellDeps = {
|
|
32
|
-
site:
|
|
32
|
+
site: ContentReads;
|
|
33
33
|
config: ResolvedConfig;
|
|
34
34
|
href: (path: string, locale: string) => string;
|
|
35
35
|
routing: LocaleRouting;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { StaticEntry } from "./static-media.ts";
|
|
2
|
+
/** Every file, fetched eight at a time (a cut asked in its format by Accept) and written under `outDir`; a file that
|
|
3
|
+
* answers non-200 fails the build by its address. Answers how many were written. */
|
|
4
|
+
export declare function downloadStaticMedia(entries: Iterable<StaticEntry>, outDir: string, fetcher?: typeof fetch): Promise<number>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// The Node half of static media (static-media.ts): the download the build runs at `astro:build:done` over what the
|
|
2
|
+
// prerendered pages registered. Loaded by the integration only: a live page never imports it.
|
|
3
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
const CONCURRENCY = 8;
|
|
6
|
+
const ACCEPT = { avif: "image/avif", webp: "image/webp", original: undefined };
|
|
7
|
+
/** Every file, fetched eight at a time (a cut asked in its format by Accept) and written under `outDir`; a file that
|
|
8
|
+
* answers non-200 fails the build by its address. Answers how many were written. */
|
|
9
|
+
export async function downloadStaticMedia(entries, outDir, fetcher = fetch) {
|
|
10
|
+
const queue = [...entries];
|
|
11
|
+
const total = queue.length;
|
|
12
|
+
const work = async () => {
|
|
13
|
+
for (let entry = queue.shift(); entry; entry = queue.shift()) {
|
|
14
|
+
const accept = ACCEPT[entry.format];
|
|
15
|
+
const res = await fetcher(entry.url, accept ? { headers: { accept } } : {});
|
|
16
|
+
if (res.status !== 200)
|
|
17
|
+
throw new Error(`smoodly: the static build could not download ${entry.url} (${res.status}) — every image a page shows is written into the output.`);
|
|
18
|
+
const target = join(outDir, entry.path);
|
|
19
|
+
await mkdir(dirname(target), { recursive: true });
|
|
20
|
+
await writeFile(target, new Uint8Array(await res.arrayBuffer()));
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
await Promise.all(Array.from({ length: Math.min(CONCURRENCY, total) }, work));
|
|
24
|
+
return total;
|
|
25
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ImageMarkup } from "../image/markup.ts";
|
|
2
|
+
export type StaticFormat = "avif" | "webp" | "original";
|
|
3
|
+
/** One file a static build writes: where it comes from, in which format, and its path in the output. */
|
|
4
|
+
export type StaticEntry = {
|
|
5
|
+
url: string;
|
|
6
|
+
format: StaticFormat;
|
|
7
|
+
path: string;
|
|
8
|
+
};
|
|
9
|
+
export declare const STATIC_MEDIA_DIR = "/_smoodly/media";
|
|
10
|
+
/** `/_smoodly/media/<sha256(sourceUrl + format), first 16 hex>.<ext>`: the same address and format, the same file. */
|
|
11
|
+
export declare function staticMediaPath(sourceUrl: string, format: StaticFormat, ext: string): Promise<string>;
|
|
12
|
+
/** The markup with every media address replaced by a file of the output, each file handed to `register`. */
|
|
13
|
+
export declare function rewriteForStatic(m: ImageMarkup, register: (entry: StaticEntry) => void): Promise<ImageMarkup>;
|
|
14
|
+
/** The build's collector (index.ts, astro:build:start): every file the pages register, once per path. */
|
|
15
|
+
export declare function collectStaticMedia(): Map<string, StaticEntry>;
|
|
16
|
+
/** What Image.astro calls in a prerendered page of static output: the rewrite, each file handed to the build. */
|
|
17
|
+
export declare function staticImage(m: ImageMarkup): Promise<ImageMarkup>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export const STATIC_MEDIA_DIR = "/_smoodly/media";
|
|
2
|
+
const hex = (bytes) => [...new Uint8Array(bytes)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3
|
+
/** `/_smoodly/media/<sha256(sourceUrl + format), first 16 hex>.<ext>`: the same address and format, the same file. */
|
|
4
|
+
export async function staticMediaPath(sourceUrl, format, ext) {
|
|
5
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(`${sourceUrl}\n${format}`));
|
|
6
|
+
return `${STATIC_MEDIA_DIR}/${hex(digest).slice(0, 16)}.${ext}`;
|
|
7
|
+
}
|
|
8
|
+
/** An original's extension from its address (`…/a1.jpg` → `jpg`), else `bin`. */
|
|
9
|
+
const extOf = (url) => /\.([a-z0-9]{1,8})$/i.exec(new URL(url).pathname)?.[1]?.toLowerCase() ?? "bin";
|
|
10
|
+
const candidates = (srcset) => srcset.split(", ").map((c) => { const [url, width] = c.trim().split(/\s+/); return { url: url, ...(width ? { width } : {}) }; });
|
|
11
|
+
/** The markup with every media address replaced by a file of the output, each file handed to `register`. */
|
|
12
|
+
export async function rewriteForStatic(m, register) {
|
|
13
|
+
const file = async (url, format) => {
|
|
14
|
+
const path = await staticMediaPath(url, format, format === "original" ? extOf(url) : format);
|
|
15
|
+
register({ url, format, path });
|
|
16
|
+
return path;
|
|
17
|
+
};
|
|
18
|
+
const inFormat = async (list, format) => (await Promise.all(list.map(async (c) => `${await file(c.url, format)}${c.width ? ` ${c.width}` : ""}`))).join(", ");
|
|
19
|
+
// A cut lists widths; an original is one address with no descriptor.
|
|
20
|
+
const cutOf = (srcset) => (srcset && candidates(srcset).every((c) => c.width) ? candidates(srcset) : null);
|
|
21
|
+
const sources = [];
|
|
22
|
+
const both = async (base, list) => {
|
|
23
|
+
sources.push({ ...base, type: "image/avif", srcset: await inFormat(list, "avif") });
|
|
24
|
+
sources.push({ ...base, type: "image/webp", srcset: await inFormat(list, "webp") });
|
|
25
|
+
};
|
|
26
|
+
for (const { srcset, type: _type, ...base } of m.sources) {
|
|
27
|
+
const list = cutOf(srcset);
|
|
28
|
+
if (list)
|
|
29
|
+
await both(base, list);
|
|
30
|
+
else
|
|
31
|
+
sources.push({ ...base, srcset: await inFormat(candidates(srcset), "original") });
|
|
32
|
+
}
|
|
33
|
+
const main = cutOf(m.img.srcset);
|
|
34
|
+
const { srcset: _srcset, sizes, ...img } = m.img;
|
|
35
|
+
if (main) {
|
|
36
|
+
await both({ ...(sizes ? { sizes } : {}), width: img.width, height: img.height }, main);
|
|
37
|
+
return { ...m, sources, img: { ...img, src: await file(m.img.src, "webp") } };
|
|
38
|
+
}
|
|
39
|
+
return { ...m, sources, img: { ...img, ...(sizes ? { sizes } : {}), src: await file(m.img.src, "original") } };
|
|
40
|
+
}
|
|
41
|
+
/** Where a prerendered page hands its files: installed by the integration for one static build. */
|
|
42
|
+
const COLLECTOR = Symbol.for("smoodly.staticMedia");
|
|
43
|
+
/** The build's collector (index.ts, astro:build:start): every file the pages register, once per path. */
|
|
44
|
+
export function collectStaticMedia() {
|
|
45
|
+
const entries = new Map();
|
|
46
|
+
globalThis[COLLECTOR] = (entry) => void entries.set(entry.path, entry);
|
|
47
|
+
return entries;
|
|
48
|
+
}
|
|
49
|
+
/** What Image.astro calls in a prerendered page of static output: the rewrite, each file handed to the build. */
|
|
50
|
+
export async function staticImage(m) {
|
|
51
|
+
const register = globalThis[COLLECTOR];
|
|
52
|
+
if (!register)
|
|
53
|
+
throw new Error("smoodly: a static page rendered outside the integration's `astro build` — no build collects its images.");
|
|
54
|
+
return rewriteForStatic(m, register);
|
|
55
|
+
}
|
package/dist/astro/static.d.ts
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import type { ResolvedConfig } from "../config.ts";
|
|
2
|
-
import type {
|
|
3
|
-
import type { PageStore } from "../store.ts";
|
|
2
|
+
import type { DeliveryReads } from "../site.ts";
|
|
4
3
|
import { type LocaleRouting } from "./locale-paths.ts";
|
|
5
4
|
type Href = (path: string, locale: string) => string;
|
|
6
|
-
/** Every published page and entry address of every locale, as Astro's getStaticPaths entries
|
|
7
|
-
* the delivery tree (a node with no path, hidden or under a code address, is not a page); entries from the paths
|
|
8
|
-
* index's live rows, kept to the entries with a published delivery row — the Supabase delivery store lists entries
|
|
9
|
-
* with their document only, no path (0.0.48 built no entry page for it), and the index is what visitors reach. */
|
|
5
|
+
/** Every published page and entry address of every locale, as Astro's getStaticPaths entries, each once. */
|
|
10
6
|
export declare function staticPaths(deps: {
|
|
11
|
-
|
|
12
|
-
pages: PageStore;
|
|
7
|
+
delivery: DeliveryReads;
|
|
13
8
|
config: ResolvedConfig;
|
|
14
9
|
href: Href;
|
|
15
10
|
}): Promise<{
|
|
@@ -18,9 +13,9 @@ export declare function staticPaths(deps: {
|
|
|
18
13
|
};
|
|
19
14
|
}[]>;
|
|
20
15
|
/** The redirect rows as Astro's `redirects` config: old address → the target's current address, 308 (spec
|
|
21
|
-
* 2026-09-26 redirects §7 at the host).
|
|
16
|
+
* 2026-09-26 redirects §7 at the host). The service leaves out a target with no live path in the locale. */
|
|
22
17
|
export declare function redirectEntries(deps: {
|
|
23
|
-
|
|
18
|
+
delivery: DeliveryReads;
|
|
24
19
|
config: ResolvedConfig;
|
|
25
20
|
href: Href;
|
|
26
21
|
}): Promise<Record<string, {
|