Skip to content

Commit

Permalink
Merge branch 'main' into feat/route-manifest-refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
ematipico authored Dec 13, 2024
2 parents 76d6928 + 901c21f commit e5c769a
Show file tree
Hide file tree
Showing 40 changed files with 273 additions and 36 deletions.
5 changes: 5 additions & 0 deletions .changeset/fuzzy-windows-cover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Fixes an issue where Astro couldn't correctly handle i18n fallback when using the i18n middleware
5 changes: 5 additions & 0 deletions .changeset/hip-kids-ring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/db': patch
---

Fixes the publishing of the package
5 changes: 5 additions & 0 deletions .changeset/neat-pumas-accept.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"astro": patch
---

Adds type support for the `closedby` attribute for `<dialog>` elements
5 changes: 5 additions & 0 deletions .changeset/selfish-paws-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Fixes a bug where Astro couldn't correctly parse `params` and `props` when receiving i18n fallback URLs
5 changes: 5 additions & 0 deletions .changeset/spicy-guests-protect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Trailing slash support for actions
5 changes: 5 additions & 0 deletions .changeset/tame-bags-remember.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Fixes a bug that caused errors in dev when editing sites with large numbers of MDX pages
5 changes: 5 additions & 0 deletions .changeset/twelve-donuts-hide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

"Added `inert` to htmlBooleanAttributes"
2 changes: 1 addition & 1 deletion benchmark/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { pathToFileURL } from 'node:url';
import mri from 'mri';
import { makeProject } from './bench/_util.js';

Expand Down
2 changes: 1 addition & 1 deletion benchmark/packages/adapter/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { AstroAdapter, AstroIntegration } from 'astro';
import type { AstroIntegration } from 'astro';

