@astrojs/cloudflare 7.6.4 → 7.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -161,6 +161,15 @@ The following example automatically generates `_routes.json` while including and
161
161
  });
162
162
  ```
163
163
 
164
+ ### `imageService`
165
+
166
+ `imageService: "passthrough" | "cloudflare"`
167
+
168
+ Determines which image service is used by the adapter. The adapter will default to `passthrough` mode when an incompatible image service is configured. Otherwise, it will use the globally configured image service:
169
+
170
+ - **`cloudflare`:** Uses the [Cloudflare Image Resizing](https://developers.cloudflare.com/images/image-resizing/) service.
171
+ - **`passthrough`:** Uses the existing [`noop`](https://docs.astro.build/en/guides/images/#configure-no-op-passthrough-service) service.
172
+
164
173
  ### `wasmModuleImports`
165
174
 
166
175
  `wasmModuleImports: boolean`
@@ -0,0 +1,3 @@
1
+ import type { ExternalImageService } from 'astro';
2
+ declare const service: ExternalImageService;
3
+ export default service;
@@ -0,0 +1,34 @@
1
+ import { baseService } from 'astro/assets';
2
+ import { isESMImportedImage, isRemoteAllowed, joinPaths } from '../utils/assets.js';
3
+ const service = {
4
+ ...baseService,
5
+ getURL: (options, imageConfig) => {
6
+ const resizingParams = [];
7
+ if (options.width)
8
+ resizingParams.push(`width=${options.width}`);
9
+ if (options.height)
10
+ resizingParams.push(`height=${options.height}`);
11
+ if (options.quality)
12
+ resizingParams.push(`quality=${options.quality}`);
13
+ if (options.fit)
14
+ resizingParams.push(`fit=${options.fit}`);
15
+ if (options.format)
16
+ resizingParams.push(`format=${options.format}`);
17
+ let imageSource = '';
18
+ if (isESMImportedImage(options.src)) {
19
+ imageSource = options.src.src;
20
+ }
21
+ else if (isRemoteAllowed(options.src, imageConfig)) {
22
+ imageSource = options.src;
23
+ }
24
+ else {
25
+ // If it's not an imported image, nor is it allowed using the current domains or remote patterns, we'll just return the original URL
26
+ return options.src;
27
+ }
28
+ const imageEndpoint = joinPaths(
29
+ // @ts-expect-error - Property 'env' does not exist on type 'ImportMeta'.ts(2339)
30
+ import.meta.env.BASE_URL, '/cdn-cgi/image', resizingParams.join(','), imageSource);
31
+ return imageEndpoint;
32
+ },
33
+ };
34
+ export default service;
@@ -14,31 +14,25 @@ export function createExports(manifest) {
14
14
  if (manifest.assets.has(pathname)) {
15
15
  return env.ASSETS.fetch(request);
16
16
  }
17
- let routeData = app.match(request, { matchNotFound: true });
18
- if (routeData) {
19
- Reflect.set(request, Symbol.for('astro.clientAddress'), request.headers.get('cf-connecting-ip'));
20
- const locals = {
21
- runtime: {
22
- waitUntil: (promise) => {
23
- context.waitUntil(promise);
24
- },
25
- env: env,
26
- cf: request.cf,
27
- caches: caches,
17
+ let routeData = app.match(request);
18
+ Reflect.set(request, Symbol.for('astro.clientAddress'), request.headers.get('cf-connecting-ip'));
19
+ const locals = {
20
+ runtime: {
21
+ waitUntil: (promise) => {
22
+ context.waitUntil(promise);
28
23
  },
29
- };
30
- let response = await app.render(request, routeData, locals);
31
- if (app.setCookieHeaders) {
32
- for (const setCookieHeader of app.setCookieHeaders(response)) {
33
- response.headers.append('Set-Cookie', setCookieHeader);
34
- }
24
+ env: env,
25
+ cf: request.cf,
26
+ caches: caches,
27
+ },
28
+ };
29
+ let response = await app.render(request, routeData, locals);
30
+ if (app.setCookieHeaders) {
31
+ for (const setCookieHeader of app.setCookieHeaders(response)) {
32
+ response.headers.append('Set-Cookie', setCookieHeader);
35
33
  }
36
- return response;
37
34
  }
38
- return new Response(null, {
39
- status: 404,
40
- statusText: 'Not found',
41
- });
35
+ return response;
42
36
  };
43
37
  return { default: { fetch } };
44
38
  }
@@ -16,31 +16,25 @@ export function createExports(manifest) {
16
16
  if (manifest.assets.has(pathname)) {
17
17
  return env.ASSETS.fetch(request);
18
18
  }
19
- let routeData = app.match(request, { matchNotFound: true });
20
- if (routeData) {
21
- Reflect.set(request, Symbol.for('astro.clientAddress'), request.headers.get('cf-connecting-ip'));
22
- const locals = {
23
- runtime: {
24
- waitUntil: (promise) => {
25
- context.waitUntil(promise);
26
- },
27
- env: context.env,
28
- cf: request.cf,
29
- caches: caches,
19
+ let routeData = app.match(request);
20
+ Reflect.set(request, Symbol.for('astro.clientAddress'), request.headers.get('cf-connecting-ip'));
21
+ const locals = {
22
+ runtime: {
23
+ waitUntil: (promise) => {
24
+ context.waitUntil(promise);
30
25
  },
31
- };
32
- let response = await app.render(request, routeData, locals);
33
- if (app.setCookieHeaders) {
34
- for (const setCookieHeader of app.setCookieHeaders(response)) {
35
- response.headers.append('Set-Cookie', setCookieHeader);
36
- }
26
+ env: context.env,
27
+ cf: request.cf,
28
+ caches: caches,
29
+ },
30
+ };
31
+ let response = await app.render(request, routeData, locals);
32
+ if (app.setCookieHeaders) {
33
+ for (const setCookieHeader of app.setCookieHeaders(response)) {
34
+ response.headers.append('Set-Cookie', setCookieHeader);
37
35
  }
38
- return response;
39
36
  }
40
- return new Response(null, {
41
- status: 404,
42
- statusText: 'Not found',
43
- });
37
+ return response;
44
38
  };
45
39
  return { onRequest, manifest };
46
40
  }
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export type { DirectoryRuntime } from './entrypoints/server.directory.js';
4
4
  type Options = {
5
5
  mode?: 'directory' | 'advanced';
6
6
  functionPerRoute?: boolean;
7
+ imageService?: 'passthrough' | 'cloudflare';
7
8
  /** Configure automatic `routes.json` generation */
8
9
  routes?: {
9
10
  /** Strategy for generating `include` and `exclude` patterns
package/dist/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects';
2
- import { passthroughImageService } from 'astro/config';
3
2
  import { AstroError } from 'astro/errors';
4
3
  import esbuild from 'esbuild';
5
4
  import { Miniflare } from 'miniflare';
@@ -11,6 +10,7 @@ import glob from 'tiny-glob';
11
10
  import { getAdapter } from './getAdapter.js';
12
11
  import { deduplicatePatterns } from './utils/deduplicatePatterns.js';
13
12
  import { getCFObject } from './utils/getCFObject.js';
13
+ import { prepareImageConfig } from './utils/image-config.js';
14
14
  import { getD1Bindings, getDOBindings, getEnvVars, getKVBindings, getR2Bindings, } from './utils/parser.js';
15
15
  import { prependForwardSlash } from './utils/prependForwardSlash.js';
16
16
  import { rewriteWasmImportPath } from './utils/rewriteWasmImportPath.js';
@@ -48,13 +48,7 @@ export default function createIntegration(args) {
48
48
  return {
49
49
  name: '@astrojs/cloudflare',
50
50
  hooks: {
51
- 'astro:config:setup': ({ config, updateConfig, logger }) => {
52
- let imageConfigOverwrite = false;
53
- if (config.image.service.entrypoint === 'astro/assets/services/sharp' ||
54
- config.image.service.entrypoint === 'astro/assets/services/squoosh') {
55
- 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`);
56
- imageConfigOverwrite = true;
57
- }
51
+ 'astro:config:setup': ({ command, config, updateConfig, logger }) => {
58
52
  updateConfig({
59
53
  build: {
60
54
  client: new URL(`.${config.base}`, config.outDir),
@@ -71,9 +65,7 @@ export default function createIntegration(args) {
71
65
  }),
72
66
  ],
73
67
  },
74
- image: imageConfigOverwrite
75
- ? { ...config.image, service: passthroughImageService() }
76
- : config.image,
68
+ image: prepareImageConfig(args?.imageService ?? 'DEFAULT', config.image, command, logger),
77
69
  });
