mirror of
https://github.com/withastro/astro.git
synced 2024-12-30 22:03:56 -05:00
Merge branch 'main' into main
This commit is contained in:
commit
45f3753179
122 changed files with 928 additions and 311 deletions
5
.changeset/breezy-radios-grab.md
Normal file
5
.changeset/breezy-radios-grab.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@astrojs/db': patch
|
||||
---
|
||||
|
||||
Fixes `isDbError()` guard for `LibsqlError`
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
"astro": patch
|
||||
---
|
||||
|
||||
Updates a reference in an error message
|
5
.changeset/fuzzy-windows-cover.md
Normal file
5
.changeset/fuzzy-windows-cover.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Fixes an issue where Astro couldn't correctly handle i18n fallback when using the i18n middleware
|
5
.changeset/hip-kids-ring.md
Normal file
5
.changeset/hip-kids-ring.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@astrojs/db': patch
|
||||
---
|
||||
|
||||
Fixes the publishing of the package
|
5
.changeset/neat-pumas-accept.md
Normal file
5
.changeset/neat-pumas-accept.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"astro": patch
|
||||
---
|
||||
|
||||
Adds type support for the `closedby` attribute for `<dialog>` elements
|
5
.changeset/selfish-paws-play.md
Normal file
5
.changeset/selfish-paws-play.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Fixes a bug where Astro couldn't correctly parse `params` and `props` when receiving i18n fallback URLs
|
5
.changeset/spicy-guests-protect.md
Normal file
5
.changeset/spicy-guests-protect.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Trailing slash support for actions
|
5
.changeset/tame-bags-remember.md
Normal file
5
.changeset/tame-bags-remember.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Fixes a bug that caused errors in dev when editing sites with large numbers of MDX pages
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
'@astrojs/markdown-remark': patch
|
||||
---
|
||||
|
||||
Avoids parsing frontmatter that are not at the top of a file
|
5
.changeset/twelve-donuts-hide.md
Normal file
5
.changeset/twelve-donuts-hide.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
"Added `inert` to htmlBooleanAttributes"
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
"@astrojs/markdown-remark": patch
|
||||
---
|
||||
|
||||
Removes trailing new line in code blocks to prevent generating a trailing empty `<span />` tag
|
5
.changeset/twenty-keys-divide.md
Normal file
5
.changeset/twenty-keys-divide.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Fixes a case where failing content entries in `astro check` would not be surfaced
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
'@astrojs/markdown-remark': patch
|
||||
---
|
||||
|
||||
Fixes frontmatter parsing if file is encoded in UTF8 with BOM
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { AstroAdapter, AstroIntegration } from 'astro';
|
||||
import type { AstroIntegration } from 'astro';
|
||||
|
||||
export default function createIntegration(): AstroIntegration {
|
||||
return {
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -10,6 +10,6 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^5.0.3"
|
||||
"astro": "^5.0.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/mdx": "^4.0.1",
|
||||
"@astrojs/mdx": "^4.0.2",
|
||||
"@astrojs/rss": "^4.0.10",
|
||||
"@astrojs/sitemap": "^3.2.1",
|
||||
"astro": "^5.0.3"
|
||||
"astro": "^5.0.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
],
|
||||
"scripts": {},
|
||||
"devDependencies": {
|
||||
"astro": "^5.0.3"
|
||||
"astro": "^5.0.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"astro": "^4.0.0 || ^5.0.0"
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
"test": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/react": "^4.0.0",
|
||||
"astro": "^5.0.3",
|
||||
"@astrojs/react": "^4.1.0",
|
||||
"astro": "^5.0.5",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"vitest": "^2.1.6"
|
||||
|
|
|
@ -13,6 +13,6 @@
|
|||
"@astrojs/alpinejs": "^0.4.0",
|
||||
"@types/alpinejs": "^3.13.10",
|
||||
"alpinejs": "^3.14.3",
|
||||
"astro": "^5.0.3"
|
||||
"astro": "^5.0.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,13 +11,13 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@astrojs/preact": "^4.0.0",
|
||||
"@astrojs/react": "^4.0.0",
|
||||
"@astrojs/react": "^4.1.0",
|
||||
"@astrojs/solid-js": "^5.0.0",
|
||||
"@astrojs/svelte": "^7.0.1",
|
||||
"@astrojs/vue": "^5.0.1",
|
||||
"@astrojs/vue": "^5.0.2",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"astro": "^5.0.3",
|
||||
"astro": "^5.0.5",
|
||||
"preact": "^10.24.3",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
"dependencies": {
|
||||
"@astrojs/preact": "^4.0.0",
|
||||
"@preact/signals": "^1.3.0",
|
||||
"astro": "^5.0.3",
|
||||
"astro": "^5.0.5",
|
||||
"preact": "^10.24.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,10 +10,10 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/react": "^4.0.0",
|
||||
"@astrojs/react": "^4.1.0",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"astro": "^5.0.3",
|
||||
"astro": "^5.0.5",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@astrojs/solid-js": "^5.0.0",
|
||||
"astro": "^5.0.3",
|
||||
"astro": "^5.0.5",
|
||||
"solid-js": "^1.9.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@astrojs/svelte": "^7.0.1",
|
||||
"astro": "^5.0.3",
|
||||
"astro": "^5.0.5",
|
||||
"svelte": "^5.1.16"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/vue": "^5.0.1",
|
||||
"astro": "^5.0.3",
|
||||
"@astrojs/vue": "^5.0.2",
|
||||
"astro": "^5.0.5",
|
||||
"vue": "^3.5.12"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,6 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@astrojs/node": "^9.0.0",
|
||||
"astro": "^5.0.3"
|
||||
"astro": "^5.0.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
],
|
||||
"scripts": {},
|
||||
"devDependencies": {
|
||||
"astro": "^5.0.3"
|
||||
"astro": "^5.0.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"astro": "^4.0.0"
|
||||
|
|
|
@ -10,6 +10,6 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^5.0.3"
|
||||
"astro": "^5.0.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,6 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^5.0.3"
|
||||
"astro": "^5.0.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
"dependencies": {
|
||||
"@astrojs/node": "^9.0.0",
|
||||
"@astrojs/svelte": "^7.0.1",
|
||||
"astro": "^5.0.3",
|
||||
"astro": "^5.0.5",
|
||||
"svelte": "^5.1.16"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^5.0.3",
|
||||
"astro": "^5.0.5",
|
||||
"sass": "^1.80.6",
|
||||
"sharp": "^0.33.3"
|
||||
}
|
||||
|
|
|
@ -15,6 +15,6 @@
|
|||
"./app": "./dist/app.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"astro": "^5.0.3"
|
||||
"astro": "^5.0.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/markdoc": "^0.12.1",
|
||||
"astro": "^5.0.3"
|
||||
"@astrojs/markdoc": "^0.12.3",
|
||||
"astro": "^5.0.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/mdx": "^4.0.1",
|
||||
"@astrojs/mdx": "^4.0.2",
|
||||
"@astrojs/preact": "^4.0.0",
|
||||
"astro": "^5.0.3",
|
||||
"astro": "^5.0.5",
|
||||
"preact": "^10.24.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
"dependencies": {
|
||||
"@astrojs/preact": "^4.0.0",
|
||||
"@nanostores/preact": "^0.5.2",
|
||||
"astro": "^5.0.3",
|
||||
"astro": "^5.0.5",
|
||||
"nanostores": "^0.11.3",
|
||||
"preact": "^10.24.3"
|
||||
}
|
||||
|
|
|
@ -10,10 +10,10 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/mdx": "^4.0.1",
|
||||
"@astrojs/mdx": "^4.0.2",
|
||||
"@astrojs/tailwind": "^5.1.3",
|
||||
"@types/canvas-confetti": "^1.6.4",
|
||||
"astro": "^5.0.3",
|
||||
"astro": "^5.0.5",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"canvas-confetti": "^1.9.3",
|
||||
"postcss": "^8.4.49",
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^5.0.3",
|
||||
"astro": "^5.0.5",
|
||||
"vitest": "^2.1.6"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,34 @@
|
|||
# astro
|
||||
|
||||
## 5.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#12705](https://github.com/withastro/astro/pull/12705) [`0d1eab5`](https://github.com/withastro/astro/commit/0d1eab560d56c51c359bbd35e8bfb51e238611ee) Thanks [@ascorbic](https://github.com/ascorbic)! - Fixes a bug where MDX files with certain characters in the name would cause builds to fail
|
||||
|
||||
- [#12707](https://github.com/withastro/astro/pull/12707) [`2aaed2d`](https://github.com/withastro/astro/commit/2aaed2d2a96ab35461af24e8d12b20f1da33983f) Thanks [@ematipico](https://github.com/ematipico)! - Fixes a bug where the middleware was incorrectly imported during the build
|
||||
|
||||
- [#12697](https://github.com/withastro/astro/pull/12697) [`1c4a032`](https://github.com/withastro/astro/commit/1c4a032247747c830be94dbdd0c953511a6bfa53) Thanks [@ascorbic](https://github.com/ascorbic)! - Fix a bug that caused builds to fail if an image had a quote mark in its name
|
||||
|
||||
- [#12694](https://github.com/withastro/astro/pull/12694) [`495f46b`](https://github.com/withastro/astro/commit/495f46bca78665732e51c629d93a68fa392b88a4) Thanks [@ematipico](https://github.com/ematipico)! - Fixes a bug where the experimental feature `experimental.svg` was incorrectly used when generating ESM images
|
||||
|
||||
- [#12658](https://github.com/withastro/astro/pull/12658) [`3169593`](https://github.com/withastro/astro/commit/316959355c3d59723ecb3e0f417becf1f03ddd74) Thanks [@jurajkapsz](https://github.com/jurajkapsz)! - Fixes astro info copy to clipboard process not returning to prompt in certain cases.
|
||||
|
||||
- [#12712](https://github.com/withastro/astro/pull/12712) [`b01c74a`](https://github.com/withastro/astro/commit/b01c74aeccc4ec76b64fa75d163df58274b37970) Thanks [@ascorbic](https://github.com/ascorbic)! - Fixes a bug which misidentified pages as markdown if a query string ended in a markdown extension
|
||||
|
||||
## 5.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#12653](https://github.com/withastro/astro/pull/12653) [`e21c7e6`](https://github.com/withastro/astro/commit/e21c7e67fde1155cf593fd2b40010c5e2c2cd3f2) Thanks [@sarah11918](https://github.com/sarah11918)! - Updates a reference in an error message
|
||||
|
||||
- [#12585](https://github.com/withastro/astro/pull/12585) [`a9373c0`](https://github.com/withastro/astro/commit/a9373c0c9a3c2e1773fc11bb14e156698b0d9d38) Thanks [@florian-lefebvre](https://github.com/florian-lefebvre)! - Fixes a case where `process.env` would be frozen despite changes made to environment variables in development
|
||||
|
||||
- [#12695](https://github.com/withastro/astro/pull/12695) [`a203d5d`](https://github.com/withastro/astro/commit/a203d5dd582166674c45e807a5dc9113e26e24f0) Thanks [@ascorbic](https://github.com/ascorbic)! - Throws a more helpful error when images are missing
|
||||
|
||||
- Updated dependencies [[`f13417b`](https://github.com/withastro/astro/commit/f13417bfbf73130c224752379e2da33084f89554), [`87231b1`](https://github.com/withastro/astro/commit/87231b1168da66bb593f681206c42fa555dfcabc), [`a71e9b9`](https://github.com/withastro/astro/commit/a71e9b93b317edc0ded49d4d50f1b7841c8cd428)]:
|
||||
- @astrojs/markdown-remark@6.0.1
|
||||
|
||||
## 5.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
|
1
packages/astro/astro-jsx.d.ts
vendored
1
packages/astro/astro-jsx.d.ts
vendored
|
@ -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 {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "astro",
|
||||
"version": "5.0.3",
|
||||
"version": "5.0.5",
|
||||
"description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.",
|
||||
"type": "module",
|
||||
"author": "withastro",
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -15,6 +15,7 @@ export async function emitESMImage(
|
|||
_watchMode: boolean,
|
||||
// FIX: in Astro 6, this function should not be passed in dev mode at all.
|
||||
// Or rethink the API so that a function that throws isn't passed through.
|
||||
experimentalSvgEnabled: boolean,
|
||||
fileEmitter?: FileEmitter,
|
||||
): Promise<ImageMetadataWithContents | undefined> {
|
||||
if (!id) {
|
||||
|
@ -44,7 +45,8 @@ export async function emitESMImage(
|
|||
});
|
||||
|
||||
// Attach file data for SVGs
|
||||
if (fileMetadata.format === 'svg') {
|
||||
// TODO: this is a workaround to prevent a memory leak, and it must be fixed before we remove the experimental flag, see
|
||||
if (fileMetadata.format === 'svg' && experimentalSvgEnabled === true) {
|
||||
emittedImage.contents = fileData;
|
||||
}
|
||||
|
||||
|
|
|
@ -205,7 +205,12 @@ export default function assets({ settings }: { settings: AstroSettings }): vite.
|
|||
}
|
||||
|
||||
const emitFile = shouldEmitFile ? this.emitFile : undefined;
|
||||
const imageMetadata = await emitESMImage(id, this.meta.watchMode, emitFile);
|
||||
const imageMetadata = await emitESMImage(
|
||||
id,
|
||||
this.meta.watchMode,
|
||||
!!settings.config.experimental.svg,
|
||||
emitFile,
|
||||
);
|
||||
|
||||
if (!imageMetadata) {
|
||||
throw new AstroError({
|
||||
|
|
|
@ -31,11 +31,7 @@ export async function check(flags: Flags) {
|
|||
// NOTE: In the future, `@astrojs/check` can expose a `before lint` hook so that this works during `astro check --watch` too.
|
||||
// For now, we run this once as usually `astro check --watch` is ran alongside `astro dev` which also calls `astro sync`.
|
||||
const { default: sync } = await import('../../core/sync/index.js');
|
||||
try {
|
||||
await sync(flagsToAstroInlineConfig(flags));
|
||||
} catch (_) {
|
||||
return process.exit(1);
|
||||
}
|
||||
await sync(flagsToAstroInlineConfig(flags));
|
||||
}
|
||||
|
||||
const { check: checker, parseArgsAsCheckConfig } = checkPackage;
|
||||
|
|
|
@ -66,7 +66,7 @@ export async function copyToClipboard(text: string, force?: boolean) {
|
|||
// Unix: check if a supported command is installed
|
||||
|
||||
const unixCommands: Array<[string, Array<string>]> = [
|
||||
['xclip', ['-sel', 'clipboard', '-l', '1']],
|
||||
['xclip', ['-selection', 'clipboard', '-l', '1']],
|
||||
['wl-copy', []],
|
||||
];
|
||||
for (const [unixCommand, unixArgs] of unixCommands) {
|
||||
|
@ -101,7 +101,7 @@ export async function copyToClipboard(text: string, force?: boolean) {
|
|||
}
|
||||
|
||||
try {
|
||||
const result = spawnSync(command, args, { input: text });
|
||||
const result = spawnSync(command, args, { input: text, stdio: ['pipe', 'ignore', 'ignore'] });
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
|
|
|
@ -155,7 +155,6 @@ function createManifest(
|
|||
i18n: manifest?.i18n,
|
||||
checkOrigin: false,
|
||||
middleware: manifest?.middleware ?? middlewareInstance,
|
||||
envGetSecretEnabled: false,
|
||||
key: createKey(),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -206,6 +206,7 @@ export class ContentLayer {
|
|||
},
|
||||
collectionWithResolvedSchema,
|
||||
false,
|
||||
!!this.#settings.config.experimental.svg,
|
||||
);
|
||||
|
||||
return parsedData;
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
|
@ -102,7 +104,7 @@ export class MutableDataStore extends ImmutableDataStore {
|
|||
const exports: Array<string> = [];
|
||||
this.#assetImports.forEach((id) => {
|
||||
const symbol = importIdToSymbolName(id);
|
||||
imports.push(`import ${symbol} from '${id}';`);
|
||||
imports.push(`import ${symbol} from ${JSON.stringify(id)};`);
|
||||
exports.push(`[${JSON.stringify(id)}, ${symbol}]`);
|
||||
});
|
||||
const code = /* js */ `
|
||||
|
@ -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 });
|
||||
}
|
||||
|
@ -137,13 +139,13 @@ export default new Map([${exports.join(', ')}]);
|
|||
// We then export them all, mapped by the import id, so we can find them again in the build.
|
||||
const lines: Array<string> = [];
|
||||
for (const [fileName, specifier] of this.#moduleImports) {
|
||||
lines.push(`['${fileName}', () => import('${specifier}')]`);
|
||||
lines.push(`[${JSON.stringify(fileName)}, () => import(${JSON.stringify(specifier)})]`);
|
||||
}
|
||||
const code = `
|
||||
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<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) =>
|
||||
|
@ -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) {
|
||||
|
|
|
@ -7,6 +7,7 @@ export function createImage(
|
|||
pluginContext: PluginContext,
|
||||
shouldEmitFile: boolean,
|
||||
entryFilePath: string,
|
||||
experimentalSvgEnabled: boolean,
|
||||
) {
|
||||
return () => {
|
||||
return z.string().transform(async (imagePath, ctx) => {
|
||||
|
@ -14,6 +15,7 @@ export function createImage(
|
|||
const metadata = (await emitESMImage(
|
||||
resolvedFilePath,
|
||||
pluginContext.meta.watchMode,
|
||||
experimentalSvgEnabled,
|
||||
shouldEmitFile ? pluginContext.emitFile : undefined,
|
||||
)) as OmitBrand<ImageMetadata>;
|
||||
|
||||
|
|
|
@ -164,6 +164,7 @@ export async function getEntryDataAndImages<
|
|||
},
|
||||
collectionConfig: CollectionConfig,
|
||||
shouldEmitFile: boolean,
|
||||
experimentalSvgEnabled: boolean,
|
||||
pluginContext?: PluginContext,
|
||||
): Promise<{ data: TOutputData; imageImports: Array<string> }> {
|
||||
let data: TOutputData;
|
||||
|
@ -182,7 +183,12 @@ export async function getEntryDataAndImages<
|
|||
if (typeof schema === 'function') {
|
||||
if (pluginContext) {
|
||||
schema = schema({
|
||||
image: createImage(pluginContext, shouldEmitFile, entry._internal.filePath),
|
||||
image: createImage(
|
||||
pluginContext,
|
||||
shouldEmitFile,
|
||||
entry._internal.filePath,
|
||||
experimentalSvgEnabled,
|
||||
),
|
||||
});
|
||||
} else if (collectionConfig.type === CONTENT_LAYER_TYPE) {
|
||||
schema = schema({
|
||||
|
@ -257,12 +263,14 @@ export async function getEntryData(
|
|||
},
|
||||
collectionConfig: CollectionConfig,
|
||||
shouldEmitFile: boolean,
|
||||
experimentalSvgEnabled: boolean,
|
||||
pluginContext?: PluginContext,
|
||||
) {
|
||||
const { data } = await getEntryDataAndImages(
|
||||
entry,
|
||||
collectionConfig,
|
||||
shouldEmitFile,
|
||||
experimentalSvgEnabled,
|
||||
pluginContext,
|
||||
);
|
||||
return data;
|
||||
|
|
|
@ -39,7 +39,7 @@ export function astroContentAssetPropagationPlugin({
|
|||
? fileURLToPath(new URL(importerParam, settings.config.root))
|
||||
: importer;
|
||||
|
||||
const resolved = this.resolve(base, importerPath, { skipSelf: true, ...opts });
|
||||
const resolved = await this.resolve(base, importerPath, { skipSelf: true, ...opts });
|
||||
if (!resolved) {
|
||||
throw new AstroError({
|
||||
...AstroErrorData.ImageNotFound,
|
||||
|
|
|
@ -245,6 +245,7 @@ async function getContentEntryModule(
|
|||
{ id, collection, _internal, unvalidatedData },
|
||||
collectionConfig,
|
||||
params.shouldEmitFile,
|
||||
!!params.config.experimental.svg,
|
||||
pluginContext,
|
||||
)
|
||||
: unvalidatedData;
|
||||
|
@ -280,6 +281,7 @@ async function getDataEntryModule(
|
|||
{ id, collection, _internal, unvalidatedData },
|
||||
collectionConfig,
|
||||
params.shouldEmitFile,
|
||||
!!params.config.experimental.svg,
|
||||
pluginContext,
|
||||
)
|
||||
: unvalidatedData;
|
||||
|
|
|
@ -5,7 +5,6 @@ import {
|
|||
REROUTABLE_STATUS_CODES,
|
||||
REROUTE_DIRECTIVE_HEADER,
|
||||
clientAddressSymbol,
|
||||
clientLocalsSymbol,
|
||||
responseSentSymbol,
|
||||
} from '../constants.js';
|
||||
import { getSetCookiesFromResponse } from '../cookies/index.js';
|
||||
|
|
|
@ -69,7 +69,6 @@ export type SSRManifest = {
|
|||
i18n: SSRManifestI18n | undefined;
|
||||
middleware?: () => Promise<AstroMiddlewareInstance> | AstroMiddlewareInstance;
|
||||
checkOrigin: boolean;
|
||||
envGetSecretEnabled: boolean;
|
||||
};
|
||||
|
||||
export type SSRManifestI18n = {
|
||||
|
|
|
@ -559,6 +559,5 @@ function createBuildManifest(
|
|||
checkOrigin:
|
||||
(settings.config.security?.checkOrigin && settings.buildOutput === 'server') ?? false,
|
||||
key,
|
||||
envGetSecretEnabled: false,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -131,11 +131,11 @@ export class BuildPipeline extends Pipeline {
|
|||
const renderers = await import(renderersEntryUrl.toString());
|
||||
|
||||
const middleware = internals.middlewareEntryPoint
|
||||
? await import(internals.middlewareEntryPoint.toString()).then((mod) => {
|
||||
return function () {
|
||||
return { onRequest: mod.onRequest };
|
||||
};
|
||||
})
|
||||
? async function () {
|
||||
// @ts-expect-error: the compiler can't understand the previous check
|
||||
const mod = await import(internals.middlewareEntryPoint.toString());
|
||||
return { onRequest: mod.onRequest };
|
||||
}
|
||||
: manifest.middleware;
|
||||
|
||||
if (!renderers) {
|
||||
|
|
|
@ -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 {
|
||||
|
@ -274,8 +273,5 @@ function buildManifest(
|
|||
(settings.config.security?.checkOrigin && settings.buildOutput === 'server') ?? false,
|
||||
serverIslandNameMap: Array.from(settings.serverIslandNameMap),
|
||||
key: encodedKey,
|
||||
envGetSecretEnabled:
|
||||
(unwrapSupportKind(settings.adapter?.supportedAstroFeatures.envGetSecret) ??
|
||||
'unsupported') !== 'unsupported',
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import type { Plugin as VitePlugin } from 'vite';
|
||||
import type { AstroSettings } from '../../../types/astro.js';
|
||||
import type { AstroAdapter } from '../../../types/public/integrations.js';
|
||||
import { routeIsRedirect } from '../../redirects/index.js';
|
||||
import { VIRTUAL_ISLAND_MAP_ID } from '../../server-islands/vite-plugin-server-islands.js';
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
astroContentImportPlugin,
|
||||
astroContentVirtualModPlugin,
|
||||
} from '../content/index.js';
|
||||
import { createEnvLoader } from '../env/env-loader.js';
|
||||
import { astroEnv } from '../env/vite-plugin-env.js';
|
||||
import astroInternationalization from '../i18n/vite-plugin-i18n.js';
|
||||
import astroPrefetch from '../prefetch/vite-plugin-prefetch.js';
|
||||
|
@ -123,6 +124,7 @@ export async function createVite(
|
|||
});
|
||||
|
||||
const srcDirPattern = glob.convertPathToPattern(fileURLToPath(settings.config.srcDir));
|
||||
const envLoader = createEnvLoader();
|
||||
|
||||
// Start with the Vite configuration that Astro core needs
|
||||
const commonConfig: vite.InlineConfig = {
|
||||
|
@ -146,8 +148,8 @@ export async function createVite(
|
|||
// The server plugin is for dev only and having it run during the build causes
|
||||
// the build to run very slow as the filewatcher is triggered often.
|
||||
command === 'dev' && vitePluginAstroServer({ settings, logger, fs, manifest, ssrManifest }), // ssrManifest is only required in dev mode, where it gets created before a Vite instance is created, and get passed to this function
|
||||
envVitePlugin({ settings }),
|
||||
astroEnv({ settings, mode, sync }),
|
||||
envVitePlugin({ envLoader }),
|
||||
astroEnv({ settings, mode, sync, envLoader }),
|
||||
markdownVitePlugin({ settings, logger }),
|
||||
htmlVitePlugin(),
|
||||
astroPostprocessVitePlugin(),
|
||||
|
|
|
@ -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));
|
||||
|
@ -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) => {
|
||||
|
|
|
@ -5,7 +5,7 @@ import type { AstroSettings } from '../types/astro.js';
|
|||
import type { AstroConfig } from '../types/public/config.js';
|
||||
import type { RouteType } from '../types/public/internal.js';
|
||||
import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from './constants.js';
|
||||
import { removeTrailingForwardSlash, slash } from './path.js';
|
||||
import { removeQueryString, removeTrailingForwardSlash, slash } from './path.js';
|
||||
|
||||
/** Returns true if argument is an object of any prototype/class (but not null). */
|
||||
export function isObject(value: unknown): value is Record<string, any> {
|
||||
|
@ -18,9 +18,10 @@ export function isURL(value: unknown): value is URL {
|
|||
}
|
||||
/** Check if a file is a markdown file based on its extension */
|
||||
export function isMarkdownFile(fileId: string, option?: { suffix?: string }): boolean {
|
||||
const id = removeQueryString(fileId);
|
||||
const _suffix = option?.suffix ?? '';
|
||||
for (let markdownFileExtension of SUPPORTED_MARKDOWN_FILE_EXTENSIONS) {
|
||||
if (fileId.endsWith(`${markdownFileExtension}${_suffix}`)) return true;
|
||||
if (id.endsWith(`${markdownFileExtension}${_suffix}`)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
60
packages/astro/src/env/env-loader.ts
vendored
Normal file
60
packages/astro/src/env/env-loader.ts
vendored
Normal file
|
@ -0,0 +1,60 @@
|
|||
import { fileURLToPath } from 'node:url';
|
||||
import { loadEnv } from 'vite';
|
||||
import type { AstroConfig } from '../types/public/index.js';
|
||||
|
||||
// Match valid JS variable names (identifiers), which accepts most alphanumeric characters,
|
||||
// except that the first character cannot be a number.
|
||||
const isValidIdentifierRe = /^[_$a-zA-Z][\w$]*$/;
|
||||
|
||||
function getPrivateEnv(
|
||||
fullEnv: Record<string, string>,
|
||||
astroConfig: AstroConfig,
|
||||
): Record<string, string> {
|
||||
const viteConfig = astroConfig.vite;
|
||||
let envPrefixes: string[] = ['PUBLIC_'];
|
||||
if (viteConfig.envPrefix) {
|
||||
envPrefixes = Array.isArray(viteConfig.envPrefix)
|
||||
? viteConfig.envPrefix
|
||||
: [viteConfig.envPrefix];
|
||||
}
|
||||
|
||||
const privateEnv: Record<string, string> = {};
|
||||
for (const key in fullEnv) {
|
||||
// Ignore public env var
|
||||
if (isValidIdentifierRe.test(key) && envPrefixes.every((prefix) => !key.startsWith(prefix))) {
|
||||
if (typeof process.env[key] !== 'undefined') {
|
||||
let value = process.env[key];
|
||||
// Replacements are always strings, so try to convert to strings here first
|
||||
if (typeof value !== 'string') {
|
||||
value = `${value}`;
|
||||
}
|
||||
// Boolean values should be inlined to support `export const prerender`
|
||||
// We already know that these are NOT sensitive values, so inlining is safe
|
||||
if (value === '0' || value === '1' || value === 'true' || value === 'false') {
|
||||
privateEnv[key] = value;
|
||||
} else {
|
||||
privateEnv[key] = `process.env.${key}`;
|
||||
}
|
||||
} else {
|
||||
privateEnv[key] = JSON.stringify(fullEnv[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return privateEnv;
|
||||
}
|
||||
|
||||
export const createEnvLoader = () => {
|
||||
let privateEnv: Record<string, string> = {};
|
||||
return {
|
||||
load: (mode: string, config: AstroConfig) => {
|
||||
const loaded = loadEnv(mode, config.vite.envDir ?? fileURLToPath(config.root), '');
|
||||
privateEnv = getPrivateEnv(loaded, config);
|
||||
return loaded;
|
||||
},
|
||||
getPrivateEnv: () => {
|
||||
return privateEnv;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export type EnvLoader = ReturnType<typeof createEnvLoader>;
|
6
packages/astro/src/env/runtime.ts
vendored
6
packages/astro/src/env/runtime.ts
vendored
|
@ -4,14 +4,14 @@ import type { ValidationResultInvalid } from './validators.js';
|
|||
export { validateEnvVariable, getEnvFieldType } from './validators.js';
|
||||
|
||||
export type GetEnv = (key: string) => string | undefined;
|
||||
type OnSetGetEnv = (reset: boolean) => void;
|
||||
type OnSetGetEnv = () => void;
|
||||
|
||||
let _getEnv: GetEnv = (key) => process.env[key];
|
||||
|
||||
export function setGetEnv(fn: GetEnv, reset = false) {
|
||||
export function setGetEnv(fn: GetEnv) {
|
||||
_getEnv = fn;
|
||||
|
||||
_onSetGetEnv(reset);
|
||||
_onSetGetEnv();
|
||||
}
|
||||
|
||||
let _onSetGetEnv: OnSetGetEnv = () => {};
|
||||
|
|
32
packages/astro/src/env/vite-plugin-env.ts
vendored
32
packages/astro/src/env/vite-plugin-env.ts
vendored
|
@ -1,6 +1,5 @@
|
|||
import { readFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { type Plugin, loadEnv } from 'vite';
|
||||
import type { Plugin } from 'vite';
|
||||
import { AstroError, AstroErrorData } from '../core/errors/index.js';
|
||||
import type { AstroSettings } from '../types/astro.js';
|
||||
import {
|
||||
|
@ -8,6 +7,7 @@ import {
|
|||
VIRTUAL_MODULES_IDS,
|
||||
VIRTUAL_MODULES_IDS_VALUES,
|
||||
} from './constants.js';
|
||||
import type { EnvLoader } from './env-loader.js';
|
||||
import { type InvalidVariable, invalidVariablesToError } from './errors.js';
|
||||
import type { EnvSchema } from './schema.js';
|
||||
import { getEnvFieldType, validateEnvVariable } from './validators.js';
|
||||
|
@ -16,21 +16,29 @@ interface AstroEnvPluginParams {
|
|||
settings: AstroSettings;
|
||||
mode: string;
|
||||
sync: boolean;
|
||||
envLoader: EnvLoader;
|
||||
}
|
||||
|
||||
export function astroEnv({ settings, mode, sync }: AstroEnvPluginParams): Plugin {
|
||||
export function astroEnv({ settings, mode, sync, envLoader }: AstroEnvPluginParams): Plugin {
|
||||
const { schema, validateSecrets } = settings.config.env;
|
||||
let isDev: boolean;
|
||||
|
||||
let templates: { client: string; server: string; internal: string } | null = null;
|
||||
|
||||
return {
|
||||
name: 'astro-env-plugin',
|
||||
enforce: 'pre',
|
||||
config(_, { command }) {
|
||||
isDev = command !== 'build';
|
||||
},
|
||||
buildStart() {
|
||||
const loadedEnv = loadEnv(mode, fileURLToPath(settings.config.root), '');
|
||||
for (const [key, value] of Object.entries(loadedEnv)) {
|
||||
if (value !== undefined) {
|
||||
process.env[key] = value;
|
||||
const loadedEnv = envLoader.load(mode, settings.config);
|
||||
|
||||
if (!isDev) {
|
||||
for (const [key, value] of Object.entries(loadedEnv)) {
|
||||
if (value !== undefined) {
|
||||
process.env[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,7 +50,7 @@ export function astroEnv({ settings, mode, sync }: AstroEnvPluginParams): Plugin
|
|||
});
|
||||
|
||||
templates = {
|
||||
...getTemplates(schema, validatedVariables),
|
||||
...getTemplates(schema, validatedVariables, isDev ? loadedEnv : null),
|
||||
internal: `export const schema = ${JSON.stringify(schema)};`,
|
||||
};
|
||||
},
|
||||
|
@ -122,6 +130,7 @@ function validatePublicVariables({
|
|||
function getTemplates(
|
||||
schema: EnvSchema,
|
||||
validatedVariables: ReturnType<typeof validatePublicVariables>,
|
||||
loadedEnv: Record<string, string> | null,
|
||||
) {
|
||||
let client = '';
|
||||
let server = readFileSync(MODULE_TEMPLATE_URL, 'utf-8');
|
||||
|
@ -142,10 +151,15 @@ function getTemplates(
|
|||
}
|
||||
|
||||
server += `export let ${key} = _internalGetSecret(${JSON.stringify(key)});\n`;
|
||||
onSetGetEnv += `${key} = reset ? undefined : _internalGetSecret(${JSON.stringify(key)});\n`;
|
||||
onSetGetEnv += `${key} = _internalGetSecret(${JSON.stringify(key)});\n`;
|
||||
}
|
||||
|
||||
server = server.replace('// @@ON_SET_GET_ENV@@', onSetGetEnv);
|
||||
if (loadedEnv) {
|
||||
server = server.replace('// @@GET_ENV@@', `return (${JSON.stringify(loadedEnv)})[key];`);
|
||||
} else {
|
||||
server = server.replace('// @@GET_ENV@@', 'return _getEnv(key);');
|
||||
}
|
||||
|
||||
return {
|
||||
client,
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -83,7 +83,6 @@ export function createI18nMiddleware(
|
|||
}
|
||||
|
||||
const { currentLocale } = context;
|
||||
|
||||
switch (i18n.strategy) {
|
||||
// NOTE: theoretically, we should never hit this code path
|
||||
case 'manual': {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -191,7 +191,6 @@ export function createDevelopmentManifest(settings: AstroSettings): SSRManifest
|
|||
i18n: i18nManifest,
|
||||
checkOrigin:
|
||||
(settings.config.security?.checkOrigin && settings.buildOutput === 'server') ?? false,
|
||||
envGetSecretEnabled: false,
|
||||
key: hasEnvironmentKey() ? getEnvironmentKey() : createKey(),
|
||||
middleware() {
|
||||
return {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -1,63 +1,14 @@
|
|||
import { fileURLToPath } from 'node:url';
|
||||
import { transform } from 'esbuild';
|
||||
import MagicString from 'magic-string';
|
||||
import type * as vite from 'vite';
|
||||
import { loadEnv } from 'vite';
|
||||
import type { AstroSettings } from '../types/astro.js';
|
||||
import type { AstroConfig } from '../types/public/config.js';
|
||||
import type { EnvLoader } from '../env/env-loader.js';
|
||||
|
||||
interface EnvPluginOptions {
|
||||
settings: AstroSettings;
|
||||
envLoader: EnvLoader;
|
||||
}
|
||||
|
||||
// Match `import.meta.env` directly without trailing property access
|
||||
const importMetaEnvOnlyRe = /\bimport\.meta\.env\b(?!\.)/;
|
||||
// Match valid JS variable names (identifiers), which accepts most alphanumeric characters,
|
||||
// except that the first character cannot be a number.
|
||||
const isValidIdentifierRe = /^[_$a-zA-Z][\w$]*$/;
|
||||
|
||||
function getPrivateEnv(
|
||||
viteConfig: vite.ResolvedConfig,
|
||||
astroConfig: AstroConfig,
|
||||
): Record<string, string> {
|
||||
let envPrefixes: string[] = ['PUBLIC_'];
|
||||
if (viteConfig.envPrefix) {
|
||||
envPrefixes = Array.isArray(viteConfig.envPrefix)
|
||||
? viteConfig.envPrefix
|
||||
: [viteConfig.envPrefix];
|
||||
}
|
||||
|
||||
// Loads environment variables from `.env` files and `process.env`
|
||||
const fullEnv = loadEnv(
|
||||
viteConfig.mode,
|
||||
viteConfig.envDir ?? fileURLToPath(astroConfig.root),
|
||||
'',
|
||||
);
|
||||
|
||||
const privateEnv: Record<string, string> = {};
|
||||
for (const key in fullEnv) {
|
||||
// Ignore public env var
|
||||
if (isValidIdentifierRe.test(key) && envPrefixes.every((prefix) => !key.startsWith(prefix))) {
|
||||
if (typeof process.env[key] !== 'undefined') {
|
||||
let value = process.env[key];
|
||||
// Replacements are always strings, so try to convert to strings here first
|
||||
if (typeof value !== 'string') {
|
||||
value = `${value}`;
|
||||
}
|
||||
// Boolean values should be inlined to support `export const prerender`
|
||||
// We already know that these are NOT sensitive values, so inlining is safe
|
||||
if (value === '0' || value === '1' || value === 'true' || value === 'false') {
|
||||
privateEnv[key] = value;
|
||||
} else {
|
||||
privateEnv[key] = `process.env.${key}`;
|
||||
}
|
||||
} else {
|
||||
privateEnv[key] = JSON.stringify(fullEnv[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return privateEnv;
|
||||
}
|
||||
|
||||
function getReferencedPrivateKeys(source: string, privateEnv: Record<string, any>): Set<string> {
|
||||
const references = new Set<string>();
|
||||
|
@ -114,13 +65,12 @@ async function replaceDefine(
|
|||
};
|
||||
}
|
||||
|
||||
export default function envVitePlugin({ settings }: EnvPluginOptions): vite.Plugin {
|
||||
export default function envVitePlugin({ envLoader }: EnvPluginOptions): vite.Plugin {
|
||||
let privateEnv: Record<string, string>;
|
||||
let defaultDefines: Record<string, string>;
|
||||
let isDev: boolean;
|
||||
let devImportMetaEnvPrepend: string;
|
||||
let viteConfig: vite.ResolvedConfig;
|
||||
const { config: astroConfig } = settings;
|
||||
return {
|
||||
name: 'astro:vite-plugin-env',
|
||||
config(_, { command }) {
|
||||
|
@ -152,7 +102,9 @@ export default function envVitePlugin({ settings }: EnvPluginOptions): vite.Plug
|
|||
}
|
||||
|
||||
// Find matches for *private* env and do our own replacement.
|
||||
privateEnv ??= getPrivateEnv(viteConfig, astroConfig);
|
||||
// Env is retrieved before process.env is populated by astro:env
|
||||
// so that import.meta.env is first replaced by values, not process.env
|
||||
privateEnv ??= envLoader.getPrivateEnv();
|
||||
|
||||
// In dev, we can assign the private env vars to `import.meta.env` directly for performance
|
||||
if (isDev) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1,13 +1,23 @@
|
|||
// @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,
|
||||
getEnv,
|
||||
getEnvFieldType,
|
||||
setOnSetGetEnv,
|
||||
validateEnvVariable,
|
||||
} from 'astro/env/runtime';
|
||||
|
||||
// @ts-expect-error
|
||||
/** @returns {string} */
|
||||
// used while generating the virtual module
|
||||
// biome-ignore lint/correctness/noUnusedFunctionParameters: `key` is used by the generated code
|
||||
// biome-ignore lint/correctness/noUnusedVariables: `key` is used by the generated code
|
||||
const getEnv = (key) => {
|
||||
// @@GET_ENV@@
|
||||
};
|
||||
|
||||
export const getSecret = (key) => {
|
||||
return getEnv(key);
|
||||
};
|
||||
|
@ -25,9 +35,6 @@ const _internalGetSecret = (key) => {
|
|||
throw createInvalidVariablesError(key, type, result);
|
||||
};
|
||||
|
||||
// used while generating the virtual module
|
||||
// biome-ignore lint/correctness/noUnusedFunctionParameters: `reset` is used by the generated code
|
||||
// biome-ignore lint/correctness/noUnusedVariables: `reset` is used by the generated code
|
||||
setOnSetGetEnv((reset) => {
|
||||
setOnSetGetEnv(() => {
|
||||
// @@ON_SET_GET_ENV@@
|
||||
});
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import assert from 'node:assert/strict';
|
||||
import { before, describe, it } from 'node:test';
|
||||
import { after, before, describe, it } from 'node:test';
|
||||
import * as cheerio from 'cheerio';
|
||||
import { fixLineEndings, loadFixture } from './test-utils.js';
|
||||
|
||||
|
@ -154,4 +154,24 @@ describe('Astro Markdown', () => {
|
|||
assert.ok(title.includes('import.meta.env.TITLE'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('dev', () => {
|
||||
let devServer;
|
||||
|
||||
before(async () => {
|
||||
devServer = await fixture.startDevServer();
|
||||
});
|
||||
|
||||
it('ignores .md extensions on query params', async () => {
|
||||
const res = await fixture.fetch('/false-positive?page=page.md');
|
||||
assert.ok(res.ok);
|
||||
const html = await res.text();
|
||||
const $ = cheerio.load(html);
|
||||
assert.equal($('p').text(), 'the page is not markdown');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await devServer.stop();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
5
packages/astro/test/fixtures/astro-markdown/src/pages/false-positive.astro
vendored
Normal file
5
packages/astro/test/fixtures/astro-markdown/src/pages/false-positive.astro
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
let page = "not markdown"
|
||||
---
|
||||
|
||||
<p>the page is {page}</p>
|
16
packages/astro/test/fixtures/content-layer-rendering/content-outside-src-mdx/I'm back!.mdx
vendored
Normal file
16
packages/astro/test/fixtures/content-layer-rendering/content-outside-src-mdx/I'm back!.mdx
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: I'm back!
|
||||
description: 'Introduction to Iguana.'
|
||||
publishedDate: 'Sat May 21 2022 00:00:00 GMT-0400 (Eastern Daylight Time)'
|
||||
tags: [cats, felines]
|
||||
---
|
||||
|
||||
import H2 from "../src/components/H2.astro";
|
||||
|
||||
<H2>Iguana</H2>
|
||||
|
||||
### Iguana
|
||||
|
||||
This is a rendered entry
|
||||
|
||||
![file](./I'm%20back.jpg)
|
BIN
packages/astro/test/fixtures/content-layer-rendering/content-outside-src-mdx/I'm back.jpg
vendored
Normal file
BIN
packages/astro/test/fixtures/content-layer-rendering/content-outside-src-mdx/I'm back.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
BIN
packages/astro/test/fixtures/content-layer/images/I'm back.jpg
vendored
Normal file
BIN
packages/astro/test/fixtures/content-layer/images/I'm back.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
|
@ -3,6 +3,8 @@ title: Endeavour
|
|||
description: 'Learn about the Endeavour NASA space shuttle.'
|
||||
publishedDate: 'Sun Jul 11 2021 00:00:00 GMT-0400 (Eastern Daylight Time)'
|
||||
tags: [space, 90s]
|
||||
heroImage: "@images/I'm back.jpg"
|
||||
|
||||
---
|
||||
|
||||
**Source:** [Wikipedia](https://en.wikipedia.org/wiki/Space_Shuttle_Endeavour)
|
||||
|
|
|
@ -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
|
||||
---
|
||||
<html>
|
||||
<head>
|
||||
<title>Astro</title>
|
||||
</head>
|
||||
<body>
|
||||
{content}
|
||||
{content || ssrContent}
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -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;
|
|||
<title>Astro</title>
|
||||
</head>
|
||||
<body>
|
||||
{content}
|
||||
{content || ssrContent}
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -9,6 +9,9 @@ export default defineConfig({
|
|||
codes: ["es", "es-ar"]
|
||||
}
|
||||
],
|
||||
routing: "manual"
|
||||
routing: "manual",
|
||||
fallback: {
|
||||
it: 'en'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -18,5 +18,6 @@ export const onRequest = sequence(
|
|||
customLogic,
|
||||
middleware({
|
||||
prefixDefaultLocale: true,
|
||||
fallbackType: "rewrite"
|
||||
})
|
||||
);
|
||||
|
|
8
packages/astro/test/fixtures/middleware-full-ssr/package.json
vendored
Normal file
8
packages/astro/test/fixtures/middleware-full-ssr/package.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "@test/middleware-full-ssr",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"astro": "workspace:*"
|
||||
}
|
||||
}
|
1
packages/astro/test/fixtures/middleware-full-ssr/src/error.js
vendored
Normal file
1
packages/astro/test/fixtures/middleware-full-ssr/src/error.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
throw new Error("Shoud not error at build time")
|
2
packages/astro/test/fixtures/middleware-full-ssr/src/middleware.js
vendored
Normal file
2
packages/astro/test/fixtures/middleware-full-ssr/src/middleware.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
import "./error.js"
|
||||
export const onRequest = (_ , next) => next();
|
14
packages/astro/test/fixtures/middleware-full-ssr/src/pages/index.astro
vendored
Normal file
14
packages/astro/test/fixtures/middleware-full-ssr/src/pages/index.astro
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
const data = Astro.locals;
|
||||
---
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Testing</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<span>Index</span>
|
||||
<p>{data?.name}</p>
|
||||
</body>
|
||||
</html>
|
|
@ -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/');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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/);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -171,6 +171,21 @@ describe('Middleware in PROD mode, SSG', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Middleware should not be executed or imported during', () => {
|
||||
/** @type {import('./test-utils').Fixture} */
|
||||
let fixture;
|
||||
|
||||
it('should build the project without errors', async () => {
|
||||
fixture = await loadFixture({
|
||||
root: './fixtures/middleware-full-ssr/',
|
||||
output: 'server',
|
||||
adapter: testAdapter({}),
|
||||
});
|
||||
await fixture.build();
|
||||
assert.ok('Should build');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Middleware API in PROD mode, SSR', () => {
|
||||
/** @type {import('./test-utils').Fixture} */
|
||||
let fixture;
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { existsSync } from 'node:fs';
|
||||
import { LibsqlError } from '@libsql/client';
|
||||
import type { AstroConfig } from 'astro';
|
||||
import { green } from 'kleur/colors';
|
||||
import type { Arguments } from 'yargs-parser';
|
||||
import { isDbError } from '../../../../runtime/utils.js';
|
||||
import {
|
||||
EXEC_DEFAULT_EXPORT_ERROR,
|
||||
EXEC_ERROR,
|
||||
|
@ -64,9 +64,7 @@ export async function cmd({
|
|||
await mod.default();
|
||||
console.info(`${green('✔')} File run successfully.`);
|
||||
} catch (e) {
|
||||
if (e instanceof LibsqlError) {
|
||||
throw new Error(EXEC_ERROR(e.message));
|
||||
}
|
||||
throw e;
|
||||
if (isDbError(e)) throw new Error(EXEC_ERROR(e.message));
|
||||
else throw e;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { stripVTControlCharacters } from 'node:util';
|
||||
import { LibsqlError } from '@libsql/client';
|
||||
import deepDiff from 'deep-diff';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { SQLiteAsyncDialect } from 'drizzle-orm/sqlite-core';
|
||||
|
@ -8,7 +7,7 @@ import { customAlphabet } from 'nanoid';
|
|||
import { hasPrimaryKey } from '../../runtime/index.js';
|
||||
import { createRemoteDatabaseClient } from '../../runtime/index.js';
|
||||
import { isSerializedSQL } from '../../runtime/types.js';
|
||||
import { safeFetch } from '../../runtime/utils.js';
|
||||
import { isDbError, safeFetch } from '../../runtime/utils.js';
|
||||
import { MIGRATION_VERSION } from '../consts.js';
|
||||
import { RENAME_COLUMN_ERROR, RENAME_TABLE_ERROR } from '../errors.js';
|
||||
import {
|
||||
|
@ -454,7 +453,7 @@ async function getDbCurrentSnapshot(
|
|||
} catch (error) {
|
||||
// Don't handle errors that are not from libSQL
|
||||
if (
|
||||
error instanceof LibsqlError &&
|
||||
isDbError(error) &&
|
||||
// If the schema was never pushed to the database yet the table won't exist.
|
||||
// Treat a missing snapshot table as an empty table.
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue