@ampless/plugin-rss 0.2.0-alpha.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ampless contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # @ampless/plugin-rss
2
+
3
+ RSS 2.0 feed plugin for [ampless](https://github.com/heavymoons/ampless).
4
+
5
+ > **Pre-release / alpha.** Breaking changes possible in any minor version until v1.0.
6
+
7
+ Regenerates `feed.xml` to S3 every time a post is published, unpublished, updated, or deleted. The Next.js `/feed.xml` route serves the latest version through.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @ampless/plugin-rss@alpha
13
+ ```
14
+
15
+ ## Configure
16
+
17
+ In `cms.config.ts`:
18
+
19
+ ```ts
20
+ import { defineConfig } from 'ampless'
21
+ import rssPlugin from '@ampless/plugin-rss'
22
+
23
+ export default defineConfig({
24
+ // ...
25
+ plugins: [
26
+ rssPlugin({
27
+ limit: 20, // most recent posts
28
+ language: 'en', // BCP 47 language tag
29
+ // siteUrl: 'https://example.com',
30
+ // feedPath: '/feed.xml',
31
+ }),
32
+ ],
33
+ })
34
+ ```
35
+
36
+ | Option | Default | Notes |
37
+ |---|---|---|
38
+ | `limit` | `20` | Number of newest published posts to include |
39
+ | `language` | `'en'` | RSS `<language>` tag (BCP 47) |
40
+ | `siteUrl` | `site.url` | Override the base URL (e.g. for staging environments) |
41
+ | `feedPath` | `/feed.xml` | Path the feed is served at; emitted in the `<atom:link rel="self">` element |
42
+
43
+ The plugin also adds an autodiscovery `<link rel="alternate" type="application/rss+xml">` to your site's `<head>` via the `siteMetadata` hook.
44
+
45
+ ## Trust level
46
+
47
+ `trusted` — runs in the trusted Lambda processor with read access to the post table and write access under `public/plugins/rss/` in the site's S3 bucket.
48
+
49
+ ## Hooks
50
+
51
+ Subscribes to:
52
+
53
+ - `content.published`
54
+ - `content.unpublished`
55
+ - `content.deleted`
56
+ - `content.updated` (so a re-titled published post produces a fresh `<title>` in the feed)
57
+
58
+ Each hook re-reads the full set of published posts and writes the regenerated XML to `s3://<site-bucket>/public/plugins/rss/feed.xml`. The Next.js route reads from there, so the feed is always at most one event behind real time.
@@ -0,0 +1,30 @@
1
+ import { Post, Config, AmplessPlugin } from 'ampless';
2
+
3
+ interface RssFeedOptions {
4
+ /** Number of most recent posts to include. Default 20. */
5
+ limit?: number;
6
+ /** Override site URL (e.g. for staging). Defaults to config.site.url. */
7
+ siteUrl?: string;
8
+ /** Path to expose the feed at. Default '/feed.xml'. */
9
+ feedPath?: string;
10
+ /** RSS <language> tag (BCP 47). Default 'en'. */
11
+ language?: string;
12
+ }
13
+ declare function buildRssFeed(posts: Post[], site: Config['site'], options?: RssFeedOptions): string;
14
+
15
+ interface RssPluginOptions extends RssFeedOptions {
16
+ }
17
+ /**
18
+ * RSS / Atom feed plugin.
19
+ *
20
+ * On `content.published`, `content.unpublished`, and `content.deleted`
21
+ * events, regenerates the site feed from all currently-published posts and
22
+ * stores it at `public/plugins/rss/feed.xml`. The Next.js `/feed.xml`
23
+ * route serves this object.
24
+ *
25
+ * Adds a <link rel="alternate" type="application/rss+xml"> to the site
26
+ * metadata so feed readers can autodiscover.
27
+ */
28
+ declare function rssPlugin(options?: RssPluginOptions): AmplessPlugin;
29
+
30
+ export { type RssFeedOptions, type RssPluginOptions, buildRssFeed, rssPlugin as default };
package/dist/index.js ADDED
@@ -0,0 +1,76 @@
1
+ // src/index.ts
2
+ import { definePlugin } from "ampless";
3
+
4
+ // src/feed.ts
5
+ import { escapeXml } from "ampless";
6
+ var toRfc822 = (iso) => new Date(iso).toUTCString();
7
+ function buildRssFeed(posts, site, options = {}) {
8
+ const baseUrl = (options.siteUrl ?? site.url).replace(/\/$/, "");
9
+ const feedPath = options.feedPath ?? "/feed.xml";
10
+ const limit = options.limit ?? 20;
11
+ const language = options.language ?? "en";
12
+ const items = posts.filter((p) => p.status === "published").slice(0, limit).map((post) => {
13
+ const url = `${baseUrl}/${post.slug}`;
14
+ const pubDate = post.publishedAt ? toRfc822(post.publishedAt) : (/* @__PURE__ */ new Date()).toUTCString();
15
+ const description = post.excerpt ?? "";
16
+ const tags = (post.tags ?? []).map((t) => ` <category>${escapeXml(t)}</category>`).join("\n");
17
+ return ` <item>
18
+ <title>${escapeXml(post.title)}</title>
19
+ <link>${escapeXml(url)}</link>
20
+ <guid isPermaLink="true">${escapeXml(url)}</guid>
21
+ <pubDate>${pubDate}</pubDate>
22
+ ${tags ? tags + "\n" : ""} <description>${escapeXml(description)}</description>
23
+ </item>`;
24
+ }).join("\n");
25
+ const lastBuild = (/* @__PURE__ */ new Date()).toUTCString();
26
+ return `<?xml version="1.0" encoding="UTF-8"?>
27
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
28
+ <channel>
29
+ <title>${escapeXml(site.name)}</title>
30
+ <link>${escapeXml(baseUrl)}</link>
31
+ <description>${escapeXml(site.description ?? site.name)}</description>
32
+ <language>${escapeXml(language)}</language>
33
+ <atom:link href="${escapeXml(baseUrl + feedPath)}" rel="self" type="application/rss+xml" />
34
+ <lastBuildDate>${lastBuild}</lastBuildDate>
35
+ ${items}
36
+ </channel>
37
+ </rss>
38
+ `;
39
+ }
40
+
41
+ // src/index.ts
42
+ function rssPlugin(options = {}) {
43
+ const feedPath = options.feedPath ?? "/feed.xml";
44
+ async function rebuild(_event, ctx) {
45
+ const posts = await ctx.listPublishedPosts();
46
+ const xml = buildRssFeed(posts, ctx.site, options);
47
+ await ctx.writePublicAsset("feed.xml", xml, "application/rss+xml; charset=utf-8");
48
+ }
49
+ return definePlugin({
50
+ name: "rss",
51
+ apiVersion: 1,
52
+ trust_level: "trusted",
53
+ hooks: {
54
+ "content.published": rebuild,
55
+ "content.unpublished": rebuild,
56
+ "content.deleted": rebuild,
57
+ // Updates can change title/excerpt of an already-published post, so
58
+ // we regenerate on every update too.
59
+ "content.updated": rebuild
60
+ },
61
+ siteMetadata(site) {
62
+ const baseUrl = (options.siteUrl ?? site.url).replace(/\/$/, "");
63
+ return {
64
+ alternates: {
65
+ types: {
66
+ "application/rss+xml": baseUrl + feedPath
67
+ }
68
+ }
69
+ };
70
+ }
71
+ });
72
+ }
73
+ export {
74
+ buildRssFeed,
75
+ rssPlugin as default
76
+ };
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@ampless/plugin-rss",
3
+ "version": "0.2.0-alpha.0",
4
+ "description": "RSS / Atom feed plugin for ampless",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "README.md"
16
+ ],
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/heavymoons/ampless.git",
23
+ "directory": "packages/plugin-rss"
24
+ },
25
+ "homepage": "https://github.com/heavymoons/ampless/tree/main/packages/plugin-rss#readme",
26
+ "bugs": "https://github.com/heavymoons/ampless/issues",
27
+ "dependencies": {
28
+ "ampless": "0.2.0-alpha.0"
29
+ },
30
+ "keywords": [
31
+ "ampless",
32
+ "plugin",
33
+ "rss",
34
+ "atom",
35
+ "feed"
36
+ ],
37
+ "scripts": {
38
+ "build": "tsup",
39
+ "dev": "tsup --watch",
40
+ "lint": "tsc --noEmit",
41
+ "test": "vitest run --passWithNoTests"
42
+ }
43
+ }