@ampless/runtime 0.2.0-alpha.4 → 0.2.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dispatchers/index.js +5 -0
- package/dist/index.d.ts +27 -1
- package/dist/index.js +12 -2
- package/dist/routes/index.d.ts +42 -9
- package/dist/routes/index.js +70 -1
- package/package.json +3 -3
|
@@ -25,6 +25,11 @@ function createThemePostDispatcher(ampless) {
|
|
|
25
25
|
if (post?.metadata?.no_layout === true) {
|
|
26
26
|
redirect(`/raw/${slug}`);
|
|
27
27
|
}
|
|
28
|
+
if (post?.format === "static") {
|
|
29
|
+
const body = post.body ?? null;
|
|
30
|
+
const entrypoint = typeof body?.entrypoint === "string" && body.entrypoint ? body.entrypoint : "index.html";
|
|
31
|
+
redirect(`/${slug}/${entrypoint}`);
|
|
32
|
+
}
|
|
28
33
|
const { module } = await ampless.resolveActiveTheme(siteId);
|
|
29
34
|
const Post = module.components.Post;
|
|
30
35
|
if (!Post) notFound();
|
package/dist/index.d.ts
CHANGED
|
@@ -109,6 +109,22 @@ interface ThemeActiveApi {
|
|
|
109
109
|
resolveActiveTheme(siteId?: string): Promise<ResolvedTheme>;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
type ColorScheme = 'auto' | 'light' | 'dark';
|
|
113
|
+
declare const DEFAULT_COLOR_SCHEME: ColorScheme;
|
|
114
|
+
/**
|
|
115
|
+
* Storage key for the per-site color-scheme override. Lives under the
|
|
116
|
+
* same `theme.*` namespace as manifest fields so it flows through the
|
|
117
|
+
* existing KvStore → S3 cache pipeline, but is read separately from
|
|
118
|
+
* the manifest field set (it's a site-wide concern, not theme-specific).
|
|
119
|
+
*/
|
|
120
|
+
declare const COLOR_SCHEME_SETTING_KEY = "theme.colorScheme";
|
|
121
|
+
/**
|
|
122
|
+
* Narrow an arbitrary stored value to a known `ColorScheme`. Anything
|
|
123
|
+
* unrecognised (including `undefined`, malformed strings, accidental
|
|
124
|
+
* objects) falls back to `'auto'` so a typo never strands a site
|
|
125
|
+
* forced into a mode it can't undo.
|
|
126
|
+
*/
|
|
127
|
+
declare function validateColorScheme(raw: unknown): ColorScheme;
|
|
112
128
|
interface EffectiveThemeConfig {
|
|
113
129
|
/** Resolved active theme name (e.g. 'blog'). */
|
|
114
130
|
activeTheme: string;
|
|
@@ -117,6 +133,16 @@ interface EffectiveThemeConfig {
|
|
|
117
133
|
values: Record<string, string>;
|
|
118
134
|
/** Subset of `values` for fields that have a `cssVar`. */
|
|
119
135
|
cssVars: Record<string, string>;
|
|
136
|
+
/**
|
|
137
|
+
* Per-site color-scheme override. `'auto'` (default) lets the
|
|
138
|
+
* visitor's system `prefers-color-scheme` decide; `'light'` /
|
|
139
|
+
* `'dark'` pin one mode regardless of system preference.
|
|
140
|
+
*
|
|
141
|
+
* Consumed by the root layout to set `<html data-color-scheme>`,
|
|
142
|
+
* which the theme tokens.css files key off in combination with the
|
|
143
|
+
* `light-dark()` CSS function.
|
|
144
|
+
*/
|
|
145
|
+
colorScheme: ColorScheme;
|
|
120
146
|
}
|
|
121
147
|
interface ThemeConfigApi {
|
|
122
148
|
loadThemeConfig(siteId?: string): Promise<EffectiveThemeConfig>;
|
|
@@ -204,4 +230,4 @@ interface Ampless {
|
|
|
204
230
|
*/
|
|
205
231
|
declare function createAmpless(opts: CreateAmplessOpts): Ampless;
|
|
206
232
|
|
|
207
|
-
export { type Ampless, type AmplessOutputs, type CreateAmplessOpts, type DataOutput, type EffectiveSiteSettings, type EffectiveThemeConfig, type ListPostsByTagOptions, type ListPostsOptions, type ListPostsResult, type PostsApi, type PublicPostConnectionShape, type PublicPostShape, type ResolvedTheme, type SeoApi, type SiteSettingsApi, type StorageOutput, type ThemeActiveApi, type ThemeConfigApi, type ThemesRegistry, createAmpless, htmlToMarkdown, markdownToHtml, renderBody, renderThemeCss, tiptapToHtml, tiptapToMarkdown };
|
|
233
|
+
export { type Ampless, type AmplessOutputs, COLOR_SCHEME_SETTING_KEY, type ColorScheme, type CreateAmplessOpts, DEFAULT_COLOR_SCHEME, type DataOutput, type EffectiveSiteSettings, type EffectiveThemeConfig, type ListPostsByTagOptions, type ListPostsOptions, type ListPostsResult, type PostsApi, type PublicPostConnectionShape, type PublicPostShape, type ResolvedTheme, type SeoApi, type SiteSettingsApi, type StorageOutput, type ThemeActiveApi, type ThemeConfigApi, type ThemesRegistry, createAmpless, htmlToMarkdown, markdownToHtml, renderBody, renderThemeCss, tiptapToHtml, tiptapToMarkdown, validateColorScheme };
|
package/dist/index.js
CHANGED
|
@@ -240,6 +240,12 @@ import {
|
|
|
240
240
|
resolveThemeValues,
|
|
241
241
|
themeSettingKey
|
|
242
242
|
} from "ampless";
|
|
243
|
+
var DEFAULT_COLOR_SCHEME = "auto";
|
|
244
|
+
var COLOR_SCHEME_SETTING_KEY = "theme.colorScheme";
|
|
245
|
+
function validateColorScheme(raw) {
|
|
246
|
+
if (raw === "light" || raw === "dark" || raw === "auto") return raw;
|
|
247
|
+
return DEFAULT_COLOR_SCHEME;
|
|
248
|
+
}
|
|
243
249
|
function createThemeConfig(themeActive, storage) {
|
|
244
250
|
async function fetchRemote(siteId) {
|
|
245
251
|
if (!storage.isStorageConfigured()) return null;
|
|
@@ -269,7 +275,8 @@ function createThemeConfig(themeActive, storage) {
|
|
|
269
275
|
}
|
|
270
276
|
const values = resolveThemeValues(manifest, stored);
|
|
271
277
|
const cssVars = collectCssVars(manifest.fields, values);
|
|
272
|
-
|
|
278
|
+
const colorScheme = validateColorScheme(flat?.[COLOR_SCHEME_SETTING_KEY]);
|
|
279
|
+
return { activeTheme: active.name, manifest, values, cssVars, colorScheme };
|
|
273
280
|
}
|
|
274
281
|
};
|
|
275
282
|
}
|
|
@@ -533,11 +540,14 @@ function createAmpless(opts) {
|
|
|
533
540
|
};
|
|
534
541
|
}
|
|
535
542
|
export {
|
|
543
|
+
COLOR_SCHEME_SETTING_KEY,
|
|
544
|
+
DEFAULT_COLOR_SCHEME,
|
|
536
545
|
createAmpless,
|
|
537
546
|
htmlToMarkdown,
|
|
538
547
|
markdownToHtml,
|
|
539
548
|
renderBody,
|
|
540
549
|
renderThemeCss,
|
|
541
550
|
tiptapToHtml,
|
|
542
|
-
tiptapToMarkdown
|
|
551
|
+
tiptapToMarkdown,
|
|
552
|
+
validateColorScheme
|
|
543
553
|
};
|
package/dist/routes/index.d.ts
CHANGED
|
@@ -2,21 +2,21 @@ import { Ampless } from '../index.js';
|
|
|
2
2
|
import 'ampless';
|
|
3
3
|
import 'next';
|
|
4
4
|
|
|
5
|
-
interface Ctx$
|
|
5
|
+
interface Ctx$4 {
|
|
6
6
|
params: Promise<{
|
|
7
7
|
siteId: string;
|
|
8
8
|
slug: string;
|
|
9
9
|
}>;
|
|
10
10
|
}
|
|
11
|
-
type OgRouteHandler = (req: Request, ctx: Ctx$
|
|
11
|
+
type OgRouteHandler = (req: Request, ctx: Ctx$4) => Promise<Response>;
|
|
12
12
|
declare function createOgRouteHandler(ampless: Ampless): OgRouteHandler;
|
|
13
13
|
|
|
14
|
-
interface Ctx$
|
|
14
|
+
interface Ctx$3 {
|
|
15
15
|
params: Promise<{
|
|
16
16
|
siteId: string;
|
|
17
17
|
}>;
|
|
18
18
|
}
|
|
19
|
-
type SitemapRouteHandler = (req: Request, ctx: Ctx$
|
|
19
|
+
type SitemapRouteHandler = (req: Request, ctx: Ctx$3) => Promise<Response>;
|
|
20
20
|
/**
|
|
21
21
|
* Sitemap route delegate. Looks up the active theme for the request's
|
|
22
22
|
* siteId and forwards to whichever `routes.sitemap` handler the theme
|
|
@@ -24,12 +24,12 @@ type SitemapRouteHandler = (req: Request, ctx: Ctx$2) => Promise<Response>;
|
|
|
24
24
|
*/
|
|
25
25
|
declare function createSitemapRouteHandler(ampless: Ampless): SitemapRouteHandler;
|
|
26
26
|
|
|
27
|
-
interface Ctx$
|
|
27
|
+
interface Ctx$2 {
|
|
28
28
|
params: Promise<{
|
|
29
29
|
siteId: string;
|
|
30
30
|
}>;
|
|
31
31
|
}
|
|
32
|
-
type FeedRouteHandler = (req: Request, ctx: Ctx$
|
|
32
|
+
type FeedRouteHandler = (req: Request, ctx: Ctx$2) => Promise<Response>;
|
|
33
33
|
/**
|
|
34
34
|
* Feed route delegate. Looks up the active theme for the request's
|
|
35
35
|
* siteId and forwards to whichever `routes.feed` handler the theme
|
|
@@ -37,13 +37,13 @@ type FeedRouteHandler = (req: Request, ctx: Ctx$1) => Promise<Response>;
|
|
|
37
37
|
*/
|
|
38
38
|
declare function createFeedRouteHandler(ampless: Ampless): FeedRouteHandler;
|
|
39
39
|
|
|
40
|
-
interface Ctx {
|
|
40
|
+
interface Ctx$1 {
|
|
41
41
|
params: Promise<{
|
|
42
42
|
siteId: string;
|
|
43
43
|
slug: string;
|
|
44
44
|
}>;
|
|
45
45
|
}
|
|
46
|
-
type RawRouteHandler = (req: Request, ctx: Ctx) => Promise<Response>;
|
|
46
|
+
type RawRouteHandler = (req: Request, ctx: Ctx$1) => Promise<Response>;
|
|
47
47
|
/**
|
|
48
48
|
* Bare HTML route. Returns the published post's rendered body as the
|
|
49
49
|
* entire HTTP response — no Next.js root layout, no theme chrome.
|
|
@@ -77,4 +77,37 @@ type RawRouteHandler = (req: Request, ctx: Ctx) => Promise<Response>;
|
|
|
77
77
|
*/
|
|
78
78
|
declare function createRawRouteHandler(ampless: Ampless): RawRouteHandler;
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
interface Ctx {
|
|
81
|
+
params: Promise<{
|
|
82
|
+
siteId: string;
|
|
83
|
+
path: string[];
|
|
84
|
+
}>;
|
|
85
|
+
}
|
|
86
|
+
type StaticRouteHandler = (req: Request, ctx: Ctx) => Promise<Response>;
|
|
87
|
+
/**
|
|
88
|
+
* Catch-all route for static bundles. Lives at
|
|
89
|
+
* `app/site/[siteId]/[...path]/route.ts` and serves every file
|
|
90
|
+
* inside a `format: 'static'` post's bundle. The first segment of
|
|
91
|
+
* `path` is the post slug; the rest is the relative path inside the
|
|
92
|
+
* bundle (or empty, in which case the manifest's entrypoint is used).
|
|
93
|
+
*
|
|
94
|
+
* Routing precedence: Next.js prefers the more specific
|
|
95
|
+
* `[siteId]/[slug]/page.tsx` for single-segment URLs, so this catch-all
|
|
96
|
+
* only fires when path.length >= 2. The post dispatcher detects
|
|
97
|
+
* `format === 'static'` and 308-redirects to `/<slug>/<entrypoint>`
|
|
98
|
+
* so the catch-all picks up from there.
|
|
99
|
+
*
|
|
100
|
+
* Single-segment requests (`/<siteId>/<slug>`) that somehow do reach
|
|
101
|
+
* here are 308-redirected to add a trailing slash, which is what makes
|
|
102
|
+
* relative paths in the bundle resolve correctly in the browser.
|
|
103
|
+
*
|
|
104
|
+
* Implementation: generate a short-lived presigned URL for the S3
|
|
105
|
+
* object and 302-redirect to it. Same model as `/api/media/[...path]`
|
|
106
|
+
* — keeps the bucket private while letting CloudFront cache the 302
|
|
107
|
+
* for the Cache-Control window. The presigned URL itself expires in
|
|
108
|
+
* an hour; the 302 cache is 5 minutes so re-issued presigns stay
|
|
109
|
+
* fresh.
|
|
110
|
+
*/
|
|
111
|
+
declare function createStaticRouteHandler(ampless: Ampless): StaticRouteHandler;
|
|
112
|
+
|
|
113
|
+
export { type FeedRouteHandler, type OgRouteHandler, type RawRouteHandler, type SitemapRouteHandler, type StaticRouteHandler, createFeedRouteHandler, createOgRouteHandler, createRawRouteHandler, createSitemapRouteHandler, createStaticRouteHandler };
|
package/dist/routes/index.js
CHANGED
|
@@ -87,9 +87,78 @@ function createRawRouteHandler(ampless) {
|
|
|
87
87
|
});
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
|
+
|
|
91
|
+
// src/routes/static.ts
|
|
92
|
+
import { createServerRunner } from "@aws-amplify/adapter-nextjs";
|
|
93
|
+
import { cookies } from "next/headers";
|
|
94
|
+
import { getUrl } from "aws-amplify/storage/server";
|
|
95
|
+
var FALLBACK_MIME = {
|
|
96
|
+
".html": "text/html; charset=utf-8",
|
|
97
|
+
".htm": "text/html; charset=utf-8"
|
|
98
|
+
};
|
|
99
|
+
function createStaticRouteHandler(ampless) {
|
|
100
|
+
const { runWithAmplifyServerContext } = createServerRunner({
|
|
101
|
+
config: ampless.outputs
|
|
102
|
+
});
|
|
103
|
+
return async function GET(request, { params }) {
|
|
104
|
+
const { siteId, path } = await params;
|
|
105
|
+
if (!path || path.length === 0) {
|
|
106
|
+
return new Response("Not Found", { status: 404 });
|
|
107
|
+
}
|
|
108
|
+
const slug = path[0];
|
|
109
|
+
const restSegments = path.slice(1);
|
|
110
|
+
if (restSegments.some(
|
|
111
|
+
(s) => !s || s === "." || s === ".." || s.includes("/") || s.includes("\\") || s.includes("\0")
|
|
112
|
+
)) {
|
|
113
|
+
return new Response("Invalid path", { status: 400 });
|
|
114
|
+
}
|
|
115
|
+
const post = await ampless.getPublishedPost(slug, { siteId });
|
|
116
|
+
if (!post || post.format !== "static") {
|
|
117
|
+
return new Response("Not Found", { status: 404 });
|
|
118
|
+
}
|
|
119
|
+
const body = post.body ?? null;
|
|
120
|
+
const entrypoint = typeof body?.entrypoint === "string" && body.entrypoint ? body.entrypoint : "index.html";
|
|
121
|
+
const fileList = Array.isArray(body?.files) ? body.files : [];
|
|
122
|
+
let rest = restSegments.join("/");
|
|
123
|
+
if (rest === "") {
|
|
124
|
+
const url = new URL(request.url);
|
|
125
|
+
if (!url.pathname.endsWith("/")) {
|
|
126
|
+
const next = new URL(url);
|
|
127
|
+
next.pathname = `${url.pathname}/`;
|
|
128
|
+
return Response.redirect(next.toString(), 308);
|
|
129
|
+
}
|
|
130
|
+
rest = entrypoint;
|
|
131
|
+
}
|
|
132
|
+
if (fileList.length > 0 && !fileList.includes(rest)) {
|
|
133
|
+
return new Response("Not Found", { status: 404 });
|
|
134
|
+
}
|
|
135
|
+
const objectPath = `public/static/${siteId}/${slug}/${rest}`;
|
|
136
|
+
try {
|
|
137
|
+
const presignedUrl = await runWithAmplifyServerContext({
|
|
138
|
+
nextServerContext: { cookies },
|
|
139
|
+
operation: async (amplifyContext) => {
|
|
140
|
+
const result = await getUrl(amplifyContext, {
|
|
141
|
+
path: objectPath,
|
|
142
|
+
options: { expiresIn: 60 * 60 }
|
|
143
|
+
});
|
|
144
|
+
return result.url.toString();
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
return Response.redirect(presignedUrl, 302);
|
|
148
|
+
} catch {
|
|
149
|
+
const ext = rest.slice(rest.lastIndexOf("."));
|
|
150
|
+
const fallback = FALLBACK_MIME[ext] ?? "text/plain; charset=utf-8";
|
|
151
|
+
return new Response("Not Found", {
|
|
152
|
+
status: 404,
|
|
153
|
+
headers: { "Content-Type": fallback }
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
90
158
|
export {
|
|
91
159
|
createFeedRouteHandler,
|
|
92
160
|
createOgRouteHandler,
|
|
93
161
|
createRawRouteHandler,
|
|
94
|
-
createSitemapRouteHandler
|
|
162
|
+
createSitemapRouteHandler,
|
|
163
|
+
createStaticRouteHandler
|
|
95
164
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ampless/runtime",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.6",
|
|
4
4
|
"description": "Public-side runtime for ampless: post fetching, theme dispatch, middleware, public route handlers",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
"clsx": "^2.1.1",
|
|
49
49
|
"lucide-react": "^1.16.0",
|
|
50
50
|
"tailwind-merge": "^3.6.0",
|
|
51
|
-
"ampless": "0.2.0-alpha.
|
|
52
|
-
"@ampless/plugin-og-image": "0.2.0-alpha.
|
|
51
|
+
"ampless": "0.2.0-alpha.3",
|
|
52
|
+
"@ampless/plugin-og-image": "0.2.0-alpha.3"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"next": "^15 || ^16",
|