diff --git a/.changeset/dull-candles-perform.md b/.changeset/dull-candles-perform.md new file mode 100644 index 000000000..587247714 --- /dev/null +++ b/.changeset/dull-candles-perform.md @@ -0,0 +1,5 @@ +--- +'@astrojs/cloudflare': minor +--- + +Removes default override of image service for astro:assets. If you relied on this before, you need to add the option `imageService: 'passthrough'` to your adapter configuration. diff --git a/.changeset/grumpy-adults-retire.md b/.changeset/grumpy-adults-retire.md new file mode 100644 index 000000000..f11dc599b --- /dev/null +++ b/.changeset/grumpy-adults-retire.md @@ -0,0 +1,5 @@ +--- +'@astrojs/netlify': patch +--- + +Updates dependencies diff --git a/.changeset/shaggy-wasps-care.md b/.changeset/shaggy-wasps-care.md index 778c1d6e4..f7b8b7f33 100644 --- a/.changeset/shaggy-wasps-care.md +++ b/.changeset/shaggy-wasps-care.md @@ -2,4 +2,4 @@ '@astrojs/cloudflare': minor --- -Astro Assets Image Service +Adds support for external Cloudflare Image Resizing Service. See Cloudflare docs for more information about Pricing and Features: https://developers.cloudflare.com/images/image-resizing/ diff --git a/packages/cloudflare/package.json b/packages/cloudflare/package.json index 96983d36c..914d75d4f 100644 --- a/packages/cloudflare/package.json +++ b/packages/cloudflare/package.json @@ -45,14 +45,14 @@ "vite": "^4.4.9" }, "peerDependencies": { - "astro": "workspace:^3.3.0" + "astro": "^3.4.3" }, "devDependencies": { "execa": "^8.0.1", "fast-glob": "^3.3.1", "@types/iarna__toml": "^2.0.2", "strip-ansi": "^7.1.0", - "astro": "^3.2.3", + "astro": "^3.4.3", "chai": "^4.3.7", "cheerio": "1.0.0-rc.12", "mocha": "^10.2.0", diff --git a/packages/cloudflare/src/entrypoints/image-service.ts b/packages/cloudflare/src/entrypoints/image-service.ts index ea607ca7a..c8bee1062 100644 --- a/packages/cloudflare/src/entrypoints/image-service.ts +++ b/packages/cloudflare/src/entrypoints/image-service.ts @@ -1,371 +1,12 @@ -import type { - AstroConfig, - ExternalImageService, - ImageMetadata, - ImageTransform, - RemotePattern, -} from 'astro'; +import type { ExternalImageService } from 'astro'; -type SrcSetValue = { - transform: ImageTransform; - descriptor?: string; - attributes?: Record; -}; - -function isESMImportedImage(src: ImageMetadata | string): src is ImageMetadata { - return typeof src === 'object'; -} - -function isRemotePath(src: string) { - return /^(http|ftp|https|ws):?\/\//.test(src) || src.startsWith('data:'); -} - -function matchHostname(url: URL, hostname?: string, allowWildcard?: boolean) { - if (!hostname) { - return true; - } else if (!allowWildcard || !hostname.startsWith('*')) { - return hostname === url.hostname; - } else if (hostname.startsWith('**.')) { - const slicedHostname = hostname.slice(2); // ** length - return slicedHostname !== url.hostname && url.hostname.endsWith(slicedHostname); - } else if (hostname.startsWith('*.')) { - const slicedHostname = hostname.slice(1); // * length - const additionalSubdomains = url.hostname - .replace(slicedHostname, '') - .split('.') - .filter(Boolean); - return additionalSubdomains.length === 1; - } - - return false; -} -export function matchPort(url: URL, port?: string) { - return !port || port === url.port; -} - -export function matchProtocol(url: URL, protocol?: string) { - return !protocol || protocol === url.protocol.slice(0, -1); -} - -export function matchPathname(url: URL, pathname?: string, allowWildcard?: boolean) { - if (!pathname) { - return true; - } else if (!allowWildcard || !pathname.endsWith('*')) { - return pathname === url.pathname; - } else if (pathname.endsWith('/**')) { - const slicedPathname = pathname.slice(0, -2); // ** length - return slicedPathname !== url.pathname && url.pathname.startsWith(slicedPathname); - } else if (pathname.endsWith('/*')) { - const slicedPathname = pathname.slice(0, -1); // * length - const additionalPathChunks = url.pathname - .replace(slicedPathname, '') - .split('/') - .filter(Boolean); - return additionalPathChunks.length === 1; - } - - return false; -} - -function matchPattern(url: URL, remotePattern: RemotePattern) { - return ( - matchProtocol(url, remotePattern.protocol) && - matchHostname(url, remotePattern.hostname, true) && - matchPort(url, remotePattern.port) && - matchPathname(url, remotePattern.pathname, true) - ); -} - -function isRemoteAllowed( - src: string, - { - domains = [], - remotePatterns = [], - }: Partial> -): boolean { - if (!isRemotePath(src)) return false; - - const url = new URL(src); - return ( - domains.some((domain) => matchHostname(url, domain)) || - remotePatterns.some((remotePattern) => matchPattern(url, remotePattern)) - ); -} - -const VALID_SUPPORTED_FORMATS = [ - 'jpeg', - 'jpg', - 'png', - 'tiff', - 'webp', - 'gif', - 'svg', - 'avif', -] as const; - -const DEFAULT_OUTPUT_FORMAT = 'webp' as const; - -function isString(path: unknown): path is string { - return typeof path === 'string' || path instanceof String; -} - -function removeTrailingForwardSlash(path: string) { - return path.endsWith('/') ? path.slice(0, path.length - 1) : path; -} - -function removeLeadingForwardSlash(path: string) { - return path.startsWith('/') ? path.substring(1) : path; -} - -function trimSlashes(path: string) { - return path.replace(/^\/|\/$/g, ''); -} - -function joinPaths(...paths: (string | undefined)[]) { - return paths - .filter(isString) - .map((path, i) => { - if (i === 0) { - return removeTrailingForwardSlash(path); - } else if (i === paths.length - 1) { - return removeLeadingForwardSlash(path); - } else { - return trimSlashes(path); - } - }) - .join('/'); -} - -/** - * Returns the final dimensions of an image based on the user's options. - * - * For local images: - * - If the user specified both width and height, we'll use those. - * - If the user specified only one of them, we'll use the original image's aspect ratio to calculate the other. - * - If the user didn't specify either, we'll use the original image's dimensions. - * - * For remote images: - * - Widths and heights are always required, so we'll use the user's specified width and height. - */ -function getTargetDimensions(options: ImageTransform) { - let targetWidth = options.width; - let targetHeight = options.height; - if (isESMImportedImage(options.src)) { - const aspectRatio = options.src.width / options.src.height; - if (targetHeight && !targetWidth) { - // If we have a height but no width, use height to calculate the width - targetWidth = Math.round(targetHeight * aspectRatio); - } else if (targetWidth && !targetHeight) { - // If we have a width but no height, use width to calculate the height - targetHeight = Math.round(targetWidth / aspectRatio); - } else if (!targetWidth && !targetHeight) { - // If we have neither width or height, use the original image's dimensions - targetWidth = options.src.width; - targetHeight = options.src.height; - } - } - - // TypeScript doesn't know this, but because of previous hooks we always know that targetWidth and targetHeight are defined - return { - targetWidth: targetWidth!, - targetHeight: targetHeight!, - }; -} +import { baseService } from 'astro/assets'; +import { isESMImportedImage, isRemoteAllowed, joinPaths } from '../utils/assets.js'; const service: ExternalImageService = { - validateOptions: (options /*, imageConfig: AstroConfig['image']*/) => { - // add custom global CF image service options - // const serviceConfig = imageConfig.service.config; - - // need to add checks for limits - // https://developers.cloudflare.com/images/image-resizing/format-limitations/#format-limitations - - // `src` is missing or is `undefined`. - if (!options.src || (typeof options.src !== 'string' && typeof options.src !== 'object')) { - throw new Error('ExpectedImage'); - // throw new AstroError({ - // ...AstroErrorData.ExpectedImage, - // message: AstroErrorData.ExpectedImage.message( - // JSON.stringify(options.src), - // typeof options.src, - // JSON.stringify(options, (_, v) => (v === undefined ? null : v)) - // ), - // }); - } - - if (!isESMImportedImage(options.src)) { - // User passed an `/@fs/` path or a filesystem path instead of the full image. - if ( - options.src.startsWith('/@fs/') || - (!isRemotePath(options.src) && !options.src.startsWith('/')) - ) { - throw new Error('LocalImageUsedWrongly'); - // throw new AstroError({ - // ...AstroErrorData.LocalImageUsedWrongly, - // message: AstroErrorData.LocalImageUsedWrongly.message(options.src), - // }); - } - - // For remote images, width and height are explicitly required as we can't infer them from the file - let missingDimension: 'width' | 'height' | 'both' | undefined; - if (!options.width && !options.height) { - missingDimension = 'both'; - } else if (!options.width && options.height) { - missingDimension = 'width'; - } else if (options.width && !options.height) { - missingDimension = 'height'; - } - - if (missingDimension) { - throw new Error('MissingImageDimension'); - // throw new AstroError({ - // ...AstroErrorData.MissingImageDimension, - // message: AstroErrorData.MissingImageDimension.message(missingDimension, options.src), - // }); - } - } else { - if (!VALID_SUPPORTED_FORMATS.includes(options.src.format)) { - throw new Error('UnsupportedImageFormat'); - // throw new AstroError({ - // ...AstroErrorData.UnsupportedImageFormat, - // message: AstroErrorData.UnsupportedImageFormat.message( - // options.src.format, - // options.src.src, - // VALID_SUPPORTED_FORMATS - // ), - // }); - } - - if (options.widths && options.densities) { - throw new Error('IncompatibleDescriptorOptions'); - // throw new AstroError(AstroErrorData.IncompatibleDescriptorOptions); - } - - // We currently do not support processing SVGs, so whenever the input format is a SVG, force the output to also be one - if (options.src.format === 'svg') { - options.format = 'svg'; - } - - if ( - (options.src.format === 'svg' && options.format !== 'svg') || - (options.src.format !== 'svg' && options.format === 'svg') - ) { - throw new Error('UnsupportedImageConversion'); - // throw new AstroError(AstroErrorData.UnsupportedImageConversion); - } - } - - // If the user didn't specify a format, we'll default to `webp`. It offers the best ratio of compatibility / quality - // In the future, hopefully we can replace this with `avif`, alas, Edge. See https://caniuse.com/avif - if (!options.format) { - options.format = DEFAULT_OUTPUT_FORMAT; - } - - // Sometimes users will pass number generated from division, which can result in floating point numbers - if (options.width) options.width = Math.round(options.width); - if (options.height) options.height = Math.round(options.height); - - return options; - }, - getHTMLAttributes: (options) => { - const { targetWidth, targetHeight } = getTargetDimensions(options); - const { src, width, height, format, quality, densities, widths, formats, ...attributes } = - options; - - return { - ...attributes, - width: targetWidth, - height: targetHeight, - loading: attributes.loading ?? 'lazy', - decoding: attributes.decoding ?? 'async', - }; - }, - getSrcSet(options) { - const srcSet: SrcSetValue[] = []; - const { targetWidth } = getTargetDimensions(options); - const { widths, densities } = options; - const targetFormat = options.format ?? DEFAULT_OUTPUT_FORMAT; - - // NOTE: Depending on the cloudflare fit value, the image might never be enlarged - // For remote images, we don't know the original image's dimensions, so we cannot know the maximum width - // It is ultimately the user's responsibility to make sure they don't request images larger than the original - let imageWidth = options.width; - let maxWidth = Infinity; - - // However, if it's an imported image, we can use the original image's width as a maximum width - if (isESMImportedImage(options.src)) { - imageWidth = options.src.width; - maxWidth = imageWidth; - } - - // Since `widths` and `densities` ultimately control the width and height of the image, - // we don't want the dimensions the user specified, we'll create those ourselves. - const { - width: transformWidth, - height: transformHeight, - ...transformWithoutDimensions - } = options; - - // Collect widths to generate from specified densities or widths - const allWidths: { maxTargetWidth: number; descriptor: `${number}x` | `${number}w` }[] = []; - if (densities) { - // Densities can either be specified as numbers, or descriptors (ex: '1x'), we'll convert them all to numbers - const densityValues = densities.map((density) => { - if (typeof density === 'number') { - return density; - } else { - return parseFloat(density); - } - }); - - // Calculate the widths for each density, rounding to avoid floats. - const densityWidths = densityValues - .sort() - .map((density) => Math.round(targetWidth * density)); - - allWidths.push( - ...densityWidths.map((width, index) => ({ - maxTargetWidth: Math.min(width, maxWidth), - descriptor: `${densityValues[index]}x` as const, - })) - ); - } else if (widths) { - allWidths.push( - ...widths.map((width) => ({ - maxTargetWidth: Math.min(width, maxWidth), - descriptor: `${width}w` as const, - })) - ); - } - - // Caution: The logic below is a bit tricky, as we need to make sure we don't generate the same image multiple times - // When making changes, make sure to test with different combinations of local/remote images widths, densities, and dimensions etc. - for (const { maxTargetWidth, descriptor } of allWidths) { - const srcSetTransform: ImageTransform = { ...transformWithoutDimensions }; - - // Only set the width if it's different from the original image's width, to avoid generating the same image multiple times - if (maxTargetWidth !== imageWidth) { - srcSetTransform.width = maxTargetWidth; - } else { - // If the width is the same as the original image's width, and we have both dimensions, it probably means - // it's a remote image, so we'll use the user's specified dimensions to avoid recreating the original image unnecessarily - if (options.width && options.height) { - srcSetTransform.width = options.width; - srcSetTransform.height = options.height; - } - } - - srcSet.push({ - transform: srcSetTransform, - descriptor, - attributes: { - type: `image/${targetFormat}`, - }, - }); - } - - return srcSet; - }, + validateOptions: baseService.validateOptions, + getHTMLAttributes: baseService.getHTMLAttributes, + getSrcSet: baseService.getSrcSet, getURL: (options, imageConfig) => { const resizingParams = []; if (options.width) resizingParams.push(`width=${options.width}`); @@ -385,13 +26,15 @@ const service: ExternalImageService = { } const imageEndpoint = joinPaths( - //@ts-ignore + // FIXME: I have no idea why we get: + // Property 'env' does not exist on type 'ImportMeta'.ts(2339) + + // @ts-ignore import.meta.env.BASE_URL, '/cdn-cgi/image', resizingParams.join(','), imageSource ); - console.log(imageEndpoint); return imageEndpoint; }, diff --git a/packages/cloudflare/src/getAdapter.ts b/packages/cloudflare/src/getAdapter.ts index e8acb283a..0a4879557 100644 --- a/packages/cloudflare/src/getAdapter.ts +++ b/packages/cloudflare/src/getAdapter.ts @@ -13,8 +13,7 @@ export function getAdapter({ serverOutput: 'stable', assets: { supportKind: 'stable', - // FIXME: UNDO THIS BEFORE RELEASE - isSharpCompatible: true, + isSharpCompatible: false, isSquooshCompatible: false, }, }; diff --git a/packages/cloudflare/src/index.ts b/packages/cloudflare/src/index.ts index 29d056f4e..228ba0bd0 100644 --- a/packages/cloudflare/src/index.ts +++ b/packages/cloudflare/src/index.ts @@ -1,7 +1,6 @@ import type { AstroConfig, AstroIntegration, RouteData } from 'astro'; import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects'; -import { passthroughImageService, sharpImageService } from 'astro/config'; import { AstroError } from 'astro/errors'; import esbuild from 'esbuild'; import { Miniflare } from 'miniflare'; @@ -13,6 +12,7 @@ import glob from 'tiny-glob'; import { getAdapter } from './getAdapter.js'; import { deduplicatePatterns } from './utils/deduplicatePatterns.js'; import { getCFObject } from './utils/getCFObject.js'; +import { prepareImageConfig } from './utils/image-config.js'; import { getD1Bindings, getDOBindings, @@ -31,6 +31,7 @@ type CF_RUNTIME = { mode: 'off' } | { mode: 'remote' } | { mode: 'local'; persis type Options = { mode?: 'directory' | 'advanced'; functionPerRoute?: boolean; + imageService?: 'passthrough' | 'external'; /** Configure automatic `routes.json` generation */ routes?: { /** Strategy for generating `include` and `exclude` patterns @@ -106,16 +107,6 @@ export default function createIntegration(args?: Options): AstroIntegration { name: '@astrojs/cloudflare', hooks: { 'astro:config:setup': ({ command, config, updateConfig, logger }) => { - let imageConfigOverwrite = false; - if ( - config.image.service.entrypoint === 'astro/assets/services/sharp' || - config.image.service.entrypoint === 'astro/assets/services/squoosh' - ) { - logger.warn( - `The current configuration does not support image optimization. To allow your project to build with the original, unoptimized images, the image service has been automatically switched to the 'noop' option. See https://docs.astro.build/en/reference/configuration-reference/#imageservice` - ); - imageConfigOverwrite = true; - } updateConfig({ build: { client: new URL(`.${config.base}`, config.outDir), @@ -132,16 +123,7 @@ export default function createIntegration(args?: Options): AstroIntegration { }), ], }, - image: imageConfigOverwrite - ? { ...config.image, service: passthroughImageService() } - : { - service: - command === 'dev' - ? sharpImageService() - : { - entrypoint: '@astrojs/cloudflare/image-service', - }, - }, + image: prepareImageConfig(args?.imageService ?? 'DEFAULT', config.image, command, logger), }); }, 'astro:config:done': ({ setAdapter, config, logger }) => { diff --git a/packages/cloudflare/src/utils/assets.ts b/packages/cloudflare/src/utils/assets.ts new file mode 100644 index 000000000..68cfe7189 --- /dev/null +++ b/packages/cloudflare/src/utils/assets.ts @@ -0,0 +1,101 @@ +import type { AstroConfig, ImageMetadata, RemotePattern } from 'astro'; + +export function isESMImportedImage(src: ImageMetadata | string): src is ImageMetadata { + return typeof src === 'object'; +} +export function isRemotePath(src: string) { + return /^(http|ftp|https|ws):?\/\//.test(src) || src.startsWith('data:'); +} +export function matchHostname(url: URL, hostname?: string, allowWildcard?: boolean) { + if (!hostname) { + return true; + } else if (!allowWildcard || !hostname.startsWith('*')) { + return hostname === url.hostname; + } else if (hostname.startsWith('**.')) { + const slicedHostname = hostname.slice(2); // ** length + return slicedHostname !== url.hostname && url.hostname.endsWith(slicedHostname); + } else if (hostname.startsWith('*.')) { + const slicedHostname = hostname.slice(1); // * length + const additionalSubdomains = url.hostname + .replace(slicedHostname, '') + .split('.') + .filter(Boolean); + return additionalSubdomains.length === 1; + } + + return false; +} +export function matchPort(url: URL, port?: string) { + return !port || port === url.port; +} +export function matchProtocol(url: URL, protocol?: string) { + return !protocol || protocol === url.protocol.slice(0, -1); +} +export function matchPathname(url: URL, pathname?: string, allowWildcard?: boolean) { + if (!pathname) { + return true; + } else if (!allowWildcard || !pathname.endsWith('*')) { + return pathname === url.pathname; + } else if (pathname.endsWith('/**')) { + const slicedPathname = pathname.slice(0, -2); // ** length + return slicedPathname !== url.pathname && url.pathname.startsWith(slicedPathname); + } else if (pathname.endsWith('/*')) { + const slicedPathname = pathname.slice(0, -1); // * length + const additionalPathChunks = url.pathname + .replace(slicedPathname, '') + .split('/') + .filter(Boolean); + return additionalPathChunks.length === 1; + } + + return false; +} +export function matchPattern(url: URL, remotePattern: RemotePattern) { + return ( + matchProtocol(url, remotePattern.protocol) && + matchHostname(url, remotePattern.hostname, true) && + matchPort(url, remotePattern.port) && + matchPathname(url, remotePattern.pathname, true) + ); +} +export function isRemoteAllowed( + src: string, + { + domains = [], + remotePatterns = [], + }: Partial> +): boolean { + if (!isRemotePath(src)) return false; + + const url = new URL(src); + return ( + domains.some((domain) => matchHostname(url, domain)) || + remotePatterns.some((remotePattern) => matchPattern(url, remotePattern)) + ); +} +export function isString(path: unknown): path is string { + return typeof path === 'string' || path instanceof String; +} +export function removeTrailingForwardSlash(path: string) { + return path.endsWith('/') ? path.slice(0, path.length - 1) : path; +} +export function removeLeadingForwardSlash(path: string) { + return path.startsWith('/') ? path.substring(1) : path; +} +export function trimSlashes(path: string) { + return path.replace(/^\/|\/$/g, ''); +} +export function joinPaths(...paths: (string | undefined)[]) { + return paths + .filter(isString) + .map((path, i) => { + if (i === 0) { + return removeTrailingForwardSlash(path); + } else if (i === paths.length - 1) { + return removeLeadingForwardSlash(path); + } else { + return trimSlashes(path); + } + }) + .join('/'); +} diff --git a/packages/cloudflare/src/utils/image-config.ts b/packages/cloudflare/src/utils/image-config.ts new file mode 100644 index 000000000..2f65140f1 --- /dev/null +++ b/packages/cloudflare/src/utils/image-config.ts @@ -0,0 +1,36 @@ +import type { AstroIntegrationLogger } from 'astro'; +import { passthroughImageService, sharpImageService } from 'astro/config'; + +export function prepareImageConfig( + service: string, + // FIXME: type + config: any, + // FIXME: type + command: any, + logger: AstroIntegrationLogger +) { + switch (service) { + case 'passthrough': + return { ...config.image, service: passthroughImageService() }; + + case 'external': + return { + ...config.image, + service: + command === 'dev' + ? sharpImageService() + : { entrypoint: '@astrojs/cloudflare/image-service' }, + }; + + default: + if ( + config.service.entrypoint === 'astro/assets/services/sharp' || + config.service.entrypoint === 'astro/assets/services/squoosh' + ) { + logger.warn( + `The current configuration does not support image optimization. To allow your project to build with the original, unoptimized images, the option \`imageService: 'passthrough'\` needs to be set. See our Adapter Docs: https://docs.astro.build/en/guides/integrations-guide/cloudflare/` + ); + } + return { ...config.image }; + } +} diff --git a/packages/cloudflare/test/astro-assets.test.js b/packages/cloudflare/test/astro-assets.test.js new file mode 100644 index 000000000..6e6c39c03 --- /dev/null +++ b/packages/cloudflare/test/astro-assets.test.js @@ -0,0 +1,19 @@ +import { loadFixture } from '@astrojs/test-utils'; +import { expect } from 'chai'; + +// Asset bundling +describe('Assets', () => { + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: new URL('./fixtures/assets/', import.meta.url).toString(), + }); + await fixture.build(); + }); + + it('adds the correct image service', async () => { + const dist = await fixture.readFile('/_worker.js'); + expect(dist).to.include('cdn-cgi/image'); + }); +}); diff --git a/packages/cloudflare/test/dev-runtime.test.js b/packages/cloudflare/test/dev-runtime.test.js index f8d2c2d6d..5bca05033 100644 --- a/packages/cloudflare/test/dev-runtime.test.js +++ b/packages/cloudflare/test/dev-runtime.test.js @@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url'; import { astroCli } from './_test-utils.js'; const root = new URL('./fixtures/dev-runtime/', import.meta.url); -describe('Runtime Astro Dev', () => { +describe('DevRuntime', () => { let cli; before(async () => { cli = astroCli(fileURLToPath(root), 'dev', '--host', '127.0.0.1'); diff --git a/packages/cloudflare/test/fixtures/assets/astro.config.mjs b/packages/cloudflare/test/fixtures/assets/astro.config.mjs new file mode 100644 index 000000000..52089ab81 --- /dev/null +++ b/packages/cloudflare/test/fixtures/assets/astro.config.mjs @@ -0,0 +1,18 @@ +//@ts-check +import { defineConfig } from 'astro/config'; +import cloudflare from "@astrojs/cloudflare"; + +// https://astro.build/config +export default defineConfig({ + output: 'server', + adapter: cloudflare({ + imageService: 'external', + }), + vite: { + build: { + minify: false, + assetsInlineLimit: 0, + }, + }, +}); + diff --git a/packages/cloudflare/test/fixtures/assets/package.json b/packages/cloudflare/test/fixtures/assets/package.json new file mode 100644 index 000000000..c0bafd249 --- /dev/null +++ b/packages/cloudflare/test/fixtures/assets/package.json @@ -0,0 +1,9 @@ +{ + "name": "@test/astro-cloudflare-assets", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/cloudflare": "workspace:*", + "astro": "^3.4.3" + } +} diff --git a/packages/cloudflare/test/fixtures/assets/src/env.d.ts b/packages/cloudflare/test/fixtures/assets/src/env.d.ts new file mode 100644 index 000000000..8c34fb45e --- /dev/null +++ b/packages/cloudflare/test/fixtures/assets/src/env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/packages/cloudflare/test/fixtures/assets/src/images/penguin1.jpg b/packages/cloudflare/test/fixtures/assets/src/images/penguin1.jpg new file mode 100644 index 000000000..1a8986ac5 Binary files /dev/null and b/packages/cloudflare/test/fixtures/assets/src/images/penguin1.jpg differ diff --git a/packages/cloudflare/test/fixtures/assets/src/images/penguin2.jpg b/packages/cloudflare/test/fixtures/assets/src/images/penguin2.jpg new file mode 100644 index 000000000..7cde45593 Binary files /dev/null and b/packages/cloudflare/test/fixtures/assets/src/images/penguin2.jpg differ diff --git a/packages/cloudflare/test/fixtures/assets/src/images/twitter.png b/packages/cloudflare/test/fixtures/assets/src/images/twitter.png new file mode 100644 index 000000000..8ffef69d6 Binary files /dev/null and b/packages/cloudflare/test/fixtures/assets/src/images/twitter.png differ diff --git a/packages/cloudflare/test/fixtures/assets/src/images/twitter@2x.png b/packages/cloudflare/test/fixtures/assets/src/images/twitter@2x.png new file mode 100644 index 000000000..d08fdc0a6 Binary files /dev/null and b/packages/cloudflare/test/fixtures/assets/src/images/twitter@2x.png differ diff --git a/packages/cloudflare/test/fixtures/assets/src/images/twitter@3x.png b/packages/cloudflare/test/fixtures/assets/src/images/twitter@3x.png new file mode 100644 index 000000000..739aaa762 Binary files /dev/null and b/packages/cloudflare/test/fixtures/assets/src/images/twitter@3x.png differ diff --git a/packages/cloudflare/test/fixtures/assets/src/pages/index.astro b/packages/cloudflare/test/fixtures/assets/src/pages/index.astro new file mode 100644 index 000000000..8da7feb0c --- /dev/null +++ b/packages/cloudflare/test/fixtures/assets/src/pages/index.astro @@ -0,0 +1,26 @@ +--- +import p1Url from '../images/penguin1.jpg'; +import p2Url from '../images/penguin2.jpg?url'; +--- + +This Site + + +

