0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2024-12-16 21:46:22 -05:00

Merge branch 'main' into next

This commit is contained in:
Princesseuh 2024-09-10 15:01:44 +02:00
commit 40760a8ace
No known key found for this signature in database
GPG key ID: 105BBD6D57F2B0C0
89 changed files with 1241 additions and 885 deletions

View file

@ -1,5 +0,0 @@
---
'@astrojs/db': patch
---
Fixes mixed environment variable for app token when using DB commands with libSQL remote.

View file

@ -1,5 +0,0 @@
---
'astro': patch
---
Fixes case where content layer did not update during clean dev builds on Linux and Windows

View file

@ -0,0 +1,63 @@
---
'astro': patch
---
Adds support for Zod discriminated unions on Action form inputs. This allows forms with different inputs to be submitted to the same action, using a given input to decide which object should be used for validation.
This example accepts either a `create` or `update` form submission, and uses the `type` field to determine which object to validate against.
```ts
import { defineAction } from 'astro:actions';
import { z } from 'astro:schema';
export const server = {
changeUser: defineAction({
accept: 'form',
input: z.discriminatedUnion('type', [
z.object({
type: z.literal('create'),
name: z.string(),
email: z.string().email(),
}),
z.object({
type: z.literal('update'),
id: z.number(),
name: z.string(),
email: z.string().email(),
}),
]),
async handler(input) {
if (input.type === 'create') {
// input is { type: 'create', name: string, email: string }
} else {
// input is { type: 'update', id: number, name: string, email: string }
}
},
}),
}
```
The corresponding `create` and `update` forms may look like this:
```astro
---
import { actions } from 'astro:actions';
---
<!--Create-->
<form action={actions.changeUser} method="POST">
<input type="hidden" name="type" value="create" />
<input type="text" name="name" required />
<input type="email" name="email" required />
<button type="submit">Create User</button>
</form>
<!--Update-->
<form action={actions.changeUser} method="POST">
<input type="hidden" name="type" value="update" />
<input type="hidden" name="id" value="user-123" />
<input type="text" name="name" required />
<input type="email" name="email" required />
<button type="submit">Update User</button>
</form>
```

View file

@ -11,7 +11,7 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/preact": "^3.5.2",
"@astrojs/preact": "^3.5.3",
"@astrojs/react": "^3.6.2",
"@astrojs/solid-js": "^4.4.1",
"@astrojs/svelte": "^6.0.0-alpha.0",
@ -24,6 +24,6 @@
"react-dom": "^18.3.1",
"solid-js": "^1.8.22",
"svelte": "^4.2.19",
"vue": "^3.4.38"
"vue": "^3.5.3"
}
}

View file

@ -11,7 +11,7 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/preact": "^3.5.2",
"@astrojs/preact": "^3.5.3",
"@preact/signals": "^1.3.0",
"astro": "^5.0.0-alpha.6",
"preact": "^10.23.2"

View file

@ -14,7 +14,7 @@
"@astrojs/react": "^3.6.2",
"@astrojs/tailwind": "^6.0.0-alpha.0",
"@fortawesome/fontawesome-free": "^6.6.0",
"@tailwindcss/forms": "^0.5.8",
"@tailwindcss/forms": "^0.5.9",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"astro": "^5.0.0-alpha.6",

View file

@ -11,7 +11,7 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/preact": "^3.5.2",
"@astrojs/preact": "^3.5.3",
"@nanostores/preact": "^0.5.2",
"astro": "^5.0.0-alpha.6",
"nanostores": "^0.11.3",

View file

@ -17,7 +17,7 @@
"astro": "^5.0.0-alpha.6",
"autoprefixer": "^10.4.20",
"canvas-confetti": "^1.9.3",
"postcss": "^8.4.43",
"postcss": "^8.4.45",
"tailwindcss": "^3.4.10"
}
}

View file

@ -55,10 +55,10 @@
"@astrojs/check": "^0.9.3",
"@biomejs/biome": "1.8.3",
"@changesets/changelog-github": "^0.5.0",
"@changesets/cli": "^2.27.7",
"@changesets/cli": "^2.27.8",
"@types/node": "^18.17.8",
"esbuild": "^0.21.5",
"eslint": "^9.9.1",
"eslint": "^9.10.0",
"eslint-plugin-no-only-tests": "^3.3.0",
"eslint-plugin-regexp": "^2.6.0",
"globby": "^14.0.2",
@ -67,7 +67,7 @@
"prettier-plugin-astro": "^0.14.1",
"turbo": "^2.1.1",
"typescript": "~5.5.4",
"typescript-eslint": "^8.3.0"
"typescript-eslint": "^8.4.0"
},
"pnpm": {
"peerDependencyRules": {

View file

@ -33,7 +33,7 @@
"xml2js": "0.6.2"
},
"dependencies": {
"fast-xml-parser": "^4.4.1",
"fast-xml-parser": "^4.5.0",
"kleur": "^4.1.5"
}
}

View file

