From c0c509b6bf3f55562d22297fdcc2b3e57969734d Mon Sep 17 00:00:00 2001 From: Ben Holmes Date: Wed, 8 May 2024 07:53:17 -0400 Subject: [PATCH] Actions experimental release (#10858) * feat: port astro-actions poc * feat: basic blog example * feat: basic validationError class * feat: standard error types and safe() wrapper * refactor: move enhanceProps to astro:actions * fix: throw internal server errors * chore: refine enhance: true error message * fix: remove FormData fallback from route * refactor: clarify what enhance: true allows * feat: progressively enhanced comments * chore: changeset * refactor: enhance -> acceptFormData * wip: migrate actions to core * feat: working actions demo from astro core! * chore: changeset * chore: delete old changeset * fix: Function type lint * refactor: expose defineAction from `astro:actions` * fix: add null check to experimental * fix: export `types/actions.d.ts` * feat: more robust form data parsing * feat: support formData from rpc call * feat: remove acceptFormData flag requirement * feat: add actions.d.ts type reference on startup * refactor: actionNameProps -> getNameProps * fix: actions type import * chore: expose zod from `astro:actions` * fix: zod export path * feat: add explicit `accept` property * Use zod package instead of relative path outside of src * feat: clean up error throwing and handling flow * fix: make `accept` optional * docs: beef up actions experimental docs * fix: defineAction type narrowing on `accept` * fix: bad `getNameProps()` arg type * refactor: move to single `error` object + `isInputError()` util * fix: move res.json() parse to avoid double parse * feat: support async zod schemas * feat: serialize and expose zod properties on input error * feat: test input error in comment example * fix: remove ZodError import * fix: add actions-module to files export * fix: use workspace for test pkg versions * refactor: default export -> server export * fix: type inference for json vs. form * refactor: accept form -> defineFormAction * refactor: better callSafely signature * feat: block action calls from the server with RFC link * feat: move getActionResult to global * refactor: getNameProps -> getActionProps * refactor: body.toString() * edit: capitAl Co-authored-by: Sarah Rainsberger * edit: highlight `actions` Co-authored-by: Sarah Rainsberger * edit: add actions file name Co-authored-by: Sarah Rainsberger * edit: not you can. You DO Co-authored-by: Sarah Rainsberger * edit: declare with feeling Co-authored-by: Sarah Rainsberger * edit: clarify what the `handler` does * edit: schema -> input * edit: add FormData mdn reference * edit: add defineFormAction() explainer * refactor: inline getDotAstroTypeRefs * edit: yeah yeah maybe * fix: existsSync test mock * refactor: use callSafely in middleware * test: upgradeFormData() * chore: stray console log * refactor: extract helper functions * fix: include status in error response * fix: return `undefined` when there's no action result * fix: content-type * test: e2e like button action * test: comment e2e * fix: existsSync mock for other sync test * test: action dev server raw fetch * test: build preview * chore: fix lock * fix: add dotAstroDir to existsSync * chore: slim down e2e fixture * chore: remove unneeded disabled test * refactor: better api context error * fix: return `false` for envDts * refactor: defineFormAction -> defineAction with accept * fix: check FormData on getActionProps * edit: uppercase Co-authored-by: Sarah Rainsberger * fix: add switch default for 500 Co-authored-by: Emanuele Stoppa * fix: add `toLowerCase()` on content-type check Co-authored-by: Emanuele Stoppa * chore: use VIRTUAL_MODULE_ID for plugin * fix: remove incorrect ts-ignore * chore: remove unneeded POST method check * refactor: route callSafely * refactor: error switch case to map * chore: add link to trpc error code table * fix: add readable error on failed json.stringify * refactor: add param -> callerParam with comment * feat: always return safe from getActionResult() * refactor: move actions module to templates/ * refactor: remove unneeded existsSync on dotAstro * fix: hasContentType util for toLowerCase() * chore: comment on 415 code * refactor: upgradeFormData -> formDataToObj * fix: avoid leaking stack in production * refactor: defineProperty with write false * fix: revert package.json back to spaces * edit: use config docs for changeset * refactor: stringifiedActionsPath -> stringifiedActionsImport * fix: avoid double-handling for route * fix: support zero arg actions * refactor: move actionHandler to helper fn * fix: restore mdast deps * docs: add `output` to config --------- Co-authored-by: Sarah Rainsberger Co-authored-by: Emanuele Stoppa Co-authored-by: bholmesdev --- .changeset/shaggy-moons-peel.md | 95 ++++++++++ packages/astro/client.d.ts | 1 + packages/astro/e2e/actions-blog.test.js | 58 ++++++ .../fixtures/actions-blog/astro.config.mjs | 17 ++ .../e2e/fixtures/actions-blog/db/config.ts | 21 +++ .../e2e/fixtures/actions-blog/db/seed.ts | 15 ++ .../e2e/fixtures/actions-blog/package.json | 24 +++ .../actions-blog/src/actions/index.ts | 45 +++++ .../src/components/BaseHead.astro | 47 +++++ .../actions-blog/src/components/Footer.astro | 62 +++++++ .../src/components/FormattedDate.astro | 17 ++ .../actions-blog/src/components/Header.astro | 83 +++++++++ .../src/components/HeaderLink.astro | 25 +++ .../actions-blog/src/components/Like.tsx | 22 +++ .../src/components/PostComment.tsx | 66 +++++++ .../e2e/fixtures/actions-blog/src/consts.ts | 5 + .../src/content/blog/first-post.md | 15 ++ .../actions-blog/src/content/config.ts | 16 ++ .../actions-blog/src/layouts/BlogPost.astro | 85 +++++++++ .../src/pages/blog/[...slug].astro | 61 ++++++ .../actions-blog/src/pages/blog/index.astro | 111 +++++++++++ .../actions-blog/src/styles/global.css | 140 ++++++++++++++ .../e2e/fixtures/actions-blog/tsconfig.json | 8 + packages/astro/package.json | 4 +- packages/astro/src/@types/astro.ts | 126 +++++++++++++ packages/astro/src/actions/consts.ts | 3 + packages/astro/src/actions/index.ts | 81 ++++++++ .../astro/src/actions/runtime/middleware.ts | 52 ++++++ packages/astro/src/actions/runtime/route.ts | 39 ++++ packages/astro/src/actions/runtime/store.ts | 18 ++ packages/astro/src/actions/runtime/utils.ts | 27 +++ .../src/actions/runtime/virtual/client.ts | 18 ++ .../src/actions/runtime/virtual/server.ts | 172 +++++++++++++++++ .../src/actions/runtime/virtual/shared.ts | 151 +++++++++++++++ packages/astro/src/actions/utils.ts | 20 ++ packages/astro/src/content/index.ts | 7 +- packages/astro/src/content/utils.ts | 11 +- packages/astro/src/core/config/schema.ts | 2 + packages/astro/src/core/middleware/index.ts | 11 +- packages/astro/src/core/render-context.ts | 12 +- packages/astro/src/integrations/hooks.ts | 4 + .../src/vite-plugin-inject-env-ts/index.ts | 56 ++++-- packages/astro/templates/actions.mjs | 61 ++++++ packages/astro/test/actions.test.js | 173 ++++++++++++++++++ packages/astro/test/astro-sync.test.js | 43 +++-- .../test/fixtures/actions/astro.config.mjs | 9 + .../astro/test/fixtures/actions/package.json | 8 + .../fixtures/actions/src/actions/index.ts | 32 ++++ .../units/actions/form-data-to-object.test.js | 138 ++++++++++++++ packages/astro/types/actions.d.ts | 3 + pnpm-lock.yaml | 52 +++++- 51 files changed, 2320 insertions(+), 52 deletions(-) create mode 100644 .changeset/shaggy-moons-peel.md create mode 100644 packages/astro/e2e/actions-blog.test.js create mode 100644 packages/astro/e2e/fixtures/actions-blog/astro.config.mjs create mode 100644 packages/astro/e2e/fixtures/actions-blog/db/config.ts create mode 100644 packages/astro/e2e/fixtures/actions-blog/db/seed.ts create mode 100644 packages/astro/e2e/fixtures/actions-blog/package.json create mode 100644 packages/astro/e2e/fixtures/actions-blog/src/actions/index.ts create mode 100644 packages/astro/e2e/fixtures/actions-blog/src/components/BaseHead.astro create mode 100644 packages/astro/e2e/fixtures/actions-blog/src/components/Footer.astro create mode 100644 packages/astro/e2e/fixtures/actions-blog/src/components/FormattedDate.astro create mode 100644 packages/astro/e2e/fixtures/actions-blog/src/components/Header.astro create mode 100644 packages/astro/e2e/fixtures/actions-blog/src/components/HeaderLink.astro create mode 100644 packages/astro/e2e/fixtures/actions-blog/src/components/Like.tsx create mode 100644 packages/astro/e2e/fixtures/actions-blog/src/components/PostComment.tsx create mode 100644 packages/astro/e2e/fixtures/actions-blog/src/consts.ts create mode 100644 packages/astro/e2e/fixtures/actions-blog/src/content/blog/first-post.md create mode 100644 packages/astro/e2e/fixtures/actions-blog/src/content/config.ts create mode 100644 packages/astro/e2e/fixtures/actions-blog/src/layouts/BlogPost.astro create mode 100644 packages/astro/e2e/fixtures/actions-blog/src/pages/blog/[...slug].astro create mode 100644 packages/astro/e2e/fixtures/actions-blog/src/pages/blog/index.astro create mode 100644 packages/astro/e2e/fixtures/actions-blog/src/styles/global.css create mode 100644 packages/astro/e2e/fixtures/actions-blog/tsconfig.json create mode 100644 packages/astro/src/actions/consts.ts create mode 100644 packages/astro/src/actions/index.ts create mode 100644 packages/astro/src/actions/runtime/middleware.ts create mode 100644 packages/astro/src/actions/runtime/route.ts create mode 100644 packages/astro/src/actions/runtime/store.ts create mode 100644 packages/astro/src/actions/runtime/utils.ts create mode 100644 packages/astro/src/actions/runtime/virtual/client.ts create mode 100644 packages/astro/src/actions/runtime/virtual/server.ts create mode 100644 packages/astro/src/actions/runtime/virtual/shared.ts create mode 100644 packages/astro/src/actions/utils.ts create mode 100644 packages/astro/templates/actions.mjs create mode 100644 packages/astro/test/actions.test.js create mode 100644 packages/astro/test/fixtures/actions/astro.config.mjs create mode 100644 packages/astro/test/fixtures/actions/package.json create mode 100644 packages/astro/test/fixtures/actions/src/actions/index.ts create mode 100644 packages/astro/test/units/actions/form-data-to-object.test.js create mode 100644 packages/astro/types/actions.d.ts diff --git a/.changeset/shaggy-moons-peel.md b/.changeset/shaggy-moons-peel.md new file mode 100644 index 0000000000..db500d5e5c --- /dev/null +++ b/.changeset/shaggy-moons-peel.md @@ -0,0 +1,95 @@ +--- +"astro": minor +--- + +Adds experimental support for the Actions API. Actions let you define type-safe endpoints you can query from client components with progressive enhancement built in. + + +Actions help you write type-safe backend functions you can call from anywhere. Enable server rendering [using the `output` property](https://docs.astro.build/en/basics/rendering-modes/#on-demand-rendered) and add the `actions` flag to the `experimental` object: + +```js +{ + output: 'hybrid', // or 'server' + experimental: { + actions: true, + }, +} +``` + +Declare all your actions in `src/actions/index.ts`. This file is the global actions handler. + +Define an action using the `defineAction()` utility from the `astro:actions` module. These accept the `handler` property to define your server-side request handler. If your action accepts arguments, apply the `input` property to validate parameters with Zod. + +This example defines two actions: `like` and `comment`. The `like` action accepts a JSON object with a `postId` string, while the `comment` action accepts [FormData](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest_API/Using_FormData_Objects) with `postId`, `author`, and `body` strings. Each `handler` updates your database and return a type-safe response. + +```ts +// src/actions/index.ts +import { defineAction, z } from "astro:actions"; + +export const server = { + like: defineAction({ + input: z.object({ postId: z.string() }), + handler: async ({ postId }, context) => { + // update likes in db + + return likes; + }, + }), + comment: defineAction({ + accept: 'form', + input: z.object({ + postId: z.string(), + author: z.string(), + body: z.string(), + }), + handler: async ({ postId }, context) => { + // insert comments in db + + return comment; + }, + }), +}; +``` + +Then, call an action from your client components using the `actions` object from `astro:actions`. You can pass a type-safe object when using JSON, or a [FormData](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest_API/Using_FormData_Objects) object when using `accept: 'form'` in your action definition: + +```tsx "actions" +// src/components/blog.tsx +import { actions } from "astro:actions"; +import { useState } from "preact/hooks"; + +export function Like({ postId }: { postId: string }) { + const [likes, setLikes] = useState(0); + return ( + + ); +} + +export function Comment({ postId }: { postId: string }) { + return ( +
{ + e.preventDefault(); + const formData = new FormData(e.target); + const result = await actions.blog.comment(formData); + // handle result + }} + > + + + + + +
+ ); +} +``` + +For a complete overview, and to give feedback on this experimental API, see the [Actions RFC](https://github.com/withastro/roadmap/blob/actions/proposals/0046-actions.md). diff --git a/packages/astro/client.d.ts b/packages/astro/client.d.ts index 9128e9dd0c..f81d652e38 100644 --- a/packages/astro/client.d.ts +++ b/packages/astro/client.d.ts @@ -1,5 +1,6 @@ /// /// +/// // eslint-disable-next-line @typescript-eslint/no-namespace declare namespace App { diff --git a/packages/astro/e2e/actions-blog.test.js b/packages/astro/e2e/actions-blog.test.js new file mode 100644 index 0000000000..b98f74143e --- /dev/null +++ b/packages/astro/e2e/actions-blog.test.js @@ -0,0 +1,58 @@ +import { expect } from '@playwright/test'; +import { testFactory } from './test-utils.js'; + +const test = testFactory({ root: './fixtures/actions-blog/' }); + +let devServer; + +test.beforeAll(async ({ astro }) => { + devServer = await astro.startDevServer(); +}); + +test.afterAll(async () => { + await devServer.stop(); +}); + +test.describe('Astro Actions - Blog', () => { + test('Like action', async ({ page, astro }) => { + await page.goto(astro.resolveUrl('/blog/first-post/')); + + const likeButton = page.getByLabel('Like'); + await expect(likeButton, 'like button starts with 10 likes').toContainText('10'); + await likeButton.click(); + await expect(likeButton, 'like button should increment likes').toContainText('11'); + }); + + test('Comment action - validation error', async ({ page, astro }) => { + await page.goto(astro.resolveUrl('/blog/first-post/')); + + const authorInput = page.locator('input[name="author"]'); + const bodyInput = page.locator('textarea[name="body"]'); + + await authorInput.fill('Ben'); + await bodyInput.fill('Too short'); + + const submitButton = page.getByLabel('Post comment'); + await submitButton.click(); + + await expect(page.locator('p[data-error="body"]')).toBeVisible(); + }); + + test('Comment action - success', async ({ page, astro }) => { + await page.goto(astro.resolveUrl('/blog/first-post/')); + + const authorInput = page.locator('input[name="author"]'); + const bodyInput = page.locator('textarea[name="body"]'); + + const body = 'This should be long enough.'; + await authorInput.fill('Ben'); + await bodyInput.fill(body); + + const submitButton = page.getByLabel('Post comment'); + await submitButton.click(); + + const comment = await page.getByTestId('comment'); + await expect(comment).toBeVisible(); + await expect(comment).toContainText(body); + }); +}); diff --git a/packages/astro/e2e/fixtures/actions-blog/astro.config.mjs b/packages/astro/e2e/fixtures/actions-blog/astro.config.mjs new file mode 100644 index 0000000000..acbed1768b --- /dev/null +++ b/packages/astro/e2e/fixtures/actions-blog/astro.config.mjs @@ -0,0 +1,17 @@ +import { defineConfig } from 'astro/config'; +import db from '@astrojs/db'; +import react from '@astrojs/react'; +import node from '@astrojs/node'; + +// https://astro.build/config +export default defineConfig({ + site: 'https://example.com', + integrations: [db(), react()], + output: 'hybrid', + adapter: node({ + mode: 'standalone', + }), + experimental: { + actions: true, + }, +}); diff --git a/packages/astro/e2e/fixtures/actions-blog/db/config.ts b/packages/astro/e2e/fixtures/actions-blog/db/config.ts new file mode 100644 index 0000000000..da005471e1 --- /dev/null +++ b/packages/astro/e2e/fixtures/actions-blog/db/config.ts @@ -0,0 +1,21 @@ +import { column, defineDb, defineTable } from "astro:db"; + +const Comment = defineTable({ + columns: { + postId: column.text(), + author: column.text(), + body: column.text(), + }, +}); + +const Likes = defineTable({ + columns: { + postId: column.text(), + likes: column.number(), + }, +}); + +// https://astro.build/db/config +export default defineDb({ + tables: { Comment, Likes }, +}); diff --git a/packages/astro/e2e/fixtures/actions-blog/db/seed.ts b/packages/astro/e2e/fixtures/actions-blog/db/seed.ts new file mode 100644 index 0000000000..11dc55f7fe --- /dev/null +++ b/packages/astro/e2e/fixtures/actions-blog/db/seed.ts @@ -0,0 +1,15 @@ +import { db, Likes, Comment } from "astro:db"; + +// https://astro.build/db/seed +export default async function seed() { + await db.insert(Likes).values({ + postId: "first-post.md", + likes: 10, + }); + + await db.insert(Comment).values({ + postId: "first-post.md", + author: "Alice", + body: "Great post!", + }); +} diff --git a/packages/astro/e2e/fixtures/actions-blog/package.json b/packages/astro/e2e/fixtures/actions-blog/package.json new file mode 100644 index 0000000000..0c69e9a98b --- /dev/null +++ b/packages/astro/e2e/fixtures/actions-blog/package.json @@ -0,0 +1,24 @@ +{ + "name": "@e2e/astro-actions-basics", + "type": "module", + "version": "0.0.1", + "scripts": { + "dev": "astro dev", + "start": "astro dev", + "build": "astro check && astro build", + "preview": "astro preview", + "astro": "astro" + }, + "dependencies": { + "@astrojs/check": "^0.5.10", + "@astrojs/db": "workspace:*", + "@astrojs/node": "workspace:*", + "@astrojs/react": "workspace:*", + "@types/react": "^18.2.79", + "@types/react-dom": "^18.2.25", + "astro": "workspace:*", + "react": "^18.3.0", + "react-dom": "^18.3.0", + "typescript": "^5.4.5" + } +} diff --git a/packages/astro/e2e/fixtures/actions-blog/src/actions/index.ts b/packages/astro/e2e/fixtures/actions-blog/src/actions/index.ts new file mode 100644 index 0000000000..4574caaaf5 --- /dev/null +++ b/packages/astro/e2e/fixtures/actions-blog/src/actions/index.ts @@ -0,0 +1,45 @@ +import { db, Comment, Likes, eq, sql } from 'astro:db'; +import { defineAction, z } from 'astro:actions'; + +export const server = { + blog: { + like: defineAction({ + input: z.object({ postId: z.string() }), + handler: async ({ postId }) => { + await new Promise((r) => setTimeout(r, 200)); + + const { likes } = await db + .update(Likes) + .set({ + likes: sql`likes + 1`, + }) + .where(eq(Likes.postId, postId)) + .returning() + .get(); + + return likes; + }, + }), + + comment: defineAction({ + accept: 'form', + input: z.object({ + postId: z.string(), + author: z.string(), + body: z.string().min(10), + }), + handler: async ({ postId, author, body }) => { + const comment = await db + .insert(Comment) + .values({ + postId, + body, + author, + }) + .returning() + .get(); + return comment; + }, + }), + }, +}; diff --git a/packages/astro/e2e/fixtures/actions-blog/src/components/BaseHead.astro b/packages/astro/e2e/fixtures/actions-blog/src/components/BaseHead.astro new file mode 100644 index 0000000000..344124012b --- /dev/null +++ b/packages/astro/e2e/fixtures/actions-blog/src/components/BaseHead.astro @@ -0,0 +1,47 @@ +--- +// Import the global.css file here so that it is included on +// all pages through the use of the component. +import '../styles/global.css'; + +interface Props { + title: string; + description: string; + image?: string; +} + +const canonicalURL = new URL(Astro.url.pathname, Astro.site); + +const { title, description, image = '/blog-placeholder-1.jpg' } = Astro.props; +--- + + + + + + + + + + + + + + + +{title} + + + + + + + + + + + + + + + + diff --git a/packages/astro/e2e/fixtures/actions-blog/src/components/Footer.astro b/packages/astro/e2e/fixtures/actions-blog/src/components/Footer.astro new file mode 100644 index 0000000000..96c2fce912 --- /dev/null +++ b/packages/astro/e2e/fixtures/actions-blog/src/components/Footer.astro @@ -0,0 +1,62 @@ +--- +const today = new Date(); +--- + + + diff --git a/packages/astro/e2e/fixtures/actions-blog/src/components/FormattedDate.astro b/packages/astro/e2e/fixtures/actions-blog/src/components/FormattedDate.astro new file mode 100644 index 0000000000..1bcce73a2b --- /dev/null +++ b/packages/astro/e2e/fixtures/actions-blog/src/components/FormattedDate.astro @@ -0,0 +1,17 @@ +--- +interface Props { + date: Date; +} + +const { date } = Astro.props; +--- + + diff --git a/packages/astro/e2e/fixtures/actions-blog/src/components/Header.astro b/packages/astro/e2e/fixtures/actions-blog/src/components/Header.astro new file mode 100644 index 0000000000..71b8cdc55c --- /dev/null +++ b/packages/astro/e2e/fixtures/actions-blog/src/components/Header.astro @@ -0,0 +1,83 @@ +--- +import HeaderLink from './HeaderLink.astro'; +import { SITE_TITLE } from '../consts'; +--- + +
+ +
+ diff --git a/packages/astro/e2e/fixtures/actions-blog/src/components/HeaderLink.astro b/packages/astro/e2e/fixtures/actions-blog/src/components/HeaderLink.astro new file mode 100644 index 0000000000..bb600fb65a --- /dev/null +++ b/packages/astro/e2e/fixtures/actions-blog/src/components/HeaderLink.astro @@ -0,0 +1,25 @@ +--- +import type { HTMLAttributes } from 'astro/types'; + +type Props = HTMLAttributes<'a'>; + +const { href, class: className, ...props } = Astro.props; + +const { pathname } = Astro.url; +const subpath = pathname.match(/[^\/]+/g); +const isActive = href === pathname || href === '/' + subpath?.[0]; +--- + + + + + diff --git a/packages/astro/e2e/fixtures/actions-blog/src/components/Like.tsx b/packages/astro/e2e/fixtures/actions-blog/src/components/Like.tsx new file mode 100644 index 0000000000..7d4e6a53d1 --- /dev/null +++ b/packages/astro/e2e/fixtures/actions-blog/src/components/Like.tsx @@ -0,0 +1,22 @@ +import { actions } from 'astro:actions'; +import { useState } from 'react'; + +export function Like({ postId, initial }: { postId: string; initial: number }) { + const [likes, setLikes] = useState(initial); + const [pending, setPending] = useState(false); + + return ( + + ); +} diff --git a/packages/astro/e2e/fixtures/actions-blog/src/components/PostComment.tsx b/packages/astro/e2e/fixtures/actions-blog/src/components/PostComment.tsx new file mode 100644 index 0000000000..1b0d10a063 --- /dev/null +++ b/packages/astro/e2e/fixtures/actions-blog/src/components/PostComment.tsx @@ -0,0 +1,66 @@ +import { getActionProps, actions, isInputError } from 'astro:actions'; +import { useState } from 'react'; + +export function PostComment({ + postId, + serverBodyError, +}: { + postId: string; + serverBodyError?: string; +}) { + const [comments, setComments] = useState<{ author: string; body: string }[]>([]); + const [bodyError, setBodyError] = useState(serverBodyError); + + return ( + <> +
{ + e.preventDefault(); + const form = e.target as HTMLFormElement; + const formData = new FormData(form); + const { data, error } = await actions.blog.comment.safe(formData); + if (isInputError(error)) { + return setBodyError(error.fields.body?.join(' ')); + } + if (data) { + setBodyError(undefined); + setComments((c) => [data, ...c]); + } + form.reset(); + }} + > + + + + + + {bodyError && ( +

+ {bodyError} +

+ )} + +
+ {comments.map((c) => ( +
+

{c.body}

+

{c.author}

+
+ ))} + + ); +} diff --git a/packages/astro/e2e/fixtures/actions-blog/src/consts.ts b/packages/astro/e2e/fixtures/actions-blog/src/consts.ts new file mode 100644 index 0000000000..0df8a61f4c --- /dev/null +++ b/packages/astro/e2e/fixtures/actions-blog/src/consts.ts @@ -0,0 +1,5 @@ +// Place any global data in this file. +// You can import this data from anywhere in your site by using the `import` keyword. + +export const SITE_TITLE = 'Astro Blog'; +export const SITE_DESCRIPTION = 'Welcome to my website!'; diff --git a/packages/astro/e2e/fixtures/actions-blog/src/content/blog/first-post.md b/packages/astro/e2e/fixtures/actions-blog/src/content/blog/first-post.md new file mode 100644 index 0000000000..ee51f15410 --- /dev/null +++ b/packages/astro/e2e/fixtures/actions-blog/src/content/blog/first-post.md @@ -0,0 +1,15 @@ +--- +title: 'First post' +description: 'Lorem ipsum dolor sit amet' +pubDate: 'Jul 08 2022' +--- + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae ultricies leo integer malesuada nunc vel risus commodo viverra. Adipiscing enim eu turpis egestas pretium. Euismod elementum nisi quis eleifend quam adipiscing. In hac habitasse platea dictumst vestibulum. Sagittis purus sit amet volutpat. Netus et malesuada fames ac turpis egestas. Eget magna fermentum iaculis eu non diam phasellus vestibulum lorem. Varius sit amet mattis vulputate enim. Habitasse platea dictumst quisque sagittis. Integer quis auctor elit sed vulputate mi. Dictumst quisque sagittis purus sit amet. + +Morbi tristique senectus et netus. Id semper risus in hendrerit gravida rutrum quisque non tellus. Habitasse platea dictumst quisque sagittis purus sit amet. Tellus molestie nunc non blandit massa. Cursus vitae congue mauris rhoncus. Accumsan tortor posuere ac ut. Fringilla urna porttitor rhoncus dolor. Elit ullamcorper dignissim cras tincidunt lobortis. In cursus turpis massa tincidunt dui ut ornare lectus. Integer feugiat scelerisque varius morbi enim nunc. Bibendum neque egestas congue quisque egestas diam. Cras ornare arcu dui vivamus arcu felis bibendum. Dignissim suspendisse in est ante in nibh mauris. Sed tempus urna et pharetra pharetra massa massa ultricies mi. + +Mollis nunc sed id semper risus in. Convallis a cras semper auctor neque. Diam sit amet nisl suscipit. Lacus viverra vitae congue eu consequat ac felis donec. Egestas integer eget aliquet nibh praesent tristique magna sit amet. Eget magna fermentum iaculis eu non diam. In vitae turpis massa sed elementum. Tristique et egestas quis ipsum suspendisse ultrices. Eget lorem dolor sed viverra ipsum. Vel turpis nunc eget lorem dolor sed viverra. Posuere ac ut consequat semper viverra nam. Laoreet suspendisse interdum consectetur libero id faucibus. Diam phasellus vestibulum lorem sed risus ultricies tristique. Rhoncus dolor purus non enim praesent elementum facilisis. Ultrices tincidunt arcu non sodales neque. Tempus egestas sed sed risus pretium quam vulputate. Viverra suspendisse potenti nullam ac tortor vitae purus faucibus ornare. Fringilla urna porttitor rhoncus dolor purus non. Amet dictum sit amet justo donec enim. + +Mattis ullamcorper velit sed ullamcorper morbi tincidunt. Tortor posuere ac ut consequat semper viverra. Tellus mauris a diam maecenas sed enim ut sem viverra. Venenatis urna cursus eget nunc scelerisque viverra mauris in. Arcu ac tortor dignissim convallis aenean et tortor at. Curabitur gravida arcu ac tortor dignissim convallis aenean et tortor. Egestas tellus rutrum tellus pellentesque eu. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Ut enim blandit volutpat maecenas volutpat blandit aliquam etiam. Id donec ultrices tincidunt arcu. Id cursus metus aliquam eleifend mi. + +Tempus quam pellentesque nec nam aliquam sem. Risus at ultrices mi tempus imperdiet. Id porta nibh venenatis cras sed felis eget velit. Ipsum a arcu cursus vitae. Facilisis magna etiam tempor orci eu lobortis elementum. Tincidunt dui ut ornare lectus sit. Quisque non tellus orci ac. Blandit libero volutpat sed cras. Nec tincidunt praesent semper feugiat nibh sed pulvinar proin gravida. Egestas integer eget aliquet nibh praesent tristique magna. diff --git a/packages/astro/e2e/fixtures/actions-blog/src/content/config.ts b/packages/astro/e2e/fixtures/actions-blog/src/content/config.ts new file mode 100644 index 0000000000..667a31cc73 --- /dev/null +++ b/packages/astro/e2e/fixtures/actions-blog/src/content/config.ts @@ -0,0 +1,16 @@ +import { defineCollection, z } from 'astro:content'; + +const blog = defineCollection({ + type: 'content', + // Type-check frontmatter using a schema + schema: z.object({ + title: z.string(), + description: z.string(), + // Transform string to Date object + pubDate: z.coerce.date(), + updatedDate: z.coerce.date().optional(), + heroImage: z.string().optional(), + }), +}); + +export const collections = { blog }; diff --git a/packages/astro/e2e/fixtures/actions-blog/src/layouts/BlogPost.astro b/packages/astro/e2e/fixtures/actions-blog/src/layouts/BlogPost.astro new file mode 100644 index 0000000000..e67b2b30f8 --- /dev/null +++ b/packages/astro/e2e/fixtures/actions-blog/src/layouts/BlogPost.astro @@ -0,0 +1,85 @@ +--- +import type { CollectionEntry } from 'astro:content'; +import BaseHead from '../components/BaseHead.astro'; +import Header from '../components/Header.astro'; +import Footer from '../components/Footer.astro'; +import FormattedDate from '../components/FormattedDate.astro'; + +type Props = CollectionEntry<'blog'>['data']; + +const { title, description, pubDate, updatedDate, heroImage } = Astro.props; +--- + + + + + + + + +
+
+
+
+ {heroImage && } +
+
+
+
+ + { + updatedDate && ( +
+ Last updated on +
+ ) + } +
+

{title}

+
+
+ +
+
+
+