wrangler 2.0.6 → 2.0.7
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/package.json +5 -3
- package/pages/functions/buildPlugin.ts +13 -0
- package/pages/functions/buildWorker.ts +13 -0
- package/src/__tests__/configuration.test.ts +14 -3
- package/src/__tests__/dev.test.tsx +11 -2
- package/src/__tests__/index.test.ts +25 -10
- package/src/__tests__/init.test.ts +61 -20
- package/src/__tests__/kv.test.ts +8 -8
- package/src/__tests__/pages.test.ts +339 -32
- package/src/__tests__/parse.test.ts +5 -1
- package/src/__tests__/publish.test.ts +184 -69
- package/src/abort.d.ts +3 -0
- package/src/cfetch/index.ts +4 -2
- package/src/cfetch/internal.ts +8 -9
- package/src/config/index.ts +162 -0
- package/src/config/validation.ts +24 -2
- package/src/create-worker-preview.ts +17 -7
- package/src/dev/dev.tsx +5 -4
- package/src/dev/remote.tsx +15 -1
- package/src/durable.ts +102 -0
- package/src/index.tsx +111 -41
- package/src/inspect.ts +39 -0
- package/src/kv.ts +74 -25
- package/src/open-in-browser.ts +5 -12
- package/src/pages.tsx +203 -61
- package/src/parse.ts +21 -4
- package/src/proxy.ts +38 -22
- package/src/publish.ts +18 -93
- package/src/sites.tsx +2 -7
- package/templates/new-worker.ts +16 -1
- package/wrangler-dist/cli.js +32779 -20005
package/src/kv.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { URLSearchParams } from "node:url";
|
|
2
2
|
import { fetchListResult, fetchResult, fetchKVGetValue } from "./cfetch";
|
|
3
|
+
import { logger } from "./logger";
|
|
3
4
|
import type { Config } from "./config";
|
|
4
5
|
|
|
5
6
|
/** The largest number of kv items we can pass to the API in a single request. */
|
|
@@ -126,26 +127,51 @@ const KeyValueKeys = new Set([
|
|
|
126
127
|
"base64",
|
|
127
128
|
]);
|
|
128
129
|
|
|
130
|
+
/**
|
|
131
|
+
* The object has the specified property.
|
|
132
|
+
*/
|
|
133
|
+
function hasProperty<T extends object>(
|
|
134
|
+
obj: object,
|
|
135
|
+
property: keyof T
|
|
136
|
+
): obj is T {
|
|
137
|
+
return property in obj;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The object has a required property of the specified type.
|
|
142
|
+
*/
|
|
143
|
+
function hasTypedProperty<T extends object>(
|
|
144
|
+
obj: object,
|
|
145
|
+
property: keyof T,
|
|
146
|
+
type: string
|
|
147
|
+
): obj is T {
|
|
148
|
+
return hasProperty(obj, property) && typeof obj[property] === type;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* The object an optional property, of the specified type.
|
|
153
|
+
*/
|
|
154
|
+
function hasOptionalTypedProperty<T extends object>(
|
|
155
|
+
obj: object,
|
|
156
|
+
property: keyof T,
|
|
157
|
+
type: string
|
|
158
|
+
): obj is Omit<T, typeof property> | T {
|
|
159
|
+
return !hasProperty(obj, property) || typeof obj[property] === type;
|
|
160
|
+
}
|
|
161
|
+
|
|
129
162
|
/**
|
|
130
163
|
* Is the given object a valid `KeyValue` type?
|
|
131
164
|
*/
|
|
132
|
-
|
|
133
|
-
export function isKVKeyValue(keyValue: any): keyValue is KeyValue {
|
|
134
|
-
// (keyValue could indeed be any-thing)
|
|
165
|
+
export function isKVKeyValue(keyValue: unknown): keyValue is KeyValue {
|
|
135
166
|
if (
|
|
167
|
+
keyValue === null ||
|
|
136
168
|
typeof keyValue !== "object" ||
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
!(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
)
|
|
143
|
-
!(
|
|
144
|
-
keyValue.expiration_ttl === undefined ||
|
|
145
|
-
typeof keyValue.expiration_ttl === "number"
|
|
146
|
-
) ||
|
|
147
|
-
!(keyValue.base64 === undefined || typeof keyValue.base64 === "boolean") ||
|
|
148
|
-
!(keyValue.metadata === undefined || typeof keyValue.metadata === "object")
|
|
169
|
+
!hasTypedProperty(keyValue, "key", "string") ||
|
|
170
|
+
!hasTypedProperty(keyValue, "value", "string") ||
|
|
171
|
+
!hasOptionalTypedProperty(keyValue, "expiration", "number") ||
|
|
172
|
+
!hasOptionalTypedProperty(keyValue, "expiration_ttl", "number") ||
|
|
173
|
+
!hasOptionalTypedProperty(keyValue, "base64", "boolean") ||
|
|
174
|
+
!hasOptionalTypedProperty(keyValue, "metadata", "object")
|
|
149
175
|
) {
|
|
150
176
|
return false;
|
|
151
177
|
}
|
|
@@ -205,15 +231,37 @@ export async function deleteKVKeyValue(
|
|
|
205
231
|
);
|
|
206
232
|
}
|
|
207
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Formatter for converting e.g. 5328 --> 5,328
|
|
236
|
+
*/
|
|
237
|
+
const formatNumber = new Intl.NumberFormat("en-US", {
|
|
238
|
+
notation: "standard",
|
|
239
|
+
}).format;
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Helper function for bulk requests, logs ongoing output to console.
|
|
243
|
+
*/
|
|
244
|
+
function logBulkProgress(
|
|
245
|
+
operation: "put" | "delete",
|
|
246
|
+
index: number,
|
|
247
|
+
total: number
|
|
248
|
+
) {
|
|
249
|
+
logger.log(
|
|
250
|
+
`${operation === "put" ? "Uploaded" : "Deleted"} ${Math.floor(
|
|
251
|
+
(100 * index) / total
|
|
252
|
+
)}% (${formatNumber(index)} out of ${formatNumber(total)})`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
208
256
|
export async function putKVBulkKeyValue(
|
|
209
257
|
accountId: string,
|
|
210
258
|
namespaceId: string,
|
|
211
259
|
keyValues: KeyValue[],
|
|
212
|
-
|
|
260
|
+
quiet = false
|
|
213
261
|
) {
|
|
214
262
|
for (let index = 0; index < keyValues.length; index += BATCH_KEY_MAX) {
|
|
215
|
-
if (
|
|
216
|
-
|
|
263
|
+
if (!quiet && keyValues.length > BATCH_KEY_MAX) {
|
|
264
|
+
logBulkProgress("put", index, keyValues.length);
|
|
217
265
|
}
|
|
218
266
|
|
|
219
267
|
await fetchResult(
|
|
@@ -225,8 +273,9 @@ export async function putKVBulkKeyValue(
|
|
|
225
273
|
}
|
|
226
274
|
);
|
|
227
275
|
}
|
|
228
|
-
|
|
229
|
-
|
|
276
|
+
|
|
277
|
+
if (!quiet && keyValues.length > BATCH_KEY_MAX) {
|
|
278
|
+
logBulkProgress("put", keyValues.length, keyValues.length);
|
|
230
279
|
}
|
|
231
280
|
}
|
|
232
281
|
|
|
@@ -234,11 +283,11 @@ export async function deleteKVBulkKeyValue(
|
|
|
234
283
|
accountId: string,
|
|
235
284
|
namespaceId: string,
|
|
236
285
|
keys: string[],
|
|
237
|
-
|
|
286
|
+
quiet = false
|
|
238
287
|
) {
|
|
239
288
|
for (let index = 0; index < keys.length; index += BATCH_KEY_MAX) {
|
|
240
|
-
if (
|
|
241
|
-
|
|
289
|
+
if (!quiet && keys.length > BATCH_KEY_MAX) {
|
|
290
|
+
logBulkProgress("delete", index, keys.length);
|
|
242
291
|
}
|
|
243
292
|
|
|
244
293
|
await fetchResult(
|
|
@@ -250,8 +299,8 @@ export async function deleteKVBulkKeyValue(
|
|
|
250
299
|
}
|
|
251
300
|
);
|
|
252
301
|
}
|
|
253
|
-
if (
|
|
254
|
-
|
|
302
|
+
if (!quiet && keys.length > BATCH_KEY_MAX) {
|
|
303
|
+
logBulkProgress("delete", keys.length, keys.length);
|
|
255
304
|
}
|
|
256
305
|
}
|
|
257
306
|
|
package/src/open-in-browser.ts
CHANGED
|
@@ -8,19 +8,12 @@ import { logger } from "./logger";
|
|
|
8
8
|
* StackBlitz, remote servers), it doesn't just crash the process.
|
|
9
9
|
*
|
|
10
10
|
* @param url the URL to point the browser at
|
|
11
|
-
* @param options open a Chromium-based browser instead of the default
|
|
12
11
|
*/
|
|
13
|
-
export default async function openInBrowser(
|
|
14
|
-
url
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const options: open.Options | undefined = forceChromium
|
|
18
|
-
? {
|
|
19
|
-
app: [{ name: open.apps.chrome }, { name: open.apps.edge }],
|
|
20
|
-
}
|
|
21
|
-
: undefined;
|
|
22
|
-
const childProcess = await open(url, options);
|
|
12
|
+
export default async function openInBrowser(url: string): Promise<void> {
|
|
13
|
+
const errorMessage = `Failed to open ${url} in a browser`;
|
|
14
|
+
|
|
15
|
+
const childProcess = await open(url);
|
|
23
16
|
childProcess.on("error", () => {
|
|
24
|
-
logger.warn(
|
|
17
|
+
logger.warn(errorMessage);
|
|
25
18
|
});
|
|
26
19
|
}
|
package/src/pages.tsx
CHANGED
|
@@ -14,6 +14,7 @@ import SelectInput from "ink-select-input";
|
|
|
14
14
|
import Spinner from "ink-spinner";
|
|
15
15
|
import Table from "ink-table";
|
|
16
16
|
import { getType } from "mime";
|
|
17
|
+
import PQueue from "p-queue";
|
|
17
18
|
import prettyBytes from "pretty-bytes";
|
|
18
19
|
import React from "react";
|
|
19
20
|
import { format as timeagoFormat } from "timeago.js";
|
|
@@ -32,7 +33,11 @@ import openInBrowser from "./open-in-browser";
|
|
|
32
33
|
import { toUrlPath } from "./paths";
|
|
33
34
|
import { requireAuth } from "./user";
|
|
34
35
|
import type { Config } from "../pages/functions/routes";
|
|
35
|
-
import type {
|
|
36
|
+
import type {
|
|
37
|
+
Headers as MiniflareHeaders,
|
|
38
|
+
Request as MiniflareRequest,
|
|
39
|
+
fetch as miniflareFetch,
|
|
40
|
+
} from "@miniflare/core";
|
|
36
41
|
import type { BuildResult } from "esbuild";
|
|
37
42
|
import type { MiniflareOptions } from "miniflare";
|
|
38
43
|
import type { BuilderCallback, CommandModule } from "yargs";
|
|
@@ -68,12 +73,23 @@ export type Deployment = {
|
|
|
68
73
|
project_name: string;
|
|
69
74
|
};
|
|
70
75
|
|
|
76
|
+
export type UploadPayloadFile = {
|
|
77
|
+
key: string;
|
|
78
|
+
value: string;
|
|
79
|
+
metadata: { contentType: string };
|
|
80
|
+
base64: boolean;
|
|
81
|
+
};
|
|
82
|
+
|
|
71
83
|
interface PagesConfigCache {
|
|
72
84
|
account_id?: string;
|
|
73
85
|
project_name?: string;
|
|
74
86
|
}
|
|
75
87
|
|
|
76
88
|
const PAGES_CONFIG_CACHE_FILENAME = "pages.json";
|
|
89
|
+
const MAX_BUCKET_SIZE = 50 * 1024 * 1024;
|
|
90
|
+
const MAX_BUCKET_FILE_COUNT = 5000;
|
|
91
|
+
const BULK_UPLOAD_CONCURRENCY = 3;
|
|
92
|
+
const MAX_UPLOAD_ATTEMPTS = 5;
|
|
77
93
|
|
|
78
94
|
// Defer importing miniflare until we really need it. This takes ~0.5s
|
|
79
95
|
// and also modifies some `stream/web` and `undici` prototypes, so we
|
|
@@ -284,7 +300,7 @@ function generateRulesMatcher<T>(
|
|
|
284
300
|
T
|
|
285
301
|
][];
|
|
286
302
|
|
|
287
|
-
return ({ request }: { request:
|
|
303
|
+
return ({ request }: { request: MiniflareRequest }) => {
|
|
288
304
|
const { pathname, host } = new URL(request.url);
|
|
289
305
|
|
|
290
306
|
return compiledRules
|
|
@@ -357,7 +373,7 @@ function generateHeadersMatcher(headersFile: string) {
|
|
|
357
373
|
)
|
|
358
374
|
);
|
|
359
375
|
|
|
360
|
-
return (request:
|
|
376
|
+
return (request: MiniflareRequest) => {
|
|
361
377
|
const matches = rulesMatcher({
|
|
362
378
|
request,
|
|
363
379
|
});
|
|
@@ -406,7 +422,7 @@ function generateRedirectsMatcher(redirectsFile: string) {
|
|
|
406
422
|
})
|
|
407
423
|
);
|
|
408
424
|
|
|
409
|
-
return (request:
|
|
425
|
+
return (request: MiniflareRequest) => {
|
|
410
426
|
const match = rulesMatcher({
|
|
411
427
|
request,
|
|
412
428
|
})[0];
|
|
@@ -461,7 +477,9 @@ function hasFileExtension(pathname: string) {
|
|
|
461
477
|
return /\/.+\.[a-z0-9]+$/i.test(pathname);
|
|
462
478
|
}
|
|
463
479
|
|
|
464
|
-
async function generateAssetsFetch(
|
|
480
|
+
async function generateAssetsFetch(
|
|
481
|
+
directory: string
|
|
482
|
+
): Promise<typeof miniflareFetch> {
|
|
465
483
|
// Defer importing miniflare until we really need it
|
|
466
484
|
const { Headers, Request, Response } = await import("@miniflare/core");
|
|
467
485
|
|
|
@@ -510,12 +528,12 @@ async function generateAssetsFetch(directory: string): Promise<typeof fetch> {
|
|
|
510
528
|
return readFileSync(file);
|
|
511
529
|
};
|
|
512
530
|
|
|
513
|
-
const generateResponse = (request:
|
|
531
|
+
const generateResponse = (request: MiniflareRequest) => {
|
|
514
532
|
const url = new URL(request.url);
|
|
515
533
|
|
|
516
534
|
const deconstructedResponse: {
|
|
517
535
|
status: number;
|
|
518
|
-
headers:
|
|
536
|
+
headers: MiniflareHeaders;
|
|
519
537
|
body?: Buffer;
|
|
520
538
|
} = {
|
|
521
539
|
status: 200,
|
|
@@ -667,8 +685,12 @@ async function generateAssetsFetch(directory: string): Promise<typeof fetch> {
|
|
|
667
685
|
};
|
|
668
686
|
|
|
669
687
|
const attachHeaders = (
|
|
670
|
-
request:
|
|
671
|
-
deconstructedResponse: {
|
|
688
|
+
request: MiniflareRequest,
|
|
689
|
+
deconstructedResponse: {
|
|
690
|
+
status: number;
|
|
691
|
+
headers: MiniflareHeaders;
|
|
692
|
+
body?: Buffer;
|
|
693
|
+
}
|
|
672
694
|
) => {
|
|
673
695
|
const headers = deconstructedResponse.headers;
|
|
674
696
|
const newHeaders = new Headers({});
|
|
@@ -722,6 +744,7 @@ async function buildFunctions({
|
|
|
722
744
|
onEnd,
|
|
723
745
|
plugin = false,
|
|
724
746
|
buildOutputDirectory,
|
|
747
|
+
nodeCompat,
|
|
725
748
|
}: {
|
|
726
749
|
outfile: string;
|
|
727
750
|
outputConfigPath?: string;
|
|
@@ -733,12 +756,13 @@ async function buildFunctions({
|
|
|
733
756
|
onEnd?: () => void;
|
|
734
757
|
plugin?: boolean;
|
|
735
758
|
buildOutputDirectory?: string;
|
|
759
|
+
nodeCompat?: boolean;
|
|
736
760
|
}) {
|
|
737
761
|
RUNNING_BUILDERS.forEach(
|
|
738
762
|
(runningBuilder) => runningBuilder.stop && runningBuilder.stop()
|
|
739
763
|
);
|
|
740
764
|
|
|
741
|
-
const routesModule = join(tmpdir(),
|
|
765
|
+
const routesModule = join(tmpdir(), `./functionsRoutes-${Math.random()}.mjs`);
|
|
742
766
|
const baseURL = toUrlPath("/");
|
|
743
767
|
|
|
744
768
|
const config: Config = await generateConfigFromFileTree({
|
|
@@ -767,6 +791,7 @@ async function buildFunctions({
|
|
|
767
791
|
minify,
|
|
768
792
|
sourcemap,
|
|
769
793
|
watch,
|
|
794
|
+
nodeCompat,
|
|
770
795
|
onEnd,
|
|
771
796
|
})
|
|
772
797
|
);
|
|
@@ -781,6 +806,7 @@ async function buildFunctions({
|
|
|
781
806
|
watch,
|
|
782
807
|
onEnd,
|
|
783
808
|
buildOutputDirectory,
|
|
809
|
+
nodeCompat,
|
|
784
810
|
})
|
|
785
811
|
);
|
|
786
812
|
}
|
|
@@ -1001,7 +1027,7 @@ const createDeployment: CommandModule<
|
|
|
1001
1027
|
|
|
1002
1028
|
if (isGitDirty && !commitDirty) {
|
|
1003
1029
|
logger.warn(
|
|
1004
|
-
`Warning: Your working directory is a git repo and has uncommitted changes\nTo
|
|
1030
|
+
`Warning: Your working directory is a git repo and has uncommitted changes\nTo silence this warning, pass in --commit-dirty=true`
|
|
1005
1031
|
);
|
|
1006
1032
|
}
|
|
1007
1033
|
|
|
@@ -1013,7 +1039,7 @@ const createDeployment: CommandModule<
|
|
|
1013
1039
|
let builtFunctions: string | undefined = undefined;
|
|
1014
1040
|
const functionsDirectory = join(cwd(), "functions");
|
|
1015
1041
|
if (existsSync(functionsDirectory)) {
|
|
1016
|
-
const outfile = join(tmpdir(),
|
|
1042
|
+
const outfile = join(tmpdir(), `./functionsWorker-${Math.random()}.js`);
|
|
1017
1043
|
|
|
1018
1044
|
await new Promise((resolve) =>
|
|
1019
1045
|
buildFunctions({
|
|
@@ -1027,12 +1053,9 @@ const createDeployment: CommandModule<
|
|
|
1027
1053
|
builtFunctions = readFileSync(outfile, "utf-8");
|
|
1028
1054
|
}
|
|
1029
1055
|
|
|
1030
|
-
type
|
|
1031
|
-
content:
|
|
1032
|
-
|
|
1033
|
-
};
|
|
1034
|
-
|
|
1035
|
-
type Metadata = {
|
|
1056
|
+
type FileContainer = {
|
|
1057
|
+
content: string;
|
|
1058
|
+
contentType: string;
|
|
1036
1059
|
sizeInBytes: number;
|
|
1037
1060
|
hash: string;
|
|
1038
1061
|
};
|
|
@@ -1047,7 +1070,7 @@ const createDeployment: CommandModule<
|
|
|
1047
1070
|
|
|
1048
1071
|
const walk = async (
|
|
1049
1072
|
dir: string,
|
|
1050
|
-
fileMap: Map<string,
|
|
1073
|
+
fileMap: Map<string, FileContainer> = new Map(),
|
|
1051
1074
|
depth = 0
|
|
1052
1075
|
) => {
|
|
1053
1076
|
const files = await readdir(dir);
|
|
@@ -1081,8 +1104,6 @@ const createDeployment: CommandModule<
|
|
|
1081
1104
|
const base64Content = fileContent.toString("base64");
|
|
1082
1105
|
const extension = extname(basename(name)).substring(1);
|
|
1083
1106
|
|
|
1084
|
-
const content = base64Content + extension;
|
|
1085
|
-
|
|
1086
1107
|
if (filestat.size > 25 * 1024 * 1024) {
|
|
1087
1108
|
throw new Error(
|
|
1088
1109
|
`Error: Pages only supports files up to ${prettyBytes(
|
|
@@ -1092,11 +1113,12 @@ const createDeployment: CommandModule<
|
|
|
1092
1113
|
}
|
|
1093
1114
|
|
|
1094
1115
|
fileMap.set(name, {
|
|
1095
|
-
content:
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1116
|
+
content: base64Content,
|
|
1117
|
+
contentType: getType(name) || "application/octet-stream",
|
|
1118
|
+
sizeInBytes: filestat.size,
|
|
1119
|
+
hash: hash(base64Content + extension)
|
|
1120
|
+
.toString("hex")
|
|
1121
|
+
.slice(0, 32),
|
|
1100
1122
|
});
|
|
1101
1123
|
}
|
|
1102
1124
|
})
|
|
@@ -1107,51 +1129,139 @@ const createDeployment: CommandModule<
|
|
|
1107
1129
|
|
|
1108
1130
|
const fileMap = await walk(directory);
|
|
1109
1131
|
|
|
1132
|
+
if (fileMap.size > 20000) {
|
|
1133
|
+
throw new FatalError(
|
|
1134
|
+
`Error: Pages only supports up to 20,000 files in a deployment. Ensure you have specified your build output directory correctly.`,
|
|
1135
|
+
1
|
|
1136
|
+
);
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
const files = [...fileMap.values()];
|
|
1140
|
+
|
|
1141
|
+
const { jwt } = await fetchResult<{ jwt: string }>(
|
|
1142
|
+
`/accounts/${accountId}/pages/projects/${projectName}/upload-token`
|
|
1143
|
+
);
|
|
1144
|
+
|
|
1110
1145
|
const start = Date.now();
|
|
1111
1146
|
|
|
1112
|
-
const
|
|
1147
|
+
const missingHashes = await fetchResult<string[]>(
|
|
1148
|
+
`/pages/assets/check-missing`,
|
|
1149
|
+
{
|
|
1150
|
+
method: "POST",
|
|
1151
|
+
headers: {
|
|
1152
|
+
"Content-Type": "application/json",
|
|
1153
|
+
Authorization: `Bearer ${jwt}`,
|
|
1154
|
+
},
|
|
1155
|
+
body: JSON.stringify({
|
|
1156
|
+
hashes: files.map(({ hash }) => hash),
|
|
1157
|
+
}),
|
|
1158
|
+
}
|
|
1159
|
+
);
|
|
1113
1160
|
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1161
|
+
const sortedFiles = files
|
|
1162
|
+
.filter((file) => missingHashes.includes(file.hash))
|
|
1163
|
+
.sort((a, b) => b.sizeInBytes - a.sizeInBytes);
|
|
1164
|
+
|
|
1165
|
+
// Start with a few buckets so small projects still get
|
|
1166
|
+
// the benefit of multiple upload streams
|
|
1167
|
+
const buckets: {
|
|
1168
|
+
files: FileContainer[];
|
|
1169
|
+
remainingSize: number;
|
|
1170
|
+
}[] = new Array(BULK_UPLOAD_CONCURRENCY).fill(null).map(() => ({
|
|
1171
|
+
files: [],
|
|
1172
|
+
remainingSize: MAX_BUCKET_SIZE,
|
|
1173
|
+
}));
|
|
1174
|
+
|
|
1175
|
+
let bucketOffset = 0;
|
|
1176
|
+
for (const file of sortedFiles) {
|
|
1177
|
+
let inserted = false;
|
|
1178
|
+
|
|
1179
|
+
for (let i = 0; i < buckets.length; i++) {
|
|
1180
|
+
// Start at a different bucket for each new file
|
|
1181
|
+
const bucket = buckets[(i + bucketOffset) % buckets.length];
|
|
1182
|
+
if (
|
|
1183
|
+
bucket.remainingSize >= file.sizeInBytes &&
|
|
1184
|
+
bucket.files.length < MAX_BUCKET_FILE_COUNT
|
|
1185
|
+
) {
|
|
1186
|
+
bucket.files.push(file);
|
|
1187
|
+
bucket.remainingSize -= file.sizeInBytes;
|
|
1188
|
+
inserted = true;
|
|
1189
|
+
break;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1119
1192
|
|
|
1120
|
-
|
|
1193
|
+
if (!inserted) {
|
|
1194
|
+
buckets.push({
|
|
1195
|
+
files: [file],
|
|
1196
|
+
remainingSize: MAX_BUCKET_SIZE - file.sizeInBytes,
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
bucketOffset++;
|
|
1200
|
+
}
|
|
1121
1201
|
|
|
1202
|
+
let counter = fileMap.size - sortedFiles.length;
|
|
1122
1203
|
const { rerender, unmount } = render(
|
|
1123
1204
|
<Progress done={counter} total={fileMap.size} />
|
|
1124
1205
|
);
|
|
1125
1206
|
|
|
1126
|
-
|
|
1127
|
-
const form = new FormData();
|
|
1128
|
-
form.append(
|
|
1129
|
-
"file",
|
|
1130
|
-
new File([new Uint8Array(file.content.buffer)], name)
|
|
1131
|
-
);
|
|
1207
|
+
const queue = new PQueue({ concurrency: BULK_UPLOAD_CONCURRENCY });
|
|
1132
1208
|
|
|
1133
|
-
|
|
1209
|
+
for (const bucket of buckets) {
|
|
1210
|
+
// Don't upload empty buckets (can happen for tiny projects)
|
|
1211
|
+
if (bucket.files.length === 0) continue;
|
|
1134
1212
|
|
|
1135
|
-
const
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1213
|
+
const payload: UploadPayloadFile[] = bucket.files.map((file) => ({
|
|
1214
|
+
key: file.hash,
|
|
1215
|
+
value: file.content,
|
|
1216
|
+
metadata: {
|
|
1217
|
+
contentType: file.contentType,
|
|
1218
|
+
},
|
|
1219
|
+
base64: true,
|
|
1220
|
+
}));
|
|
1221
|
+
|
|
1222
|
+
let attempts = 0;
|
|
1223
|
+
const doUpload = async (): Promise<void> => {
|
|
1224
|
+
try {
|
|
1225
|
+
return await fetchResult(`/pages/assets/upload`, {
|
|
1226
|
+
method: "POST",
|
|
1227
|
+
headers: {
|
|
1228
|
+
"Content-Type": "application/json",
|
|
1229
|
+
Authorization: `Bearer ${jwt}`,
|
|
1230
|
+
},
|
|
1231
|
+
body: JSON.stringify(payload),
|
|
1232
|
+
});
|
|
1233
|
+
} catch (e) {
|
|
1234
|
+
if (attempts < MAX_UPLOAD_ATTEMPTS) {
|
|
1235
|
+
// Linear backoff, 0 second first time, then 1 second etc.
|
|
1236
|
+
await new Promise((resolve) =>
|
|
1237
|
+
setTimeout(resolve, attempts++ * 1000)
|
|
1238
|
+
);
|
|
1239
|
+
return doUpload();
|
|
1240
|
+
} else {
|
|
1241
|
+
throw e;
|
|
1242
|
+
}
|
|
1148
1243
|
}
|
|
1149
|
-
}
|
|
1244
|
+
};
|
|
1150
1245
|
|
|
1151
|
-
|
|
1152
|
-
|
|
1246
|
+
queue.add(() =>
|
|
1247
|
+
doUpload().then(
|
|
1248
|
+
() => {
|
|
1249
|
+
counter += bucket.files.length;
|
|
1250
|
+
rerender(<Progress done={counter} total={fileMap.size} />);
|
|
1251
|
+
},
|
|
1252
|
+
(error) => {
|
|
1253
|
+
return Promise.reject(
|
|
1254
|
+
new FatalError(
|
|
1255
|
+
"Failed to upload files. Please try again.",
|
|
1256
|
+
error.code || 1
|
|
1257
|
+
)
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
)
|
|
1261
|
+
);
|
|
1262
|
+
}
|
|
1153
1263
|
|
|
1154
|
-
await
|
|
1264
|
+
await queue.onIdle();
|
|
1155
1265
|
|
|
1156
1266
|
unmount();
|
|
1157
1267
|
|
|
@@ -1169,7 +1279,7 @@ const createDeployment: CommandModule<
|
|
|
1169
1279
|
Object.fromEntries(
|
|
1170
1280
|
[...fileMap.entries()].map(([fileName, file]) => [
|
|
1171
1281
|
`/${fileName}`,
|
|
1172
|
-
file.
|
|
1282
|
+
file.hash,
|
|
1173
1283
|
])
|
|
1174
1284
|
)
|
|
1175
1285
|
)
|
|
@@ -1299,6 +1409,12 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1299
1409
|
default: false,
|
|
1300
1410
|
description: "Auto reload HTML pages when change is detected",
|
|
1301
1411
|
},
|
|
1412
|
+
"node-compat": {
|
|
1413
|
+
describe: "Enable node.js compatibility",
|
|
1414
|
+
default: false,
|
|
1415
|
+
type: "boolean",
|
|
1416
|
+
hidden: true,
|
|
1417
|
+
},
|
|
1302
1418
|
// TODO: Miniflare user options
|
|
1303
1419
|
})
|
|
1304
1420
|
.epilogue(pagesBetaWarning);
|
|
@@ -1313,6 +1429,7 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1313
1429
|
kv: kvs = [],
|
|
1314
1430
|
do: durableObjects = [],
|
|
1315
1431
|
"live-reload": liveReload,
|
|
1432
|
+
"node-compat": nodeCompat,
|
|
1316
1433
|
_: [_pages, _dev, ...remaining],
|
|
1317
1434
|
}) => {
|
|
1318
1435
|
// Beta message for `wrangler pages <commands>` usage
|
|
@@ -1346,7 +1463,16 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1346
1463
|
);
|
|
1347
1464
|
|
|
1348
1465
|
if (usingFunctions) {
|
|
1349
|
-
const outfile = join(
|
|
1466
|
+
const outfile = join(
|
|
1467
|
+
tmpdir(),
|
|
1468
|
+
`./functionsWorker-${Math.random()}.js`
|
|
1469
|
+
);
|
|
1470
|
+
|
|
1471
|
+
if (nodeCompat) {
|
|
1472
|
+
console.warn(
|
|
1473
|
+
"Enabling node.js compatibility mode for builtins and globals. This is experimental and has serious tradeoffs. Please see https://github.com/ionic-team/rollup-plugin-node-polyfills/ for more details."
|
|
1474
|
+
);
|
|
1475
|
+
}
|
|
1350
1476
|
|
|
1351
1477
|
logger.log(`Compiling worker to "${outfile}"...`);
|
|
1352
1478
|
|
|
@@ -1358,6 +1484,7 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1358
1484
|
watch: true,
|
|
1359
1485
|
onEnd: () => scriptReadyResolve(),
|
|
1360
1486
|
buildOutputDirectory: directory,
|
|
1487
|
+
nodeCompat,
|
|
1361
1488
|
});
|
|
1362
1489
|
} catch {}
|
|
1363
1490
|
|
|
@@ -1372,6 +1499,7 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1372
1499
|
watch: true,
|
|
1373
1500
|
onEnd: () => scriptReadyResolve(),
|
|
1374
1501
|
buildOutputDirectory: directory,
|
|
1502
|
+
nodeCompat,
|
|
1375
1503
|
});
|
|
1376
1504
|
});
|
|
1377
1505
|
|
|
@@ -1455,7 +1583,7 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1455
1583
|
|
|
1456
1584
|
// env.ASSETS.fetch
|
|
1457
1585
|
serviceBindings: {
|
|
1458
|
-
async ASSETS(request:
|
|
1586
|
+
async ASSETS(request: MiniflareRequest) {
|
|
1459
1587
|
if (proxyPort) {
|
|
1460
1588
|
try {
|
|
1461
1589
|
const url = new URL(request.url);
|
|
@@ -1579,6 +1707,12 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1579
1707
|
type: "string",
|
|
1580
1708
|
description: "The directory to output static assets to",
|
|
1581
1709
|
},
|
|
1710
|
+
"node-compat": {
|
|
1711
|
+
describe: "Enable node.js compatibility",
|
|
1712
|
+
default: false,
|
|
1713
|
+
type: "boolean",
|
|
1714
|
+
hidden: true,
|
|
1715
|
+
},
|
|
1582
1716
|
})
|
|
1583
1717
|
.epilogue(pagesBetaWarning),
|
|
1584
1718
|
async ({
|
|
@@ -1591,12 +1725,19 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1591
1725
|
watch,
|
|
1592
1726
|
plugin,
|
|
1593
1727
|
"build-output-directory": buildOutputDirectory,
|
|
1728
|
+
"node-compat": nodeCompat,
|
|
1594
1729
|
}) => {
|
|
1595
1730
|
if (!isInPagesCI) {
|
|
1596
1731
|
// Beta message for `wrangler pages <commands>` usage
|
|
1597
1732
|
logger.log(pagesBetaWarning);
|
|
1598
1733
|
}
|
|
1599
1734
|
|
|
1735
|
+
if (nodeCompat) {
|
|
1736
|
+
console.warn(
|
|
1737
|
+
"Enabling node.js compatibility mode for builtins and globals. This is experimental and has serious tradeoffs. Please see https://github.com/ionic-team/rollup-plugin-node-polyfills/ for more details."
|
|
1738
|
+
);
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1600
1741
|
buildOutputDirectory ??= dirname(outfile);
|
|
1601
1742
|
|
|
1602
1743
|
await buildFunctions({
|
|
@@ -1609,6 +1750,7 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1609
1750
|
watch,
|
|
1610
1751
|
plugin,
|
|
1611
1752
|
buildOutputDirectory,
|
|
1753
|
+
nodeCompat,
|
|
1612
1754
|
});
|
|
1613
1755
|
}
|
|
1614
1756
|
)
|
|
@@ -1833,7 +1975,7 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1833
1975
|
} as CommandModule);
|
|
1834
1976
|
};
|
|
1835
1977
|
|
|
1836
|
-
const invalidAssetsFetch: typeof
|
|
1978
|
+
const invalidAssetsFetch: typeof miniflareFetch = () => {
|
|
1837
1979
|
throw new Error(
|
|
1838
1980
|
"Trying to fetch assets directly when there is no `directory` option specified, and not in `local` mode."
|
|
1839
1981
|
);
|