@ -69,6 +69,42 @@
});
```
## 4.15.4
### Patch Changes
- [#11879](https://github.com/withastro/astro/pull/11879) [`bd1d4aa`](https://github.com/withastro/astro/commit/bd1d4aaf8262187b4f132d7fe0365902131ddf1a) Thanks [@matthewp](https://github.com/matthewp)! - Allow passing a cryptography key via ASTRO_KEY
For Server islands Astro creates a cryptography key in order to hash props for the islands, preventing accidental leakage of secrets.
If you deploy to an environment with rolling updates then there could be multiple instances of your app with different keys, causing potential key mismatches.
To fix this you can now pass the `ASTRO_KEY` environment variable to your build in order to reuse the same key.
To generate a key use:
```
astro create-key
```
This will print out an environment variable to set like:
```
ASTRO_KEY=PIAuyPNn2aKU/bviapEuc/nVzdzZPizKNo3OqF/5PmQ=
```
- [#11935](https://github.com/withastro/astro/pull/11935) [`c58193a`](https://github.com/withastro/astro/commit/c58193a691775af5c568e461c63040a42e2471f7) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Fixes `astro add` not using the proper export point when adding certain adapters
## 4.15.3
### Patch Changes
- [#11902](https://github.com/withastro/astro/pull/11902) [`d63bc50`](https://github.com/withastro/astro/commit/d63bc50d9940c1107e0fee7687e5c332549a0eff) Thanks [@ascorbic](https://github.com/ascorbic)! - Fixes case where content layer did not update during clean dev builds on Linux and Windows
- [#11886](https://github.com/withastro/astro/pull/11886) [`7ff7134`](https://github.com/withastro/astro/commit/7ff7134b8038a3b798293b2218bbf6dd02d2ac32) Thanks [@matthewp](https://github.com/matthewp)! - Fixes a missing error message when actions throws during `astro sync`
- [#11904](https://github.com/withastro/astro/pull/11904) [`ca54e3f`](https://github.com/withastro/astro/commit/ca54e3f819fad009ac3c3c8b57a26014a2652a73) Thanks [@wtchnm](https://github.com/wtchnm)! - perf(assets): avoid downloading original image when using cache
## 5.0.0-alpha.5
### Major Changes

View file

@ -5,6 +5,6 @@
"dependencies": {
"@astrojs/vue": "workspace:*",
"astro": "workspace:*",
"vue": "^3.4.38"
"vue": "^3.5.3"
}
}

View file

@ -16,6 +16,6 @@
"react-dom": "^18.3.1",
"solid-js": "^1.8.22",
"svelte": "^4.2.19",
"vue": "^3.4.38"
"vue": "^3.5.3"
}
}

View file

@ -4,6 +4,6 @@
"private": true,
"dependencies": {
"astro": "workspace:*",
"sass": "^1.77.8"
"sass": "^1.78.0"
}
}

View file

@ -12,9 +12,9 @@
"preact": "^10.23.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"sass": "^1.77.8",
"sass": "^1.78.0",
"solid-js": "^1.8.22",
"svelte": "^4.2.19",
"vue": "^3.4.38"
"vue": "^3.5.3"
}
}

View file

@ -4,6 +4,6 @@
"private": true,
"devDependencies": {
"astro": "workspace:*",
"sass": "^1.77.8"
"sass": "^1.78.0"
}
}

View file

@ -18,6 +18,6 @@
"react-dom": "^18.3.1",
"solid-js": "^1.8.22",
"svelte": "^4.2.19",
"vue": "^3.4.38"
"vue": "^3.5.3"
}
}

View file

@ -16,6 +16,6 @@
"react-dom": "^18.3.1",
"solid-js": "^1.8.22",
"svelte": "^4.2.19",
"vue": "^3.4.38"
"vue": "^3.5.3"
}
}

View file

@ -16,6 +16,6 @@
"react-dom": "^18.3.1",
"solid-js": "^1.8.22",
"svelte": "^4.2.19",
"vue": "^3.4.38"
"vue": "^3.5.3"
}
}

View file

@ -16,6 +16,6 @@
"react-dom": "^18.3.1",
"solid-js": "^1.8.22",
"svelte": "^4.2.19",
"vue": "^3.4.38"
"vue": "^3.5.3"
}
}

View file

@ -16,6 +16,6 @@
"react-dom": "^18.3.1",
"solid-js": "^1.8.22",
"svelte": "^4.2.19",
"vue": "^3.4.38"
"vue": "^3.5.3"
}
}

View file

@ -16,6 +16,6 @@
"react-dom": "^18.3.1",
"solid-js": "^1.8.22",
"svelte": "^4.2.19",
"vue": "^3.4.38"
"vue": "^3.5.3"
}
}

View file

@ -16,7 +16,7 @@
"react-dom": "^18.3.1",
"solid-js": "^1.8.22",
"svelte": "^4.2.19",
"vue": "^3.4.38"
"vue": "^3.5.3"
},
"scripts": {
"dev": "astro dev"

View file

@ -0,0 +1,12 @@
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
// https://astro.build/config
export default defineConfig({
output: 'server',
adapter: node({ mode: 'standalone' }),
integrations: [],
experimental: {
serverIslands: true,
}
});

View file

@ -0,0 +1,12 @@
{
"name": "@e2e/server-islands-key",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "astro dev"
},
"dependencies": {
"astro": "workspace:*",
"@astrojs/node": "^8.3.3"
}
}

View file

@ -0,0 +1,6 @@
---
const { secret } = Astro.props;
---
<h2 id="island">I am an island</h2>
<slot />
<h3 id="secret">{secret}</h3>

View file

@ -0,0 +1,14 @@
---
import Island from '../components/Island.astro';
---
<html>
<head>
<!-- Head Stuff -->
</head>
<body>
<Island server:defer secret="test">
<h3 id="children">children</h3>
</Island>
</body>
</html>

View file

@ -6,7 +6,7 @@
"@astrojs/tailwind": "workspace:*",
"astro": "workspace:*",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.43",
"postcss": "^8.4.45",
"tailwindcss": "^3.4.10"
}
}

View file

@ -11,6 +11,6 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"svelte": "^4.2.19",
"vue": "^3.4.38"
"vue": "^3.5.3"
}
}

View file

@ -6,6 +6,6 @@
"@astrojs/mdx": "workspace:*",
"@astrojs/vue": "workspace:*",
"astro": "workspace:*",
"vue": "^3.4.38"
"vue": "^3.5.3"
}
}

View file

@ -0,0 +1,33 @@
import { expect } from '@playwright/test';
import { testFactory } from './test-utils.js';
const test = testFactory(import.meta.url, { root: './fixtures/server-islands-key/' });
test.describe('Server islands - Key reuse', () => {
test.describe('Production', () => {
let previewServer;
test.beforeAll(async ({ astro }) => {
// Playwright's Node version doesn't have these functions, so stub them.
process.stdout.clearLine = () => {};
process.stdout.cursorTo = () => {};
process.env.ASTRO_KEY = 'eKBaVEuI7YjfanEXHuJe/pwZKKt3LkAHeMxvTU7aR0M=';
await astro.build();
previewServer = await astro.preview();
});
test.afterAll(async () => {
await previewServer.stop();
delete process.env.ASTRO_KEY;
});
test('Components render properly', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
let el = page.locator('#island');
await expect(el, 'element rendered').toBeVisible();
await expect(el, 'should have content').toHaveText('I am an island');
});
});
});

View file

@ -14,6 +14,10 @@ const testFileToPort = new Map();
for (let i = 0; i < testFiles.length; i++) {
const file = testFiles[i];
if (file.endsWith('.test.js')) {
// Port 4045 is an unsafe port in Chrome, so skip it.
if (4000 + i === 4045) {
i++;
}
testFileToPort.set(file, 4000 + i);
}
}

View file

@ -139,7 +139,7 @@
"common-ancestor-path": "^1.0.1",
"cookie": "^0.6.0",
"cssesc": "^3.0.0",
"debug": "^4.3.6",
"debug": "^4.3.7",
"deterministic-object-hash": "^2.0.2",
"devalue": "^5.0.0",
"diff": "^5.2.0",
@ -170,14 +170,14 @@
"prompts": "^2.4.2",
"rehype": "^13.0.1",
"semver": "^7.6.3",
"shiki": "^1.16.1",
"shiki": "^1.16.2",
"string-width": "^7.2.0",
"strip-ansi": "^7.1.0",
"tinyexec": "^0.3.0",
"tsconfck": "^3.1.3",
"unist-util-visit": "^5.0.0",
"vfile": "^6.0.3",
"vite": "^5.4.2",
"vite": "^5.4.3",
"vitefu": "^1.0.2",
"which-pm": "^3.0.0",
"xxhash-wasm": "^1.0.2",
@ -191,7 +191,7 @@
},
"devDependencies": {
"@astrojs/check": "^0.9.3",
"@playwright/test": "^1.46.1",
"@playwright/test": "^1.47.0",
"@types/aria-query": "^5.0.4",
"@types/common-ancestor-path": "^1.0.2",
"@types/cssesc": "^3.0.2",
@ -222,7 +222,7 @@
"rehype-toc": "^3.0.2",
"remark-code-titles": "^0.1.2",
"rollup": "^4.21.2",
"sass": "^1.77.8",
"sass": "^1.78.0",
"undici": "^6.19.8",
"unified": "^11.0.5"
},

View file

@ -92,7 +92,7 @@ function getFormServerHandler<TOutput, TInputSchema extends z.ZodType>(
if (!inputSchema) return await handler(unparsedInput, context);
const baseSchema = unwrapSchemaEffects(inputSchema);
const baseSchema = unwrapBaseObjectSchema(inputSchema, unparsedInput);
const parsed = await inputSchema.safeParseAsync(
baseSchema instanceof z.ZodObject
? formDataToObject(unparsedInput, baseSchema)
@ -191,7 +191,7 @@ function handleFormDataGet(
return validator instanceof z.ZodNumber ? Number(value) : value;
}
function unwrapSchemaEffects(schema: z.ZodType) {
function unwrapBaseObjectSchema(schema: z.ZodType, unparsedInput: FormData) {
while (schema instanceof z.ZodEffects || schema instanceof z.ZodPipeline) {
if (schema instanceof z.ZodEffects) {
schema = schema._def.schema;
@ -200,5 +200,15 @@ function unwrapSchemaEffects(schema: z.ZodType) {
schema = schema._def.in;
}
}
if (schema instanceof z.ZodDiscriminatedUnion) {
const typeKey = schema._def.discriminator;
const typeValue = unparsedInput.get(typeKey);
if (typeof typeValue !== 'string') return schema;
const objSchema = schema._def.optionsMap.get(typeValue);
if (!objSchema) return schema;
return objSchema;
}
return schema;
}

View file

@ -98,11 +98,11 @@ export async function generateImagesForPath(
env: AssetEnv,
queue: PQueue,
) {
const originalImageData = await loadImage(originalFilePath, env);
let originalImage: ImageData;
for (const [_, transform] of transformsAndPath.transforms) {
await queue
.add(async () => generateImage(originalImageData, transform.finalPath, transform.transform))
.add(async () => generateImage(transform.finalPath, transform.transform))
.catch((e) => {
throw e;
});
@ -128,13 +128,9 @@ export async function generateImagesForPath(
}
}
async function generateImage(
originalImage: ImageData,
filepath: string,
options: ImageTransform,
) {
async function generateImage(filepath: string, options: ImageTransform) {
const timeStart = performance.now();
const generationData = await generateImageInternal(originalImage, filepath, options);
const generationData = await generateImageInternal(filepath, options);
const timeEnd = performance.now();
const timeChange = getTimeStat(timeStart, timeEnd);
@ -151,7 +147,6 @@ export async function generateImagesForPath(
}
async function generateImageInternal(
originalImage: ImageData,
filepath: string,
options: ImageTransform,
): Promise<GenerationData> {
@ -207,6 +202,10 @@ export async function generateImagesForPath(
? (options.src as ImageMetadata).src
: (options.src as string);
if (!originalImage) {
originalImage = await loadImage(originalFilePath, env);
}
let resultData: Partial<ImageData> = {
data: undefined,
expires: originalImage.expires,

View file

@ -279,7 +279,7 @@ export async function add(names: string[], { flags }: AddOptions) {
if (isAdapter(integration)) {
const officialExportName = OFFICIAL_ADAPTER_TO_IMPORT_MAP[integration.id];
if (officialExportName) {
setAdapter(mod, integration);
setAdapter(mod, integration, officialExportName);
} else {
logger.info(
'SKIP_FORMAT',
@ -447,7 +447,11 @@ function addIntegration(mod: ProxifiedModule<any>, integration: IntegrationInfo)
}
}
export function setAdapter(mod: ProxifiedModule<any>, adapter: IntegrationInfo) {
export function setAdapter(
mod: ProxifiedModule<any>,
adapter: IntegrationInfo,
exportName: string,
) {
const config = getDefaultExportOptions(mod);
const adapterId = toIdent(adapter.id);
@ -455,7 +459,7 @@ export function setAdapter(mod: ProxifiedModule<any>, adapter: IntegrationInfo)
mod.imports.$append({
imported: 'default',
local: adapterId,
from: adapter.packageName,
from: exportName,
});
}

View file

@ -0,0 +1,33 @@
import { createNodeLogger } from '../../core/config/logging.js';
import { createKey as createCryptoKey, encodeKey } from '../../core/encryption.js';
import { type Flags, flagsToAstroInlineConfig } from '../flags.js';
interface CreateKeyOptions {
flags: Flags;
}
export async function createKey({ flags }: CreateKeyOptions): Promise<0 | 1> {
try {
const inlineConfig = flagsToAstroInlineConfig(flags);
const logger = createNodeLogger(inlineConfig);
const keyPromise = createCryptoKey();
const key = await keyPromise;
const encoded = await encodeKey(key);
logger.info(
'crypto',
`Generated a key to encrypt props passed to Server islands. To reuse the same key across builds, set this value as ASTRO_KEY in an environment variable on your build server.
ASTRO_KEY=${encoded}`,
);
} catch (err: unknown) {
if (err != null) {
// eslint-disable-next-line no-console
console.error(err.toString());
}
return 1;
}
return 0;
}

View file

@ -7,6 +7,7 @@ type CLICommand =
| 'help'
| 'version'
| 'add'
| 'create-key'
| 'docs'
| 'dev'
| 'build'
@ -30,6 +31,7 @@ async function printAstroHelp() {
['add', 'Add an integration.'],
['build', 'Build your project and write it to disk.'],
['check', 'Check your project for errors.'],
['create-key', 'Create a cryptography key'],
['db', 'Manage your Astro database.'],
['dev', 'Start the development server.'],
['docs', 'Open documentation in your web browser.'],
@ -78,6 +80,7 @@ function resolveCommand(flags: yargs.Arguments): CLICommand {
'build',
'preview',
'check',
'create-key',
'docs',
'db',
'info',
@ -111,6 +114,11 @@ async function runCommand(cmd: string, flags: yargs.Arguments) {
await printInfo({ flags });
return;
}
case 'create-key': {
const { createKey } = await import('./create-key/index.js');
const exitCode = await createKey({ flags });
return process.exit(exitCode);
}
case 'docs': {
const { docs } = await import('./docs/index.js');
await docs({ flags });

View file

@ -18,7 +18,7 @@ import { resolveConfig } from '../config/config.js';
import { createNodeLogger } from '../config/logging.js';
import { createSettings } from '../config/settings.js';
import { createVite } from '../create-vite.js';
import { createKey } from '../encryption.js';
import { createKey, getEnvironmentKey, hasEnvironmentKey } from '../encryption.js';
import { AstroError, AstroErrorData } from '../errors/index.js';
import type { Logger } from '../logger/core.js';
import { levels, timerMessage } from '../logger/core.js';
@ -193,6 +193,9 @@ class AstroBuilder {
green(`✓ Completed in ${getTimeStat(this.timer.init, performance.now())}.`),
);
const hasKey = hasEnvironmentKey();
const keyPromise = hasKey ? getEnvironmentKey() : createKey();
const opts: StaticBuildOptions = {
allPages,
settings: this.settings,
@ -203,7 +206,7 @@ class AstroBuilder {
pageNames,
teardownCompiler: this.teardownCompiler,
viteConfig,
key: createKey(),
key: keyPromise,
};
const { internals, ssrOutputChunkNames, contentFileNames } = await viteBuild(opts);

View file

@ -20,6 +20,37 @@ export async function createKey() {
return key;
}
// The environment variable name that can be used to provide the encrypted key.
const ENVIRONMENT_KEY_NAME = 'ASTRO_KEY' as const;
/**
* Get the encoded value of the ASTRO_KEY env var.
*/
export function getEncodedEnvironmentKey(): string {
return process.env[ENVIRONMENT_KEY_NAME] || '';
}
/**
* See if the environment variable key ASTRO_KEY is set.
*/
export function hasEnvironmentKey(): boolean {
return getEncodedEnvironmentKey() !== '';
}
/**
* Get the environment variable key and decode it into a CryptoKey.
*/
export async function getEnvironmentKey(): Promise<CryptoKey> {
// This should never happen, because we always check `hasEnvironmentKey` before this is called.
if (!hasEnvironmentKey()) {
throw new Error(
`There is no environment key defined. If you see this error there is a bug in Astro.`,
);
}
const encodedKey = getEncodedEnvironmentKey();
return decodeKey(encodedKey);
}
/**
* Takes a key that has been serialized to an array of bytes and returns a CryptoKey
*/

View file

@ -18,6 +18,7 @@ export type LoggerLabel =
| 'check'
| 'config'
| 'content'
| 'crypto'
| 'deprecated'
| 'markdown'
| 'router'
@ -27,6 +28,7 @@ export type LoggerLabel =
| 'middleware'
| 'preferences'
| 'redirects'
| 'sync'
| 'toolbar'
| 'assets'
| 'env'

View file

@ -66,7 +66,19 @@ export default async function sync(
logger,
});
const manifest = await createRouteManifest({ settings, fsMod: fs }, logger);
await runHookConfigDone({ settings, logger, command: 'sync' });
// Run `astro:config:done`
// Actions will throw if there is misconfiguration, so catch here.
try {
await runHookConfigDone({ settings, logger });
} catch (err) {
if (err instanceof Error) {
const errorMessage = err.toString();
logger.error('sync', errorMessage);
}
throw err;
}
return await syncInternal({ settings, logger, fs, force: inlineConfig.force, manifest });
}

View file

@ -9,6 +9,8 @@ interface ImportedStyle {
content: string;
}
const inlineQueryRE = /(?:\?|&)inline(?:$|&)/;
/** Given a filePath URL, crawl Vites module graph to find all style imports. */
export async function getStylesForURL(
filePath: URL,
@ -32,21 +34,20 @@ export async function getStylesForURL(
}
// Else try to load it
else {
const url = new URL(importedModule.url, 'http://localhost');
let modId = importedModule.url;
// Mark url with ?inline so Vite will return the CSS as plain string, even for CSS modules
url.searchParams.set('inline', '');
const modId = `${decodeURI(url.pathname)}${url.search}`;
if (!inlineQueryRE.test(importedModule.url)) {
if (importedModule.url.includes('?')) {
modId = importedModule.url.replace('?', '?inline&');
} else {
modId += '?inline';
}
}
try {
// The SSR module is possibly not loaded. Load it if it's null.
const ssrModule = await loader.import(modId);
css = ssrModule.default;
} catch {
// Some CSS modules, e.g. from Vue files, may not work with the ?inline query.
// If so, we fallback to a url instead
if (modId.includes('.module.')) {
importedCssUrls.add(importedModule.url);
}
// The module may not be inline-able, e.g. SCSS partials. Skip it as it may already
// be inlined into other modules if it happens to be in the graph.
continue;

View file

@ -225,7 +225,7 @@ describe('CSS', function () {
it('<style module>', async () => {
const el = $('#vue-modules');
const classes = el.attr('class').split(' ');
const moduleClass = classes.find((name) => /^_title_[\w-]+/.test(name));
const moduleClass = classes.find((name) => /^_vueModules_[\w-]+/.test(name));
// 1. check HTML
assert.equal(el.attr('class').includes(moduleClass), true);
@ -405,18 +405,13 @@ describe('CSS', function () {
});
it('resolves CSS from Vue', async () => {
const styles = ['VueModules.vue?vue&type=style&index=0&lang.module.scss'];
for (const style of styles) {
const href = $(`link[href$="${style}"]`).attr('href');
assert.equal((await fixture.fetch(href)).status, 200, style);
}
const allInjectedStyles = $('style').text().replace(/\s*/g, '');
assert.equal(allInjectedStyles.includes('.vue-css{'), true);
assert.equal(allInjectedStyles.includes('.vue-sass{'), true);
assert.equal(allInjectedStyles.includes('.vue-scss{'), true);
assert.equal(allInjectedStyles.includes('.vue-scoped[data-v-'), true);
assert.equal(allInjectedStyles.includes('._vueModules_'), true);
});
it('remove unused styles from client:load components', async () => {

View file

@ -395,6 +395,39 @@ describe('Astro Actions', () => {
assert.ok(value.date instanceof Date);
assert.ok(value.set instanceof Set);
});
it('Supports discriminated union for different form fields', async () => {
const formData = new FormData();
formData.set('type', 'first-chunk');
formData.set('alt', 'Cool image');
formData.set('image', new File([''], 'chunk-1.png'));
const reqFirst = new Request('http://example.com/_actions/imageUploadInChunks', {
method: 'POST',
body: formData,
});
const resFirst = await app.render(reqFirst);
assert.equal(resFirst.status, 200);
assert.equal(resFirst.headers.get('Content-Type'), 'application/json+devalue');
const data = devalue.parse(await resFirst.text());
const uploadId = data?.uploadId;
assert.ok(uploadId);
const formDataRest = new FormData();
formDataRest.set('type', 'rest-chunk');
formDataRest.set('uploadId', 'fake');
formDataRest.set('image', new File([''], 'chunk-2.png'));
const reqRest = new Request('http://example.com/_actions/imageUploadInChunks', {
method: 'POST',
body: formDataRest,
});
const resRest = await app.render(reqRest);
assert.equal(resRest.status, 200);
assert.equal(resRest.headers.get('Content-Type'), 'application/json+devalue');
const dataRest = devalue.parse(await resRest.text());
assert.equal('fake', dataRest?.uploadId);
});
});
});

View file

@ -10,6 +10,6 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"svelte": "^4.2.19",
"vue": "^3.4.38"
"vue": "^3.5.3"
}
}

View file

@ -1,10 +1,10 @@
<style module lang="scss">
$type: cursive;
.title {
.vueModules {
font-family: $type;
}
</style>
<template>
<h1 id="vue-modules" :class="$style.title">Vue CSS Modules</h1>
<h1 id="vue-modules" :class="$style.vueModules">Vue CSS Modules</h1>
</template>

View file

@ -7,6 +7,29 @@ const passwordSchema = z
.max(128, 'Password length exceeded. Max 128 chars.');
export const server = {
imageUploadInChunks: defineAction({
accept: 'form',
input: z.discriminatedUnion('type', [
z.object({
type: z.literal('first-chunk'),
image: z.instanceof(File),
alt: z.string(),
}),
z.object({ type: z.literal('rest-chunk'), image: z.instanceof(File), uploadId: z.string() }),
]),
handler: async (data) => {
if (data.type === 'first-chunk') {
const uploadId = Math.random().toString(36).slice(2);
return {
uploadId,
};
} else {
return {
uploadId: data.uploadId,
};
}
},
}),
subscribe: defineAction({
input: z.object({ channel: z.string() }),
handler: async ({ channel }) => {

View file

@ -9,6 +9,6 @@
"astro": "workspace:*",
"preact": "^10.23.2",
"svelte": "^4.2.19",
"vue": "^3.4.38"
"vue": "^3.5.3"
}
}

View file

@ -5,6 +5,6 @@
"dependencies": {
"@astrojs/vue": "workspace:*",
"astro": "workspace:*",
"vue": "^3.4.38"
"vue": "^3.5.3"
}
}

View file

@ -14,6 +14,6 @@
"react-dom": "^18.3.1",
"solid-js": "^1.8.22",
"svelte": "^4.2.19",
"vue": "^3.4.38"
"vue": "^3.5.3"
}
}

View file

@ -9,6 +9,6 @@
"astro": "workspace:*",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"vue": "^3.4.38"
"vue": "^3.5.3"
}
}

View file

@ -9,6 +9,6 @@
"astro": "workspace:*",
"preact": "^10.23.2",
"svelte": "^4.2.19",
"vue": "^3.4.38"
"vue": "^3.5.3"
}
}

View file

@ -3,8 +3,8 @@
"version": "0.0.0",
"private": true,
"dependencies": {
"@fontsource/monofett": "5.0.21",
"@fontsource/montserrat": "5.0.19",
"@fontsource/monofett": "5.0.22",
"@fontsource/montserrat": "5.0.20",
"astro": "workspace:*"
}
}

View file

@ -17,6 +17,6 @@
"react-dom": "^18.3.1",
"solid-js": "^1.8.22",
"svelte": "^4.2.19",
"vue": "^3.4.38"
"vue": "^3.5.3"
}
}

View file

@ -8,10 +8,10 @@
"@astrojs/vue": "workspace:*",
"astro": "workspace:*",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.43",
"postcss": "^8.4.45",
"solid-js": "^1.8.22",
"svelte": "^4.2.19",
"vue": "^3.4.38"
"vue": "^3.5.3"
},
"devDependencies": {
"postcss-preset-env": "^10.0.2"

View file

@ -0,0 +1,7 @@
import { h } from 'preact';
export default ({ nullProp }) => {
return <div id="preact-component-with-null-prop">
<p>I have a null prop: {nullProp}</p>
</div>
}

View file

@ -3,6 +3,7 @@ import { signal } from '@preact/signals';
import Signals from '../components/Signals';
import SignalsInArray from '../components/SignalsInArray';
import SignalsInObject from '../components/SignalsInObject';
import ComponentWithNullProp from '../components/ComponentWithNullProp';
const count = signal(1);
const secondCount = signal(2);
---
@ -14,6 +15,7 @@ const secondCount = signal(2);
<Signals client:load count={count} />
<Signals client:load count={count} />
<SignalsInArray client:load signalsArray={["I'm not a signal", count, count, 12345, secondCount]} />
<SignalsInObject client:load signalsObject={{title:'I am a title', counter: count}} />
<SignalsInObject client:load signalsObject={{title:'I am a title', counter: count}}, />
<ComponentWithNullProp client:load nullProp={null} />
</body>
</html>

View file

@ -1,7 +1,9 @@
import svelte from '@astrojs/svelte';
import { defineConfig } from 'astro/config';
import testAdapter from '../../../test-adapter.js';
export default defineConfig({
adapter: testAdapter(),
output: 'server',
integrations: [
svelte()

View file

@ -6,6 +6,6 @@
"@astrojs/mdx": "workspace:*",
"@astrojs/vue": "workspace:*",
"astro": "workspace:*",
"vue": "^3.4.38"
"vue": "^3.5.3"
}
}

View file

@ -5,7 +5,7 @@
"dependencies": {
"@astrojs/tailwind": "workspace:*",
"astro": "workspace:*",
"postcss": "^8.4.43",
"postcss": "^8.4.45",
"tailwindcss": "^3.4.10"
}
}

View file

@ -7,7 +7,7 @@
"@astrojs/tailwind": "workspace:*",
"astro": "workspace:*",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.43",
"postcss": "^8.4.45",
"tailwindcss": "^3.4.10"
}
}

View file

@ -5,6 +5,6 @@
"dependencies": {
"@astrojs/vue": "workspace:*",
"astro": "workspace:*",
"vue": "^3.4.38"
"vue": "^3.5.3"
}
}

View file

@ -5,6 +5,6 @@
"dependencies": {
"@astrojs/vue": "workspace:*",
"astro": "workspace:*",
"vue": "^3.4.38"
"vue": "^3.5.3"
}
}

View file

@ -7,6 +7,6 @@
"@astrojs/vue": "workspace:*",
"astro": "workspace:*",
"svelte": "^4.2.19",
"vue": "^3.4.38"
"vue": "^3.5.3"
}
}

View file

@ -140,4 +140,11 @@ describe('Preact component', () => {
assert.equal(element.find('h1').text(), 'I am a title');
assert.equal(element.find('p').text(), '1');
});
it('Can use null props', async () => {
const html = await fixture.readFile('/signals/index.html');
const $ = cheerio.load(html);
assert.equal($('#preact-component-with-null-prop').length, 1);
});
});

View file

@ -1,5 +1,11 @@
# create-astro
## 4.9.0
### Minor Changes
- [#11924](https://github.com/withastro/astro/pull/11924) [`7d70ba3`](https://github.com/withastro/astro/commit/7d70ba317889b9281c7891038779a68fcb8f0778) Thanks [@florian-lefebvre](https://github.com/florian-lefebvre)! - Updates the default Astro config with `// @ts-check` if the Typescript preset is `strict` or `strictest`
## 4.8.4
### Patch Changes

