diff --git a/.changeset/honest-houses-deny.md b/.changeset/honest-houses-deny.md new file mode 100644 index 000000000000..962e0442347b --- /dev/null +++ b/.changeset/honest-houses-deny.md @@ -0,0 +1,25 @@ +--- +'@astrojs/rss': major +--- + +Update the `rss()` default export to return a `Response` instead of a simple object, which is deprecated in Astro 3.0. If you were directly returning the `rss()` result from an endpoint before, this breaking change should not affect you. + +You can also import `getRssString()` to get the RSS string directly and use it to return your own Response: + +```ts +// src/pages/rss.xml.js +import { getRssString } from '@astrojs/rss'; + +export async function get(context) { + const rssString = await getRssString({ + title: 'Buzz’s Blog', + ... + }); + + return new Response(rssString, { + headers: { + 'Content-Type': 'application/xml', + }, + }); +} +``` diff --git a/packages/astro-rss/README.md b/packages/astro-rss/README.md index 2a2fb7db2640..4e7555a97c87 100644 --- a/packages/astro-rss/README.md +++ b/packages/astro-rss/README.md @@ -370,7 +370,27 @@ export async function get(context) { } ``` ---- +## `getRssString()` + +As `rss()` returns a `Response`, you can also use `getRssString()` to get the RSS string directly and use it in your own response: + +```ts "getRssString" +// src/pages/rss.xml.js +import { getRssString } from '@astrojs/rss'; + +export async function get(context) { + const rssString = await getRssString({ + title: 'Buzz’s Blog', + ... + }); + + return new Response(rssString, { + headers: { + 'Content-Type': 'application/xml', + }, + }); +} +``` For more on building with Astro, [visit the Astro docs][astro-rss]. diff --git a/packages/astro-rss/src/index.ts b/packages/astro-rss/src/index.ts index 843edd1466bb..a611afc1637d 100644 --- a/packages/astro-rss/src/index.ts +++ b/packages/astro-rss/src/index.ts @@ -98,12 +98,18 @@ const rssOptionsValidator = z.object({ trailingSlash: z.boolean().default(true), }); -export default async function getRSS(rssOptions: RSSOptions) { - const validatedRssOptions = await validateRssOptions(rssOptions); +export default async function getRssResponse(rssOptions: RSSOptions): Promise { + const rssString = await getRssString(rssOptions); + return new Response(rssString, { + headers: { + 'Content-Type': 'application/xml', + }, + }); +} - return { - body: await generateRSS(validatedRssOptions), - }; +export async function getRssString(rssOptions: RSSOptions): Promise { + const validatedRssOptions = await validateRssOptions(rssOptions); + return await generateRSS(validatedRssOptions); } async function validateRssOptions(rssOptions: RSSOptions) { diff --git a/packages/astro-rss/test/rss.test.js b/packages/astro-rss/test/rss.test.js index feca92546abf..5dfb48b32adf 100644 --- a/packages/astro-rss/test/rss.test.js +++ b/packages/astro-rss/test/rss.test.js @@ -1,4 +1,4 @@ -import rss from '../dist/index.js'; +import rss, { getRssString } from '../dist/index.js'; import { rssSchema } from '../dist/schema.js'; import chai from 'chai'; import chaiPromises from 'chai-as-promised'; @@ -36,41 +36,73 @@ const validXmlWithStylesheet = `<![CDATA[${title}]]>${site}/`; describe('rss', () => { + it('should return a response', async () => { + const response = await rss({ + title, + description, + items: [phpFeedItem, web1FeedItem], + site, + }); + + const str = await response.text(); + chai.expect(str).xml.to.equal(validXmlResult); + + const contentType = response.headers.get('Content-Type'); + chai.expect(contentType).to.equal('application/xml'); + }); + + it('should be the same string as getRssString', async () => { + const options = { + title, + description, + items: [phpFeedItem, web1FeedItem], + site, + }; + + const response = await rss(options); + const str1 = await response.text(); + const str2 = await getRssString(options); + + chai.expect(str1).to.equal(str2); + }); +}); + +describe('getRssString', () => { it('should generate on valid RSSFeedItem array', async () => { - const { body } = await rss({ + const str = await getRssString({ title, description, items: [phpFeedItem, web1FeedItem], site, }); - chai.expect(body).xml.to.equal(validXmlResult); + chai.expect(str).xml.to.equal(validXmlResult); }); it('should generate on valid RSSFeedItem array with HTML content included', async () => { - const { body } = await rss({ + const str = await getRssString({ title, description, items: [phpFeedItemWithContent, web1FeedItemWithContent], site, }); - chai.expect(body).xml.to.equal(validXmlWithContentResult); + chai.expect(str).xml.to.equal(validXmlWithContentResult); }); it('should generate on valid RSSFeedItem array with all RSS content included', async () => { - const { body } = await rss({ + const str = await getRssString({ title, description, items: [phpFeedItem, web1FeedItemWithAllData], site, }); - chai.expect(body).xml.to.equal(validXmlResultWithAllData); + chai.expect(str).xml.to.equal(validXmlResultWithAllData); }); it('should generate on valid RSSFeedItem array with custom data included', async () => { - const { body } = await rss({ + const str = await getRssString({ xmlns: { dc: 'http://purl.org/dc/elements/1.1/', }, @@ -80,11 +112,11 @@ describe('rss', () => { site, }); - chai.expect(body).xml.to.equal(validXmlWithCustomDataResult); + chai.expect(str).xml.to.equal(validXmlWithCustomDataResult); }); it('should include xml-stylesheet instruction when stylesheet is defined', async () => { - const { body } = await rss({ + const str = await getRssString({ title, description, items: [], @@ -92,11 +124,11 @@ describe('rss', () => { stylesheet: '/feedstylesheet.css', }); - chai.expect(body).xml.to.equal(validXmlWithStylesheet); + chai.expect(str).xml.to.equal(validXmlWithStylesheet); }); it('should include xml-stylesheet instruction with xsl type when stylesheet is set to xsl file', async () => { - const { body } = await rss({ + const str = await getRssString({ title, description, items: [], @@ -104,13 +136,13 @@ describe('rss', () => { stylesheet: '/feedstylesheet.xsl', }); - chai.expect(body).xml.to.equal(validXmlWithXSLStylesheet); + chai.expect(str).xml.to.equal(validXmlWithXSLStylesheet); }); it('should preserve self-closing tags on `customData`', async () => { const customData = ''; - const { body } = await rss({ + const str = await getRssString({ title, description, items: [], @@ -121,22 +153,22 @@ describe('rss', () => { customData, }); - chai.expect(body).to.contain(customData); + chai.expect(str).to.contain(customData); }); it('should filter out entries marked as `draft`', async () => { - const { body } = await rss({ + const str = await getRssString({ title, description, items: [phpFeedItem, { ...web1FeedItem, draft: true }], site, }); - chai.expect(body).xml.to.equal(validXmlWithoutWeb1FeedResult); + chai.expect(str).xml.to.equal(validXmlWithoutWeb1FeedResult); }); it('should respect drafts option', async () => { - const { body } = await rss({ + const str = await getRssString({ title, description, items: [phpFeedItem, { ...web1FeedItem, draft: true }], @@ -144,11 +176,11 @@ describe('rss', () => { drafts: true, }); - chai.expect(body).xml.to.equal(validXmlResult); + chai.expect(str).xml.to.equal(validXmlResult); }); it('should not append trailing slash to URLs with the given option', async () => { - const { body } = await rss({ + const str = await getRssString({ title, description, items: [phpFeedItem, { ...web1FeedItem, draft: true }], @@ -157,8 +189,8 @@ describe('rss', () => { trailingSlash: false, }); - chai.expect(body).xml.to.contain('https://example.com/<'); - chai.expect(body).xml.to.contain('https://example.com/php<'); + chai.expect(str).xml.to.contain('https://example.com/<'); + chai.expect(str).xml.to.contain('https://example.com/php<'); }); it('Deprecated import.meta.glob mapping still works', async () => { @@ -187,14 +219,14 @@ describe('rss', () => { ), }; - const { body } = await rss({ + const str = await getRssString({ title, description, items: globResult, site, }); - chai.expect(body).xml.to.equal(validXmlResult); + chai.expect(str).xml.to.equal(validXmlResult); }); it('should fail when an invalid date string is provided', async () => {