export default function createIntegration(): AstroIntegration {
return {
Expand Down
4 changes: 3 additions & 1 deletion biome.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"correctness": {
"noUnusedVariables": "info",
"noUnusedFunctionParameters": "info",
"noUnusedImports": "warn",
},
},
},
Expand Down Expand Up @@ -87,11 +88,12 @@
},
},
{
"include": ["*.astro", "client.d.ts"],
"include": ["*.astro", "client.d.ts", "jsx-runtime.d.ts"],
"linter": {
"rules": {
"correctness": {
"noUnusedVariables": "off",
"noUnusedImports": "off",
},
},
},
Expand Down
1 change: 1 addition & 0 deletions packages/astro/astro-jsx.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,7 @@ declare namespace astroHTML.JSX {

interface DialogHTMLAttributes extends HTMLAttributes {
open?: boolean | string | undefined | null;
closedby?: 'none' | 'closerequest' | 'any' | undefined | null;
}

interface EmbedHTMLAttributes extends HTMLAttributes {
Expand Down
7 changes: 7 additions & 0 deletions packages/astro/src/actions/plugins.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type fsMod from 'node:fs';
import type { Plugin as VitePlugin } from 'vite';
import { shouldAppendForwardSlash } from '../core/build/util.js';
import type { AstroSettings } from '../types/astro.js';
import {
NOOP_ACTIONS,
Expand Down Expand Up @@ -84,6 +85,12 @@ export function vitePluginActions({
code += `\nexport * from 'astro/actions/runtime/virtual/server.js';`;
} else {
code += `\nexport * from 'astro/actions/runtime/virtual/client.js';`;
code = code.replace(
"'/** @TRAILING_SLASH@ **/'",
JSON.stringify(
shouldAppendForwardSlash(settings.config.trailingSlash, settings.config.build.format),
),
);
}
return code;
},
Expand Down
14 changes: 13 additions & 1 deletion packages/astro/src/actions/runtime/virtual/server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { z } from 'zod';
import type { Pipeline } from '../../../core/base-pipeline.js';
import { shouldAppendForwardSlash } from '../../../core/build/util.js';
import { ActionCalledFromServerError } from '../../../core/errors/errors-data.js';
import { AstroError } from '../../../core/errors/errors.js';
import { removeTrailingForwardSlash } from '../../../core/path.js';
import { apiContextRoutesSymbol } from '../../../core/render-context.js';
import type { APIContext } from '../../../types/public/index.js';
import { ACTION_RPC_ROUTE_PATTERN } from '../../consts.js';
import {
Expand Down Expand Up @@ -279,7 +283,15 @@ export function getActionContext(context: APIContext): ActionMiddlewareContext {
calledFrom: callerInfo.from,
name: callerInfo.name,
handler: async () => {
const baseAction = await getAction(callerInfo.name);
const pipeline: Pipeline = Reflect.get(context, apiContextRoutesSymbol);
const callerInfoName = shouldAppendForwardSlash(
pipeline.manifest.trailingSlash,
pipeline.manifest.buildFormat,
)
? removeTrailingForwardSlash(callerInfo.name)
: callerInfo.name;

const baseAction = await getAction(callerInfoName);
let input;
try {
input = await parseRequestBody(context.request);
Expand Down
3 changes: 3 additions & 0 deletions packages/astro/src/actions/runtime/virtual/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { z } from 'zod';
import { REDIRECT_STATUS_CODES } from '../../../core/constants.js';
import { ActionsReturnedInvalidDataError } from '../../../core/errors/errors-data.js';
import { AstroError } from '../../../core/errors/errors.js';
import { appendForwardSlash as _appendForwardSlash } from '../../../core/path.js';
import { ACTION_QUERY_PARAMS as _ACTION_QUERY_PARAMS } from '../../consts.js';
import type {
ErrorInferenceObject,
Expand All @@ -13,6 +14,8 @@ import type {
export type ActionAPIContext = _ActionAPIContext;
export const ACTION_QUERY_PARAMS = _ACTION_QUERY_PARAMS;

export const appendForwardSlash = _appendForwardSlash;

export const ACTION_ERROR_CODES = [
'BAD_REQUEST',
'UNAUTHORIZED',
Expand Down
1 change: 0 additions & 1 deletion packages/astro/src/assets/utils/imageAttributes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { toStyleString } from '../../runtime/server/render/util.js';
import type { AstroConfig } from '../../types/public/config.js';
import type { GetImageResult, ImageLayout, LocalImageProps, RemoteImageProps } from '../types.js';

export function addCSSVarsToStyle(
Expand Down
48 changes: 43 additions & 5 deletions packages/astro/src/content/mutable-data-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { contentModuleToId } from './utils.js';

const SAVE_DEBOUNCE_MS = 500;

const MAX_DEPTH = 10;

/**
* Extends the DataStore with the ability to change entries and write them to disk.
* This is kept as a separate class to avoid needing node builtins at runtime, when read-only access is all that is needed.
Expand Down Expand Up @@ -86,7 +88,7 @@ export class MutableDataStore extends ImmutableDataStore {

if (this.#assetImports.size === 0) {
try {
await fs.writeFile(filePath, 'export default new Map();');
await this.#writeFileAtomic(filePath, 'export default new Map();');
} catch (err) {
throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err });
}
Expand All @@ -110,7 +112,7 @@ ${imports.join('\n')}
export default new Map([${exports.join(', ')}]);
`;
try {
await fs.writeFile(filePath, code);
await this.#writeFileAtomic(filePath, code);
} catch (err) {
throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err });
}
Expand All @@ -122,7 +124,7 @@ export default new Map([${exports.join(', ')}]);

if (this.#moduleImports.size === 0) {
try {
await fs.writeFile(filePath, 'export default new Map();');
await this.#writeFileAtomic(filePath, 'export default new Map();');
} catch (err) {
throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err });
}
Expand All @@ -143,7 +145,7 @@ export default new Map([${exports.join(', ')}]);
export default new Map([\n${lines.join(',\n')}]);
`;
try {
await fs.writeFile(filePath, code);
await this.#writeFileAtomic(filePath, code);
} catch (err) {
throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err });
}
Expand Down Expand Up @@ -190,6 +192,42 @@ export default new Map([\n${lines.join(',\n')}]);
}
}

#writing = new Set<string>();
#pending = new Set<string>();

async #writeFileAtomic(filePath: PathLike, data: string, depth = 0) {
if (depth > MAX_DEPTH) {
// If we hit the max depth, we skip a write to prevent the stack from growing too large
// In theory this means we may miss the latest data, but in practice this will only happen when the file is being written to very frequently
// so it will be saved on the next write. This is unlikely to ever happen in practice, as the writes are debounced. It requires lots of writes to very large files.
return;
}
const fileKey = filePath.toString();
// If we are already writing this file, instead of writing now, flag it as pending and write it when we're done.
if (this.#writing.has(fileKey)) {
this.#pending.add(fileKey);
return;
}
// Prevent concurrent writes to this file by flagging it as being written
this.#writing.add(fileKey);

const tempFile = filePath instanceof URL ? new URL(`${filePath.href}.tmp`) : `${filePath}.tmp`;
try {
// Write it to a temporary file first and then move it to prevent partial reads.
await fs.writeFile(tempFile, data);
await fs.rename(tempFile, filePath);
} finally {
// We're done writing. Unflag the file and check if there are any pending writes for this file.
this.#writing.delete(fileKey);
// If there are pending writes, we need to write again to ensure we flush the latest data.
if (this.#pending.has(fileKey)) {
this.#pending.delete(fileKey);
// Call ourself recursively to write the file again
await this.#writeFileAtomic(filePath, data, depth + 1);
}
}
}

scopedStore(collectionName: string): DataStore {
return {
get: <TData extends Record<string, unknown> = Record<string, unknown>>(key: string) =>
Expand Down Expand Up @@ -298,7 +336,7 @@ export default new Map([\n${lines.join(',\n')}]);
return;
}
try {
await fs.writeFile(filePath, this.toString());
await this.#writeFileAtomic(filePath, this.toString());
this.#file = filePath;
this.#dirty = false;
} catch (err) {
Expand Down
1 change: 0 additions & 1 deletion packages/astro/src/core/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
REROUTABLE_STATUS_CODES,
REROUTE_DIRECTIVE_HEADER,
clientAddressSymbol,
clientLocalsSymbol,
responseSentSymbol,
} from '../constants.js';
import { getSetCookiesFromResponse } from '../cookies/index.js';
Expand Down
1 change: 0 additions & 1 deletion packages/astro/src/core/build/plugins/plugin-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import type { Plugin as VitePlugin } from 'vite';
import { getAssetsPrefix } from '../../../assets/utils/getAssetsPrefix.js';
import { normalizeTheLocale } from '../../../i18n/index.js';
import { toFallbackType, toRoutingStrategy } from '../../../i18n/utils.js';
import { unwrapSupportKind } from '../../../integrations/features-validation.js';
import { runHookBuildSsr } from '../../../integrations/hooks.js';
import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../../vite-plugin-scripts/index.js';
import type {
Expand Down
9 changes: 7 additions & 2 deletions packages/astro/src/core/render/params-and-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ export async function getProps(opts: GetParamsAndPropsOptions): Promise<Props> {
base,
});

// The pathname used here comes from the server, which already encored.
if (!staticPaths.length) return {};
// The pathname used here comes from the server, which already encoded.
// Since we decided to not mess up with encoding anymore, we need to decode them back so the parameters can match
// the ones expected from the users
const params = getParams(route, decodeURI(pathname));
Expand Down Expand Up @@ -77,7 +78,11 @@ export function getParams(route: RouteData, pathname: string): Params {
if (!route.params.length) return {};
// The RegExp pattern expects a decoded string, but the pathname is encoded
// when the URL contains non-English characters.
const paramsMatch = route.pattern.exec(pathname);
const paramsMatch =
route.pattern.exec(pathname) ||
route.fallbackRoutes
.map((fallbackRoute) => fallbackRoute.pattern.exec(pathname))
.find((x) => x);
if (!paramsMatch) return {};
const params: Params = {};
route.params.forEach((key, i) => {
Expand Down
9 changes: 7 additions & 2 deletions packages/astro/src/i18n/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,9 +298,14 @@ export function redirectToDefaultLocale({
}

// NOTE: public function exported to the users via `astro:i18n` module
export function notFound({ base, locales }: MiddlewarePayload) {
export function notFound({ base, locales, fallback }: MiddlewarePayload) {
return function (context: APIContext, response?: Response): Response | undefined {
if (response?.headers.get(REROUTE_DIRECTIVE_HEADER) === 'no') return response;
if (
response?.headers.get(REROUTE_DIRECTIVE_HEADER) === 'no' &&
typeof fallback === 'undefined'
) {
return response;
}

const url = context.url;
// We return a 404 if:
Expand Down
1 change: 0 additions & 1 deletion packages/astro/src/i18n/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ export function createI18nMiddleware(
}

const { currentLocale } = context;

switch (i18n.strategy) {
// NOTE: theoretically, we should never hit this code path
case 'manual': {
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/runtime/server/render/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { HTMLString, markHTMLString } from '../escape.js';
export const voidElementNames =
/^(area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/i;
const htmlBooleanAttributes =
/^(?:allowfullscreen|async|autofocus|autoplay|checked|controls|default|defer|disabled|disablepictureinpicture|disableremoteplayback|formnovalidate|hidden|loop|nomodule|novalidate|open|playsinline|readonly|required|reversed|scoped|seamless|selected|itemscope)$/i;
/^(?:allowfullscreen|async|autofocus|autoplay|checked|controls|default|defer|disabled|disablepictureinpicture|disableremoteplayback|formnovalidate|hidden|inert|loop|nomodule|novalidate|open|playsinline|readonly|required|reversed|scoped|seamless|selected|itemscope)$/i;

const AMPERSAND_REGEX = /&/g;
const DOUBLE_QUOTE_REGEX = /"/g;
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/virtual-modules/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,10 +378,10 @@ if (i18n?.routing === 'manual') {
fallbackType = toFallbackType(customOptions);
const manifest: SSRManifest['i18n'] = {
...i18n,
fallback: undefined,
strategy,
domainLookupTable: {},
fallbackType,
fallback: i18n.fallback,
};
return I18nInternals.createMiddleware(manifest, base, trailingSlash, format);
};
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/vite-plugin-astro-server/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { req } from '../core/messages.js';
import { loadMiddleware } from '../core/middleware/loadMiddleware.js';
import { routeIsRedirect } from '../core/redirects/index.js';
import { RenderContext } from '../core/render-context.js';
import { type SSROptions, getProps } from '../core/render/index.js';
import { getProps } from '../core/render/index.js';
import { createRequest } from '../core/request.js';
import { redirectTemplate } from '../core/routing/3xx.js';
import { matchAllRoutes } from '../core/routing/index.js';
Expand Down
17 changes: 15 additions & 2 deletions packages/astro/templates/actions.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { ActionError, deserializeActionResult, getActionQueryString } from 'astro:actions';
import {
ActionError,
appendForwardSlash,
deserializeActionResult,
getActionQueryString,
} from 'astro:actions';

const ENCODED_DOT = '%2E';

Expand Down Expand Up @@ -83,7 +88,15 @@ async function handleAction(param, path, context) {
headers.set('Content-Length', '0');
}
}
const rawResult = await fetch(`${import.meta.env.BASE_URL.replace(/\/$/, '')}/_actions/${path}`, {

const shouldAppendTrailingSlash = '/** @TRAILING_SLASH@ **/';
let actionPath = import.meta.env.BASE_URL.replace(/\/$/, '') + '/_actions/' + path;

if (shouldAppendTrailingSlash) {
actionPath = appendForwardSlash(actionPath);
}

const rawResult = await fetch(actionPath, {
method: 'POST',
body,
headers,
Expand Down
1 change: 1 addition & 0 deletions packages/astro/templates/env.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @ts-check
import { schema } from 'virtual:astro:env/internal';
import {
// biome-ignore lint/correctness/noUnusedImports: `_getEnv` is used by the generated code
getEnv as _getEnv,
createInvalidVariablesError,
getEnvFieldType,
Expand Down
Loading

0 comments on commit e5c769a

Please sign in to comment.