View file

@ -140,6 +140,21 @@ const FILES_TO_UPDATE = {
}
}
},
'astro.config.mjs': async (file: string, options: { value: string }) => {
if (!(options.value === 'strict' || options.value === 'strictest')) {
return;
}
try {
let data = await readFile(file, { encoding: 'utf-8' });
data = `// @ts-check\n${data}`;
await writeFile(file, data, { encoding: 'utf-8' });
} catch (err) {
// if there's no astro.config.mjs (which is very unlikely), then do nothing
if (err && (err as any).code === 'ENOENT') return;
if (err instanceof Error) throw new Error(err.message);
}
},
};
export async function setupTypeScript(value: string, ctx: PickedTypeScriptContext) {

View file

@ -1,6 +1,6 @@
# @astrojs/db
## 0.13.2-alpha.1
## 0.14.1
### Patch Changes

View file

@ -92,6 +92,6 @@
"astro-scripts": "workspace:*",
"cheerio": "1.0.0",
"typescript": "^5.5.4",
"vite": "^5.4.2"
"vite": "^5.4.3"
}
}

View file

@ -38,10 +38,10 @@
"alpinejs": "^3.0.0"
},
"devDependencies": {
"@playwright/test": "1.46.1",
"@playwright/test": "1.47.0",
"astro": "workspace:*",
"astro-scripts": "workspace:*",
"vite": "^5.4.2"
"vite": "^5.4.3"
},
"publishConfig": {
"provenance": true

View file

@ -80,7 +80,7 @@
"astro-scripts": "workspace:*",
"devalue": "^5.0.0",
"linkedom": "^0.18.4",
"vite": "^5.4.2"
"vite": "^5.4.3"
},
"engines": {
"node": "^18.17.1 || ^20.3.0 || >=21.0.0"

View file

@ -69,9 +69,9 @@
"remark-rehype": "^11.1.0",
"remark-shiki-twoslash": "^3.1.3",
"remark-toc": "^9.0.0",
"shiki": "^1.16.1",
"shiki": "^1.16.2",
"unified": "^11.0.5",
"vite": "^5.4.2"
"vite": "^5.4.3"
},
"engines": {
"node": "^18.17.1 || ^20.3.0 || >=21.0.0"

View file

@ -1,5 +1,11 @@
# @astrojs/preact
## 3.5.3
### Patch Changes
- [#11930](https://github.com/withastro/astro/pull/11930) [`4a44e82`](https://github.com/withastro/astro/commit/4a44e82bbdf0572190618d8c5882c63a6525a198) Thanks [@lukasbachlechner](https://github.com/lukasbachlechner)! - Preact components no longer throw an error if a property is null.
## 3.5.2
### Patch Changes

View file

@ -1,7 +1,7 @@
{
"name": "@astrojs/preact",
"description": "Use Preact components within Astro",
"version": "3.5.2",
"version": "3.5.3",
"type": "module",
"types": "./dist/index.d.ts",
"author": "withastro",

View file

@ -38,7 +38,9 @@ export function serializeSignals(
const signals: Signals = {};
for (const [key, value] of Object.entries(props)) {
const isPropArray = Array.isArray(value);
const isPropObject = !isSignal(value) && typeof props[key] === 'object' && !isPropArray;
// `typeof null` is 'object' in JS, so we need to check for `null` specifically
const isPropObject =
!isSignal(value) && typeof props[key] === 'object' && props[key] !== null && !isPropArray;
if (isPropObject || isPropArray) {
const values = isPropObject ? Object.keys(props[key]) : value;

View file

@ -66,7 +66,7 @@
"cheerio": "1.0.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"vite": "^5.4.2"
"vite": "^5.4.3"
},
"peerDependencies": {
"@types/react": "^17.0.50 || ^18.0.21",

View file

@ -8,6 +8,6 @@
"astro": "workspace:*",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"vue": "^3.4.38"
"vue": "^3.5.3"
}
}

View file

@ -41,7 +41,7 @@
"astro": "workspace:*",
"astro-scripts": "workspace:*",
"solid-js": "^1.8.22",
"vite": "^5.4.2"
"vite": "^5.4.3"
},
"peerDependencies": {
"solid-devtools": "^0.30.1",

View file

@ -51,13 +51,13 @@
},
"dependencies": {
"@sveltejs/vite-plugin-svelte": "^3.1.2",
"svelte2tsx": "^0.7.16"
"svelte2tsx": "^0.7.18"
},
"devDependencies": {
"astro": "workspace:*",
"astro-scripts": "workspace:*",
"svelte": "^4.2.19",
"vite": "^5.4.2"
"vite": "^5.4.3"
},
"peerDependencies": {
"astro": "^5.0.0-alpha.0",

View file

@ -34,14 +34,14 @@
},
"dependencies": {
"autoprefixer": "^10.4.20",
"postcss": "^8.4.43",
"postcss": "^8.4.45",
"postcss-load-config": "^4.0.2"
},
"devDependencies": {
"astro": "workspace:*",
"astro-scripts": "workspace:*",
"tailwindcss": "^3.4.10",
"vite": "^5.4.2"
"vite": "^5.4.3"
},
"peerDependencies": {
"astro": "^5.0.0-alpha.0",

View file

@ -46,16 +46,16 @@
"dependencies": {
"@vitejs/plugin-vue": "^5.1.3",
"@vitejs/plugin-vue-jsx": "^4.0.1",
"@vue/compiler-sfc": "^3.4.38",
"vite-plugin-vue-devtools": "^7.3.9"
"@vue/compiler-sfc": "^3.5.3",
"vite-plugin-vue-devtools": "^7.4.4"
},
"devDependencies": {
"astro": "workspace:*",
"astro-scripts": "workspace:*",
"cheerio": "1.0.0",
"linkedom": "^0.18.4",
"vite": "^5.4.2",
"vue": "^3.4.38"
"vite": "^5.4.3",
"vue": "^3.5.3"
},
"peerDependencies": {
"astro": "^5.0.0-alpha.0",

View file

@ -6,6 +6,6 @@
"@astrojs/vue": "workspace:*",
"astro": "workspace:*",
"vite-svg-loader": "5.1.0",
"vue": "^3.4.38"
"vue": "^3.5.3"
}
}

View file

@ -9,6 +9,6 @@
"@astrojs/vue": "workspace:*",
"astro": "workspace:*",
"vite-svg-loader": "5.1.0",
"vue": "^3.4.38"
"vue": "^3.5.3"
}
}

View file

@ -6,6 +6,6 @@
"@astrojs/vue": "workspace:*",
"astro": "workspace:*",
"vite-svg-loader": "5.1.0",
"vue": "^3.4.38"
"vue": "^3.5.3"
}
}

View file

@ -44,7 +44,7 @@
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.0",
"remark-smartypants": "^3.0.2",
"shiki": "^1.16.1",
"shiki": "^1.16.2",
"unified": "^11.0.5",
"unist-util-remove-position": "^5.0.0",
"unist-util-visit": "^5.0.0",

View file

@ -42,6 +42,6 @@
"astro": "workspace:*",
"astro-scripts": "workspace:*",
"typescript": "^5.5.4",
"vite": "^5.4.2"
"vite": "^5.4.3"
}
}

View file

@ -30,7 +30,7 @@
],
"dependencies": {
"ci-info": "^4.0.0",
"debug": "^4.3.6",
"debug": "^4.3.7",
"dlv": "^1.1.3",
"dset": "^3.1.3",
"is-docker": "^3.0.0",

File diff suppressed because it is too large Load diff