smoodly 0.0.51 → 0.0.53
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.d.ts +3 -11
- package/dist/admin/auth.js +1 -30
- package/dist/admin/client-ops.d.ts +5 -4
- package/dist/admin/client-ops.js +15 -12
- 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 +6 -9
- package/dist/admin/index.js +3 -6
- package/dist/admin/ops.d.ts +40 -32
- package/dist/admin/page-data.d.ts +14 -6
- package/dist/admin/page-data.js +1 -1
- package/dist/admin/session-flow.d.ts +65 -0
- package/dist/admin/session-flow.js +121 -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 +10 -8
- 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 +19 -12
- package/dist/admin/shell/session.js +114 -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 +7 -9
- 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 +5 -303
- package/dist/asset-store.d.ts +28 -30
- package/dist/asset-store.js +26 -121
- package/dist/astro/cache.d.ts +1 -1
- 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 +61 -27
- 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 +3 -2
- package/dist/astro/runtime.js +5 -4
- package/dist/astro/server.d.ts +7 -11
- package/dist/astro/server.js +22 -28
- package/dist/astro/shell.d.ts +3 -3
- package/dist/astro/shell.js +1 -1
- 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 +23 -23
- package/dist/content/content-api.js +48 -56
- package/dist/content/mime.d.ts +1 -0
- package/dist/content/mime.js +6 -0
- package/dist/css.js +1 -1
- package/dist/deploy-store.d.ts +19 -0
- package/dist/entry-store.d.ts +8 -94
- package/dist/entry-store.js +9 -557
- 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 +13 -23
- 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 +58 -4
- package/dist/media.js +126 -16
- package/dist/member-store.d.ts +2 -50
- package/dist/member-store.js +0 -56
- 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/paths.d.ts +1 -1
- package/dist/paths.js +1 -1
- 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 +6 -3
- package/dist/space-transfer.js +18 -6
- 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/auth-config.d.ts +0 -6
- package/dist/admin/browser/fetch-op.d.ts +0 -10
- package/dist/admin/browser/fetch-op.js +0 -44
- package/dist/admin/browser/main.d.ts +0 -1
- package/dist/admin/browser/main.js +0 -23
- 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/{admin/auth-config.js → deploy-store.js} +0 -0
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
// link per locale the page is published in, the Open Graph values, and noindex. Next writes the same through
|
|
5
5
|
// smoodlyMetadata. Absolute addresses from `site` in astro.config, else the request's origin. A layout places
|
|
6
6
|
// it inside <head>; `fallback` titles a document Smoodly has no title for (an entry whose collection has no
|
|
7
|
-
// seo). A layout that writes its own <title> leaves `fallback` out and reads `of.title` itself.
|
|
7
|
+
// seo). A layout that writes its own <title> leaves `fallback` out and reads `of.title` itself. A page that shows an
|
|
8
|
+
// image preconnects to the media host, with no crossorigin: an <img> is fetched with credentials and would not use an
|
|
9
|
+
// anonymous connection (spec 2026-10-18 media on R2 and Images §7).
|
|
8
10
|
const { of: head, fallback } = Astro.props;
|
|
9
11
|
const origin = (Astro.site ?? Astro.url).origin;
|
|
10
12
|
const absolute = (path: string) => new URL(path, origin).href;
|
|
@@ -12,6 +14,7 @@ const title = head.title ?? fallback;
|
|
|
12
14
|
const canonical = head.canonical ? absolute(head.canonical) : undefined;
|
|
13
15
|
const image = head.image;
|
|
14
16
|
---
|
|
17
|
+
{head.mediaOrigin && <link rel="preconnect" href={head.mediaOrigin} />}
|
|
15
18
|
{title && <title>{title}</title>}
|
|
16
19
|
{head.description && <meta name="description" content={head.description} />}
|
|
17
20
|
{canonical && <link rel="canonical" href={canonical} />}
|
|
@@ -1,20 +1,15 @@
|
|
|
1
1
|
---
|
|
2
|
-
// <Image>:
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
2
|
+
// <Image>: the one image markup (src/image/markup.ts) written as HTML, attribute for attribute what React writes for
|
|
3
|
+
// <SmoodlyImage> (spec 2026-10-18 media on R2 and Images §7): a box, a plain <img> over the cut's widths, or a <picture>
|
|
4
|
+
// with a <source> per crop the view places in `media`. No Astro image service and no Smoodly JavaScript. Data
|
|
5
|
+
// attributes land on the box. In a prerendered page of static output the addresses become files of the site's own
|
|
6
|
+
// output, AVIF and WebP (static-media.ts, spec §8); the preview address, rendered on demand, keeps the media host's.
|
|
7
|
+
import { imageHtml, imageMarkup, staticImage } from "smoodly/astro/runtime";
|
|
8
|
+
import { output } from "smoodly:build";
|
|
9
9
|
|
|
10
|
-
const { image, aspect, sizes, priority, loading, alt,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
const src = transformed?.src ?? image.url;
|
|
15
|
-
const srcset = transformed?.srcSet.attribute || undefined;
|
|
10
|
+
const { image, aspect, media, sizes, priority, loading, alt, quality, maxWidth, class: className, style, ...data } = Astro.props;
|
|
11
|
+
let markup = imageMarkup(image, { aspect, media, sizes, priority, loading, alt, quality, maxWidth });
|
|
12
|
+
if (output === "static" && Astro.isPrerendered) markup = await staticImage(markup);
|
|
13
|
+
const html = imageHtml(markup, { class: className, style: typeof style === "string" && style ? style : undefined, data });
|
|
16
14
|
---
|
|
17
|
-
<
|
|
18
|
-
<img src={src} srcset={srcset} sizes={srcset ? sizes : undefined} alt={alt ?? image.alt} width={image.width} height={image.height}
|
|
19
|
-
loading={priority ? "eager" : loading ?? "lazy"} fetchpriority={priority ? "high" : undefined} decoding="async" style={placed.frame} />
|
|
20
|
-
</span>
|
|
15
|
+
<Fragment set:html={html} />
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
---
|
|
2
|
-
// A <video>
|
|
2
|
+
// A <video> from the media host with its poster (a cut where there is one) and size, no transcoding (spec 2026-09-08
|
|
3
|
+
// media §9).
|
|
4
|
+
import { posterUrl } from "smoodly/astro/runtime";
|
|
5
|
+
|
|
3
6
|
const { video, ...rest } = Astro.props;
|
|
4
7
|
---
|
|
5
|
-
<video src={video.url} poster={video
|
|
8
|
+
<video src={video.url} poster={posterUrl(video)} width={video.width} height={video.height} playsinline {...rest}></video>
|
package/dist/astro/head.d.ts
CHANGED
|
@@ -9,5 +9,8 @@ export type PageHead = {
|
|
|
9
9
|
canonical?: string;
|
|
10
10
|
/** locale → the page's address there, for a multi-locale site; empty otherwise. */
|
|
11
11
|
languages: Record<string, string>;
|
|
12
|
+
/** The media host of the first image the page shows, for a preconnect (spec 2026-10-18 media on R2 and Images §7):
|
|
13
|
+
* the connection opens while the HTML is parsed. */
|
|
14
|
+
mediaOrigin?: string;
|
|
12
15
|
};
|
|
13
16
|
export declare function headOf(hit: SiteHit, locale: string, href: (path: string, locale: string) => string, locales: LocaleSet): PageHead;
|
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,30 @@ export default function smoodly(options = {}) {
|
|
|
75
75
|
let output = "server";
|
|
76
76
|
let root = process.cwd();
|
|
77
77
|
let watermark = null;
|
|
78
|
-
/** The
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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 config at build, from its config file (the one `options.config` names), loaded once and shared by
|
|
83
|
+
* every hook; and its API client, from the env the setup hook loaded, made once. Two steps, since the routing
|
|
84
|
+
* check wants the config before any env is needed. */
|
|
85
|
+
let builtConfig = null;
|
|
86
|
+
const buildConfig = () => (builtConfig ?? (builtConfig = options.config ? loadConfigFile(join(root, options.config), root) : loadConfig(root)));
|
|
87
|
+
let builtApi = null;
|
|
88
|
+
const buildApi = () => (builtApi ?? (builtApi = (async () => {
|
|
89
|
+
const loaded = await buildConfig();
|
|
90
|
+
return { loaded, api: createSmoodlyApi({ ...apiEnv(process.env), config: loaded }) };
|
|
91
|
+
})()));
|
|
92
|
+
/** A build read that fails names the service's address and stops the build before any page renders (a static build
|
|
93
|
+
* with the Worker down would otherwise fail page by page). */
|
|
94
|
+
const fromApi = async (read) => {
|
|
95
|
+
try {
|
|
96
|
+
return await read();
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
const address = process.env.SMOODLY_API_URL || DEFAULT_API_URL;
|
|
100
|
+
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.`);
|
|
101
|
+
}
|
|
82
102
|
};
|
|
83
103
|
return {
|
|
84
104
|
name: "smoodly",
|
|
@@ -86,23 +106,21 @@ export default function smoodly(options = {}) {
|
|
|
86
106
|
"astro:config:setup": async ({ config, command, updateConfig, injectRoute, addMiddleware, logger }) => {
|
|
87
107
|
root = fileURLToPath(config.root);
|
|
88
108
|
output = config.output === "static" ? "static" : "server";
|
|
109
|
+
i18n = config.i18n;
|
|
89
110
|
// Root-relative with a leading slash, as Vite's import.meta.glob reads it; a source dir that is the root itself is "".
|
|
90
111
|
const rel = relative(root, fileURLToPath(config.srcDir)).split("\\").join("/");
|
|
91
112
|
const srcDir = rel === "" ? "" : "/" + rel;
|
|
92
113
|
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.");
|
|
114
|
+
// No image service config: <Image> is plain markup over the cuts from Smoodly's media host (spec 2026-10-18 media
|
|
115
|
+
// on R2 and Images §7), so no remote pattern and no /_image.
|
|
97
116
|
updateConfig({
|
|
98
|
-
image: { remotePatterns: mediaUrl ? [mediaRemotePattern(mediaUrl)] : [] },
|
|
99
117
|
vite: { plugins: [virtualModules(root, options.config ?? "smoodly.config.ts", srcDir, output)] },
|
|
100
118
|
});
|
|
101
119
|
for (const route of routes(adminPath, output))
|
|
102
120
|
injectRoute(route);
|
|
103
121
|
addMiddleware({ entrypoint: here(`./middleware${ext}`), order: "pre" });
|
|
104
122
|
if (output === "server" && !config.cache?.provider) {
|
|
105
|
-
logger.warn("smoodly: no cache provider in astro.config — every page renders from the
|
|
123
|
+
logger.warn("smoodly: no cache provider in astro.config — every page renders from the service. Add `cache: { provider: memoryCache() }` (from \"astro/config\").");
|
|
106
124
|
}
|
|
107
125
|
// astro.config is evaluated before Astro loads .env, so `site: process.env.SITE_URL` there sees only the
|
|
108
126
|
// shell's value; the env files were read above, and SITE_URL from them sets `site` here. A value
|
|
@@ -122,10 +140,10 @@ export default function smoodly(options = {}) {
|
|
|
122
140
|
// Static output (spec 2026-10-09 static mode §3): the redirect rows and the prefix-always root become the
|
|
123
141
|
// host's redirects, answered without a function. Read at build only; `astro dev` serves them on demand.
|
|
124
142
|
if (output === "static" && command === "build") {
|
|
125
|
-
const { loaded,
|
|
126
|
-
const routing = astroRouting(config.i18n, loaded.locales);
|
|
143
|
+
const { loaded, api } = await buildApi();
|
|
144
|
+
const routing = astroRouting(config.i18n, loaded.locales, loaded.routing);
|
|
127
145
|
const href = localeHref(loaded.locales, routing);
|
|
128
|
-
const redirects = { ...(await redirectEntries({
|
|
146
|
+
const redirects = { ...(await fromApi(() => redirectEntries({ delivery: api, config: loaded, href }))), ...rootRedirect(routing, href, loaded.locales) };
|
|
129
147
|
updateConfig({ redirects: hostRedirects(config.redirects, redirects) });
|
|
130
148
|
logger.info(`smoodly: static output — ${Object.keys(redirects).length} redirects handed to the host.`);
|
|
131
149
|
}
|
|
@@ -133,15 +151,33 @@ export default function smoodly(options = {}) {
|
|
|
133
151
|
// The marker's watermark (spec 2026-10-09 static mode §5) is read BEFORE any page renders: a publish that lands
|
|
134
152
|
// while the build runs is then newer than it and counts as waiting, which it is — the build may not hold it. A
|
|
135
153
|
// row the build itself rebuilds keeps its publishedAt (a heal is not a publish, project.ts), so it never counts.
|
|
136
|
-
|
|
154
|
+
// The schema push (spec 2026-10-16 the control plane §4) comes first, in either output: the space then holds the
|
|
155
|
+
// registry this build renders with. A failed push never fails the build; the site runs against the schema the space has.
|
|
156
|
+
"astro:build:start": async ({ logger }) => {
|
|
157
|
+
const loaded = await buildConfig();
|
|
158
|
+
// The runtime's startup check, at build in either output: a deploy that builds must not refuse every request.
|
|
159
|
+
astroRouting(i18n, loaded.locales, loaded.routing);
|
|
160
|
+
try {
|
|
161
|
+
const env = apiEnv(process.env);
|
|
162
|
+
const { version, changed } = await pushSchema(env, pushBody(loaded, process.env, output === "static" ? "static" : "live"));
|
|
163
|
+
logger.info(changed ? `smoodly: pushed schema version ${version} to ${env.spaceId}.` : `smoodly: schema unchanged (version ${version}).`);
|
|
164
|
+
}
|
|
165
|
+
catch (e) {
|
|
166
|
+
logger.warn(`smoodly: the schema push failed — ${e instanceof Error ? e.message : String(e)}; the site runs against the schema the space has.`);
|
|
167
|
+
}
|
|
137
168
|
if (output !== "static")
|
|
138
169
|
return;
|
|
139
|
-
|
|
170
|
+
// The media every prerendered page registers (static-media.ts), collected in this process for the build's end.
|
|
171
|
+
staticMedia = collectStaticMedia();
|
|
172
|
+
const { api } = await buildApi();
|
|
173
|
+
watermark = await fromApi(() => api.newestPublishedAt());
|
|
140
174
|
},
|
|
141
175
|
// The build marker: beside the pages, the only trace of Smoodly in a static build.
|
|
142
176
|
"astro:build:done": async ({ dir, logger }) => {
|
|
143
177
|
if (output !== "static")
|
|
144
178
|
return;
|
|
179
|
+
// Every image the pages show, into the output beside them (spec 2026-10-18 media on R2 and Images §8).
|
|
180
|
+
logger.info(`smoodly: wrote ${await downloadStaticMedia(staticMedia.values(), fileURLToPath(dir))} media files into ${STATIC_MEDIA_DIR}.`);
|
|
145
181
|
const marker = markerJson({ builtAt: Date.now(), publishedThrough: watermark });
|
|
146
182
|
const file = join(fileURLToPath(dir), MARKER_PATH);
|
|
147
183
|
await mkdir(dirname(file), { recursive: true });
|
|
@@ -162,10 +198,8 @@ export function createContent(config, options = {}) {
|
|
|
162
198
|
loadSmoodlyEnv(options.root ?? process.cwd(), process.env.NODE_ENV === "production" ? "production" : "development");
|
|
163
199
|
env = process.env;
|
|
164
200
|
}
|
|
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 };
|
|
201
|
+
const href = localeHref(config.locales, astroRouting(options.i18n, config.locales, config.routing));
|
|
202
|
+
// Over Smoodly's API with the secret key (spec 2026-10-14 §1): the service runs the operations, so a publish from a
|
|
203
|
+
// script writes the delivery rows and expires the site's tags as the admin's does.
|
|
204
|
+
return { content: createSmoodlyContent({ config, href, transport: apiTransport(apiEnv(env)) }), href };
|
|
171
205
|
}
|
|
@@ -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";
|
|
@@ -18,7 +19,7 @@ export type { PageHead } from "./head.ts";
|
|
|
18
19
|
export type { ViewFiles } from "./views-check.ts";
|
|
19
20
|
import { type Shell, type SmoodlyLocals } from "./shell.ts";
|
|
20
21
|
/** The shell's decision with the project's bound site: what routes/page.astro calls, with `Astro.cache`.
|
|
21
|
-
* A page read from the
|
|
22
|
+
* A page read from the service gets its lifetime here, before its view renders. The first call compares
|
|
22
23
|
* the project's view files to the registry and names every disagreement once. */
|
|
23
24
|
export declare function shell(pathname: string, search: string, state: SmoodlyLocals, views: ViewFiles, cache?: unknown): Promise<Shell>;
|
|
24
25
|
/** getStaticPaths for the static page shell (spec 2026-10-09 static mode §3): every published address of the bound site. */
|
package/dist/astro/runtime.js
CHANGED
|
@@ -11,20 +11,21 @@ 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";
|
|
25
26
|
let viewsChecked = false;
|
|
26
27
|
/** The shell's decision with the project's bound site: what routes/page.astro calls, with `Astro.cache`.
|
|
27
|
-
* A page read from the
|
|
28
|
+
* A page read from the service gets its lifetime here, before its view renders. The first call compares
|
|
28
29
|
* the project's view files to the registry and names every disagreement once. */
|
|
29
30
|
export async function shell(pathname, search, state, views, cache) {
|
|
30
31
|
if (!viewsChecked) {
|
|
@@ -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";
|
|
@@ -14,7 +14,7 @@ export type SmoodlyLocals = {
|
|
|
14
14
|
editor: boolean;
|
|
15
15
|
/** History's stored version, inside a preview only. */
|
|
16
16
|
version?: string;
|
|
17
|
-
/** Set by the shell when it rendered a page read from the
|
|
17
|
+
/** Set by the shell when it rendered a page read from the service: the response may be cached under its tags. */
|
|
18
18
|
cacheable: boolean;
|
|
19
19
|
/** Set by the shell when it rendered a page at all: in a preview, only that response is a canvas. */
|
|
20
20
|
shell: boolean;
|
|
@@ -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;
|
package/dist/astro/shell.js
CHANGED
|
@@ -52,7 +52,7 @@ export async function resolveShell(deps, pathname, search, state, views) {
|
|
|
52
52
|
console.warn(`smoodly: no src/page-types/${hit.template}.astro — the record at ${hit.path} is a 404.`);
|
|
53
53
|
return NOT_FOUND;
|
|
54
54
|
}
|
|
55
|
-
// Only a page read from the
|
|
55
|
+
// Only a page read from the service has a tag to expire it by.
|
|
56
56
|
state.cacheable = !hit.record.id.startsWith("code:") && !state.draft;
|
|
57
57
|
state.shell = true;
|
|
58
58
|
const zoneNames = Object.keys(schema.zones ?? {});
|
|
@@ -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>;
|