Skip to content

Commit 14dffcc

Browse files
authored
fix(actions): support trailing slash (#12657)
* fix(actions): support trailing slash * refactoring
1 parent ccc5ad1 commit 14dffcc

File tree

6 files changed

+67
-3
lines changed

6 files changed

+67
-3
lines changed

.changeset/spicy-guests-protect.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'astro': patch
3+
---
4+
5+
Trailing slash support for actions

packages/astro/src/actions/plugins.ts

+7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type fsMod from 'node:fs';
22
import type { Plugin as VitePlugin } from 'vite';
33
import type { AstroSettings } from '../types/astro.js';
4+
import { shouldAppendForwardSlash } from '../core/build/util.js';
45
import {
56
NOOP_ACTIONS,
67
RESOLVED_VIRTUAL_INTERNAL_MODULE_ID,
@@ -84,6 +85,12 @@ export function vitePluginActions({
8485
code += `\nexport * from 'astro/actions/runtime/virtual/server.js';`;
8586
} else {
8687
code += `\nexport * from 'astro/actions/runtime/virtual/client.js';`;
88+
code = code.replace(
89+
"'/** @TRAILING_SLASH@ **/'",
90+
JSON.stringify(
91+
shouldAppendForwardSlash(settings.config.trailingSlash, settings.config.build.format),
92+
),
93+
);
8794
}
8895
return code;
8996
},

packages/astro/src/actions/runtime/virtual/server.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { z } from 'zod';
22
import { ActionCalledFromServerError } from '../../../core/errors/errors-data.js';
33
import { AstroError } from '../../../core/errors/errors.js';
4+
import type { Pipeline } from '../../../core/base-pipeline.js';
5+
import { apiContextRoutesSymbol } from '../../../core/render-context.js';
6+
import { shouldAppendForwardSlash } from '../../../core/build/util.js';
7+
import { removeTrailingForwardSlash } from '../../../core/path.js';
48
import type { APIContext } from '../../../types/public/index.js';
59
import { ACTION_RPC_ROUTE_PATTERN } from '../../consts.js';
610
import {
@@ -279,7 +283,15 @@ export function getActionContext(context: APIContext): ActionMiddlewareContext {
279283
calledFrom: callerInfo.from,
280284
name: callerInfo.name,
281285
handler: async () => {
282-
const baseAction = await getAction(callerInfo.name);
286+
const pipeline: Pipeline = Reflect.get(context, apiContextRoutesSymbol);
287+
const callerInfoName = shouldAppendForwardSlash(
288+
pipeline.manifest.trailingSlash,
289+
pipeline.manifest.buildFormat,
290+
)
291+
? removeTrailingForwardSlash(callerInfo.name)
292+
: callerInfo.name;
293+
294+
const baseAction = await getAction(callerInfoName);
283295
let input;
284296
try {
285297
input = await parseRequestBody(context.request);

packages/astro/src/actions/runtime/virtual/shared.ts

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { parse as devalueParse, stringify as devalueStringify } from 'devalue';
22
import type { z } from 'zod';
33
import { REDIRECT_STATUS_CODES } from '../../../core/constants.js';
44
import { ActionsReturnedInvalidDataError } from '../../../core/errors/errors-data.js';
5+
import { appendForwardSlash as _appendForwardSlash } from '../../../core/path.js';
56
import { AstroError } from '../../../core/errors/errors.js';
67
import { ACTION_QUERY_PARAMS as _ACTION_QUERY_PARAMS } from '../../consts.js';
78
import type {
@@ -13,6 +14,8 @@ import type {
1314
export type ActionAPIContext = _ActionAPIContext;
1415
export const ACTION_QUERY_PARAMS = _ACTION_QUERY_PARAMS;
1516

17+
export const appendForwardSlash = _appendForwardSlash;
18+
1619
export const ACTION_ERROR_CODES = [
1720
'BAD_REQUEST',
1821
'UNAUTHORIZED',

packages/astro/templates/actions.mjs

+15-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { ActionError, deserializeActionResult, getActionQueryString } from 'astro:actions';
1+
import {
2+
ActionError,
3+
deserializeActionResult,
4+
getActionQueryString,
5+
appendForwardSlash,
6+
} from 'astro:actions';
27

38
const ENCODED_DOT = '%2E';
49

@@ -83,7 +88,15 @@ async function handleAction(param, path, context) {
8388
headers.set('Content-Length', '0');
8489
}
8590
}
86-
const rawResult = await fetch(`${import.meta.env.BASE_URL.replace(/\/$/, '')}/_actions/${path}`, {
91+
92+
const shouldAppendTrailingSlash = '/** @TRAILING_SLASH@ **/';
93+
let actionPath = import.meta.env.BASE_URL.replace(/\/$/, '') + '/_actions/' + path;
94+
95+
if (shouldAppendTrailingSlash) {
96+
actionPath = appendForwardSlash(actionPath);
97+
}
98+
99+
const rawResult = await fetch(actionPath, {
87100
method: 'POST',
88101
body,
89102
headers,

packages/astro/test/actions.test.js

+24
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,30 @@ it('Base path should be used', async () => {
564564
await devServer.stop();
565565
});
566566

567+
it('Should support trailing slash', async () => {
568+
const fixture = await loadFixture({
569+
root: './fixtures/actions/',
570+
adapter: testAdapter(),
571+
trailingSlash: "always"
572+
});
573+
const devServer = await fixture.startDevServer();
574+
const formData = new FormData();
575+
formData.append('channel', 'bholmesdev');
576+
formData.append('comment', 'Hello, World!');
577+
const res = await fixture.fetch('/_actions/comment/', {
578+
method: 'POST',
579+
body: formData,
580+
});
581+
582+
assert.equal(res.ok, true);
583+
assert.equal(res.headers.get('Content-Type'), 'application/json+devalue');
584+
585+
const data = devalue.parse(await res.text());
586+
assert.equal(data.channel, 'bholmesdev');
587+
assert.equal(data.comment, 'Hello, World!');
588+
await devServer.stop();
589+
});
590+
567591
/**
568592
* Follow an expected redirect response.
569593
*

0 commit comments

Comments
 (0)