@astrojs/cloudflare 0.0.0-cf-assets-20231021193132
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 +412 -0
- package/dist/entrypoints/image-service.d.ts +6 -0
- package/dist/entrypoints/image-service.js +340 -0
- package/dist/entrypoints/server.advanced.d.ts +21 -0
- package/dist/entrypoints/server.advanced.js +44 -0
- package/dist/entrypoints/server.directory.d.ts +14 -0
- package/dist/entrypoints/server.directory.js +46 -0
- package/dist/getAdapter.d.ts +5 -0
- package/dist/getAdapter.js +31 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +517 -0
- package/dist/util.d.ts +2 -0
- package/dist/util.js +13 -0
- package/dist/utils/deduplicatePatterns.d.ts +8 -0
- package/dist/utils/deduplicatePatterns.js +23 -0
- package/dist/utils/getCFObject.d.ts +2 -0
- package/dist/utils/getCFObject.js +67 -0
- package/dist/utils/parser.d.ts +27 -0
- package/dist/utils/parser.js +149 -0
- package/dist/utils/prependForwardSlash.d.ts +1 -0
- package/dist/utils/prependForwardSlash.js +3 -0
- package/dist/utils/rewriteWasmImportPath.d.ts +8 -0
- package/dist/utils/rewriteWasmImportPath.js +23 -0
- package/dist/utils/wasm-module-loader.d.ts +14 -0
- package/dist/utils/wasm-module-loader.js +101 -0
- package/package.json +65 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects';
|
|
2
|
+
import { sharpImageService } from 'astro/config';
|
|
3
|
+
import { AstroError } from 'astro/errors';
|
|
4
|
+
import esbuild from 'esbuild';
|
|
5
|
+
import { Miniflare } from 'miniflare';
|
|
6
|
+
import * as fs from 'node:fs';
|
|
7
|
+
import * as os from 'node:os';
|
|
8
|
+
import { dirname, relative, sep } from 'node:path';
|
|
9
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
10
|
+
import glob from 'tiny-glob';
|
|
11
|
+
import { getAdapter } from './getAdapter.js';
|
|
12
|
+
import { deduplicatePatterns } from './utils/deduplicatePatterns.js';
|
|
13
|
+
import { getCFObject } from './utils/getCFObject.js';
|
|
14
|
+
import { getD1Bindings, getDOBindings, getEnvVars, getKVBindings, getR2Bindings, } from './utils/parser.js';
|
|
15
|
+
import { prependForwardSlash } from './utils/prependForwardSlash.js';
|
|
16
|
+
import { rewriteWasmImportPath } from './utils/rewriteWasmImportPath.js';
|
|
17
|
+
import { wasmModuleLoader } from './utils/wasm-module-loader.js';
|
|
18
|
+
const RUNTIME_WARNING = `You are using a deprecated string format for the \`runtime\` API. Please update to the current format for better support.
|
|
19
|
+
|
|
20
|
+
Example:
|
|
21
|
+
Old Format: 'local'
|
|
22
|
+
Current Format: { mode: 'local', persistTo: '.wrangler/state/v3' }
|
|
23
|
+
|
|
24
|
+
Please refer to our runtime documentation for more details on the format. https://docs.astro.build/en/guides/integrations-guide/cloudflare/#runtime`;
|
|
25
|
+
export default function createIntegration(args) {
|
|
26
|
+
let _config;
|
|
27
|
+
let _buildConfig;
|
|
28
|
+
let _mf;
|
|
29
|
+
let _entryPoints = new Map();
|
|
30
|
+
const SERVER_BUILD_FOLDER = '/$server_build/';
|
|
31
|
+
const isModeDirectory = args?.mode === 'directory';
|
|
32
|
+
const functionPerRoute = args?.functionPerRoute ?? false;
|
|
33
|
+
let runtimeMode = { mode: 'off' };
|
|
34
|
+
if (args?.runtime === 'remote') {
|
|
35
|
+
runtimeMode = { mode: 'remote' };
|
|
36
|
+
}
|
|
37
|
+
else if (args?.runtime === 'local') {
|
|
38
|
+
runtimeMode = { mode: 'local', persistTo: '.wrangler/state/v3' };
|
|
39
|
+
}
|
|
40
|
+
else if (typeof args?.runtime === 'object' &&
|
|
41
|
+
args?.runtime.mode === 'local' &&
|
|
42
|
+
args.runtime.persistTo === undefined) {
|
|
43
|
+
runtimeMode = { mode: 'local', persistTo: '.wrangler/state/v3' };
|
|
44
|
+
}
|
|
45
|
+
else if (args?.runtime) {
|
|
46
|
+
runtimeMode = args?.runtime;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
name: '@astrojs/cloudflare',
|
|
50
|
+
hooks: {
|
|
51
|
+
'astro:config:setup': ({ command, config, updateConfig }) => {
|
|
52
|
+
updateConfig({
|
|
53
|
+
build: {
|
|
54
|
+
client: new URL(`.${config.base}`, config.outDir),
|
|
55
|
+
server: new URL(`.${SERVER_BUILD_FOLDER}`, config.outDir),
|
|
56
|
+
serverEntry: '_worker.mjs',
|
|
57
|
+
redirects: false,
|
|
58
|
+
},
|
|
59
|
+
vite: {
|
|
60
|
+
// load .wasm files as WebAssembly modules
|
|
61
|
+
plugins: [
|
|
62
|
+
wasmModuleLoader({
|
|
63
|
+
disabled: !args?.wasmModuleImports,
|
|
64
|
+
assetsDirectory: config.build.assets,
|
|
65
|
+
}),
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
image: {
|
|
69
|
+
service: command === 'dev'
|
|
70
|
+
? sharpImageService()
|
|
71
|
+
: {
|
|
72
|
+
entrypoint: '@astrojs/cloudflare/image-service',
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
'astro:config:done': ({ setAdapter, config, logger }) => {
|
|
78
|
+
setAdapter(getAdapter({ isModeDirectory, functionPerRoute }));
|
|
79
|
+
_config = config;
|
|
80
|
+
_buildConfig = config.build;
|
|
81
|
+
if (_config.output === 'static') {
|
|
82
|
+
throw new AstroError('[@astrojs/cloudflare] `output: "server"` or `output: "hybrid"` is required to use this adapter. Otherwise, this adapter is not necessary to deploy a static site to Cloudflare.');
|
|
83
|
+
}
|
|
84
|
+
if (_config.base === SERVER_BUILD_FOLDER) {
|
|
85
|
+
throw new AstroError('[@astrojs/cloudflare] `base: "${SERVER_BUILD_FOLDER}"` is not allowed. Please change your `base` config to something else.');
|
|
86
|
+
}
|
|
87
|
+
if (typeof args?.runtime === 'string') {
|
|
88
|
+
logger.warn(RUNTIME_WARNING);
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
'astro:server:setup': ({ server }) => {
|
|
92
|
+
if (runtimeMode.mode === 'local') {
|
|
93
|
+
const typedRuntimeMode = runtimeMode;
|
|
94
|
+
server.middlewares.use(async function middleware(req, res, next) {
|
|
95
|
+
try {
|
|
96
|
+
const cf = await getCFObject(typedRuntimeMode.mode);
|
|
97
|
+
const vars = await getEnvVars();
|
|
98
|
+
const D1Bindings = await getD1Bindings();
|
|
99
|
+
const R2Bindings = await getR2Bindings();
|
|
100
|
+
const KVBindings = await getKVBindings();
|
|
101
|
+
const DOBindings = await getDOBindings();
|
|
102
|
+
let bindingsEnv = new Object({});
|
|
103
|
+
// fix for the error "kj/filesystem-disk-unix.c++:1709: warning: PWD environment variable doesn't match current directory."
|
|
104
|
+
// note: This mismatch might be primarily due to the test runner.
|
|
105
|
+
const originalPWD = process.env.PWD;
|
|
106
|
+
process.env.PWD = process.cwd();
|
|
107
|
+
_mf = new Miniflare({
|
|
108
|
+
modules: true,
|
|
109
|
+
script: '',
|
|
110
|
+
cache: true,
|
|
111
|
+
cachePersist: `${typedRuntimeMode.persistTo}/cache`,
|
|
112
|
+
cacheWarnUsage: true,
|
|
113
|
+
d1Databases: D1Bindings,
|
|
114
|
+
d1Persist: `${typedRuntimeMode.persistTo}/d1`,
|
|
115
|
+
r2Buckets: R2Bindings,
|
|
116
|
+
r2Persist: `${typedRuntimeMode.persistTo}/r2`,
|
|
117
|
+
kvNamespaces: KVBindings,
|
|
118
|
+
kvPersist: `${typedRuntimeMode.persistTo}/kv`,
|
|
119
|
+
durableObjects: DOBindings,
|
|
120
|
+
durableObjectsPersist: `${typedRuntimeMode.persistTo}/do`,
|
|
121
|
+
});
|
|
122
|
+
await _mf.ready;
|
|
123
|
+
for (const D1Binding of D1Bindings) {
|
|
124
|
+
const db = await _mf.getD1Database(D1Binding);
|
|
125
|
+
Reflect.set(bindingsEnv, D1Binding, db);
|
|
126
|
+
}
|
|
127
|
+
for (const R2Binding of R2Bindings) {
|
|
128
|
+
const bucket = await _mf.getR2Bucket(R2Binding);
|
|
129
|
+
Reflect.set(bindingsEnv, R2Binding, bucket);
|
|
130
|
+
}
|
|
131
|
+
for (const KVBinding of KVBindings) {
|
|
132
|
+
const namespace = await _mf.getKVNamespace(KVBinding);
|
|
133
|
+
Reflect.set(bindingsEnv, KVBinding, namespace);
|
|
134
|
+
}
|
|
135
|
+
for (const key in DOBindings) {
|
|
136
|
+
if (Object.prototype.hasOwnProperty.call(DOBindings, key)) {
|
|
137
|
+
const DO = await _mf.getDurableObjectNamespace(key);
|
|
138
|
+
Reflect.set(bindingsEnv, key, DO);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const mfCache = await _mf.getCaches();
|
|
142
|
+
process.env.PWD = originalPWD;
|
|
143
|
+
const clientLocalsSymbol = Symbol.for('astro.locals');
|
|
144
|
+
Reflect.set(req, clientLocalsSymbol, {
|
|
145
|
+
runtime: {
|
|
146
|
+
env: {
|
|
147
|
+
// default binding for static assets will be dynamic once we support mocking of bindings
|
|
148
|
+
ASSETS: {},
|
|
149
|
+
// this is just a VAR for CF to change build behavior, on dev it should be 0
|
|
150
|
+
CF_PAGES: '0',
|
|
151
|
+
// will be fetched from git dynamically once we support mocking of bindings
|
|
152
|
+
CF_PAGES_BRANCH: 'TBA',
|
|
153
|
+
// will be fetched from git dynamically once we support mocking of bindings
|
|
154
|
+
CF_PAGES_COMMIT_SHA: 'TBA',
|
|
155
|
+
CF_PAGES_URL: `http://${req.headers.host}`,
|
|
156
|
+
...bindingsEnv,
|
|
157
|
+
...vars,
|
|
158
|
+
},
|
|
159
|
+
cf: cf,
|
|
160
|
+
waitUntil: (_promise) => {
|
|
161
|
+
return;
|
|
162
|
+
},
|
|
163
|
+
caches: mfCache,
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
next();
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
next();
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
'astro:server:done': async ({ logger }) => {
|
|
175
|
+
if (_mf) {
|
|
176
|
+
logger.info('Cleaning up the Miniflare instance, and shutting down the workerd server.');
|
|
177
|
+
await _mf.dispose();
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
'astro:build:setup': ({ vite, target }) => {
|
|
181
|
+
if (target === 'server') {
|
|
182
|
+
vite.resolve ||= {};
|
|
183
|
+
vite.resolve.alias ||= {};
|
|
184
|
+
const aliases = [
|
|
185
|
+
{
|
|
186
|
+
find: 'react-dom/server',
|
|
187
|
+
replacement: 'react-dom/server.browser',
|
|
188
|
+
},
|
|
189
|
+
];
|
|
190
|
+
if (Array.isArray(vite.resolve.alias)) {
|
|
191
|
+
vite.resolve.alias = [...vite.resolve.alias, ...aliases];
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
for (const alias of aliases) {
|
|
195
|
+
vite.resolve.alias[alias.find] = alias.replacement;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
vite.ssr ||= {};
|
|
199
|
+
vite.ssr.target = 'webworker';
|
|
200
|
+
// Cloudflare env is only available per request. This isn't feasible for code that access env vars
|
|
201
|
+
// in a global way, so we shim their access as `process.env.*`. We will populate `process.env` later
|
|
202
|
+
// in its fetch handler.
|
|
203
|
+
vite.define = {
|
|
204
|
+
'process.env': 'process.env',
|
|
205
|
+
...vite.define,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
'astro:build:ssr': ({ entryPoints }) => {
|
|
210
|
+
_entryPoints = entryPoints;
|
|
211
|
+
},
|
|
212
|
+
'astro:build:done': async ({ pages, routes, dir }) => {
|
|
213
|
+
const functionsUrl = new URL('functions/', _config.root);
|
|
214
|
+
const assetsUrl = new URL(_buildConfig.assets, _buildConfig.client);
|
|
215
|
+
if (isModeDirectory) {
|
|
216
|
+
await fs.promises.mkdir(functionsUrl, { recursive: true });
|
|
217
|
+
}
|
|
218
|
+
// TODO: remove _buildConfig.split in Astro 4.0
|
|
219
|
+
if (isModeDirectory && (_buildConfig.split || functionPerRoute)) {
|
|
220
|
+
const entryPointsURL = [..._entryPoints.values()];
|
|
221
|
+
const entryPaths = entryPointsURL.map((entry) => fileURLToPath(entry));
|
|
222
|
+
const outputUrl = new URL('$astro', _buildConfig.server);
|
|
223
|
+
const outputDir = fileURLToPath(outputUrl);
|
|
224
|
+
//
|
|
225
|
+
// Sadly, when wasmModuleImports is enabled, this needs to build esbuild for each depth of routes/entrypoints
|
|
226
|
+
// independently so that relative import paths to the assets are the correct depth of '../' traversals
|
|
227
|
+
// This is inefficient, so wasmModuleImports is opt-in. This could potentially be improved in the future by
|
|
228
|
+
// taking advantage of the esbuild "onEnd" hook to rewrite import code per entry point relative to where the final
|
|
229
|
+
// destination of the entrypoint is
|
|
230
|
+
const entryPathsGroupedByDepth = !args.wasmModuleImports
|
|
231
|
+
? [entryPaths]
|
|
232
|
+
: entryPaths
|
|
233
|
+
.reduce((sum, thisPath) => {
|
|
234
|
+
const depthFromRoot = thisPath.split(sep).length;
|
|
235
|
+
sum.set(depthFromRoot, (sum.get(depthFromRoot) || []).concat(thisPath));
|
|
236
|
+
return sum;
|
|
237
|
+
}, new Map())
|
|
238
|
+
.values();
|
|
239
|
+
for (const pathsGroup of entryPathsGroupedByDepth) {
|
|
240
|
+
// for some reason this exports to "entry.pages" on windows instead of "pages" on unix environments.
|
|
241
|
+
// This deduces the name of the "pages" build directory
|
|
242
|
+
const pagesDirname = relative(fileURLToPath(_buildConfig.server), pathsGroup[0]).split(sep)[0];
|
|
243
|
+
const absolutePagesDirname = fileURLToPath(new URL(pagesDirname, _buildConfig.server));
|
|
244
|
+
const urlWithinFunctions = new URL(relative(absolutePagesDirname, pathsGroup[0]), functionsUrl);
|
|
245
|
+
const relativePathToAssets = relative(dirname(fileURLToPath(urlWithinFunctions)), fileURLToPath(assetsUrl));
|
|
246
|
+
await esbuild.build({
|
|
247
|
+
target: 'es2022',
|
|
248
|
+
platform: 'browser',
|
|
249
|
+
conditions: ['workerd', 'worker', 'browser'],
|
|
250
|
+
external: [
|
|
251
|
+
'node:assert',
|
|
252
|
+
'node:async_hooks',
|
|
253
|
+
'node:buffer',
|
|
254
|
+
'node:crypto',
|
|
255
|
+
'node:diagnostics_channel',
|
|
256
|
+
'node:events',
|
|
257
|
+
'node:path',
|
|
258
|
+
'node:process',
|
|
259
|
+
'node:stream',
|
|
260
|
+
'node:string_decoder',
|
|
261
|
+
'node:util',
|
|
262
|
+
'cloudflare:*',
|
|
263
|
+
],
|
|
264
|
+
entryPoints: pathsGroup,
|
|
265
|
+
outbase: absolutePagesDirname,
|
|
266
|
+
outdir: outputDir,
|
|
267
|
+
allowOverwrite: true,
|
|
268
|
+
format: 'esm',
|
|
269
|
+
bundle: true,
|
|
270
|
+
minify: _config.vite?.build?.minify !== false,
|
|
271
|
+
banner: {
|
|
272
|
+
js: `globalThis.process = {
|
|
273
|
+
argv: [],
|
|
274
|
+
env: {},
|
|
275
|
+
};`,
|
|
276
|
+
},
|
|
277
|
+
logOverride: {
|
|
278
|
+
'ignored-bare-import': 'silent',
|
|
279
|
+
},
|
|
280
|
+
plugins: !args?.wasmModuleImports
|
|
281
|
+
? []
|
|
282
|
+
: [rewriteWasmImportPath({ relativePathToAssets })],
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
const outputFiles = await glob(`**/*`, {
|
|
286
|
+
cwd: outputDir,
|
|
287
|
+
filesOnly: true,
|
|
288
|
+
});
|
|
289
|
+
// move the files into the functions folder
|
|
290
|
+
// & make sure the file names match Cloudflare syntax for routing
|
|
291
|
+
for (const outputFile of outputFiles) {
|
|
292
|
+
const path = outputFile.split(sep);
|
|
293
|
+
const finalSegments = path.map((segment) => segment
|
|
294
|
+
.replace(/(\_)(\w+)(\_)/g, (_, __, prop) => {
|
|
295
|
+
return `[${prop}]`;
|
|
296
|
+
})
|
|
297
|
+
.replace(/(\_\-\-\-)(\w+)(\_)/g, (_, __, prop) => {
|
|
298
|
+
return `[[${prop}]]`;
|
|
299
|
+
}));
|
|
300
|
+
finalSegments[finalSegments.length - 1] = finalSegments[finalSegments.length - 1]
|
|
301
|
+
.replace('entry.', '')
|
|
302
|
+
.replace(/(.*)\.(\w+)\.(\w+)$/g, (_, fileName, __, newExt) => {
|
|
303
|
+
return `${fileName}.${newExt}`;
|
|
304
|
+
});
|
|
305
|
+
const finalDirPath = finalSegments.slice(0, -1).join(sep);
|
|
306
|
+
const finalPath = finalSegments.join(sep);
|
|
307
|
+
const newDirUrl = new URL(finalDirPath, functionsUrl);
|
|
308
|
+
await fs.promises.mkdir(newDirUrl, { recursive: true });
|
|
309
|
+
const oldFileUrl = new URL(`$astro/${outputFile}`, outputUrl);
|
|
310
|
+
const newFileUrl = new URL(finalPath, functionsUrl);
|
|
311
|
+
await fs.promises.rename(oldFileUrl, newFileUrl);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
const entryPath = fileURLToPath(new URL(_buildConfig.serverEntry, _buildConfig.server));
|
|
316
|
+
const entryUrl = new URL(_buildConfig.serverEntry, _config.outDir);
|
|
317
|
+
const buildPath = fileURLToPath(entryUrl);
|
|
318
|
+
// A URL for the final build path after renaming
|
|
319
|
+
const finalBuildUrl = pathToFileURL(buildPath.replace(/\.mjs$/, '.js'));
|
|
320
|
+
await esbuild.build({
|
|
321
|
+
target: 'es2022',
|
|
322
|
+
platform: 'browser',
|
|
323
|
+
conditions: ['workerd', 'worker', 'browser'],
|
|
324
|
+
external: [
|
|
325
|
+
'node:assert',
|
|
326
|
+
'node:async_hooks',
|
|
327
|
+
'node:buffer',
|
|
328
|
+
'node:crypto',
|
|
329
|
+
'node:diagnostics_channel',
|
|
330
|
+
'node:events',
|
|
331
|
+
'node:path',
|
|
332
|
+
'node:process',
|
|
333
|
+
'node:stream',
|
|
334
|
+
'node:string_decoder',
|
|
335
|
+
'node:util',
|
|
336
|
+
'cloudflare:*',
|
|
337
|
+
],
|
|
338
|
+
entryPoints: [entryPath],
|
|
339
|
+
outfile: buildPath,
|
|
340
|
+
allowOverwrite: true,
|
|
341
|
+
format: 'esm',
|
|
342
|
+
bundle: true,
|
|
343
|
+
minify: _config.vite?.build?.minify !== false,
|
|
344
|
+
banner: {
|
|
345
|
+
js: `globalThis.process = {
|
|
346
|
+
argv: [],
|
|
347
|
+
env: {},
|
|
348
|
+
};`,
|
|
349
|
+
},
|
|
350
|
+
logOverride: {
|
|
351
|
+
'ignored-bare-import': 'silent',
|
|
352
|
+
},
|
|
353
|
+
plugins: !args?.wasmModuleImports
|
|
354
|
+
? []
|
|
355
|
+
: [
|
|
356
|
+
rewriteWasmImportPath({
|
|
357
|
+
relativePathToAssets: isModeDirectory
|
|
358
|
+
? relative(fileURLToPath(functionsUrl), fileURLToPath(assetsUrl))
|
|
359
|
+
: relative(fileURLToPath(_buildConfig.client), fileURLToPath(assetsUrl)),
|
|
360
|
+
}),
|
|
361
|
+
],
|
|
362
|
+
});
|
|
363
|
+
// Rename to worker.js
|
|
364
|
+
await fs.promises.rename(buildPath, finalBuildUrl);
|
|
365
|
+
if (isModeDirectory) {
|
|
366
|
+
const directoryUrl = new URL('[[path]].js', functionsUrl);
|
|
367
|
+
await fs.promises.rename(finalBuildUrl, directoryUrl);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
// throw the server folder in the bin
|
|
371
|
+
const serverUrl = new URL(_buildConfig.server);
|
|
372
|
+
await fs.promises.rm(serverUrl, { recursive: true, force: true });
|
|
373
|
+
// move cloudflare specific files to the root
|
|
374
|
+
const cloudflareSpecialFiles = ['_headers', '_redirects', '_routes.json'];
|
|
375
|
+
if (_config.base !== '/') {
|
|
376
|
+
for (const file of cloudflareSpecialFiles) {
|
|
377
|
+
try {
|
|
378
|
+
await fs.promises.rename(new URL(file, _buildConfig.client), new URL(file, _config.outDir));
|
|
379
|
+
}
|
|
380
|
+
catch (e) {
|
|
381
|
+
// ignore
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
// Add also the worker file so it's excluded from the _routes.json generation
|
|
386
|
+
if (!isModeDirectory) {
|
|
387
|
+
cloudflareSpecialFiles.push('_worker.js');
|
|
388
|
+
}
|
|
389
|
+
const routesExists = await fs.promises
|
|
390
|
+
.stat(new URL('./_routes.json', _config.outDir))
|
|
391
|
+
.then((stat) => stat.isFile())
|
|
392
|
+
.catch(() => false);
|
|
393
|
+
// this creates a _routes.json, in case there is none present to enable
|
|
394
|
+
// cloudflare to handle static files and support _redirects configuration
|
|
395
|
+
if (!routesExists) {
|
|
396
|
+
/**
|
|
397
|
+
* These route types are candiates for being part of the `_routes.json` `include` array.
|
|
398
|
+
*/
|
|
399
|
+
const potentialFunctionRouteTypes = ['endpoint', 'page'];
|
|
400
|
+
const functionEndpoints = routes
|
|
401
|
+
// Certain route types, when their prerender option is set to false, run on the server as function invocations
|
|
402
|
+
.filter((route) => potentialFunctionRouteTypes.includes(route.type) && !route.prerender)
|
|
403
|
+
.map((route) => {
|
|
404
|
+
const includePattern = '/' +
|
|
405
|
+
route.segments
|
|
406
|
+
.flat()
|
|
407
|
+
.map((segment) => (segment.dynamic ? '*' : segment.content))
|
|
408
|
+
.join('/');
|
|
409
|
+
const regexp = new RegExp('^\\/' +
|
|
410
|
+
route.segments
|
|
411
|
+
.flat()
|
|
412
|
+
.map((segment) => (segment.dynamic ? '(.*)' : segment.content))
|
|
413
|
+
.join('\\/') +
|
|
414
|
+
'$');
|
|
415
|
+
return {
|
|
416
|
+
includePattern,
|
|
417
|
+
regexp,
|
|
418
|
+
};
|
|
419
|
+
});
|
|
420
|
+
const staticPathList = (await glob(`${fileURLToPath(_buildConfig.client)}/**/*`, {
|
|
421
|
+
cwd: fileURLToPath(_config.outDir),
|
|
422
|
+
filesOnly: true,
|
|
423
|
+
dot: true,
|
|
424
|
+
}))
|
|
425
|
+
.filter((file) => cloudflareSpecialFiles.indexOf(file) < 0)
|
|
426
|
+
.map((file) => `/${file.replace(/\\/g, '/')}`);
|
|
427
|
+
for (let page of pages) {
|
|
428
|
+
let pagePath = prependForwardSlash(page.pathname);
|
|
429
|
+
if (_config.base !== '/') {
|
|
430
|
+
const base = _config.base.endsWith('/') ? _config.base.slice(0, -1) : _config.base;
|
|
431
|
+
pagePath = `${base}${pagePath}`;
|
|
432
|
+
}
|
|
433
|
+
staticPathList.push(pagePath);
|
|
434
|
+
}
|
|
435
|
+
const redirectsExists = await fs.promises
|
|
436
|
+
.stat(new URL('./_redirects', _config.outDir))
|
|
437
|
+
.then((stat) => stat.isFile())
|
|
438
|
+
.catch(() => false);
|
|
439
|
+
// convert all redirect source paths into a list of routes
|
|
440
|
+
// and add them to the static path
|
|
441
|
+
if (redirectsExists) {
|
|
442
|
+
const redirects = (await fs.promises.readFile(new URL('./_redirects', _config.outDir), 'utf-8'))
|
|
443
|
+
.split(os.EOL)
|
|
444
|
+
.map((line) => {
|
|
445
|
+
const parts = line.split(' ');
|
|
446
|
+
if (parts.length < 2) {
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
// convert /products/:id to /products/*
|
|
451
|
+
return (parts[0]
|
|
452
|
+
.replace(/\/:.*?(?=\/|$)/g, '/*')
|
|
453
|
+
// remove query params as they are not supported by cloudflare
|
|
454
|
+
.replace(/\?.*$/, ''));
|
|
455
|
+
}
|
|
456
|
+
})
|
|
457
|
+
.filter((line, index, arr) => line !== null && arr.indexOf(line) === index);
|
|
458
|
+
if (redirects.length > 0) {
|
|
459
|
+
staticPathList.push(...redirects);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
const redirectRoutes = routes
|
|
463
|
+
.filter((r) => r.type === 'redirect')
|
|
464
|
+
.map((r) => {
|
|
465
|
+
return [r, ''];
|
|
466
|
+
});
|
|
467
|
+
const trueRedirects = createRedirectsFromAstroRoutes({
|
|
468
|
+
config: _config,
|
|
469
|
+
routeToDynamicTargetMap: new Map(Array.from(redirectRoutes)),
|
|
470
|
+
dir,
|
|
471
|
+
});
|
|
472
|
+
if (!trueRedirects.empty()) {
|
|
473
|
+
await fs.promises.appendFile(new URL('./_redirects', _config.outDir), trueRedirects.print());
|
|
474
|
+
}
|
|
475
|
+
staticPathList.push(...routes.filter((r) => r.type === 'redirect').map((r) => r.route));
|
|
476
|
+
const strategy = args?.routes?.strategy ?? 'auto';
|
|
477
|
+
// Strategy `include`: include all function endpoints, and then exclude static paths that would be matched by an include pattern
|
|
478
|
+
const includeStrategy = strategy === 'exclude'
|
|
479
|
+
? undefined
|
|
480
|
+
: {
|
|
481
|
+
include: deduplicatePatterns(functionEndpoints
|
|
482
|
+
.map((endpoint) => endpoint.includePattern)
|
|
483
|
+
.concat(args?.routes?.include ?? [])),
|
|
484
|
+
exclude: deduplicatePatterns(staticPathList
|
|
485
|
+
.filter((file) => functionEndpoints.some((endpoint) => endpoint.regexp.test(file)))
|
|
486
|
+
.concat(args?.routes?.exclude ?? [])),
|
|
487
|
+
};
|
|
488
|
+
// Cloudflare requires at least one include pattern:
|
|
489
|
+
// https://developers.cloudflare.com/pages/platform/functions/routing/#limits
|
|
490
|
+
// So we add a pattern that we immediately exclude again
|
|
491
|
+
if (includeStrategy?.include.length === 0) {
|
|
492
|
+
includeStrategy.include = ['/'];
|
|
493
|
+
includeStrategy.exclude = ['/'];
|
|
494
|
+
}
|
|
495
|
+
// Strategy `exclude`: include everything, and then exclude all static paths
|
|
496
|
+
const excludeStrategy = strategy === 'include'
|
|
497
|
+
? undefined
|
|
498
|
+
: {
|
|
499
|
+
include: ['/*'],
|
|
500
|
+
exclude: deduplicatePatterns(staticPathList.concat(args?.routes?.exclude ?? [])),
|
|
501
|
+
};
|
|
502
|
+
const includeStrategyLength = includeStrategy
|
|
503
|
+
? includeStrategy.include.length + includeStrategy.exclude.length
|
|
504
|
+
: Infinity;
|
|
505
|
+
const excludeStrategyLength = excludeStrategy
|
|
506
|
+
? excludeStrategy.include.length + excludeStrategy.exclude.length
|
|
507
|
+
: Infinity;
|
|
508
|
+
const winningStrategy = includeStrategyLength <= excludeStrategyLength ? includeStrategy : excludeStrategy;
|
|
509
|
+
await fs.promises.writeFile(new URL('./_routes.json', _config.outDir), JSON.stringify({
|
|
510
|
+
version: 1,
|
|
511
|
+
...winningStrategy,
|
|
512
|
+
}, null, 2));
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
},
|
|
516
|
+
};
|
|
517
|
+
}
|
package/dist/util.d.ts
ADDED
package/dist/util.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const isNode = typeof process === 'object' && Object.prototype.toString.call(process) === '[object process]';
|
|
2
|
+
export function getProcessEnvProxy() {
|
|
3
|
+
return new Proxy({}, {
|
|
4
|
+
get: (target, prop) => {
|
|
5
|
+
console.warn(
|
|
6
|
+
// NOTE: \0 prevents Vite replacement
|
|
7
|
+
`Unable to access \`import.meta\0.env.${prop.toString()}\` on initialization ` +
|
|
8
|
+
`as the Cloudflare platform only provides the environment variables per request. ` +
|
|
9
|
+
`Please move the environment variable access inside a function ` +
|
|
10
|
+
`that's only called after a request has been received.`);
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remove duplicates and redundant patterns from an `include` or `exclude` list.
|
|
3
|
+
* Otherwise Cloudflare will throw an error on deployment. Plus, it saves more entries.
|
|
4
|
+
* E.g. `['/foo/*', '/foo/*', '/foo/bar'] => ['/foo/*']`
|
|
5
|
+
* @param patterns a list of `include` or `exclude` patterns
|
|
6
|
+
* @returns a deduplicated list of patterns
|
|
7
|
+
*/
|
|
8
|
+
export declare function deduplicatePatterns(patterns: string[]): string[];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remove duplicates and redundant patterns from an `include` or `exclude` list.
|
|
3
|
+
* Otherwise Cloudflare will throw an error on deployment. Plus, it saves more entries.
|
|
4
|
+
* E.g. `['/foo/*', '/foo/*', '/foo/bar'] => ['/foo/*']`
|
|
5
|
+
* @param patterns a list of `include` or `exclude` patterns
|
|
6
|
+
* @returns a deduplicated list of patterns
|
|
7
|
+
*/
|
|
8
|
+
export function deduplicatePatterns(patterns) {
|
|
9
|
+
const openPatterns = [];
|
|
10
|
+
// A value in the set may only occur once; it is unique in the set's collection.
|
|
11
|
+
// ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
|
|
12
|
+
return [...new Set(patterns)]
|
|
13
|
+
.sort((a, b) => a.length - b.length)
|
|
14
|
+
.filter((pattern) => {
|
|
15
|
+
if (openPatterns.some((p) => p.test(pattern))) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
if (pattern.endsWith('*')) {
|
|
19
|
+
openPatterns.push(new RegExp(`^${pattern.replace(/(\*\/)*\*$/g, '.*')}`));
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export async function getCFObject(runtimeMode) {
|
|
2
|
+
const CF_ENDPOINT = 'https://workers.cloudflare.com/cf.json';
|
|
3
|
+
const CF_FALLBACK = {
|
|
4
|
+
asOrganization: '',
|
|
5
|
+
asn: 395747,
|
|
6
|
+
colo: 'DFW',
|
|
7
|
+
city: 'Austin',
|
|
8
|
+
region: 'Texas',
|
|
9
|
+
regionCode: 'TX',
|
|
10
|
+
metroCode: '635',
|
|
11
|
+
postalCode: '78701',
|
|
12
|
+
country: 'US',
|
|
13
|
+
continent: 'NA',
|
|
14
|
+
timezone: 'America/Chicago',
|
|
15
|
+
latitude: '30.27130',
|
|
16
|
+
longitude: '-97.74260',
|
|
17
|
+
clientTcpRtt: 0,
|
|
18
|
+
httpProtocol: 'HTTP/1.1',
|
|
19
|
+
requestPriority: 'weight=192;exclusive=0',
|
|
20
|
+
tlsCipher: 'AEAD-AES128-GCM-SHA256',
|
|
21
|
+
tlsVersion: 'TLSv1.3',
|
|
22
|
+
tlsClientAuth: {
|
|
23
|
+
certPresented: '0',
|
|
24
|
+
certVerified: 'NONE',
|
|
25
|
+
certRevoked: '0',
|
|
26
|
+
certIssuerDN: '',
|
|
27
|
+
certSubjectDN: '',
|
|
28
|
+
certIssuerDNRFC2253: '',
|
|
29
|
+
certSubjectDNRFC2253: '',
|
|
30
|
+
certIssuerDNLegacy: '',
|
|
31
|
+
certSubjectDNLegacy: '',
|
|
32
|
+
certSerial: '',
|
|
33
|
+
certIssuerSerial: '',
|
|
34
|
+
certSKI: '',
|
|
35
|
+
certIssuerSKI: '',
|
|
36
|
+
certFingerprintSHA1: '',
|
|
37
|
+
certFingerprintSHA256: '',
|
|
38
|
+
certNotBefore: '',
|
|
39
|
+
certNotAfter: '',
|
|
40
|
+
},
|
|
41
|
+
edgeRequestKeepAliveStatus: 0,
|
|
42
|
+
hostMetadata: undefined,
|
|
43
|
+
clientTrustScore: 99,
|
|
44
|
+
botManagement: {
|
|
45
|
+
corporateProxy: false,
|
|
46
|
+
verifiedBot: false,
|
|
47
|
+
ja3Hash: '25b4882c2bcb50cd6b469ff28c596742',
|
|
48
|
+
staticResource: false,
|
|
49
|
+
detectionIds: [],
|
|
50
|
+
score: 99,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
if (runtimeMode === 'local') {
|
|
54
|
+
return CF_FALLBACK;
|
|
55
|
+
}
|
|
56
|
+
else if (runtimeMode === 'remote') {
|
|
57
|
+
try {
|
|
58
|
+
const res = await fetch(CF_ENDPOINT);
|
|
59
|
+
const cfText = await res.text();
|
|
60
|
+
const storedCf = JSON.parse(cfText);
|
|
61
|
+
return storedCf;
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
return CF_FALLBACK;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is a derivative work of wrangler by Cloudflare
|
|
3
|
+
* An upstream request for exposing this API was made here:
|
|
4
|
+
* https://github.com/cloudflare/workers-sdk/issues/3897
|
|
5
|
+
*
|
|
6
|
+
* Until further notice, we will be using this file as a workaround
|
|
7
|
+
* TODO: Tackle this file, once their is an decision on the upstream request
|
|
8
|
+
*/
|
|
9
|
+
import dotenv from 'dotenv';
|
|
10
|
+
export interface DotEnv {
|
|
11
|
+
path: string;
|
|
12
|
+
parsed: dotenv.DotenvParseOutput;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Loads a dotenv file from <path>, preferring to read <path>.<environment> if
|
|
16
|
+
* <environment> is defined and that file exists.
|
|
17
|
+
*/
|
|
18
|
+
export declare function loadDotEnv(path: string): DotEnv | undefined;
|
|
19
|
+
export declare function getEnvVars(): Promise<any>;
|
|
20
|
+
export declare function getD1Bindings(): Promise<string[]>;
|
|
21
|
+
export declare function getR2Bindings(): Promise<string[]>;
|
|
22
|
+
export declare function getKVBindings(): Promise<string[]>;
|
|
23
|
+
export declare function getDOBindings(): Record<string, {
|
|
24
|
+
scriptName?: string | undefined;
|
|
25
|
+
unsafeUniqueKey?: string | undefined;
|
|
26
|
+
className: string;
|
|
27
|
+
}>;
|