zastro-websockets-cloudflare 0.0.0-729d313

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.
@@ -0,0 +1,3 @@
1
+ import type { APIRoute } from 'astro';
2
+ export declare const prerender = false;
3
+ export declare const GET: APIRoute;
@@ -0,0 +1,15 @@
1
+ const prerender = false;
2
+ const GET = (ctx) => {
3
+ const href = ctx.url.searchParams.get("href");
4
+ if (!href) {
5
+ return new Response("Missing 'href' query parameter", {
6
+ status: 400,
7
+ statusText: "Missing 'href' query parameter"
8
+ });
9
+ }
10
+ return fetch(new URL(href, ctx.url.origin));
11
+ };
12
+ export {
13
+ GET,
14
+ prerender
15
+ };
@@ -0,0 +1,3 @@
1
+ import type { ExternalImageService } from 'astro';
2
+ declare const service: ExternalImageService;
3
+ export default service;
@@ -0,0 +1,35 @@
1
+ import { joinPaths } from "@astrojs/internal-helpers/path";
2
+ import { baseService } from "astro/assets";
3
+ import { isESMImportedImage } from "astro/assets/utils";
4
+ import { isRemoteAllowed } from "../utils/assets.js";
5
+ const service = {
6
+ ...baseService,
7
+ getURL: (options, imageConfig) => {
8
+ const resizingParams = ["onerror=redirect"];
9
+ if (options.width) resizingParams.push(`width=${options.width}`);
10
+ if (options.height) resizingParams.push(`height=${options.height}`);
11
+ if (options.quality) resizingParams.push(`quality=${options.quality}`);
12
+ if (options.fit) resizingParams.push(`fit=${options.fit}`);
13
+ if (options.format) resizingParams.push(`format=${options.format}`);
14
+ let imageSource = "";
15
+ if (isESMImportedImage(options.src)) {
16
+ imageSource = options.src.src;
17
+ } else if (isRemoteAllowed(options.src, imageConfig)) {
18
+ imageSource = String(options.src);
19
+ } else {
20
+ return String(options.src);
21
+ }
22
+ const imageEndpoint = joinPaths(
23
+ // @ts-expect-error Can't recognise import.meta.env
24
+ import.meta.env.BASE_URL,
25
+ "/cdn-cgi/image",
26
+ resizingParams.join(","),
27
+ imageSource
28
+ );
29
+ return imageEndpoint;
30
+ }
31
+ };
32
+ var image_service_default = service;
33
+ export {
34
+ image_service_default as default
35
+ };
@@ -0,0 +1,2 @@
1
+ import type { MiddlewareHandler } from 'astro';
2
+ export declare const onRequest: MiddlewareHandler;
@@ -0,0 +1,11 @@
1
+ const onRequest = (context, next) => {
2
+ if (context.isPrerendered) {
3
+ context.locals.runtime ??= {
4
+ env: process.env
5
+ };
6
+ }
7
+ return next();
8
+ };
9
+ export {
10
+ onRequest
11
+ };
@@ -0,0 +1,15 @@
1
+ import type { ExecutionContext, ExportedHandlerFetchHandler } from '@cloudflare/workers-types';
2
+ import type { SSRManifest } from 'astro';
3
+ type Env = {
4
+ [key: string]: unknown;
5
+ ASSETS: {
6
+ fetch: (req: Request | string) => Promise<Response>;
7
+ };
8
+ ASTRO_STUDIO_APP_TOKEN?: string;
9
+ };
10
+ export declare function createExports(manifest: SSRManifest): {
11
+ default: {
12
+ fetch: (request: Parameters<ExportedHandlerFetchHandler>[0], env: Env, context: ExecutionContext) => Promise<Response>;
13
+ };
14
+ };
15
+ export {};
@@ -0,0 +1,12 @@
1
+ import { App } from "astro/app";
2
+ import { handle } from "../utils/handler.js";
3
+ function createExports(manifest) {
4
+ const app = new App(manifest);
5
+ const fetch = async (request, env, context) => {
6
+ return await handle(manifest, app, request, env, context);
7
+ };
8
+ return { default: { fetch } };
9
+ }
10
+ export {
11
+ createExports
12
+ };
@@ -0,0 +1,94 @@
1
+ import type { AstroIntegration } from 'astro';
2
+ import { type GetPlatformProxyOptions } from 'wrangler';
3
+ import { type ImageService } from './utils/image-config.js';
4
+ export type { Runtime } from './utils/handler.js';
5
+ export type Options = {
6
+ /** Options for handling images. */
7
+ imageService?: ImageService;
8
+ /** Configuration for `_routes.json` generation. A _routes.json file controls when your Function is invoked. This file will include three different properties:
9
+ *
10
+ * - version: Defines the version of the schema. Currently there is only one version of the schema (version 1), however, we may add more in the future and aim to be backwards compatible.
11
+ * - include: Defines routes that will be invoked by Functions. Accepts wildcard behavior.
12
+ * - exclude: Defines routes that will not be invoked by Functions. Accepts wildcard behavior. `exclude` always take priority over `include`.
13
+ *
14
+ * Wildcards match any number of path segments (slashes). For example, `/users/*` will match everything after the `/users/` path.
15
+ *
16
+ */
17
+ routes?: {
18
+ /** Extend `_routes.json` */
19
+ extend: {
20
+ /** Paths which should be routed to the SSR function */
21
+ include?: {
22
+ /** Generally this is in pathname format, but does support wildcards, e.g. `/users`, `/products/*` */
23
+ pattern: string;
24
+ }[];
25
+ /** Paths which should be routed as static assets */
26
+ exclude?: {
27
+ /** Generally this is in pathname format, but does support wildcards, e.g. `/static`, `/assets/*`, `/images/avatar.jpg` */
28
+ pattern: string;
29
+ }[];
30
+ };
31
+ };
32
+ /**
33
+ * Proxy configuration for the platform.
34
+ */
35
+ platformProxy?: GetPlatformProxyOptions & {
36
+ /** Toggle the proxy. Default `undefined`, which equals to `true`. */
37
+ enabled?: boolean;
38
+ };
39
+ /**
40
+ * Allow bundling cloudflare worker specific file types as importable modules. Defaults to true.
41
+ * When enabled, allows imports of '.wasm', '.bin', and '.txt' file types
42
+ *
43
+ * See https://developers.cloudflare.com/pages/functions/module-support/
44
+ * for reference on how these file types are exported
45
+ */
46
+ cloudflareModules?: boolean;
47
+ /**
48
+ * By default, Astro will be configured to use Cloudflare KV to store session data. If you want to use sessions,
49
+ * you must create a KV namespace and declare it in your wrangler config file. You can do this with the wrangler command:
50
+ *
51
+ * ```sh
52
+ * npx wrangler kv namespace create SESSION
53
+ * ```
54
+ *
55
+ * This will log the id of the created namespace. You can then add it to your `wrangler.json` file like this:
56
+ *
57
+ * ```json
58
+ * {
59
+ * "kv_namespaces": [
60
+ * {
61
+ * "binding": "SESSION",
62
+ * "id": "<your kv namespace id here>"
63
+ * }
64
+ * ]
65
+ * }
66
+ * ```
67
+ * By default, the driver looks for the binding named `SESSION`, but you can override this by providing a different name here.
68
+ *
69
+ * See https://developers.cloudflare.com/kv/concepts/kv-namespaces/ for more details on using KV namespaces.
70
+ *
71
+ */
72
+ sessionKVBindingName?: string;
73
+ /**
74
+ * This configuration option allows you to specify a custom entryPoint for your Cloudflare Worker.
75
+ * The entry point is the file that will be executed when your Worker is invoked.
76
+ * By default, this is set to `@astrojs/cloudflare/entrypoints/server.js` and `['default']`.
77
+ * @docs https://docs.astro.build/en/guides/integrations-guide/cloudflare/#workerEntryPoint
78
+ */
79
+ workerEntryPoint?: {
80
+ /**
81
+ * The path to the entry file. This should be a relative path from the root of your Astro project.
82
+ * @example`'src/worker.ts'`
83
+ * @docs https://docs.astro.build/en/guides/integrations-guide/cloudflare/#workerentrypointpath
84
+ */
85
+ path: string | URL;
86
+ /**
87
+ * Additional named exports to use for the entry file. Astro always includes the default export (`['default']`). If you need to have other top level named exports use this option.
88
+ * @example ['MyDurableObject', 'namedExport']
89
+ * @docs https://docs.astro.build/en/guides/integrations-guide/cloudflare/#workerentrypointnamedexports
90
+ */
91
+ namedExports?: string[];
92
+ };
93
+ };
94
+ export default function createIntegration(args?: Options): AstroIntegration;
package/dist/index.js ADDED
@@ -0,0 +1,307 @@
1
+ import { createReadStream } from "node:fs";
2
+ import { appendFile, stat } from "node:fs/promises";
3
+ import { createRequire } from "node:module";
4
+ import { createInterface } from "node:readline/promises";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
6
+ import {
7
+ appendForwardSlash,
8
+ prependForwardSlash,
9
+ removeLeadingForwardSlash
10
+ } from "@astrojs/internal-helpers/path";
11
+ import { createRedirectsFromAstroRoutes, printAsRedirects } from "@astrojs/underscore-redirects";
12
+ import { AstroError } from "astro/errors";
13
+ import { defaultClientConditions } from "vite";
14
+ import { getPlatformProxy } from "wrangler";
15
+ import {
16
+ cloudflareModuleLoader
17
+ } from "./utils/cloudflare-module-loader.js";
18
+ import { createGetEnv } from "./utils/env.js";
19
+ import { createRoutesFile, getParts } from "./utils/generate-routes-json.js";
20
+ import { setImageConfig } from "./utils/image-config.js";
21
+ function wrapWithSlashes(path) {
22
+ return prependForwardSlash(appendForwardSlash(path));
23
+ }
24
+ function setProcessEnv(config, env) {
25
+ const getEnv = createGetEnv(env);
26
+ if (config.env?.schema) {
27
+ for (const key of Object.keys(config.env.schema)) {
28
+ const value = getEnv(key);
29
+ if (value !== void 0) {
30
+ process.env[key] = value;
31
+ }
32
+ }
33
+ }
34
+ }
35
+ function createIntegration(args) {
36
+ let _config;
37
+ let finalBuildOutput;
38
+ const cloudflareModulePlugin = cloudflareModuleLoader(
39
+ args?.cloudflareModules ?? true
40
+ );
41
+ let _routes;
42
+ return {
43
+ name: "zastro-websockets-cloudflare",
44
+ hooks: {
45
+ "astro:config:setup": ({
46
+ command,
47
+ config,
48
+ updateConfig,
49
+ logger,
50
+ addWatchFile,
51
+ addMiddleware,
52
+ createCodegenDir
53
+ }) => {
54
+ let session = config.session;
55
+ const isBuild = command === "build";
56
+ if (!session?.driver) {
57
+ const sessionDir = isBuild ? void 0 : createCodegenDir();
58
+ const bindingName = args?.sessionKVBindingName ?? "SESSION";
59
+ if (isBuild) {
60
+ logger.info(
61
+ `Enabling sessions with Cloudflare KV for production with the "${bindingName}" KV binding.`
62
+ );
63
+ logger.info(
64
+ `If you see the error "Invalid binding \`${bindingName}\`" in your build output, you need to add the binding to your wrangler config file.`
65
+ );
66
+ }
67
+ session = isBuild ? {
68
+ ...session,
69
+ driver: "cloudflare-kv-binding",
70
+ options: {
71
+ binding: bindingName,
72
+ ...session?.options
73
+ }
74
+ } : {
75
+ ...session,
76
+ driver: "fs-lite",
77
+ options: {
78
+ base: fileURLToPath(new URL("sessions", sessionDir)),
79
+ ...session?.options
80
+ }
81
+ };
82
+ }
83
+ updateConfig({
84
+ build: {
85
+ client: new URL(`.${wrapWithSlashes(config.base)}`, config.outDir),
86
+ server: new URL("./_worker.js/", config.outDir),
87
+ serverEntry: "index.js",
88
+ redirects: false
89
+ },
90
+ session,
91
+ vite: {
92
+ plugins: [
93
+ // https://developers.cloudflare.com/pages/functions/module-support/
94
+ // Allows imports of '.wasm', '.bin', and '.txt' file types
95
+ cloudflareModulePlugin,
96
+ {
97
+ name: "vite:cf-imports",
98
+ enforce: "pre",
99
+ resolveId(source) {
100
+ if (source.startsWith("cloudflare:")) {
101
+ return { id: source, external: true };
102
+ }
103
+ return null;
104
+ }
105
+ }
106
+ ]
107
+ },
108
+ image: setImageConfig(args?.imageService ?? "compile", config.image, command, logger)
109
+ });
110
+ if (args?.platformProxy?.configPath) {
111
+ addWatchFile(new URL(args.platformProxy.configPath, config.root));
112
+ } else {
113
+ addWatchFile(new URL("./wrangler.toml", config.root));
114
+ addWatchFile(new URL("./wrangler.json", config.root));
115
+ addWatchFile(new URL("./wrangler.jsonc", config.root));
116
+ }
117
+ addMiddleware({
118
+ entrypoint: "zastro-websockets-cloudflare/entrypoints/middleware.js",
119
+ order: "pre"
120
+ });
121
+ },
122
+ "astro:routes:resolved": ({ routes }) => {
123
+ _routes = routes;
124
+ },
125
+ "astro:config:done": ({ setAdapter, config, buildOutput, logger }) => {
126
+ if (buildOutput === "static") {
127
+ logger.warn(
128
+ "[@astrojs/cloudflare] This adapter is intended to be used with server rendered pages, which this project does not contain any of. As such, this adapter is unnecessary."
129
+ );
130
+ }
131
+ _config = config;
132
+ finalBuildOutput = buildOutput;
133
+ let customWorkerEntryPoint;
134
+ if (args?.workerEntryPoint && typeof args.workerEntryPoint.path === "string") {
135
+ const require2 = createRequire(config.root);
136
+ try {
137
+ customWorkerEntryPoint = pathToFileURL(require2.resolve(args.workerEntryPoint.path));
138
+ } catch {
139
+ customWorkerEntryPoint = new URL(args.workerEntryPoint.path, config.root);
140
+ }
141
+ }
142
+ setAdapter({
143
+ name: "zastro-websockets-cloudflare",
144
+ serverEntrypoint: customWorkerEntryPoint ?? "zastro-websockets-cloudflare/entrypoints/server.js",
145
+ exports: args?.workerEntryPoint?.namedExports ? ["default", ...args.workerEntryPoint.namedExports] : ["default"],
146
+ adapterFeatures: {
147
+ edgeMiddleware: false,
148
+ buildOutput: "server"
149
+ },
150
+ supportedAstroFeatures: {
151
+ serverOutput: "stable",
152
+ hybridOutput: "stable",
153
+ staticOutput: "unsupported",
154
+ i18nDomains: "experimental",
155
+ sharpImageService: {
156
+ support: "limited",
157
+ message: 'Cloudflare does not support sharp at runtime. However, you can configure `imageService: "compile"` to optimize images with sharp on prerendered pages during build time.',
158
+ // For explicitly set image services, we suppress the warning about sharp not being supported at runtime,
159
+ // inferring the user is aware of the limitations.
160
+ suppress: args?.imageService ? "all" : "default"
161
+ },
162
+ envGetSecret: "stable"
163
+ }
164
+ });
165
+ },
166
+ "astro:server:setup": async ({ server }) => {
167
+ if ((args?.platformProxy?.enabled ?? true) === true) {
168
+ const platformProxy = await getPlatformProxy(args?.platformProxy);
169
+ server.httpServer?.on("close", async () => {
170
+ await platformProxy.dispose();
171
+ });
172
+ setProcessEnv(_config, platformProxy.env);
173
+ const clientLocalsSymbol = Symbol.for("astro.locals");
174
+ server.middlewares.use(async function middleware(req, _res, next) {
175
+ Reflect.set(req, clientLocalsSymbol, {
176
+ runtime: {
177
+ env: platformProxy.env,
178
+ cf: platformProxy.cf,
179
+ caches: platformProxy.caches,
180
+ ctx: {
181
+ waitUntil: (promise) => platformProxy.ctx.waitUntil(promise),
182
+ // Currently not available: https://developers.cloudflare.com/pages/platform/known-issues/#pages-functions
183
+ passThroughOnException: () => {
184
+ throw new AstroError(
185
+ "`passThroughOnException` is currently not available in Cloudflare Pages. See https://developers.cloudflare.com/pages/platform/known-issues/#pages-functions."
186
+ );
187
+ }
188
+ }
189
+ }
190
+ });
191
+ next();
192
+ });
193
+ }
194
+ },
195
+ "astro:build:setup": ({ vite, target }) => {
196
+ if (target === "server") {
197
+ vite.resolve ||= {};
198
+ vite.resolve.alias ||= {};
199
+ const aliases = [
200
+ {
201
+ find: "react-dom/server",
202
+ replacement: "react-dom/server.browser"
203
+ }
204
+ ];
205
+ if (Array.isArray(vite.resolve.alias)) {
206
+ vite.resolve.alias = [...vite.resolve.alias, ...aliases];
207
+ } else {
208
+ for (const alias of aliases) {
209
+ vite.resolve.alias[alias.find] = alias.replacement;
210
+ }
211
+ }
212
+ vite.ssr ||= {};
213
+ vite.ssr.resolve ||= {};
214
+ vite.ssr.resolve.conditions ||= [...defaultClientConditions];
215
+ vite.ssr.resolve.conditions.push("workerd", "worker");
216
+ vite.ssr.target = "webworker";
217
+ vite.ssr.noExternal = true;
218
+ vite.build ||= {};
219
+ vite.build.rollupOptions ||= {};
220
+ vite.build.rollupOptions.output ||= {};
221
+ vite.build.rollupOptions.output.banner ||= "globalThis.process ??= {}; globalThis.process.env ??= {};";
222
+ vite.define = {
223
+ "process.env": "process.env",
224
+ // Allows the request handler to know what the binding name is
225
+ "globalThis.__ASTRO_SESSION_BINDING_NAME": JSON.stringify(
226
+ args?.sessionKVBindingName ?? "SESSION"
227
+ ),
228
+ ...vite.define
229
+ };
230
+ }
231
+ },
232
+ "astro:build:done": async ({ pages, dir, logger, assets }) => {
233
+ await cloudflareModulePlugin.afterBuildCompleted(_config);
234
+ let redirectsExists = false;
235
+ try {
236
+ const redirectsStat = await stat(new URL("./_redirects", _config.outDir));
237
+ if (redirectsStat.isFile()) {
238
+ redirectsExists = true;
239
+ }
240
+ } catch (_error) {
241
+ redirectsExists = false;
242
+ }
243
+ const redirects = [];
244
+ if (redirectsExists) {
245
+ const rl = createInterface({
246
+ input: createReadStream(new URL("./_redirects", _config.outDir)),
247
+ crlfDelay: Number.POSITIVE_INFINITY
248
+ });
249
+ for await (const line of rl) {
250
+ const parts = line.split(" ");
251
+ if (parts.length >= 2) {
252
+ const p = removeLeadingForwardSlash(parts[0]).split("/").filter(Boolean).map((s) => {
253
+ const syntax = s.replace(/\/:.*?(?=\/|$)/g, "/*").replace(/\?.*$/, "");
254
+ return getParts(syntax);
255
+ });
256
+ redirects.push(p);
257
+ }
258
+ }
259
+ }
260
+ let routesExists = false;
261
+ try {
262
+ const routesStat = await stat(new URL("./_routes.json", _config.outDir));
263
+ if (routesStat.isFile()) {
264
+ routesExists = true;
265
+ }
266
+ } catch (_error) {
267
+ routesExists = false;
268
+ }
269
+ if (!routesExists) {
270
+ await createRoutesFile(
271
+ _config,
272
+ logger,
273
+ _routes,
274
+ pages,
275
+ redirects,
276
+ args?.routes?.extend?.include,
277
+ args?.routes?.extend?.exclude
278
+ );
279
+ }
280
+ const trueRedirects = createRedirectsFromAstroRoutes({
281
+ config: _config,
282
+ routeToDynamicTargetMap: new Map(
283
+ Array.from(
284
+ _routes.filter((route) => route.type === "redirect").map((route) => [route, ""])
285
+ )
286
+ ),
287
+ dir,
288
+ buildOutput: finalBuildOutput,
289
+ assets
290
+ });
291
+ if (!trueRedirects.empty()) {
292
+ try {
293
+ await appendFile(
294
+ new URL("./_redirects", _config.outDir),
295
+ printAsRedirects(trueRedirects)
296
+ );
297
+ } catch (_error) {
298
+ logger.error("Failed to write _redirects file");
299
+ }
300
+ }
301
+ }
302
+ }
303
+ };
304
+ }
305
+ export {
306
+ createIntegration as default
307
+ };
@@ -0,0 +1,2 @@
1
+ import type { AstroConfig } from 'astro';
2
+ export declare function isRemoteAllowed(src: string, { domains, remotePatterns, }: Partial<Pick<AstroConfig['image'], 'domains' | 'remotePatterns'>>): boolean;
@@ -0,0 +1,57 @@
1
+ import { isRemotePath } from "@astrojs/internal-helpers/path";
2
+ function matchHostname(url, hostname, allowWildcard) {
3
+ if (!hostname) {
4
+ return true;
5
+ }
6
+ if (!allowWildcard || !hostname.startsWith("*")) {
7
+ return hostname === url.hostname;
8
+ }
9
+ if (hostname.startsWith("**.")) {
10
+ const slicedHostname = hostname.slice(2);
11
+ return slicedHostname !== url.hostname && url.hostname.endsWith(slicedHostname);
12
+ }
13
+ if (hostname.startsWith("*.")) {
14
+ const slicedHostname = hostname.slice(1);
15
+ const additionalSubdomains = url.hostname.replace(slicedHostname, "").split(".").filter(Boolean);
16
+ return additionalSubdomains.length === 1;
17
+ }
18
+ return false;
19
+ }
20
+ function matchPort(url, port) {
21
+ return !port || port === url.port;
22
+ }
23
+ function matchProtocol(url, protocol) {
24
+ return !protocol || protocol === url.protocol.slice(0, -1);
25
+ }
26
+ function matchPathname(url, pathname, allowWildcard) {
27
+ if (!pathname) {
28
+ return true;
29
+ }
30
+ if (!allowWildcard || !pathname.endsWith("*")) {
31
+ return pathname === url.pathname;
32
+ }
33
+ if (pathname.endsWith("/**")) {
34
+ const slicedPathname = pathname.slice(0, -2);
35
+ return slicedPathname !== url.pathname && url.pathname.startsWith(slicedPathname);
36
+ }
37
+ if (pathname.endsWith("/*")) {
38
+ const slicedPathname = pathname.slice(0, -1);
39
+ const additionalPathChunks = url.pathname.replace(slicedPathname, "").split("/").filter(Boolean);
40
+ return additionalPathChunks.length === 1;
41
+ }
42
+ return false;
43
+ }
44
+ function matchPattern(url, remotePattern) {
45
+ return matchProtocol(url, remotePattern.protocol) && matchHostname(url, remotePattern.hostname, true) && matchPort(url, remotePattern.port) && matchPathname(url, remotePattern.pathname, true);
46
+ }
47
+ function isRemoteAllowed(src, {
48
+ domains = [],
49
+ remotePatterns = []
50
+ }) {
51
+ if (!isRemotePath(src)) return false;
52
+ const url = new URL(src);
53
+ return domains.some((domain) => matchHostname(url, domain)) || remotePatterns.some((remotePattern) => matchPattern(url, remotePattern));
54
+ }
55
+ export {
56
+ isRemoteAllowed
57
+ };
@@ -0,0 +1,20 @@
1
+ import type { AstroConfig } from 'astro';
2
+ import type { PluginOption } from 'vite';
3
+ export interface CloudflareModulePluginExtra {
4
+ afterBuildCompleted(config: AstroConfig): Promise<void>;
5
+ }
6
+ /**
7
+ * Enables support for various non-standard extensions in module imports that cloudflare workers supports.
8
+ *
9
+ * See https://developers.cloudflare.com/pages/functions/module-support/ for reference
10
+ *
11
+ * This adds supports for imports in the following formats:
12
+ * - .wasm
13
+ * - .wasm?module
14
+ * - .bin
15
+ * - .txt
16
+ *
17
+ * @param enabled - if true, will load all cloudflare pages supported types
18
+ * @returns Vite plugin with additional extension method to hook into astro build
19
+ */
20
+ export declare function cloudflareModuleLoader(enabled: boolean): PluginOption & CloudflareModulePluginExtra;