@webflow/cloud-app 0.1.0-beta.10

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.
Files changed (195) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +161 -0
  3. package/dist/bindings.d.ts +190 -0
  4. package/dist/bindings.d.ts.map +1 -0
  5. package/dist/bindings.js +126 -0
  6. package/dist/bindings.js.map +1 -0
  7. package/dist/compat-date.d.ts +68 -0
  8. package/dist/compat-date.d.ts.map +1 -0
  9. package/dist/compat-date.js +169 -0
  10. package/dist/compat-date.js.map +1 -0
  11. package/dist/config/package-json.d.ts +57 -0
  12. package/dist/config/package-json.d.ts.map +1 -0
  13. package/dist/config/package-json.js +94 -0
  14. package/dist/config/package-json.js.map +1 -0
  15. package/dist/config/webflow-json.d.ts +47 -0
  16. package/dist/config/webflow-json.d.ts.map +1 -0
  17. package/dist/config/webflow-json.js +72 -0
  18. package/dist/config/webflow-json.js.map +1 -0
  19. package/dist/create-webflow-json.d.ts +21 -0
  20. package/dist/create-webflow-json.d.ts.map +1 -0
  21. package/dist/create-webflow-json.js +168 -0
  22. package/dist/create-webflow-json.js.map +1 -0
  23. package/dist/diagnostics.d.ts +23 -0
  24. package/dist/diagnostics.d.ts.map +1 -0
  25. package/dist/diagnostics.js +31 -0
  26. package/dist/diagnostics.js.map +1 -0
  27. package/dist/errors.d.ts +13 -0
  28. package/dist/errors.d.ts.map +1 -0
  29. package/dist/errors.js +23 -0
  30. package/dist/errors.js.map +1 -0
  31. package/dist/frameworks/astro/adapter-patches/assert-adapter-nested-under-mount.d.ts +28 -0
  32. package/dist/frameworks/astro/adapter-patches/assert-adapter-nested-under-mount.d.ts.map +1 -0
  33. package/dist/frameworks/astro/adapter-patches/assert-adapter-nested-under-mount.js +148 -0
  34. package/dist/frameworks/astro/adapter-patches/assert-adapter-nested-under-mount.js.map +1 -0
  35. package/dist/frameworks/astro/adapter-patches/ensure-compatible-cloudflare-adapter.d.ts +34 -0
  36. package/dist/frameworks/astro/adapter-patches/ensure-compatible-cloudflare-adapter.d.ts.map +1 -0
  37. package/dist/frameworks/astro/adapter-patches/ensure-compatible-cloudflare-adapter.js +89 -0
  38. package/dist/frameworks/astro/adapter-patches/ensure-compatible-cloudflare-adapter.js.map +1 -0
  39. package/dist/frameworks/astro/adapter-patches/resolve-cloudflare-adapter.d.ts +39 -0
  40. package/dist/frameworks/astro/adapter-patches/resolve-cloudflare-adapter.d.ts.map +1 -0
  41. package/dist/frameworks/astro/adapter-patches/resolve-cloudflare-adapter.js +93 -0
  42. package/dist/frameworks/astro/adapter-patches/resolve-cloudflare-adapter.js.map +1 -0
  43. package/dist/frameworks/astro/astro5.d.ts +28 -0
  44. package/dist/frameworks/astro/astro5.d.ts.map +1 -0
  45. package/dist/frameworks/astro/astro5.js +276 -0
  46. package/dist/frameworks/astro/astro5.js.map +1 -0
  47. package/dist/frameworks/astro/astro6.d.ts +28 -0
  48. package/dist/frameworks/astro/astro6.d.ts.map +1 -0
  49. package/dist/frameworks/astro/astro6.js +305 -0
  50. package/dist/frameworks/astro/astro6.js.map +1 -0
  51. package/dist/frameworks/astro/astro7.d.ts +28 -0
  52. package/dist/frameworks/astro/astro7.d.ts.map +1 -0
  53. package/dist/frameworks/astro/astro7.js +303 -0
  54. package/dist/frameworks/astro/astro7.js.map +1 -0
  55. package/dist/frameworks/astro/index.d.ts +10 -0
  56. package/dist/frameworks/astro/index.d.ts.map +1 -0
  57. package/dist/frameworks/astro/index.js +16 -0
  58. package/dist/frameworks/astro/index.js.map +1 -0
  59. package/dist/frameworks/astro/session-check.d.ts +24 -0
  60. package/dist/frameworks/astro/session-check.d.ts.map +1 -0
  61. package/dist/frameworks/astro/session-check.js +166 -0
  62. package/dist/frameworks/astro/session-check.js.map +1 -0
  63. package/dist/frameworks/astro/templates/astro.config.webflow.ts +61 -0
  64. package/dist/frameworks/astro/templates/v5/astro.config.mjs.template +25 -0
  65. package/dist/frameworks/astro/templates/v5/webflow-loader.ts +126 -0
  66. package/dist/frameworks/astro/templates/v5/wrangler.json +6 -0
  67. package/dist/frameworks/astro/templates/v6/astro.config.mjs.template +35 -0
  68. package/dist/frameworks/astro/templates/v6/webflow-loader.ts +126 -0
  69. package/dist/frameworks/astro/templates/v6/wrangler.json +9 -0
  70. package/dist/frameworks/astro/templates/v7/astro.config.mjs.template +34 -0
  71. package/dist/frameworks/astro/templates/v7/webflow-loader.ts +125 -0
  72. package/dist/frameworks/astro/templates/v7/wrangler.json +9 -0
  73. package/dist/frameworks/astro/templates/webflow-image-url.ts +140 -0
  74. package/dist/frameworks/base.d.ts +134 -0
  75. package/dist/frameworks/base.d.ts.map +1 -0
  76. package/dist/frameworks/base.js +175 -0
  77. package/dist/frameworks/base.js.map +1 -0
  78. package/dist/frameworks/errors.d.ts +72 -0
  79. package/dist/frameworks/errors.d.ts.map +1 -0
  80. package/dist/frameworks/errors.js +120 -0
  81. package/dist/frameworks/errors.js.map +1 -0
  82. package/dist/frameworks/nextjs/adapter-resolver.d.ts +72 -0
  83. package/dist/frameworks/nextjs/adapter-resolver.d.ts.map +1 -0
  84. package/dist/frameworks/nextjs/adapter-resolver.js +246 -0
  85. package/dist/frameworks/nextjs/adapter-resolver.js.map +1 -0
  86. package/dist/frameworks/nextjs/index.d.ts +38 -0
  87. package/dist/frameworks/nextjs/index.d.ts.map +1 -0
  88. package/dist/frameworks/nextjs/index.js +268 -0
  89. package/dist/frameworks/nextjs/index.js.map +1 -0
  90. package/dist/frameworks/nextjs/templates/next.config.ts.template +13 -0
  91. package/dist/frameworks/nextjs/templates/next.config.webflow.ts +46 -0
  92. package/dist/frameworks/nextjs/templates/open-next.config.ts +3 -0
  93. package/dist/frameworks/nextjs/templates/webflow-loader.ts +44 -0
  94. package/dist/frameworks/nextjs/templates/wrangler.json +6 -0
  95. package/dist/frameworks/registry.d.ts +127 -0
  96. package/dist/frameworks/registry.d.ts.map +1 -0
  97. package/dist/frameworks/registry.js +257 -0
  98. package/dist/frameworks/registry.js.map +1 -0
  99. package/dist/frameworks/static/index.d.ts +116 -0
  100. package/dist/frameworks/static/index.d.ts.map +1 -0
  101. package/dist/frameworks/static/index.js +394 -0
  102. package/dist/frameworks/static/index.js.map +1 -0
  103. package/dist/frameworks/static/templates/wrangler.json +8 -0
  104. package/dist/frameworks/types.d.ts +138 -0
  105. package/dist/frameworks/types.d.ts.map +1 -0
  106. package/dist/frameworks/types.js +3 -0
  107. package/dist/frameworks/types.js.map +1 -0
  108. package/dist/frameworks/vite/index.d.ts +46 -0
  109. package/dist/frameworks/vite/index.d.ts.map +1 -0
  110. package/dist/frameworks/vite/index.js +303 -0
  111. package/dist/frameworks/vite/index.js.map +1 -0
  112. package/dist/frameworks/vite/templates/vite.config.ts.template +14 -0
  113. package/dist/frameworks/vite/templates/vite.config.webflow.ts +52 -0
  114. package/dist/frameworks/vite/templates/wrangler.json +10 -0
  115. package/dist/index.d.ts +49 -0
  116. package/dist/index.d.ts.map +1 -0
  117. package/dist/index.js +136 -0
  118. package/dist/index.js.map +1 -0
  119. package/dist/pipeline.d.ts +12 -0
  120. package/dist/pipeline.d.ts.map +1 -0
  121. package/dist/pipeline.js +293 -0
  122. package/dist/pipeline.js.map +1 -0
  123. package/dist/ports/command-runner.d.ts +59 -0
  124. package/dist/ports/command-runner.d.ts.map +1 -0
  125. package/dist/ports/command-runner.js +126 -0
  126. package/dist/ports/command-runner.js.map +1 -0
  127. package/dist/ports/filesystem.d.ts +19 -0
  128. package/dist/ports/filesystem.d.ts.map +1 -0
  129. package/dist/ports/filesystem.js +68 -0
  130. package/dist/ports/filesystem.js.map +1 -0
  131. package/dist/ports/index.d.ts +17 -0
  132. package/dist/ports/index.d.ts.map +1 -0
  133. package/dist/ports/index.js +14 -0
  134. package/dist/ports/index.js.map +1 -0
  135. package/dist/ports/logger.d.ts +22 -0
  136. package/dist/ports/logger.d.ts.map +1 -0
  137. package/dist/ports/logger.js +18 -0
  138. package/dist/ports/logger.js.map +1 -0
  139. package/dist/ports/telemetry.d.ts +23 -0
  140. package/dist/ports/telemetry.d.ts.map +1 -0
  141. package/dist/ports/telemetry.js +12 -0
  142. package/dist/ports/telemetry.js.map +1 -0
  143. package/dist/static-base-href.d.ts +31 -0
  144. package/dist/static-base-href.d.ts.map +1 -0
  145. package/dist/static-base-href.js +183 -0
  146. package/dist/static-base-href.js.map +1 -0
  147. package/dist/toolchain/errors.d.ts +32 -0
  148. package/dist/toolchain/errors.d.ts.map +1 -0
  149. package/dist/toolchain/errors.js +31 -0
  150. package/dist/toolchain/errors.js.map +1 -0
  151. package/dist/toolchain/resolver.d.ts +25 -0
  152. package/dist/toolchain/resolver.d.ts.map +1 -0
  153. package/dist/toolchain/resolver.js +200 -0
  154. package/dist/toolchain/resolver.js.map +1 -0
  155. package/dist/toolchain/types.d.ts +52 -0
  156. package/dist/toolchain/types.d.ts.map +1 -0
  157. package/dist/toolchain/types.js +10 -0
  158. package/dist/toolchain/types.js.map +1 -0
  159. package/dist/types.d.ts +71 -0
  160. package/dist/types.d.ts.map +1 -0
  161. package/dist/types.js +3 -0
  162. package/dist/types.js.map +1 -0
  163. package/dist/utils/fs.d.ts +29 -0
  164. package/dist/utils/fs.d.ts.map +1 -0
  165. package/dist/utils/fs.js +126 -0
  166. package/dist/utils/fs.js.map +1 -0
  167. package/dist/utils/index.d.ts +11 -0
  168. package/dist/utils/index.d.ts.map +1 -0
  169. package/dist/utils/index.js +27 -0
  170. package/dist/utils/index.js.map +1 -0
  171. package/dist/utils/node-version.d.ts +20 -0
  172. package/dist/utils/node-version.d.ts.map +1 -0
  173. package/dist/utils/node-version.js +113 -0
  174. package/dist/utils/node-version.js.map +1 -0
  175. package/dist/utils/process.d.ts +25 -0
  176. package/dist/utils/process.d.ts.map +1 -0
  177. package/dist/utils/process.js +40 -0
  178. package/dist/utils/process.js.map +1 -0
  179. package/dist/utils/reserved-env.d.ts +26 -0
  180. package/dist/utils/reserved-env.d.ts.map +1 -0
  181. package/dist/utils/reserved-env.js +51 -0
  182. package/dist/utils/reserved-env.js.map +1 -0
  183. package/dist/utils/template.d.ts +15 -0
  184. package/dist/utils/template.d.ts.map +1 -0
  185. package/dist/utils/template.js +75 -0
  186. package/dist/utils/template.js.map +1 -0
  187. package/dist/utils/version.d.ts +9 -0
  188. package/dist/utils/version.d.ts.map +1 -0
  189. package/dist/utils/version.js +52 -0
  190. package/dist/utils/version.js.map +1 -0
  191. package/dist/wrangler.d.ts +195 -0
  192. package/dist/wrangler.d.ts.map +1 -0
  193. package/dist/wrangler.js +372 -0
  194. package/dist/wrangler.js.map +1 -0
  195. package/package.json +48 -0
