diff --git a/.changeset/fuzzy-windows-cover.md b/.changeset/fuzzy-windows-cover.md new file mode 100644 index 000000000000..7f3b766fc0af --- /dev/null +++ b/.changeset/fuzzy-windows-cover.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes an issue where Astro couldn't correctly handle i18n fallback when using the i18n middleware diff --git a/.changeset/hip-kids-ring.md b/.changeset/hip-kids-ring.md new file mode 100644 index 000000000000..5dc22c5b8ea3 --- /dev/null +++ b/.changeset/hip-kids-ring.md @@ -0,0 +1,5 @@ +--- +'@astrojs/db': patch +--- + +Fixes the publishing of the package diff --git a/.changeset/neat-pumas-accept.md b/.changeset/neat-pumas-accept.md new file mode 100644 index 000000000000..8babf49e78ff --- /dev/null +++ b/.changeset/neat-pumas-accept.md @@ -0,0 +1,5 @@ +--- +"astro": patch +--- + +Adds type support for the `closedby` attribute for `` elements diff --git a/.changeset/selfish-paws-play.md b/.changeset/selfish-paws-play.md new file mode 100644 index 000000000000..6f0e3049b297 --- /dev/null +++ b/.changeset/selfish-paws-play.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes a bug where Astro couldn't correctly parse `params` and `props` when receiving i18n fallback URLs diff --git a/.changeset/spicy-guests-protect.md b/.changeset/spicy-guests-protect.md new file mode 100644 index 000000000000..5f77d5ee0100 --- /dev/null +++ b/.changeset/spicy-guests-protect.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Trailing slash support for actions diff --git a/.changeset/tame-bags-remember.md b/.changeset/tame-bags-remember.md new file mode 100644 index 000000000000..3f4f61bce750 --- /dev/null +++ b/.changeset/tame-bags-remember.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes a bug that caused errors in dev when editing sites with large numbers of MDX pages diff --git a/.changeset/twelve-donuts-hide.md b/.changeset/twelve-donuts-hide.md new file mode 100644 index 000000000000..504d1b5bea41 --- /dev/null +++ b/.changeset/twelve-donuts-hide.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +"Added `inert` to htmlBooleanAttributes" diff --git a/benchmark/index.js b/benchmark/index.js index 956b9c3afa0c..0c62036d98d3 100755 --- a/benchmark/index.js +++ b/benchmark/index.js @@ -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'; diff --git a/benchmark/packages/adapter/src/index.ts b/benchmark/packages/adapter/src/index.ts index f2345deb0885..0fc6d67f99c2 100644 --- a/benchmark/packages/adapter/src/index.ts +++ b/benchmark/packages/adapter/src/index.ts @@ -1,4 +1,4 @@ -import type { AstroAdapter, AstroIntegration } from 'astro'; +import type { AstroIntegration } from 'astro'; export default function createIntegration(): AstroIntegration { return { diff --git a/biome.jsonc b/biome.jsonc index bab1f1a05b61..a1000760a743 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -34,6 +34,7 @@ "correctness": { "noUnusedVariables": "info", "noUnusedFunctionParameters": "info", + "noUnusedImports": "warn", }, }, }, @@ -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", }, }, }, diff --git a/packages/astro/astro-jsx.d.ts b/packages/astro/astro-jsx.d.ts index ca54b991ed5a..39cb40f61bd4 100644 --- a/packages/astro/astro-jsx.d.ts +++ b/packages/astro/astro-jsx.d.ts @@ -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 { diff --git a/packages/astro/src/actions/plugins.ts b/packages/astro/src/actions/plugins.ts index 77cbddca15af..f5bd074dfcca 100644 --- a/packages/astro/src/actions/plugins.ts +++ b/packages/astro/src/actions/plugins.ts @@ -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, @@ -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; }, diff --git a/packages/astro/src/actions/runtime/virtual/server.ts b/packages/astro/src/actions/runtime/virtual/server.ts index 47decf18e5d4..10624134b2cb 100644 --- a/packages/astro/src/actions/runtime/virtual/server.ts +++ b/packages/astro/src/actions/runtime/virtual/server.ts @@ -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 { @@ -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); diff --git a/packages/astro/src/actions/runtime/virtual/shared.ts b/packages/astro/src/actions/runtime/virtual/shared.ts index 4067ad321dfd..02cc07b52cd6 100644 --- a/packages/astro/src/actions/runtime/virtual/shared.ts +++ b/packages/astro/src/actions/runtime/virtual/shared.ts @@ -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, @@ -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', diff --git a/packages/astro/src/assets/utils/imageAttributes.ts b/packages/astro/src/assets/utils/imageAttributes.ts index 1b17e11b6320..aa67b528f286 100644 --- a/packages/astro/src/assets/utils/imageAttributes.ts +++ b/packages/astro/src/assets/utils/imageAttributes.ts @@ -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( diff --git a/packages/astro/src/content/mutable-data-store.ts b/packages/astro/src/content/mutable-data-store.ts index fdffec7cb8cc..7f125ed4b4d3 100644 --- a/packages/astro/src/content/mutable-data-store.ts +++ b/packages/astro/src/content/mutable-data-store.ts @@ -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. @@ -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 }); } @@ -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 }); } @@ -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 }); } @@ -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 }); } @@ -190,6 +192,42 @@ export default new Map([\n${lines.join(',\n')}]); } } + #writing = new Set(); + #pending = new Set(); + + 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: = Record>(key: string) => @@ -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) { diff --git a/packages/astro/src/core/app/index.ts b/packages/astro/src/core/app/index.ts index 648e33c47c68..a3d08bb642e9 100644 --- a/packages/astro/src/core/app/index.ts +++ b/packages/astro/src/core/app/index.ts @@ -5,7 +5,6 @@ import { REROUTABLE_STATUS_CODES, REROUTE_DIRECTIVE_HEADER, clientAddressSymbol, - clientLocalsSymbol, responseSentSymbol, } from '../constants.js'; import { getSetCookiesFromResponse } from '../cookies/index.js'; diff --git a/packages/astro/src/core/build/plugins/plugin-manifest.ts b/packages/astro/src/core/build/plugins/plugin-manifest.ts index a39160c57bde..a3bd6239a4ed 100644 --- a/packages/astro/src/core/build/plugins/plugin-manifest.ts +++ b/packages/astro/src/core/build/plugins/plugin-manifest.ts @@ -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 { diff --git a/packages/astro/src/core/render/params-and-props.ts b/packages/astro/src/core/render/params-and-props.ts index c1fe318cebd3..f8799115b0bc 100644 --- a/packages/astro/src/core/render/params-and-props.ts +++ b/packages/astro/src/core/render/params-and-props.ts @@ -47,7 +47,8 @@ export async function getProps(opts: GetParamsAndPropsOptions): Promise { 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)); @@ -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) => { diff --git a/packages/astro/src/i18n/index.ts b/packages/astro/src/i18n/index.ts index 455e2e141487..e1e3750ea40b 100644 --- a/packages/astro/src/i18n/index.ts +++ b/packages/astro/src/i18n/index.ts @@ -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: diff --git a/packages/astro/src/i18n/middleware.ts b/packages/astro/src/i18n/middleware.ts index eefb8a9dd56e..c2805b1a3266 100644 --- a/packages/astro/src/i18n/middleware.ts +++ b/packages/astro/src/i18n/middleware.ts @@ -83,7 +83,6 @@ export function createI18nMiddleware( } const { currentLocale } = context; - switch (i18n.strategy) { // NOTE: theoretically, we should never hit this code path case 'manual': { diff --git a/packages/astro/src/runtime/server/render/util.ts b/packages/astro/src/runtime/server/render/util.ts index 9c771a0de02b..d693ad070f3a 100644 --- a/packages/astro/src/runtime/server/render/util.ts +++ b/packages/astro/src/runtime/server/render/util.ts @@ -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; diff --git a/packages/astro/src/virtual-modules/i18n.ts b/packages/astro/src/virtual-modules/i18n.ts index 8f85ae5f6146..d2e193fd7c04 100644 --- a/packages/astro/src/virtual-modules/i18n.ts +++ b/packages/astro/src/virtual-modules/i18n.ts @@ -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); }; diff --git a/packages/astro/src/vite-plugin-astro-server/route.ts b/packages/astro/src/vite-plugin-astro-server/route.ts index b4b659805408..eb353e501fe0 100644 --- a/packages/astro/src/vite-plugin-astro-server/route.ts +++ b/packages/astro/src/vite-plugin-astro-server/route.ts @@ -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'; diff --git a/packages/astro/templates/actions.mjs b/packages/astro/templates/actions.mjs index 82a287448a22..93aaa4d76262 100644 --- a/packages/astro/templates/actions.mjs +++ b/packages/astro/templates/actions.mjs @@ -1,4 +1,9 @@ -import { ActionError, deserializeActionResult, getActionQueryString } from 'astro:actions'; +import { + ActionError, + appendForwardSlash, + deserializeActionResult, + getActionQueryString, +} from 'astro:actions'; const ENCODED_DOT = '%2E'; @@ -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, diff --git a/packages/astro/templates/env.mjs b/packages/astro/templates/env.mjs index 9f36f1175b1c..6526033514de 100644 --- a/packages/astro/templates/env.mjs +++ b/packages/astro/templates/env.mjs @@ -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, diff --git a/packages/astro/test/actions.test.js b/packages/astro/test/actions.test.js index d8da4e72e308..2af8ebdd97fb 100644 --- a/packages/astro/test/actions.test.js +++ b/packages/astro/test/actions.test.js @@ -564,6 +564,30 @@ it('Base path should be used', async () => { await devServer.stop(); }); +it('Should support trailing slash', async () => { + const fixture = await loadFixture({ + root: './fixtures/actions/', + adapter: testAdapter(), + trailingSlash: 'always', + }); + const devServer = await fixture.startDevServer(); + const formData = new FormData(); + formData.append('channel', 'bholmesdev'); + formData.append('comment', 'Hello, World!'); + const res = await fixture.fetch('/_actions/comment/', { + method: 'POST', + body: formData, + }); + + assert.equal(res.ok, true); + assert.equal(res.headers.get('Content-Type'), 'application/json+devalue'); + + const data = devalue.parse(await res.text()); + assert.equal(data.channel, 'bholmesdev'); + assert.equal(data.comment, 'Hello, World!'); + await devServer.stop(); +}); + /** * Follow an expected redirect response. * diff --git a/packages/astro/test/fixtures/i18n-routing-fallback/src/pages/blog/[id].astro b/packages/astro/test/fixtures/i18n-routing-fallback/src/pages/blog/[id].astro index 97b41230d6e9..f277b93efe23 100644 --- a/packages/astro/test/fixtures/i18n-routing-fallback/src/pages/blog/[id].astro +++ b/packages/astro/test/fixtures/i18n-routing-fallback/src/pages/blog/[id].astro @@ -1,18 +1,28 @@ --- +// for SSR +const blogs = { + 1: { content: "Hello world" }, + 2: { content: "Eat Something" }, + 3: { content: "How are you?" }, +} +const id = Astro.params?.id; +const ssrContent = id && blogs[id]?.content; + +// for SSG export function getStaticPaths() { return [ {params: {id: '1'}, props: { content: "Hello world" }}, {params: {id: '2'}, props: { content: "Eat Something" }}, {params: {id: '3'}, props: { content: "How are you?" }}, - ]; + ] } -const { content } = Astro.props; +const { content } = Astro.props --- Astro -{content} +{content || ssrContent} diff --git a/packages/astro/test/fixtures/i18n-routing-fallback/src/pages/pt/blog/[id].astro b/packages/astro/test/fixtures/i18n-routing-fallback/src/pages/pt/blog/[id].astro index e37f83a30243..ed4415fc5beb 100644 --- a/packages/astro/test/fixtures/i18n-routing-fallback/src/pages/pt/blog/[id].astro +++ b/packages/astro/test/fixtures/i18n-routing-fallback/src/pages/pt/blog/[id].astro @@ -1,4 +1,12 @@ --- +const blogs = { + 1: { content: "Hola mundo" }, + 2: { content: "Eat Something" }, + 3: { content: "How are you?" }, +} +const id = Astro.params?.id; +const ssrContent = id && blogs[id]?.content; + export function getStaticPaths() { return [ {params: {id: '1'}, props: { content: "Hola mundo" }}, @@ -13,6 +21,6 @@ const { content } = Astro.props; Astro -{content} +{content || ssrContent} diff --git a/packages/astro/test/fixtures/i18n-routing-manual-with-default-middleware/astro.config.mjs b/packages/astro/test/fixtures/i18n-routing-manual-with-default-middleware/astro.config.mjs index 0638988f063b..8006c260f80c 100644 --- a/packages/astro/test/fixtures/i18n-routing-manual-with-default-middleware/astro.config.mjs +++ b/packages/astro/test/fixtures/i18n-routing-manual-with-default-middleware/astro.config.mjs @@ -9,6 +9,9 @@ export default defineConfig({ codes: ["es", "es-ar"] } ], - routing: "manual" + routing: "manual", + fallback: { + it: 'en' + } } }) diff --git a/packages/astro/test/fixtures/i18n-routing-manual-with-default-middleware/src/middleware.js b/packages/astro/test/fixtures/i18n-routing-manual-with-default-middleware/src/middleware.js index 60d179ec7111..afc3c6c607f9 100644 --- a/packages/astro/test/fixtures/i18n-routing-manual-with-default-middleware/src/middleware.js +++ b/packages/astro/test/fixtures/i18n-routing-manual-with-default-middleware/src/middleware.js @@ -18,5 +18,6 @@ export const onRequest = sequence( customLogic, middleware({ prefixDefaultLocale: true, + fallbackType: "rewrite" }) ); diff --git a/packages/astro/test/i18n-routing-manual-with-default-middleware.test.js b/packages/astro/test/i18n-routing-manual-with-default-middleware.test.js index af900a43b589..0b24c6aa7aa5 100644 --- a/packages/astro/test/i18n-routing-manual-with-default-middleware.test.js +++ b/packages/astro/test/i18n-routing-manual-with-default-middleware.test.js @@ -117,4 +117,13 @@ describe('SSR manual routing', () => { const $ = cheerio.load(html); assert.equal($('p').text(), '/en/blog/title/'); }); + + it('should use the fallback', async () => { + let request = new Request('http://example.com/it/start'); + let response = await app.render(request); + assert.equal(response.status, 200); + const html = await response.text(); + const $ = cheerio.load(html); + assert.equal($('p').text(), '/en/blog/title/'); + }); }); diff --git a/packages/astro/test/i18n-routing.test.js b/packages/astro/test/i18n-routing.test.js index 441823fc7a7d..27491069d37c 100644 --- a/packages/astro/test/i18n-routing.test.js +++ b/packages/astro/test/i18n-routing.test.js @@ -2000,12 +2000,14 @@ describe('Fallback rewrite dev server', () => { root: './fixtures/i18n-routing-fallback/', i18n: { defaultLocale: 'en', - locales: ['en', 'fr'], + locales: ['en', 'fr', 'es', 'it', 'pt'], routing: { prefixDefaultLocale: false, }, fallback: { fr: 'en', + it: 'en', + es: 'pt', }, fallbackType: 'rewrite', }, @@ -2021,6 +2023,27 @@ describe('Fallback rewrite dev server', () => { assert.match(html, /Hello/); // assert.fail() }); + + it('should render fallback locale paths with path parameters correctly (fr)', async () => { + let response = await fixture.fetch('/fr/blog/1'); + assert.equal(response.status, 200); + const text = await response.text(); + assert.match(text, /Hello world/); + }); + + it('should render fallback locale paths with path parameters correctly (es)', async () => { + let response = await fixture.fetch('/es/blog/1'); + assert.equal(response.status, 200); + const text = await response.text(); + assert.match(text, /Hola mundo/); + }); + + it('should render fallback locale paths with query parameters correctly (it)', async () => { + let response = await fixture.fetch('/it/blog/1'); + assert.equal(response.status, 200); + const text = await response.text(); + assert.match(text, /Hello world/); + }); }); describe('Fallback rewrite SSG', () => { @@ -2032,13 +2055,15 @@ describe('Fallback rewrite SSG', () => { root: './fixtures/i18n-routing-fallback/', i18n: { defaultLocale: 'en', - locales: ['en', 'fr'], + locales: ['en', 'fr', 'es', 'it', 'pt'], routing: { prefixDefaultLocale: false, fallbackType: 'rewrite', }, fallback: { fr: 'en', + it: 'en', + es: 'pt', }, }, }); @@ -2051,6 +2076,21 @@ describe('Fallback rewrite SSG', () => { assert.match(html, /Hello/); // assert.fail() }); + + it('should render fallback locale paths with path parameters correctly (fr)', async () => { + const html = await fixture.readFile('/fr/blog/1/index.html'); + assert.match(html, /Hello world/); + }); + + it('should render fallback locale paths with path parameters correctly (es)', async () => { + const html = await fixture.readFile('/es/blog/1/index.html'); + assert.match(html, /Hola mundo/); + }); + + it('should render fallback locale paths with query parameters correctly (it)', async () => { + const html = await fixture.readFile('/it/blog/1/index.html'); + assert.match(html, /Hello world/); + }); }); describe('Fallback rewrite SSR', () => { @@ -2066,13 +2106,15 @@ describe('Fallback rewrite SSR', () => { adapter: testAdapter(), i18n: { defaultLocale: 'en', - locales: ['en', 'fr'], + locales: ['en', 'fr', 'es', 'it', 'pt'], routing: { prefixDefaultLocale: false, fallbackType: 'rewrite', }, fallback: { fr: 'en', + it: 'en', + es: 'pt', }, }, }); @@ -2087,4 +2129,28 @@ describe('Fallback rewrite SSR', () => { const html = await response.text(); assert.match(html, /Hello/); }); + + it('should render fallback locale paths with path parameters correctly (fr)', async () => { + let request = new Request('http://example.com/new-site/fr/blog/1'); + let response = await app.render(request); + assert.equal(response.status, 200); + const text = await response.text(); + assert.match(text, /Hello world/); + }); + + it('should render fallback locale paths with path parameters correctly (es)', async () => { + let request = new Request('http://example.com/new-site/es/blog/1'); + let response = await app.render(request); + assert.equal(response.status, 200); + const text = await response.text(); + assert.match(text, /Hola mundo/); + }); + + it('should render fallback locale paths with query parameters correctly (it)', async () => { + let request = new Request('http://example.com/new-site/it/blog/1'); + let response = await app.render(request); + assert.equal(response.status, 200); + const text = await response.text(); + assert.match(text, /Hello world/); + }); }); diff --git a/packages/astro/test/units/routing/route-matching.test.js b/packages/astro/test/units/routing/route-matching.test.js index b524a7ea103e..72900d3156f8 100644 --- a/packages/astro/test/units/routing/route-matching.test.js +++ b/packages/astro/test/units/routing/route-matching.test.js @@ -1,6 +1,5 @@ import * as assert from 'node:assert/strict'; import { after, before, describe, it } from 'node:test'; -import { fileURLToPath } from 'node:url'; import * as cheerio from 'cheerio'; import { createContainer } from '../../../dist/core/dev/container.js'; import { createViteLoader } from '../../../dist/core/module-loader/vite.js'; diff --git a/packages/db/package.json b/packages/db/package.json index 7bac47649a82..16b9b17216f7 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "@astrojs/db", - "version": "0.14.1", + "version": "0.14.3", "description": "Add libSQL and Astro Studio support to your Astro site", "license": "MIT", "repository": { diff --git a/packages/db/src/core/integration/index.ts b/packages/db/src/core/integration/index.ts index 51d3f80116d9..36dc6b0f8bce 100644 --- a/packages/db/src/core/integration/index.ts +++ b/packages/db/src/core/integration/index.ts @@ -26,7 +26,6 @@ import { type LateSeedFiles, type LateTables, type SeedHandler, - resolved, vitePluginDb, } from './vite-plugin-db.js'; diff --git a/packages/db/src/runtime/virtual.ts b/packages/db/src/runtime/virtual.ts index 3da0c3549165..5f17823a8547 100644 --- a/packages/db/src/runtime/virtual.ts +++ b/packages/db/src/runtime/virtual.ts @@ -1,4 +1,3 @@ -import { LibsqlError } from '@libsql/client'; import { sql as _sql } from 'drizzle-orm'; import type { BooleanColumnInput, diff --git a/packages/integrations/markdoc/src/html/transform/html-token-transform.ts b/packages/integrations/markdoc/src/html/transform/html-token-transform.ts index 2c6a5d1e2f09..b80595f97b35 100644 --- a/packages/integrations/markdoc/src/html/transform/html-token-transform.ts +++ b/packages/integrations/markdoc/src/html/transform/html-token-transform.ts @@ -1,6 +1,7 @@ import type { Tokenizer } from '@markdoc/markdoc'; import { Parser } from 'htmlparser2'; // @ts-expect-error This type isn't exported +// biome-ignore lint/correctness/noUnusedImports: not correctly detected because type isn't exported import type * as Token from 'markdown-it/lib/token'; export function htmlTokenTransform(tokenizer: Tokenizer, tokens: Token[]): Token[] { diff --git a/packages/integrations/tailwind/test/fixtures/basic/tailwind.config.js b/packages/integrations/tailwind/test/fixtures/basic/tailwind.config.js index f1090968169a..278abf14d6a1 100644 --- a/packages/integrations/tailwind/test/fixtures/basic/tailwind.config.js +++ b/packages/integrations/tailwind/test/fixtures/basic/tailwind.config.js @@ -1,6 +1,7 @@ import path from 'node:path'; +import {fileURLToPath} from "node:url"; /** @type {import('tailwindcss').Config} */ export default { - content: [path.join(__dirname, 'src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}')], + content: [path.join(path.dirname(fileURLToPath(import.meta.url)), 'src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}')], }; diff --git a/scripts/cmd/build.js b/scripts/cmd/build.js index cf0ee5ad2b89..d5a9336d85df 100644 --- a/scripts/cmd/build.js +++ b/scripts/cmd/build.js @@ -1,6 +1,5 @@ import fs from 'node:fs/promises'; import esbuild from 'esbuild'; -import { copy } from 'esbuild-plugin-copy'; import glob from 'fast-glob'; import { dim, green, red, yellow } from 'kleur/colors'; import prebuild from './prebuild.js';