Icons

+ + + + + + + + + + + + diff --git a/packages/cloudflare/test/fixtures/dev-runtime/package.json b/packages/cloudflare/test/fixtures/dev-runtime/package.json index 9fbfe89df..24474660d 100644 --- a/packages/cloudflare/test/fixtures/dev-runtime/package.json +++ b/packages/cloudflare/test/fixtures/dev-runtime/package.json @@ -4,9 +4,9 @@ "private": true, "dependencies": { "@astrojs/cloudflare": "workspace:*", - "astro": "^3.2.3" + "astro": "3.4.3" }, "devDependencies": { - "wrangler": "^3.13.1" + "wrangler": "3.15.0" } } diff --git a/packages/cloudflare/test/fixtures/routes-json/src/dynamicOnly/env.d.ts b/packages/cloudflare/test/fixtures/routes-json/src/dynamicOnly/env.d.ts new file mode 100644 index 000000000..8c34fb45e --- /dev/null +++ b/packages/cloudflare/test/fixtures/routes-json/src/dynamicOnly/env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/packages/cloudflare/test/fixtures/routes-json/src/mixed/env.d.ts b/packages/cloudflare/test/fixtures/routes-json/src/mixed/env.d.ts new file mode 100644 index 000000000..8c34fb45e --- /dev/null +++ b/packages/cloudflare/test/fixtures/routes-json/src/mixed/env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/packages/cloudflare/test/fixtures/routes-json/src/staticOnly/env.d.ts b/packages/cloudflare/test/fixtures/routes-json/src/staticOnly/env.d.ts new file mode 100644 index 000000000..8c34fb45e --- /dev/null +++ b/packages/cloudflare/test/fixtures/routes-json/src/staticOnly/env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/packages/cloudflare/test/fixtures/wasm-directory/src/env.d.ts b/packages/cloudflare/test/fixtures/wasm-directory/src/env.d.ts new file mode 100644 index 000000000..8c34fb45e --- /dev/null +++ b/packages/cloudflare/test/fixtures/wasm-directory/src/env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/packages/cloudflare/test/fixtures/wasm-function-per-route/src/env.d.ts b/packages/cloudflare/test/fixtures/wasm-function-per-route/src/env.d.ts new file mode 100644 index 000000000..8c34fb45e --- /dev/null +++ b/packages/cloudflare/test/fixtures/wasm-function-per-route/src/env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/packages/cloudflare/test/fixtures/wasm/src/env.d.ts b/packages/cloudflare/test/fixtures/wasm/src/env.d.ts new file mode 100644 index 000000000..8c34fb45e --- /dev/null +++ b/packages/cloudflare/test/fixtures/wasm/src/env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/packages/netlify/package.json b/packages/netlify/package.json index 58f7d556e..d1be68db0 100644 --- a/packages/netlify/package.json +++ b/packages/netlify/package.json @@ -41,13 +41,13 @@ "esbuild": "^0.19.2" }, "peerDependencies": { - "astro": "^3.3.0" + "astro": "^3.4.3" }, "devDependencies": { "@netlify/edge-functions": "^2.0.0", "@netlify/edge-handler-types": "^0.34.1", "@types/node": "^18.17.8", - "astro": "^3.3.0", + "astro": "^3.4.3", "chai": "^4.3.7", "chai-jest-snapshot": "^2.0.0", "cheerio": "1.0.0-rc.12", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fe4ecf6b5..e3535eddd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -100,8 +100,8 @@ importers: specifier: ^2.0.2 version: 2.0.2 astro: - specifier: ^3.2.3 - version: 3.3.0(@types/node@18.17.8)(typescript@5.1.6) + specifier: ^3.4.3 + version: 3.4.3(@types/node@18.17.8)(typescript@5.1.6) chai: specifier: ^4.3.7 version: 4.3.7 @@ -117,6 +117,9 @@ importers: mocha: specifier: ^10.2.0 version: 10.2.0 + srcset-parse: + specifier: ^1.1.0 + version: 1.1.0 strip-ansi: specifier: ^7.1.0 version: 7.1.0 @@ -124,6 +127,15 @@ importers: specifier: ^3.11.0 version: 3.11.0 + packages/cloudflare/test/fixtures/assets: + dependencies: + '@astrojs/cloudflare': + specifier: workspace:* + version: link:../../.. + astro: + specifier: ^3.4.3 + version: 3.4.3(@types/node@18.17.8)(typescript@5.1.6) + packages/cloudflare/test/fixtures/dev-runtime: dependencies: '@astrojs/cloudflare': @@ -267,8 +279,8 @@ importers: specifier: ^18.17.8 version: 18.17.8 astro: - specifier: ^3.3.0 - version: 3.3.0(@types/node@18.17.8)(typescript@5.2.2) + specifier: ^3.4.3 + version: 3.4.3(@types/node@18.17.8)(typescript@5.2.2) chai: specifier: ^4.3.7 version: 4.3.7 @@ -530,6 +542,30 @@ packages: vfile: 5.3.7 transitivePeerDependencies: - supports-color + dev: false + + /@astrojs/markdown-remark@3.3.0(astro@3.4.3): + resolution: {integrity: sha512-ezFzEiZygc/ASe2Eul9v1yrTbNGqSbR348UGNXQ4Dtkx8MYRwfiBfmPm6VnEdfIGkW+bi5qIUReKfc7mPVUkIg==} + peerDependencies: + astro: '*' + dependencies: + '@astrojs/prism': 3.0.0 + astro: 3.4.3(@types/node@18.17.8)(typescript@5.1.6) + github-slugger: 2.0.0 + import-meta-resolve: 3.0.0 + mdast-util-definitions: 6.0.0 + rehype-raw: 6.1.1 + rehype-stringify: 9.0.4 + remark-gfm: 3.0.1 + remark-parse: 10.0.2 + remark-rehype: 10.1.0 + remark-smartypants: 2.0.0 + shikiji: 0.6.10 + unified: 10.1.2 + unist-util-visit: 4.1.2 + vfile: 5.3.7 + transitivePeerDependencies: + - supports-color /@astrojs/prism@3.0.0: resolution: {integrity: sha512-g61lZupWq1bYbcBnYZqdjndShr/J3l/oFobBKPA3+qMat146zce3nz2kdO4giGbhYDt4gYdhmoBz0vZJ4sIurQ==} @@ -563,6 +599,21 @@ packages: which-pm-runs: 1.1.0 transitivePeerDependencies: - supports-color + dev: false + + /@astrojs/telemetry@3.0.4: + resolution: {integrity: sha512-A+0c7k/Xy293xx6odsYZuXiaHO0PL+bnDoXOc47sGDF5ffIKdKQGRPFl2NMlCF4L0NqN4Ynbgnaip+pPF0s7pQ==} + engines: {node: '>=18.14.1'} + dependencies: + ci-info: 3.8.0 + debug: 4.3.4(supports-color@8.1.1) + dlv: 1.1.3 + dset: 3.1.2 + is-docker: 3.0.0 + is-wsl: 3.1.0 + which-pm-runs: 1.1.0 + transitivePeerDependencies: + - supports-color /@astrojs/underscore-redirects@0.3.3: resolution: {integrity: sha512-qDAKhFO4M1KzP7mxoJfiehf8oyf3EB158MxAa6z10NeD2pR3o4K3LlOQI8CfJgXE+BDBQcnaLvVCg/Mz/Gkg4Q==} @@ -2659,16 +2710,17 @@ packages: - supports-color - terser - typescript + dev: false - /astro@3.3.0(@types/node@18.17.8)(typescript@5.2.2): - resolution: {integrity: sha512-O3MsXULamxQMy6sBgv07iVe5teJ41o+9tVScB/Yo2Io0XwvLXVhjVrjAxKpulBcKpU3/LyOpVfj/x63fcONbPA==} + /astro@3.4.3(@types/node@18.17.8)(typescript@5.1.6): + resolution: {integrity: sha512-6zcONz2FjX6GaAy6Mysie4gT8rH5QCBXyPjkuYZKiPdV+sXmDpLm44J2MpQwA3mHb8YQ35L5O4nNg09d4luQ7g==} engines: {node: '>=18.14.1', npm: '>=6.14.0'} hasBin: true dependencies: '@astrojs/compiler': 2.1.0 '@astrojs/internal-helpers': 0.2.1 - '@astrojs/markdown-remark': 3.3.0(astro@3.3.0) - '@astrojs/telemetry': 3.0.3 + '@astrojs/markdown-remark': 3.3.0(astro@3.4.3) + '@astrojs/telemetry': 3.0.4 '@babel/core': 7.23.0 '@babel/generator': 7.23.0 '@babel/parser': 7.23.0 @@ -2684,6 +2736,7 @@ packages: common-ancestor-path: 1.0.1 cookie: 0.5.0 debug: 4.3.4(supports-color@8.1.1) + deterministic-object-hash: 1.3.1 devalue: 4.3.2 diff: 5.1.0 es-module-lexer: 1.3.1 @@ -2698,9 +2751,11 @@ packages: js-yaml: 4.1.0 kleur: 4.1.5 magic-string: 0.30.3 + mdast-util-to-hast: 12.3.0 mime: 3.0.0 ora: 7.0.1 p-limit: 4.0.0 + p-queue: 7.4.1 path-to-regexp: 6.2.1 preferred-pm: 3.1.2 probe-image-size: 7.2.3 @@ -2712,14 +2767,90 @@ packages: shikiji: 0.6.10 string-width: 6.1.0 strip-ansi: 7.1.0 - tsconfck: 3.0.0-next.9(typescript@5.2.2) + tsconfck: 3.0.0(typescript@5.1.6) unist-util-visit: 4.1.2 vfile: 5.3.7 vite: 4.4.9(@types/node@18.17.8) vitefu: 0.2.4(vite@4.4.9) which-pm: 2.1.1 yargs-parser: 21.1.1 - zod: 3.21.1 + zod: 3.22.4 + optionalDependencies: + sharp: 0.32.6 + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + - typescript + + /astro@3.4.3(@types/node@18.17.8)(typescript@5.2.2): + resolution: {integrity: sha512-6zcONz2FjX6GaAy6Mysie4gT8rH5QCBXyPjkuYZKiPdV+sXmDpLm44J2MpQwA3mHb8YQ35L5O4nNg09d4luQ7g==} + engines: {node: '>=18.14.1', npm: '>=6.14.0'} + hasBin: true + dependencies: + '@astrojs/compiler': 2.1.0 + '@astrojs/internal-helpers': 0.2.1 + '@astrojs/markdown-remark': 3.3.0(astro@3.4.3) + '@astrojs/telemetry': 3.0.4 + '@babel/core': 7.23.0 + '@babel/generator': 7.23.0 + '@babel/parser': 7.23.0 + '@babel/plugin-transform-react-jsx': 7.22.15(@babel/core@7.23.0) + '@babel/traverse': 7.23.0 + '@babel/types': 7.23.0 + '@types/babel__core': 7.20.2 + acorn: 8.10.0 + boxen: 7.1.1 + chokidar: 3.5.3 + ci-info: 3.8.0 + clsx: 2.0.0 + common-ancestor-path: 1.0.1 + cookie: 0.5.0 + debug: 4.3.4(supports-color@8.1.1) + deterministic-object-hash: 1.3.1 + devalue: 4.3.2 + diff: 5.1.0 + es-module-lexer: 1.3.1 + esbuild: 0.19.2 + estree-walker: 3.0.3 + execa: 8.0.1 + fast-glob: 3.3.1 + github-slugger: 2.0.0 + gray-matter: 4.0.3 + html-escaper: 3.0.3 + http-cache-semantics: 4.1.1 + js-yaml: 4.1.0 + kleur: 4.1.5 + magic-string: 0.30.3 + mdast-util-to-hast: 12.3.0 + mime: 3.0.0 + ora: 7.0.1 + p-limit: 4.0.0 + p-queue: 7.4.1 + path-to-regexp: 6.2.1 + preferred-pm: 3.1.2 + probe-image-size: 7.2.3 + prompts: 2.4.2 + rehype: 12.0.1 + resolve: 1.22.6 + semver: 7.5.4 + server-destroy: 1.0.1 + shikiji: 0.6.10 + string-width: 6.1.0 + strip-ansi: 7.1.0 + tsconfck: 3.0.0(typescript@5.2.2) + unist-util-visit: 4.1.2 + vfile: 5.3.7 + vite: 4.4.9(@types/node@18.17.8) + vitefu: 0.2.4(vite@4.4.9) + which-pm: 2.1.1 + yargs-parser: 21.1.1 + zod: 3.22.4 optionalDependencies: sharp: 0.32.6 transitivePeerDependencies: @@ -3459,6 +3590,9 @@ packages: requiresBuild: true optional: true + /deterministic-object-hash@1.3.1: + resolution: {integrity: sha512-kQDIieBUreEgY+akq0N7o4FzZCr27dPG1xr3wq267vPwDlSXQ3UMcBXHqTGUBaM/5WDS1jwTYjxRhUzHeuiAvw==} + /devalue@4.3.2: resolution: {integrity: sha512-KqFl6pOgOW+Y6wJgu80rHpo2/3H07vr8ntR9rkkFIRETewbf5GaYYcakYfiKz89K+sLsuPkQIZaXDMjUObZwWg==} @@ -3925,6 +4059,9 @@ packages: engines: {node: '>=0.10.0'} dev: true + /eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + /execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -5995,6 +6132,17 @@ packages: aggregate-error: 4.0.1 dev: true + /p-queue@7.4.1: + resolution: {integrity: sha512-vRpMXmIkYF2/1hLBKisKeVYJZ8S2tZ0zEAmIJgdVKP2nq0nh4qCdf8bgw+ZgKrkh71AOCaqzwbJJk1WtdcF3VA==} + engines: {node: '>=12'} + dependencies: + eventemitter3: 5.0.1 + p-timeout: 5.1.0 + + /p-timeout@5.1.0: + resolution: {integrity: sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==} + engines: {node: '>=12'} + /p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -6781,6 +6929,10 @@ packages: /sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + /srcset-parse@1.1.0: + resolution: {integrity: sha512-JWp4cG2eybkvKA1QUHGoNK6JDEYcOnSuhzNGjZuYUPqXreDl/VkkvP2sZW7Rmh+icuCttrR9ccb2WPIazyM/Cw==} + dev: true + /stacktracey@2.1.8: resolution: {integrity: sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==} dependencies: @@ -7105,8 +7257,8 @@ packages: code-block-writer: 11.0.3 dev: true - /tsconfck@3.0.0-next.9(typescript@5.1.6): - resolution: {integrity: sha512-bgVlu3qcRUZpm9Au1IHiPDkb8XU+72bRkXrBaJsiAjIlixtkbKLe4q1odrrqG0rVHvh0Q4R3adT/nh1FwzftXA==} + /tsconfck@3.0.0(typescript@5.1.6): + resolution: {integrity: sha512-w3wnsIrJNi7avf4Zb0VjOoodoO0woEqGgZGQm+LHH9przdUI+XDKsWAXwxHA1DaRTjeuZNcregSzr7RaA8zG9A==} engines: {node: ^18 || >=20} hasBin: true peerDependencies: @@ -7117,8 +7269,8 @@ packages: dependencies: typescript: 5.1.6 - /tsconfck@3.0.0-next.9(typescript@5.2.2): - resolution: {integrity: sha512-bgVlu3qcRUZpm9Au1IHiPDkb8XU+72bRkXrBaJsiAjIlixtkbKLe4q1odrrqG0rVHvh0Q4R3adT/nh1FwzftXA==} + /tsconfck@3.0.0(typescript@5.2.2): + resolution: {integrity: sha512-w3wnsIrJNi7avf4Zb0VjOoodoO0woEqGgZGQm+LHH9przdUI+XDKsWAXwxHA1DaRTjeuZNcregSzr7RaA8zG9A==} engines: {node: ^18 || >=20} hasBin: true peerDependencies: @@ -7130,6 +7282,19 @@ packages: typescript: 5.2.2 dev: true + /tsconfck@3.0.0-next.9(typescript@5.1.6): + resolution: {integrity: sha512-bgVlu3qcRUZpm9Au1IHiPDkb8XU+72bRkXrBaJsiAjIlixtkbKLe4q1odrrqG0rVHvh0Q4R3adT/nh1FwzftXA==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + dependencies: + typescript: 5.1.6 + dev: false + /tsconfig-resolver@3.0.1: resolution: {integrity: sha512-ZHqlstlQF449v8glscGRXzL6l2dZvASPCdXJRWG4gHEZlUVx2Jtmr+a2zeVG4LCsKhDXKRj5R3h0C/98UcVAQg==} dependencies: @@ -8082,5 +8247,8 @@ packages: /zod@3.21.1: resolution: {integrity: sha512-+dTu2m6gmCbO9Ahm4ZBDapx2O6ZY9QSPXst2WXjcznPMwf2YNpn3RevLx4KkZp1OPW/ouFcoBtBzFz/LeY69oA==} + /zod@3.22.4: + resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} + /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}