@@ -0,0 +1,25 @@
1
+ // Generated by the Webflow Cloud builder. Merge logic lives in ./astro.config.webflow.ts.
2
+ import { defineConfig } from 'astro/config';
3
+ import cloudflare from '@astrojs/cloudflare';
4
+ import react from '@astrojs/react';
5
+ import userConfig from './clouduser.astro.config';
6
+ import { withWebflowOverrides } from './astro.config.webflow';
7
+
8
+ export default defineConfig(
9
+ withWebflowOverrides(userConfig, {
10
+ mountPath: '${COSMIC_MOUNT_PATH}',
11
+ deployUrl: '${COSMIC_DEPLOY_URL}',
12
+ adapter: cloudflare({
13
+ imageService: 'custom',
14
+ platformProxy: {
15
+ enabled: true,
16
+ },
17
+ }),
18
+ reactIntegration: react(),
19
+ // Use react-dom/server.edge instead of react-dom/server.browser for React 19.
20
+ // Without this, MessageChannel from node:worker_threads needs to be polyfilled.
21
+ reactDomServerAlias: import.meta.env.PROD
22
+ ? { 'react-dom/server': 'react-dom/server.edge' }
23
+ : undefined,
24
+ }),
25
+ );
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Webflow Image Loader for Astro 5
3
+ *
4
+ * A custom Astro image service that resizes images on demand through
5
+ * Cloudflare's zone-level Image Resizing (`/cdn-cgi/image/...`). URL-building
6
+ * and responsive-width logic live in the framework-agnostic ./webflow-image-url
7
+ * helper (unit-tested); this file is the thin Astro-typed glue. Same loader as
8
+ * Astro 6 and 7.
9
+ *
10
+ * We set the adapter's imageService to 'custom' to keep resizing on the zone
11
+ * endpoint rather than the Worker Images binding.
12
+ */
13
+
14
+ import type { ExternalImageService, ImageTransform, AstroConfig } from 'astro';
15
+ import { normalizeSrc, isRemote, buildImageUrl, computeSrcSetWidths } from './webflow-image-url';
16
+
17
+ // Default implementation copied from Astro's baseService
18
+ function getTargetDimensions(options: ImageTransform) {
19
+ let targetWidth = options.width;
20
+ let targetHeight = options.height;
21
+
22
+ // For ESM imported images, calculate missing dimensions based on aspect ratio
23
+ if (typeof options.src === 'object' && 'width' in options.src && 'height' in options.src) {
24
+ const aspectRatio = options.src.width / options.src.height;
25
+ if (targetHeight && !targetWidth) {
26
+ targetWidth = Math.round(targetHeight * aspectRatio);
27
+ } else if (targetWidth && !targetHeight) {
28
+ targetHeight = Math.round(targetWidth / aspectRatio);
29
+ } else if (!targetWidth && !targetHeight) {
30
+ targetWidth = options.src.width;
31
+ targetHeight = options.src.height;
32
+ }
33
+ }
34
+
35
+ return {
36
+ targetWidth,
37
+ targetHeight,
38
+ };
39
+ }
40
+
41
+ const cloudflareLoader: ExternalImageService = {
42
+ getURL(options: ImageTransform, imageConfig: AstroConfig['image']) {
43
+ const { deployUrl, mountPath } = imageConfig.service.config;
44
+ const normalizedSrc = normalizeSrc(
45
+ typeof options.src === 'object' ? options.src.src : options.src,
46
+ deployUrl,
47
+ mountPath,
48
+ );
49
+ const isSvg =
50
+ typeof options.src === 'object' ? options.src.format === 'svg' : options.src.endsWith('.svg');
51
+
52
+ return buildImageUrl(
53
+ normalizedSrc,
54
+ {
55
+ width: options.width,
56
+ height: options.height,
57
+ quality: options.quality,
58
+ format: options.format,
59
+ },
60
+ deployUrl,
61
+ isSvg,
62
+ );
63
+ },
64
+
65
+ // Responsive variants. Astro resolves each entry's URL by calling getURL on
66
+ // its `transform`, so format=auto (set in getURL) flows through here too.
67
+ getSrcSet(options: ImageTransform, imageConfig: AstroConfig['image']) {
68
+ // Mirror getURL's passthrough: remote and SVG sources are not resized, so a
69
+ // srcset of identical URLs would be misleading markup. Normalize first, as
70
+ // getURL does, since build.assetsPrefix makes local asset srcs absolute on
71
+ // the deploy origin.
72
+ const { deployUrl, mountPath } = imageConfig.service.config;
73
+ const srcString = typeof options.src === 'object' ? options.src.src : options.src;
74
+ const normalizedSrc = normalizeSrc(srcString, deployUrl, mountPath);
75
+ const isSvg =
76
+ typeof options.src === 'object' ? options.src.format === 'svg' : srcString.endsWith('.svg');
77
+ if (isSvg || isRemote(normalizedSrc)) {
78
+ return [];
79
+ }
80
+
81
+ const { targetWidth, targetHeight } = getTargetDimensions(options);
82
+ const maxWidth =
83
+ typeof options.src === 'object' && 'width' in options.src ? options.src.width : undefined;
84
+
85
+ return computeSrcSetWidths(options, targetWidth, targetHeight, maxWidth).map(
86
+ ({ width, height, descriptor }) => ({
87
+ transform: { ...options, width, height },
88
+ descriptor,
89
+ // Only advertise a MIME type when a concrete format was requested. With
90
+ // the default (format=auto) Cloudflare negotiates the output, so
91
+ // `type: "image/auto"` would be an invalid, misleading hint.
92
+ attributes: options.format ? { type: `image/${options.format}` } : {},
93
+ }),
94
+ );
95
+ },
96
+
97
+ // Default implementation copied from Astro's baseService
98
+ getHTMLAttributes(options: ImageTransform) {
99
+ const { targetWidth, targetHeight } = getTargetDimensions(options);
100
+ const {
101
+ src,
102
+ width,
103
+ height,
104
+ format,
105
+ quality,
106
+ densities,
107
+ widths,
108
+ formats,
109
+ layout,
110
+ priority,
111
+ fit,
112
+ position,
113
+ ...attributes
114
+ } = options;
115
+
116
+ return {
117
+ ...attributes,
118
+ width: targetWidth,
119
+ height: targetHeight,
120
+ loading: attributes.loading ?? 'lazy',
121
+ decoding: attributes.decoding ?? 'async',
122
+ };
123
+ },
124
+ };
125
+
126
+ export default cloudflareLoader;
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "cosmic-builder-dry-run",
3
+ "main": "dist/_worker.js/index.js",
4
+ "compatibility_date": "2025-03-03",
5
+ "compatibility_flags": ["nodejs_compat"]
6
+ }
@@ -0,0 +1,35 @@
1
+ // Generated by the Webflow Cloud builder. Merge logic lives in ./astro.config.webflow.ts.
2
+ import { defineConfig } from 'astro/config';
3
+ import { readFileSync } from 'node:fs';
4
+ import cloudflare from '@astrojs/cloudflare';
5
+ import react from '@astrojs/react';
6
+ import userConfig from './clouduser.astro.config';
7
+ import { withWebflowOverrides } from './astro.config.webflow';
8
+
9
+ // Detect React 19+ so we know whether to alias react-dom/server -> .edge.
10
+ let isReact19 = false;
11
+ try {
12
+ const pkg = JSON.parse(readFileSync('./package.json', 'utf8'));
13
+ const reactVersion = pkg.dependencies?.react || pkg.devDependencies?.react || '';
14
+ // Match versions like "19", "^19", "~19", "19.0.0", "^19.0.0", etc.
15
+ isReact19 = /(?:^|[^\d])19(?:\.|$)/.test(reactVersion);
16
+ } catch {
17
+ // Could not read package.json, assume not React 19.
18
+ }
19
+
20
+ export default defineConfig(
21
+ withWebflowOverrides(userConfig, {
22
+ mountPath: '${COSMIC_MOUNT_PATH}',
23
+ deployUrl: '${COSMIC_DEPLOY_URL}',
24
+ adapter: cloudflare({
25
+ imageService: 'custom',
26
+ // Note: platformProxy removed in Astro 6+ — workerd runs natively via @cloudflare/vite-plugin.
27
+ }),
28
+ reactIntegration: react(),
29
+ // Only React 19+ has react-dom/server.edge. React 18 has only .browser, which doesn't run in edge runtimes.
30
+ reactDomServerAlias:
31
+ import.meta.env.PROD && isReact19
32
+ ? { 'react-dom/server': 'react-dom/server.edge' }
33
+ : undefined,
34
+ }),
35
+ );
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Webflow Image Loader for Astro 6
3
+ *
4
+ * A custom Astro image service that resizes images on demand through
5
+ * Cloudflare's zone-level Image Resizing (`/cdn-cgi/image/...`). URL-building
6
+ * and responsive-width logic live in the framework-agnostic ./webflow-image-url
7
+ * helper (unit-tested); this file is the thin Astro-typed glue. Same loader as
8
+ * Astro 5 and 7.
9
+ *
10
+ * We set the adapter's imageService to 'custom' to keep resizing on the zone
11
+ * endpoint rather than the Worker Images binding.
12
+ */
13
+
14
+ import type { ExternalImageService, ImageTransform, AstroConfig } from 'astro';
15
+ import { normalizeSrc, isRemote, buildImageUrl, computeSrcSetWidths } from './webflow-image-url';
16
+
17
+ // Default implementation copied from Astro's baseService
18
+ function getTargetDimensions(options: ImageTransform) {
19
+ let targetWidth = options.width;
20
+ let targetHeight = options.height;
21
+
22
+ // For ESM imported images, calculate missing dimensions based on aspect ratio
23
+ if (typeof options.src === 'object' && 'width' in options.src && 'height' in options.src) {
24
+ const aspectRatio = options.src.width / options.src.height;
25
+ if (targetHeight && !targetWidth) {
26
+ targetWidth = Math.round(targetHeight * aspectRatio);
27
+ } else if (targetWidth && !targetHeight) {
28
+ targetHeight = Math.round(targetWidth / aspectRatio);
29
+ } else if (!targetWidth && !targetHeight) {
30
+ targetWidth = options.src.width;
31
+ targetHeight = options.src.height;
32
+ }
33
+ }
34
+
35
+ return {
36
+ targetWidth,
37
+ targetHeight,
38
+ };
39
+ }
40
+
41
+ const cloudflareLoader: ExternalImageService = {
42
+ getURL(options: ImageTransform, imageConfig: AstroConfig['image']) {
43
+ const { deployUrl, mountPath } = imageConfig.service.config;
44
+ const normalizedSrc = normalizeSrc(
45
+ typeof options.src === 'object' ? options.src.src : options.src,
46
+ deployUrl,
47
+ mountPath,
48
+ );
49
+ const isSvg =
50
+ typeof options.src === 'object' ? options.src.format === 'svg' : options.src.endsWith('.svg');
51
+
52
+ return buildImageUrl(
53
+ normalizedSrc,
54
+ {
55
+ width: options.width,
56
+ height: options.height,
57
+ quality: options.quality,
58
+ format: options.format,
59
+ },
60
+ deployUrl,
61
+ isSvg,
62
+ );
63
+ },
64
+
65
+ // Responsive variants. Astro resolves each entry's URL by calling getURL on
66
+ // its `transform`, so format=auto (set in getURL) flows through here too.
67
+ getSrcSet(options: ImageTransform, imageConfig: AstroConfig['image']) {
68
+ // Mirror getURL's passthrough: remote and SVG sources are not resized, so a
69
+ // srcset of identical URLs would be misleading markup. Normalize first, as
70
+ // getURL does, since build.assetsPrefix makes local asset srcs absolute on
71
+ // the deploy origin.
72
+ const { deployUrl, mountPath } = imageConfig.service.config;
73
+ const srcString = typeof options.src === 'object' ? options.src.src : options.src;
74
+ const normalizedSrc = normalizeSrc(srcString, deployUrl, mountPath);
75
+ const isSvg =
76
+ typeof options.src === 'object' ? options.src.format === 'svg' : srcString.endsWith('.svg');
77
+ if (isSvg || isRemote(normalizedSrc)) {
78
+ return [];
79
+ }
80
+
81
+ const { targetWidth, targetHeight } = getTargetDimensions(options);
82
+ const maxWidth =
83
+ typeof options.src === 'object' && 'width' in options.src ? options.src.width : undefined;
84
+
85
+ return computeSrcSetWidths(options, targetWidth, targetHeight, maxWidth).map(
86
+ ({ width, height, descriptor }) => ({
87
+ transform: { ...options, width, height },
88
+ descriptor,
89
+ // Only advertise a MIME type when a concrete format was requested. With
90
+ // the default (format=auto) Cloudflare negotiates the output, so
91
+ // `type: "image/auto"` would be an invalid, misleading hint.
92
+ attributes: options.format ? { type: `image/${options.format}` } : {},
93
+ }),
94
+ );
95
+ },
96
+
97
+ // Default implementation copied from Astro's baseService
98
+ getHTMLAttributes(options: ImageTransform) {
99
+ const { targetWidth, targetHeight } = getTargetDimensions(options);
100
+ const {
101
+ src,
102
+ width,
103
+ height,
104
+ format,
105
+ quality,
106
+ densities,
107
+ widths,
108
+ formats,
109
+ layout,
110
+ priority,
111
+ fit,
112
+ position,
113
+ ...attributes
114
+ } = options;
115
+
116
+ return {
117
+ ...attributes,
118
+ width: targetWidth,
119
+ height: targetHeight,
120
+ loading: attributes.loading ?? 'lazy',
121
+ decoding: attributes.decoding ?? 'async',
122
+ };
123
+ },
124
+ };
125
+
126
+ export default cloudflareLoader;
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "cosmic-builder-dry-run",
3
+ "main": "@astrojs/cloudflare/entrypoints/server",
4
+ "compatibility_date": "2025-03-03",
5
+ "compatibility_flags": ["nodejs_compat"],
6
+ "assets": {
7
+ "directory": "./dist/client"
8
+ }
9
+ }
@@ -0,0 +1,34 @@
1
+ // Generated by the Webflow Cloud builder. Merge logic lives in ./astro.config.webflow.ts.
2
+ import { defineConfig } from 'astro/config';
3
+ import { readFileSync } from 'node:fs';
4
+ import cloudflare from '@astrojs/cloudflare';
5
+ import react from '@astrojs/react';
6
+ import userConfig from './clouduser.astro.config';
7
+ import { withWebflowOverrides } from './astro.config.webflow';
8
+
9
+ // Detect React 19+ so we know whether to alias react-dom/server -> .edge.
10
+ let isReact19 = false;
11
+ try {
12
+ const pkg = JSON.parse(readFileSync('./package.json', 'utf8'));
13
+ const reactVersion = pkg.dependencies?.react || pkg.devDependencies?.react || '';
14
+ // Match versions like "19", "^19", "~19", "19.0.0", "^19.0.0", etc.
15
+ isReact19 = /(?:^|[^\d])19(?:\.|$)/.test(reactVersion);
16
+ } catch {
17
+ // Could not read package.json, assume not React 19.
18
+ }
19
+
20
+ export default defineConfig(
21
+ withWebflowOverrides(userConfig, {
22
+ mountPath: '${COSMIC_MOUNT_PATH}',
23
+ deployUrl: '${COSMIC_DEPLOY_URL}',
24
+ adapter: cloudflare({
25
+ imageService: 'custom',
26
+ // Note: platformProxy removed in Astro 6+ — workerd runs natively via @cloudflare/vite-plugin.
27
+ }),
28
+ reactIntegration: react(),
29
+ // Only React 19+ has react-dom/server.edge. React 18 has only .browser, which doesn't run in edge runtimes.
30
+ reactDomServerAlias: import.meta.env.PROD && isReact19
31
+ ? { 'react-dom/server': 'react-dom/server.edge' }
32
+ : undefined,
33
+ })
34
+ );
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Webflow Image Loader for Astro 7
3
+ *
4
+ * A custom Astro image service that resizes images on demand through
5
+ * Cloudflare's zone-level Image Resizing (`/cdn-cgi/image/...`). URL-building
6
+ * and responsive-width logic live in the framework-agnostic ./webflow-image-url
7
+ * helper (unit-tested); this file is the thin Astro-typed glue.
8
+ *
9
+ * Note: Astro 7 defaults to 'cloudflare-binding' for imageService, but we use
10
+ * 'custom' to keep resizing on the zone endpoint rather than the Worker binding.
11
+ */
12
+
13
+ import type { ExternalImageService, ImageTransform, AstroConfig } from 'astro';
14
+ import { normalizeSrc, isRemote, buildImageUrl, computeSrcSetWidths } from './webflow-image-url';
15
+
16
+ // Default implementation copied from Astro's baseService
17
+ function getTargetDimensions(options: ImageTransform) {
18
+ let targetWidth = options.width;
19
+ let targetHeight = options.height;
20
+
21
+ // For ESM imported images, calculate missing dimensions based on aspect ratio
22
+ if (typeof options.src === 'object' && 'width' in options.src && 'height' in options.src) {
23
+ const aspectRatio = options.src.width / options.src.height;
24
+ if (targetHeight && !targetWidth) {
25
+ targetWidth = Math.round(targetHeight * aspectRatio);
26
+ } else if (targetWidth && !targetHeight) {
27
+ targetHeight = Math.round(targetWidth / aspectRatio);
28
+ } else if (!targetWidth && !targetHeight) {
29
+ targetWidth = options.src.width;
30
+ targetHeight = options.src.height;
31
+ }
32
+ }
33
+
34
+ return {
35
+ targetWidth,
36
+ targetHeight,
37
+ };
38
+ }
39
+
40
+ const cloudflareLoader: ExternalImageService = {
41
+ getURL(options: ImageTransform, imageConfig: AstroConfig['image']) {
42
+ const { deployUrl, mountPath } = imageConfig.service.config;
43
+ const normalizedSrc = normalizeSrc(
44
+ typeof options.src === 'object' ? options.src.src : options.src,
45
+ deployUrl,
46
+ mountPath,
47
+ );
48
+ const isSvg =
49
+ typeof options.src === 'object' ? options.src.format === 'svg' : options.src.endsWith('.svg');
50
+
51
+ return buildImageUrl(
52
+ normalizedSrc,
53
+ {
54
+ width: options.width,
55
+ height: options.height,
56
+ quality: options.quality,
57
+ format: options.format,
58
+ },
59
+ deployUrl,
60
+ isSvg,
61
+ );
62
+ },
63
+
64
+ // Responsive variants. Astro resolves each entry's URL by calling getURL on
65
+ // its `transform`, so format=auto (set in getURL) flows through here too.
66
+ getSrcSet(options: ImageTransform, imageConfig: AstroConfig['image']) {
67
+ // Mirror getURL's passthrough: remote and SVG sources are not resized, so a
68
+ // srcset of identical URLs would be misleading markup. Normalize first, as
69
+ // getURL does, since build.assetsPrefix makes local asset srcs absolute on
70
+ // the deploy origin.
71
+ const { deployUrl, mountPath } = imageConfig.service.config;
72
+ const srcString = typeof options.src === 'object' ? options.src.src : options.src;
73
+ const normalizedSrc = normalizeSrc(srcString, deployUrl, mountPath);
74
+ const isSvg =
75
+ typeof options.src === 'object' ? options.src.format === 'svg' : srcString.endsWith('.svg');
76
+ if (isSvg || isRemote(normalizedSrc)) {
77
+ return [];
78
+ }
79
+
80
+ const { targetWidth, targetHeight } = getTargetDimensions(options);
81
+ const maxWidth =
82
+ typeof options.src === 'object' && 'width' in options.src ? options.src.width : undefined;
83
+
84
+ return computeSrcSetWidths(options, targetWidth, targetHeight, maxWidth).map(
85
+ ({ width, height, descriptor }) => ({
86
+ transform: { ...options, width, height },
87
+ descriptor,
88
+ // Only advertise a MIME type when a concrete format was requested. With
89
+ // the default (format=auto) Cloudflare negotiates the output, so
90
+ // `type: "image/auto"` would be an invalid, misleading hint.
91
+ attributes: options.format ? { type: `image/${options.format}` } : {},
92
+ }),
93
+ );
94
+ },
95
+
96
+ // Default implementation copied from Astro's baseService
97
+ getHTMLAttributes(options: ImageTransform) {
98
+ const { targetWidth, targetHeight } = getTargetDimensions(options);
99
+ const {
100
+ src,
101
+ width,
102
+ height,
103
+ format,
104
+ quality,
105
+ densities,
106
+ widths,
107
+ formats,
108
+ layout,
109
+ priority,
110
+ fit,
111
+ position,
112
+ ...attributes
113
+ } = options;
114
+
115
+ return {
116
+ ...attributes,
117
+ width: targetWidth,
118
+ height: targetHeight,
119
+ loading: attributes.loading ?? 'lazy',
120
+ decoding: attributes.decoding ?? 'async',
121
+ };
122
+ },
123
+ };
124
+
125
+ export default cloudflareLoader;
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "cosmic-builder-dry-run",
3
+ "main": "@astrojs/cloudflare/entrypoints/server",
4
+ "compatibility_date": "2025-03-03",
5
+ "compatibility_flags": ["nodejs_compat"],
6
+ "assets": {
7
+ "directory": "./dist/client"
8
+ }
9
+ }
@@ -0,0 +1,140 @@
1
+ // Pure helpers for Webflow Cloud's on-demand Cloudflare image URLs.
2
+ //
3
+ // Copied verbatim into the user's project (imported by webflow-loader.ts) and
4
+ // unit-tested directly in @webflow/cloud-app. Deliberately free of any `astro`
5
+ // import so it can be type-checked and tested without the framework installed;
6
+ // the loader supplies the Astro-typed glue. Shared across Astro 5/6/7.
7
+
8
+ export type ImageUrlOptions = {
9
+ width?: number;
10
+ height?: number;
11
+ quality?: number | string;
12
+ format?: string;
13
+ };
14
+
15
+ /**
16
+ * Rewrite an image src to a path relative to the deploy: add the mount path,
17
+ * drop the deploy URL, and remove the leading slash. Remote URLs are returned
18
+ * unchanged so the caller can detect and pass them through.
19
+ */
20
+ export function normalizeSrc(src: string, deployUrl?: string, mountPath?: string): string {
21
+ // For local assets, include the mount path but not the deploy url, and remove the leading slash
22
+ if (deployUrl && src.startsWith(deployUrl)) {
23
+ return `${src.slice(deployUrl.length)}`.slice(1);
24
+ } else if (mountPath && src.startsWith(mountPath)) {
25
+ return src.slice(1);
26
+ } else if (src.startsWith('/')) {
27
+ return `${mountPath}${src}`.slice(1);
28
+ }
29
+ return src;
30
+ }
31
+
32
+ export function isRemote(normalizedSrc: string): boolean {
33
+ return normalizedSrc.startsWith('http://') || normalizedSrc.startsWith('https://');
34
+ }
35
+
36
+ /**
37
+ * Build the on-demand Cloudflare Image Resizing URL (`/cdn-cgi/image/...`) for a
38
+ * local asset. Remote images and SVGs, and requests with nothing to transform,
39
+ * are returned as the plain asset URL.
40
+ *
41
+ * When the caller pins no `format`, default to `auto` so Cloudflare negotiates
42
+ * WebP/AVIF (via the Accept header) instead of serving the heavier source
43
+ * format. An explicit `format` is honored as-is.
44
+ */
45
+ export function buildImageUrl(
46
+ normalizedSrc: string,
47
+ options: ImageUrlOptions,
48
+ deployUrl: string,
49
+ isSvg: boolean,
50
+ ): string {
51
+ // Remote images are served as-is, never routed through Cloudflare resizing:
52
+ // our zone does not optimize external images, and re-optimizing Webflow-hosted
53
+ // images (cdn.website-files.com) would double-bill bandwidth (the origin zone
54
+ // for the full image plus the cosmic zone for the resized one). Astro hands
55
+ // http(s) srcs straight through.
56
+ if (isRemote(normalizedSrc)) {
57
+ return normalizedSrc;
58
+ }
59
+
60
+ const params: string[] = [];
61
+ for (const key of ['width', 'height', 'quality'] as const) {
62
+ const value = options[key];
63
+ if (value) {
64
+ params.push(`${key}=${value}`);
65
+ }
66
+ }
67
+
68
+ // Only route through /cdn-cgi/image when there is something to transform. An
69
+ // explicit format alone still counts (e.g. `<Image format="webp" />`).
70
+ const transforming = params.length > 0 || Boolean(options.format);
71
+ if (isSvg || !transforming) {
72
+ return `${deployUrl}/${normalizedSrc}`;
73
+ }
74
+
75
+ params.push(`format=${options.format || 'auto'}`);
76
+ return `${deployUrl}/cdn-cgi/image/${params.join(',')}/${normalizedSrc}`;
77
+ }
78
+
79
+ export type SrcSetWidth = { width: number; height?: number; descriptor: string };
80
+
81
+ /**
82
+ * Compute the responsive variants Astro's `<Image>` asked for, mirroring
83
+ * Astro's own baseService: `densities` yield `Nx` descriptors, `widths` yield
84
+ * `Nw` descriptors. Each width's height is derived from the target aspect ratio.
85
+ * Variants are de-duplicated and kept within `maxWidth` (the source's intrinsic
86
+ * width, when known): `widths` above it are clamped, and `densities` that would
87
+ * resolve above it are dropped (matching Astro, which never upscales). Returns
88
+ * `[]` when no responsive variants were requested, so the loader can omit
89
+ * `srcset` entirely.
90
+ */
91
+ export function computeSrcSetWidths(
92
+ options: { widths?: number[]; densities?: Array<number | string> },
93
+ targetWidth: number | undefined,
94
+ targetHeight: number | undefined,
95
+ maxWidth?: number,
96
+ ): SrcSetWidth[] {
97
+ if (!targetWidth) {
98
+ return [];
99
+ }
100
+
101
+ const aspectRatio = targetHeight ? targetWidth / targetHeight : undefined;
102
+ const heightFor = (w: number) => (aspectRatio ? Math.round(w / aspectRatio) : undefined);
103
+
104
+ if (options.densities?.length) {
105
+ const seen = new Set<number>();
106
+ const result: SrcSetWidth[] = [];
107
+ for (const d of options.densities) {
108
+ const density = typeof d === 'string' ? parseFloat(d) : d;
109
+ const width = Math.round(targetWidth * density);
110
+ // Drop densities that would upscale past the source's intrinsic width:
111
+ // Astro's baseService ignores them rather than clamping, so an `Nx`
112
+ // descriptor never ends up pointing at a smaller image.
113
+ if (maxWidth && width > maxWidth) {
114
+ continue;
115
+ }
116
+ if (seen.has(width)) {
117
+ continue;
118
+ }
119
+ seen.add(width);
120
+ result.push({ width, height: heightFor(width), descriptor: `${density}x` });
121
+ }
122
+ return result;
123
+ }
124
+
125
+ if (options.widths?.length) {
126
+ const seen = new Set<number>();
127
+ const result: SrcSetWidth[] = [];
128
+ for (const raw of options.widths) {
129
+ const width = maxWidth ? Math.min(raw, maxWidth) : raw;
130
+ if (seen.has(width)) {
131
+ continue;
132
+ }
133
+ seen.add(width);
134
+ result.push({ width, height: heightFor(width), descriptor: `${width}w` });
135
+ }
136
+ return result;
137
+ }
138
+
139
+ return [];
140
+ }