78
70
  },
79
71
  'astro:config:done': ({ setAdapter, config, logger }) => {
@@ -398,11 +390,14 @@ export default function createIntegration(args) {
398
390
  /**
399
391
  * These route types are candiates for being part of the `_routes.json` `include` array.
400
392
  */
393
+ let notFoundIsSSR = false;
401
394
  const potentialFunctionRouteTypes = ['endpoint', 'page'];
402
395
  const functionEndpoints = routes
403
396
  // Certain route types, when their prerender option is set to false, run on the server as function invocations
404
397
  .filter((route) => potentialFunctionRouteTypes.includes(route.type) && !route.prerender)
405
398
  .map((route) => {
399
+ if (route.component === 'src/pages/404.astro' && route.prerender === false)
400
+ notFoundIsSSR = true;
406
401
  const includePattern = '/' +
407
402
  route.segments
408
403
  .flat()
@@ -507,7 +502,11 @@ export default function createIntegration(args) {
507
502
  const excludeStrategyLength = excludeStrategy
508
503
  ? excludeStrategy.include.length + excludeStrategy.exclude.length
509
504
  : Infinity;
510
- const winningStrategy = includeStrategyLength <= excludeStrategyLength ? includeStrategy : excludeStrategy;
505
+ const winningStrategy = notFoundIsSSR
506
+ ? excludeStrategy
507
+ : includeStrategyLength <= excludeStrategyLength
508
+ ? includeStrategy
509
+ : excludeStrategy;
511
510
  await fs.promises.writeFile(new URL('./_routes.json', _config.outDir), JSON.stringify({
512
511
  version: 1,
513
512
  ...winningStrategy,
@@ -0,0 +1,14 @@
1
+ import type { AstroConfig, ImageMetadata, RemotePattern } from 'astro';
2
+ export declare function isESMImportedImage(src: ImageMetadata | string): src is ImageMetadata;
3
+ export declare function isRemotePath(src: string): boolean;
4
+ export declare function matchHostname(url: URL, hostname?: string, allowWildcard?: boolean): boolean;
5
+ export declare function matchPort(url: URL, port?: string): boolean;
6
+ export declare function matchProtocol(url: URL, protocol?: string): boolean;
7
+ export declare function matchPathname(url: URL, pathname?: string, allowWildcard?: boolean): boolean;
8
+ export declare function matchPattern(url: URL, remotePattern: RemotePattern): boolean;
9
+ export declare function isRemoteAllowed(src: string, { domains, remotePatterns, }: Partial<Pick<AstroConfig['image'], 'domains' | 'remotePatterns'>>): boolean;
10
+ export declare function isString(path: unknown): path is string;
11
+ export declare function removeTrailingForwardSlash(path: string): string;
12
+ export declare function removeLeadingForwardSlash(path: string): string;
13
+ export declare function trimSlashes(path: string): string;
14
+ export declare function joinPaths(...paths: (string | undefined)[]): string;
@@ -0,0 +1,95 @@
1
+ export function isESMImportedImage(src) {
2
+ return typeof src === 'object';
3
+ }
4
+ export function isRemotePath(src) {
5
+ return /^(http|ftp|https|ws):?\/\//.test(src) || src.startsWith('data:');
6
+ }
7
+ export function matchHostname(url, hostname, allowWildcard) {
8
+ if (!hostname) {
9
+ return true;
10
+ }
11
+ else if (!allowWildcard || !hostname.startsWith('*')) {
12
+ return hostname === url.hostname;
13
+ }
14
+ else if (hostname.startsWith('**.')) {
15
+ const slicedHostname = hostname.slice(2); // ** length
16
+ return slicedHostname !== url.hostname && url.hostname.endsWith(slicedHostname);
17
+ }
18
+ else if (hostname.startsWith('*.')) {
19
+ const slicedHostname = hostname.slice(1); // * length
20
+ const additionalSubdomains = url.hostname
21
+ .replace(slicedHostname, '')
22
+ .split('.')
23
+ .filter(Boolean);
24
+ return additionalSubdomains.length === 1;
25
+ }
26
+ return false;
27
+ }
28
+ export function matchPort(url, port) {
29
+ return !port || port === url.port;
30
+ }
31
+ export function matchProtocol(url, protocol) {
32
+ return !protocol || protocol === url.protocol.slice(0, -1);
33
+ }
34
+ export function matchPathname(url, pathname, allowWildcard) {
35
+ if (!pathname) {
36
+ return true;
37
+ }
38
+ else if (!allowWildcard || !pathname.endsWith('*')) {
39
+ return pathname === url.pathname;
40
+ }
41
+ else if (pathname.endsWith('/**')) {
42
+ const slicedPathname = pathname.slice(0, -2); // ** length
43
+ return slicedPathname !== url.pathname && url.pathname.startsWith(slicedPathname);
44
+ }
45
+ else if (pathname.endsWith('/*')) {
46
+ const slicedPathname = pathname.slice(0, -1); // * length
47
+ const additionalPathChunks = url.pathname
48
+ .replace(slicedPathname, '')
49
+ .split('/')
50
+ .filter(Boolean);
51
+ return additionalPathChunks.length === 1;
52
+ }
53
+ return false;
54
+ }
55
+ export function matchPattern(url, remotePattern) {
56
+ return (matchProtocol(url, remotePattern.protocol) &&
57
+ matchHostname(url, remotePattern.hostname, true) &&
58
+ matchPort(url, remotePattern.port) &&
59
+ matchPathname(url, remotePattern.pathname, true));
60
+ }
61
+ export function isRemoteAllowed(src, { domains = [], remotePatterns = [], }) {
62
+ if (!isRemotePath(src))
63
+ return false;
64
+ const url = new URL(src);
65
+ return (domains.some((domain) => matchHostname(url, domain)) ||
66
+ remotePatterns.some((remotePattern) => matchPattern(url, remotePattern)));
67
+ }
68
+ export function isString(path) {
69
+ return typeof path === 'string' || path instanceof String;
70
+ }
71
+ export function removeTrailingForwardSlash(path) {
72
+ return path.endsWith('/') ? path.slice(0, path.length - 1) : path;
73
+ }
74
+ export function removeLeadingForwardSlash(path) {
75
+ return path.startsWith('/') ? path.substring(1) : path;
76
+ }
77
+ export function trimSlashes(path) {
78
+ return path.replace(/^\/|\/$/g, '');
79
+ }
80
+ export function joinPaths(...paths) {
81
+ return paths
82
+ .filter(isString)
83
+ .map((path, i) => {
84
+ if (i === 0) {
85
+ return removeTrailingForwardSlash(path);
86
+ }
87
+ else if (i === paths.length - 1) {
88
+ return removeLeadingForwardSlash(path);
89
+ }
90
+ else {
91
+ return trimSlashes(path);
92
+ }
93
+ })
94
+ .join('/');
95
+ }
@@ -0,0 +1,12 @@
1
+ import type { AstroConfig, AstroIntegrationLogger } from 'astro';
2
+ export declare function prepareImageConfig(service: string, config: AstroConfig['image'], command: 'dev' | 'build' | 'preview', logger: AstroIntegrationLogger): {
3
+ service: import("astro").ImageServiceConfig<Record<string, any>>;
4
+ domains: string[];
5
+ remotePatterns: {
6
+ protocol?: string | undefined;
7
+ hostname?: string | undefined;
8
+ port?: string | undefined;
9
+ pathname?: string | undefined;
10
+ }[];
11
+ endpoint?: string | undefined;
12
+ };
@@ -0,0 +1,21 @@
1
+ import { passthroughImageService, sharpImageService } from 'astro/config';
2
+ export function prepareImageConfig(service, config, command, logger) {
3
+ switch (service) {
4
+ case 'passthrough':
5
+ return { ...config, service: passthroughImageService() };
6
+ case 'cloudflare':
7
+ return {
8
+ ...config,
9
+ service: command === 'dev'
10
+ ? sharpImageService()
11
+ : { entrypoint: '@astrojs/cloudflare/image-service' },
12
+ };
13
+ default:
14
+ if (config.service.entrypoint === 'astro/assets/services/sharp' ||
15
+ config.service.entrypoint === 'astro/assets/services/squoosh') {
16
+ 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`);
17
+ return { ...config, service: passthroughImageService() };
18
+ }
19
+ return { ...config };
20
+ }
21
+ }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "//comment": "test changeset-bot",
3
3
  "name": "@astrojs/cloudflare",
4
4
  "description": "Deploy your site to Cloudflare Workers/Pages",
5
- "version": "7.6.4",
5
+ "version": "7.7.1",
6
6
  "type": "module",
7
7
  "types": "./dist/index.d.ts",
8
8
  "author": "withastro",
@@ -22,6 +22,7 @@
22
22
  ".": "./dist/index.js",
23
23
  "./entrypoints/server.advanced.js": "./dist/entrypoints/server.advanced.js",
24
24
  "./entrypoints/server.directory.js": "./dist/entrypoints/server.directory.js",
25
+ "./image-service": "./dist/entrypoints/image-service.js",
25
26
  "./package.json": "./package.json"
26
27
  },
27
28
  "files": [
@@ -29,28 +30,28 @@
29
30
  ],
30
31
  "dependencies": {
31
32
  "@astrojs/underscore-redirects": "^0.3.3",
32
- "@cloudflare/workers-types": "^4.20230821.0",
33
- "miniflare": "3.20231010.0",
33
+ "@cloudflare/workers-types": "^4.20231025.0",
34
+ "miniflare": "3.20231025.1",
34
35
  "@iarna/toml": "^2.2.5",
35
36
  "dotenv": "^16.3.1",
36
- "esbuild": "^0.19.2",
37
+ "esbuild": "^0.19.5",
37
38
  "find-up": "^6.3.0",
38
39
  "tiny-glob": "^0.2.9",
39
- "vite": "^4.4.9"
40
+ "vite": "^4.5.0"
40
41
  },
41
42
  "peerDependencies": {
42
- "astro": "^3.3.0"
43
+ "astro": "^3.4.3"
43
44
  },
44
45
  "devDependencies": {
45
46
  "execa": "^8.0.1",
46
47
  "fast-glob": "^3.3.1",
47
48
  "@types/iarna__toml": "^2.0.2",
48
49
  "strip-ansi": "^7.1.0",
49
- "astro": "^3.2.3",
50
- "chai": "^4.3.7",
50
+ "astro": "^3.4.3",
51
+ "chai": "^4.3.10",
51
52
  "cheerio": "1.0.0-rc.12",
52
53
  "mocha": "^10.2.0",
53
- "wrangler": "^3.11.0",
54
+ "wrangler": "^3.15.0",
54
55
  "@astrojs/test-utils": "0.0.1"
55
56
  },
56
57
  "publishConfig": {