wrangler 0.0.0-e95e5a0 → 0.0.0-e978986
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/Cloudflare_CA.pem +18 -0
- package/README.md +3 -1
- package/bin/wrangler.js +36 -4
- package/package.json +11 -7
- package/pages/functions/buildPlugin.ts +13 -0
- package/pages/functions/buildWorker.ts +14 -1
- package/pages/functions/template-plugin.ts +3 -2
- package/src/__tests__/configuration.test.ts +336 -87
- package/src/__tests__/dev.test.tsx +243 -25
- package/src/__tests__/helpers/mock-dialogs.ts +41 -1
- package/src/__tests__/index.test.ts +79 -72
- package/src/__tests__/init.test.ts +182 -131
- package/src/__tests__/jest.setup.ts +8 -0
- package/src/__tests__/kv.test.ts +173 -146
- package/src/__tests__/package-manager.test.ts +154 -7
- package/src/__tests__/pages.test.ts +381 -53
- package/src/__tests__/parse.test.ts +8 -0
- package/src/__tests__/publish.test.ts +682 -89
- package/src/__tests__/r2.test.ts +55 -36
- package/src/__tests__/secret.test.ts +39 -4
- package/src/abort.d.ts +3 -0
- package/src/bundle.ts +33 -2
- package/src/cfetch/index.ts +21 -4
- package/src/cfetch/internal.ts +17 -9
- package/src/config/config.ts +1 -1
- package/src/config/environment.ts +40 -14
- package/src/config/index.ts +162 -0
- package/src/config/validation.ts +180 -65
- package/src/create-worker-preview.ts +17 -7
- package/src/create-worker-upload-form.ts +22 -8
- package/src/dev/dev.tsx +7 -6
- package/src/dev/local.tsx +6 -0
- package/src/dev/remote.tsx +15 -1
- package/src/dialogs.tsx +48 -0
- package/src/durable.ts +102 -0
- package/src/entry.ts +64 -9
- package/src/index.tsx +357 -177
- package/src/inspect.ts +39 -0
- package/src/kv.ts +114 -25
- package/src/open-in-browser.ts +5 -12
- package/src/package-manager.ts +50 -3
- package/src/pages.tsx +271 -110
- package/src/parse.ts +23 -4
- package/src/proxy.ts +38 -22
- package/src/publish.ts +227 -113
- package/src/sites.tsx +19 -17
- package/src/user.tsx +11 -1
- package/src/worker.ts +8 -0
- package/templates/new-worker-scheduled.js +17 -0
- package/templates/new-worker-scheduled.ts +32 -0
- package/templates/new-worker.ts +16 -1
- package/wrangler-dist/cli.js +36017 -22687
package/src/pages.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import { execSync, spawn } from "node:child_process";
|
|
|
4
4
|
import { existsSync, lstatSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { readdir, readFile, stat } from "node:fs/promises";
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
|
-
import { dirname, join, sep } from "node:path";
|
|
7
|
+
import { dirname, join, sep, extname, basename } from "node:path";
|
|
8
8
|
import { cwd } from "node:process";
|
|
9
9
|
import { URL } from "node:url";
|
|
10
10
|
import { hash } from "blake3-wasm";
|
|
@@ -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
|
|
@@ -82,6 +98,8 @@ const PAGES_CONFIG_CACHE_FILENAME = "pages.json";
|
|
|
82
98
|
export const pagesBetaWarning =
|
|
83
99
|
"🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose";
|
|
84
100
|
|
|
101
|
+
const isInPagesCI = !!process.env.CF_PAGES;
|
|
102
|
+
|
|
85
103
|
const CLEANUP_CALLBACKS: (() => void)[] = [];
|
|
86
104
|
const CLEANUP = () => {
|
|
87
105
|
CLEANUP_CALLBACKS.forEach((callback) => callback());
|
|
@@ -282,7 +300,7 @@ function generateRulesMatcher<T>(
|
|
|
282
300
|
T
|
|
283
301
|
][];
|
|
284
302
|
|
|
285
|
-
return ({ request }: { request:
|
|
303
|
+
return ({ request }: { request: MiniflareRequest }) => {
|
|
286
304
|
const { pathname, host } = new URL(request.url);
|
|
287
305
|
|
|
288
306
|
return compiledRules
|
|
@@ -355,7 +373,7 @@ function generateHeadersMatcher(headersFile: string) {
|
|
|
355
373
|
)
|
|
356
374
|
);
|
|
357
375
|
|
|
358
|
-
return (request:
|
|
376
|
+
return (request: MiniflareRequest) => {
|
|
359
377
|
const matches = rulesMatcher({
|
|
360
378
|
request,
|
|
361
379
|
});
|
|
@@ -404,7 +422,7 @@ function generateRedirectsMatcher(redirectsFile: string) {
|
|
|
404
422
|
})
|
|
405
423
|
);
|
|
406
424
|
|
|
407
|
-
return (request:
|
|
425
|
+
return (request: MiniflareRequest) => {
|
|
408
426
|
const match = rulesMatcher({
|
|
409
427
|
request,
|
|
410
428
|
})[0];
|
|
@@ -459,7 +477,9 @@ function hasFileExtension(pathname: string) {
|
|
|
459
477
|
return /\/.+\.[a-z0-9]+$/i.test(pathname);
|
|
460
478
|
}
|
|
461
479
|
|
|
462
|
-
async function generateAssetsFetch(
|
|
480
|
+
async function generateAssetsFetch(
|
|
481
|
+
directory: string
|
|
482
|
+
): Promise<typeof miniflareFetch> {
|
|
463
483
|
// Defer importing miniflare until we really need it
|
|
464
484
|
const { Headers, Request, Response } = await import("@miniflare/core");
|
|
465
485
|
|
|
@@ -508,12 +528,12 @@ async function generateAssetsFetch(directory: string): Promise<typeof fetch> {
|
|
|
508
528
|
return readFileSync(file);
|
|
509
529
|
};
|
|
510
530
|
|
|
511
|
-
const generateResponse = (request:
|
|
531
|
+
const generateResponse = (request: MiniflareRequest) => {
|
|
512
532
|
const url = new URL(request.url);
|
|
513
533
|
|
|
514
534
|
const deconstructedResponse: {
|
|
515
535
|
status: number;
|
|
516
|
-
headers:
|
|
536
|
+
headers: MiniflareHeaders;
|
|
517
537
|
body?: Buffer;
|
|
518
538
|
} = {
|
|
519
539
|
status: 200,
|
|
@@ -665,8 +685,12 @@ async function generateAssetsFetch(directory: string): Promise<typeof fetch> {
|
|
|
665
685
|
};
|
|
666
686
|
|
|
667
687
|
const attachHeaders = (
|
|
668
|
-
request:
|
|
669
|
-
deconstructedResponse: {
|
|
688
|
+
request: MiniflareRequest,
|
|
689
|
+
deconstructedResponse: {
|
|
690
|
+
status: number;
|
|
691
|
+
headers: MiniflareHeaders;
|
|
692
|
+
body?: Buffer;
|
|
693
|
+
}
|
|
670
694
|
) => {
|
|
671
695
|
const headers = deconstructedResponse.headers;
|
|
672
696
|
const newHeaders = new Headers({});
|
|
@@ -720,6 +744,7 @@ async function buildFunctions({
|
|
|
720
744
|
onEnd,
|
|
721
745
|
plugin = false,
|
|
722
746
|
buildOutputDirectory,
|
|
747
|
+
nodeCompat,
|
|
723
748
|
}: {
|
|
724
749
|
outfile: string;
|
|
725
750
|
outputConfigPath?: string;
|
|
@@ -731,12 +756,13 @@ async function buildFunctions({
|
|
|
731
756
|
onEnd?: () => void;
|
|
732
757
|
plugin?: boolean;
|
|
733
758
|
buildOutputDirectory?: string;
|
|
759
|
+
nodeCompat?: boolean;
|
|
734
760
|
}) {
|
|
735
761
|
RUNNING_BUILDERS.forEach(
|
|
736
762
|
(runningBuilder) => runningBuilder.stop && runningBuilder.stop()
|
|
737
763
|
);
|
|
738
764
|
|
|
739
|
-
const routesModule = join(tmpdir(),
|
|
765
|
+
const routesModule = join(tmpdir(), `./functionsRoutes-${Math.random()}.mjs`);
|
|
740
766
|
const baseURL = toUrlPath("/");
|
|
741
767
|
|
|
742
768
|
const config: Config = await generateConfigFromFileTree({
|
|
@@ -765,6 +791,7 @@ async function buildFunctions({
|
|
|
765
791
|
minify,
|
|
766
792
|
sourcemap,
|
|
767
793
|
watch,
|
|
794
|
+
nodeCompat,
|
|
768
795
|
onEnd,
|
|
769
796
|
})
|
|
770
797
|
);
|
|
@@ -779,6 +806,7 @@ async function buildFunctions({
|
|
|
779
806
|
watch,
|
|
780
807
|
onEnd,
|
|
781
808
|
buildOutputDirectory,
|
|
809
|
+
nodeCompat,
|
|
782
810
|
})
|
|
783
811
|
);
|
|
784
812
|
}
|
|
@@ -802,34 +830,30 @@ const createDeployment: CommandModule<
|
|
|
802
830
|
return yargs
|
|
803
831
|
.positional("directory", {
|
|
804
832
|
type: "string",
|
|
805
|
-
|
|
806
|
-
description: "The directory of
|
|
833
|
+
demandOption: true,
|
|
834
|
+
description: "The directory of static files to upload",
|
|
807
835
|
})
|
|
808
836
|
.options({
|
|
809
837
|
"project-name": {
|
|
810
838
|
type: "string",
|
|
811
|
-
description:
|
|
812
|
-
"The name of the project you want to list deployments for",
|
|
839
|
+
description: "The name of the project you want to deploy to",
|
|
813
840
|
},
|
|
814
841
|
branch: {
|
|
815
842
|
type: "string",
|
|
816
|
-
description:
|
|
817
|
-
"The branch of the project you want to list deployments for",
|
|
843
|
+
description: "The name of the branch you want to deploy to",
|
|
818
844
|
},
|
|
819
845
|
"commit-hash": {
|
|
820
846
|
type: "string",
|
|
821
|
-
description:
|
|
822
|
-
"The branch of the project you want to list deployments for",
|
|
847
|
+
description: "The SHA to attach to this deployment",
|
|
823
848
|
},
|
|
824
849
|
"commit-message": {
|
|
825
850
|
type: "string",
|
|
826
|
-
description:
|
|
827
|
-
"The branch of the project you want to list deployments for",
|
|
851
|
+
description: "The commit message to attach to this deployment",
|
|
828
852
|
},
|
|
829
853
|
"commit-dirty": {
|
|
830
854
|
type: "boolean",
|
|
831
855
|
description:
|
|
832
|
-
"
|
|
856
|
+
"Whether or not the workspace should be considered dirty for this deployment",
|
|
833
857
|
},
|
|
834
858
|
})
|
|
835
859
|
.epilogue(pagesBetaWarning);
|
|
@@ -842,6 +866,10 @@ const createDeployment: CommandModule<
|
|
|
842
866
|
commitMessage,
|
|
843
867
|
commitDirty,
|
|
844
868
|
}) => {
|
|
869
|
+
if (!directory) {
|
|
870
|
+
throw new FatalError("Must specify a directory.", 1);
|
|
871
|
+
}
|
|
872
|
+
|
|
845
873
|
const config = getConfigCache<PagesConfigCache>(
|
|
846
874
|
PAGES_CONFIG_CACHE_FILENAME
|
|
847
875
|
);
|
|
@@ -851,40 +879,45 @@ const createDeployment: CommandModule<
|
|
|
851
879
|
|
|
852
880
|
const isInteractive = process.stdin.isTTY;
|
|
853
881
|
if (!projectName && isInteractive) {
|
|
854
|
-
const
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
+
const projects = (await listProjects({ accountId })).filter(
|
|
883
|
+
(project) => !project.source
|
|
884
|
+
);
|
|
885
|
+
|
|
886
|
+
let existingOrNew: "existing" | "new" = "new";
|
|
887
|
+
|
|
888
|
+
if (projects.length > 0) {
|
|
889
|
+
existingOrNew = await new Promise<"new" | "existing">((resolve) => {
|
|
890
|
+
const { unmount } = render(
|
|
891
|
+
<>
|
|
892
|
+
<Text>
|
|
893
|
+
No project selected. Would you like to create one or use an
|
|
894
|
+
existing project?
|
|
895
|
+
</Text>
|
|
896
|
+
<SelectInput
|
|
897
|
+
items={[
|
|
898
|
+
{
|
|
899
|
+
key: "new",
|
|
900
|
+
label: "Create a new project",
|
|
901
|
+
value: "new",
|
|
902
|
+
},
|
|
903
|
+
{
|
|
904
|
+
key: "existing",
|
|
905
|
+
label: "Use an existing project",
|
|
906
|
+
value: "existing",
|
|
907
|
+
},
|
|
908
|
+
]}
|
|
909
|
+
onSelect={async (selected) => {
|
|
910
|
+
resolve(selected.value as "new" | "existing");
|
|
911
|
+
unmount();
|
|
912
|
+
}}
|
|
913
|
+
/>
|
|
914
|
+
</>
|
|
915
|
+
);
|
|
916
|
+
});
|
|
917
|
+
}
|
|
882
918
|
|
|
883
919
|
switch (existingOrNew) {
|
|
884
920
|
case "existing": {
|
|
885
|
-
const projects = (await listProjects({ accountId })).filter(
|
|
886
|
-
(project) => !project.source
|
|
887
|
-
);
|
|
888
921
|
projectName = await new Promise((resolve) => {
|
|
889
922
|
const { unmount } = render(
|
|
890
923
|
<>
|
|
@@ -994,7 +1027,7 @@ const createDeployment: CommandModule<
|
|
|
994
1027
|
|
|
995
1028
|
if (isGitDirty && !commitDirty) {
|
|
996
1029
|
logger.warn(
|
|
997
|
-
`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`
|
|
998
1031
|
);
|
|
999
1032
|
}
|
|
1000
1033
|
|
|
@@ -1006,7 +1039,7 @@ const createDeployment: CommandModule<
|
|
|
1006
1039
|
let builtFunctions: string | undefined = undefined;
|
|
1007
1040
|
const functionsDirectory = join(cwd(), "functions");
|
|
1008
1041
|
if (existsSync(functionsDirectory)) {
|
|
1009
|
-
const outfile = join(tmpdir(),
|
|
1042
|
+
const outfile = join(tmpdir(), `./functionsWorker-${Math.random()}.js`);
|
|
1010
1043
|
|
|
1011
1044
|
await new Promise((resolve) =>
|
|
1012
1045
|
buildFunctions({
|
|
@@ -1020,12 +1053,9 @@ const createDeployment: CommandModule<
|
|
|
1020
1053
|
builtFunctions = readFileSync(outfile, "utf-8");
|
|
1021
1054
|
}
|
|
1022
1055
|
|
|
1023
|
-
type
|
|
1024
|
-
content:
|
|
1025
|
-
|
|
1026
|
-
};
|
|
1027
|
-
|
|
1028
|
-
type Metadata = {
|
|
1056
|
+
type FileContainer = {
|
|
1057
|
+
content: string;
|
|
1058
|
+
contentType: string;
|
|
1029
1059
|
sizeInBytes: number;
|
|
1030
1060
|
hash: string;
|
|
1031
1061
|
};
|
|
@@ -1036,11 +1066,12 @@ const createDeployment: CommandModule<
|
|
|
1036
1066
|
"_headers",
|
|
1037
1067
|
".DS_Store",
|
|
1038
1068
|
"node_modules",
|
|
1069
|
+
".git",
|
|
1039
1070
|
];
|
|
1040
1071
|
|
|
1041
1072
|
const walk = async (
|
|
1042
1073
|
dir: string,
|
|
1043
|
-
fileMap: Map<string,
|
|
1074
|
+
fileMap: Map<string, FileContainer> = new Map(),
|
|
1044
1075
|
depth = 0
|
|
1045
1076
|
) => {
|
|
1046
1077
|
const files = await readdir(dir);
|
|
@@ -1072,10 +1103,7 @@ const createDeployment: CommandModule<
|
|
|
1072
1103
|
const fileContent = await readFile(filepath);
|
|
1073
1104
|
|
|
1074
1105
|
const base64Content = fileContent.toString("base64");
|
|
1075
|
-
const extension =
|
|
1076
|
-
name.split(".").length > 1 ? name.split(".").at(-1) || "" : "";
|
|
1077
|
-
|
|
1078
|
-
const content = base64Content + extension;
|
|
1106
|
+
const extension = extname(basename(name)).substring(1);
|
|
1079
1107
|
|
|
1080
1108
|
if (filestat.size > 25 * 1024 * 1024) {
|
|
1081
1109
|
throw new Error(
|
|
@@ -1086,11 +1114,12 @@ const createDeployment: CommandModule<
|
|
|
1086
1114
|
}
|
|
1087
1115
|
|
|
1088
1116
|
fileMap.set(name, {
|
|
1089
|
-
content:
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1117
|
+
content: base64Content,
|
|
1118
|
+
contentType: getType(name) || "application/octet-stream",
|
|
1119
|
+
sizeInBytes: filestat.size,
|
|
1120
|
+
hash: hash(base64Content + extension)
|
|
1121
|
+
.toString("hex")
|
|
1122
|
+
.slice(0, 32),
|
|
1094
1123
|
});
|
|
1095
1124
|
}
|
|
1096
1125
|
})
|
|
@@ -1101,51 +1130,139 @@ const createDeployment: CommandModule<
|
|
|
1101
1130
|
|
|
1102
1131
|
const fileMap = await walk(directory);
|
|
1103
1132
|
|
|
1133
|
+
if (fileMap.size > 20000) {
|
|
1134
|
+
throw new FatalError(
|
|
1135
|
+
`Error: Pages only supports up to 20,000 files in a deployment. Ensure you have specified your build output directory correctly.`,
|
|
1136
|
+
1
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
const files = [...fileMap.values()];
|
|
1141
|
+
|
|
1142
|
+
const { jwt } = await fetchResult<{ jwt: string }>(
|
|
1143
|
+
`/accounts/${accountId}/pages/projects/${projectName}/upload-token`
|
|
1144
|
+
);
|
|
1145
|
+
|
|
1104
1146
|
const start = Date.now();
|
|
1105
1147
|
|
|
1106
|
-
const
|
|
1148
|
+
const missingHashes = await fetchResult<string[]>(
|
|
1149
|
+
`/pages/assets/check-missing`,
|
|
1150
|
+
{
|
|
1151
|
+
method: "POST",
|
|
1152
|
+
headers: {
|
|
1153
|
+
"Content-Type": "application/json",
|
|
1154
|
+
Authorization: `Bearer ${jwt}`,
|
|
1155
|
+
},
|
|
1156
|
+
body: JSON.stringify({
|
|
1157
|
+
hashes: files.map(({ hash }) => hash),
|
|
1158
|
+
}),
|
|
1159
|
+
}
|
|
1160
|
+
);
|
|
1107
1161
|
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1162
|
+
const sortedFiles = files
|
|
1163
|
+
.filter((file) => missingHashes.includes(file.hash))
|
|
1164
|
+
.sort((a, b) => b.sizeInBytes - a.sizeInBytes);
|
|
1165
|
+
|
|
1166
|
+
// Start with a few buckets so small projects still get
|
|
1167
|
+
// the benefit of multiple upload streams
|
|
1168
|
+
const buckets: {
|
|
1169
|
+
files: FileContainer[];
|
|
1170
|
+
remainingSize: number;
|
|
1171
|
+
}[] = new Array(BULK_UPLOAD_CONCURRENCY).fill(null).map(() => ({
|
|
1172
|
+
files: [],
|
|
1173
|
+
remainingSize: MAX_BUCKET_SIZE,
|
|
1174
|
+
}));
|
|
1175
|
+
|
|
1176
|
+
let bucketOffset = 0;
|
|
1177
|
+
for (const file of sortedFiles) {
|
|
1178
|
+
let inserted = false;
|
|
1179
|
+
|
|
1180
|
+
for (let i = 0; i < buckets.length; i++) {
|
|
1181
|
+
// Start at a different bucket for each new file
|
|
1182
|
+
const bucket = buckets[(i + bucketOffset) % buckets.length];
|
|
1183
|
+
if (
|
|
1184
|
+
bucket.remainingSize >= file.sizeInBytes &&
|
|
1185
|
+
bucket.files.length < MAX_BUCKET_FILE_COUNT
|
|
1186
|
+
) {
|
|
1187
|
+
bucket.files.push(file);
|
|
1188
|
+
bucket.remainingSize -= file.sizeInBytes;
|
|
1189
|
+
inserted = true;
|
|
1190
|
+
break;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1113
1193
|
|
|
1114
|
-
|
|
1194
|
+
if (!inserted) {
|
|
1195
|
+
buckets.push({
|
|
1196
|
+
files: [file],
|
|
1197
|
+
remainingSize: MAX_BUCKET_SIZE - file.sizeInBytes,
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
bucketOffset++;
|
|
1201
|
+
}
|
|
1115
1202
|
|
|
1203
|
+
let counter = fileMap.size - sortedFiles.length;
|
|
1116
1204
|
const { rerender, unmount } = render(
|
|
1117
1205
|
<Progress done={counter} total={fileMap.size} />
|
|
1118
1206
|
);
|
|
1119
1207
|
|
|
1120
|
-
|
|
1121
|
-
const form = new FormData();
|
|
1122
|
-
form.append(
|
|
1123
|
-
"file",
|
|
1124
|
-
new File([new Uint8Array(file.content.buffer)], name)
|
|
1125
|
-
);
|
|
1208
|
+
const queue = new PQueue({ concurrency: BULK_UPLOAD_CONCURRENCY });
|
|
1126
1209
|
|
|
1127
|
-
|
|
1210
|
+
for (const bucket of buckets) {
|
|
1211
|
+
// Don't upload empty buckets (can happen for tiny projects)
|
|
1212
|
+
if (bucket.files.length === 0) continue;
|
|
1128
1213
|
|
|
1129
|
-
const
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1214
|
+
const payload: UploadPayloadFile[] = bucket.files.map((file) => ({
|
|
1215
|
+
key: file.hash,
|
|
1216
|
+
value: file.content,
|
|
1217
|
+
metadata: {
|
|
1218
|
+
contentType: file.contentType,
|
|
1219
|
+
},
|
|
1220
|
+
base64: true,
|
|
1221
|
+
}));
|
|
1222
|
+
|
|
1223
|
+
let attempts = 0;
|
|
1224
|
+
const doUpload = async (): Promise<void> => {
|
|
1225
|
+
try {
|
|
1226
|
+
return await fetchResult(`/pages/assets/upload`, {
|
|
1227
|
+
method: "POST",
|
|
1228
|
+
headers: {
|
|
1229
|
+
"Content-Type": "application/json",
|
|
1230
|
+
Authorization: `Bearer ${jwt}`,
|
|
1231
|
+
},
|
|
1232
|
+
body: JSON.stringify(payload),
|
|
1233
|
+
});
|
|
1234
|
+
} catch (e) {
|
|
1235
|
+
if (attempts < MAX_UPLOAD_ATTEMPTS) {
|
|
1236
|
+
// Linear backoff, 0 second first time, then 1 second etc.
|
|
1237
|
+
await new Promise((resolve) =>
|
|
1238
|
+
setTimeout(resolve, attempts++ * 1000)
|
|
1239
|
+
);
|
|
1240
|
+
return doUpload();
|
|
1241
|
+
} else {
|
|
1242
|
+
throw e;
|
|
1243
|
+
}
|
|
1142
1244
|
}
|
|
1143
|
-
}
|
|
1245
|
+
};
|
|
1144
1246
|
|
|
1145
|
-
|
|
1146
|
-
|
|
1247
|
+
queue.add(() =>
|
|
1248
|
+
doUpload().then(
|
|
1249
|
+
() => {
|
|
1250
|
+
counter += bucket.files.length;
|
|
1251
|
+
rerender(<Progress done={counter} total={fileMap.size} />);
|
|
1252
|
+
},
|
|
1253
|
+
(error) => {
|
|
1254
|
+
return Promise.reject(
|
|
1255
|
+
new FatalError(
|
|
1256
|
+
"Failed to upload files. Please try again.",
|
|
1257
|
+
error.code || 1
|
|
1258
|
+
)
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
)
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1147
1264
|
|
|
1148
|
-
await
|
|
1265
|
+
await queue.onIdle();
|
|
1149
1266
|
|
|
1150
1267
|
unmount();
|
|
1151
1268
|
|
|
@@ -1163,7 +1280,7 @@ const createDeployment: CommandModule<
|
|
|
1163
1280
|
Object.fromEntries(
|
|
1164
1281
|
[...fileMap.entries()].map(([fileName, file]) => [
|
|
1165
1282
|
`/${fileName}`,
|
|
1166
|
-
file.
|
|
1283
|
+
file.hash,
|
|
1167
1284
|
])
|
|
1168
1285
|
)
|
|
1169
1286
|
)
|
|
@@ -1237,7 +1354,7 @@ const createDeployment: CommandModule<
|
|
|
1237
1354
|
export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
1238
1355
|
return yargs
|
|
1239
1356
|
.command(
|
|
1240
|
-
"dev [directory] [-- command]",
|
|
1357
|
+
"dev [directory] [-- command..]",
|
|
1241
1358
|
"🧑💻 Develop your full-stack Pages application locally",
|
|
1242
1359
|
(yargs) => {
|
|
1243
1360
|
return yargs
|
|
@@ -1293,6 +1410,12 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1293
1410
|
default: false,
|
|
1294
1411
|
description: "Auto reload HTML pages when change is detected",
|
|
1295
1412
|
},
|
|
1413
|
+
"node-compat": {
|
|
1414
|
+
describe: "Enable node.js compatibility",
|
|
1415
|
+
default: false,
|
|
1416
|
+
type: "boolean",
|
|
1417
|
+
hidden: true,
|
|
1418
|
+
},
|
|
1296
1419
|
// TODO: Miniflare user options
|
|
1297
1420
|
})
|
|
1298
1421
|
.epilogue(pagesBetaWarning);
|
|
@@ -1307,6 +1430,7 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1307
1430
|
kv: kvs = [],
|
|
1308
1431
|
do: durableObjects = [],
|
|
1309
1432
|
"live-reload": liveReload,
|
|
1433
|
+
"node-compat": nodeCompat,
|
|
1310
1434
|
_: [_pages, _dev, ...remaining],
|
|
1311
1435
|
}) => {
|
|
1312
1436
|
// Beta message for `wrangler pages <commands>` usage
|
|
@@ -1340,7 +1464,16 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1340
1464
|
);
|
|
1341
1465
|
|
|
1342
1466
|
if (usingFunctions) {
|
|
1343
|
-
const outfile = join(
|
|
1467
|
+
const outfile = join(
|
|
1468
|
+
tmpdir(),
|
|
1469
|
+
`./functionsWorker-${Math.random()}.js`
|
|
1470
|
+
);
|
|
1471
|
+
|
|
1472
|
+
if (nodeCompat) {
|
|
1473
|
+
console.warn(
|
|
1474
|
+
"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."
|
|
1475
|
+
);
|
|
1476
|
+
}
|
|
1344
1477
|
|
|
1345
1478
|
logger.log(`Compiling worker to "${outfile}"...`);
|
|
1346
1479
|
|
|
@@ -1352,6 +1485,7 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1352
1485
|
watch: true,
|
|
1353
1486
|
onEnd: () => scriptReadyResolve(),
|
|
1354
1487
|
buildOutputDirectory: directory,
|
|
1488
|
+
nodeCompat,
|
|
1355
1489
|
});
|
|
1356
1490
|
} catch {}
|
|
1357
1491
|
|
|
@@ -1366,6 +1500,7 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1366
1500
|
watch: true,
|
|
1367
1501
|
onEnd: () => scriptReadyResolve(),
|
|
1368
1502
|
buildOutputDirectory: directory,
|
|
1503
|
+
nodeCompat,
|
|
1369
1504
|
});
|
|
1370
1505
|
});
|
|
1371
1506
|
|
|
@@ -1388,6 +1523,9 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1388
1523
|
} else {
|
|
1389
1524
|
logger.log("No functions. Shimming...");
|
|
1390
1525
|
miniflareArgs = {
|
|
1526
|
+
// cfFetch sets the `cf` object that a function could expect
|
|
1527
|
+
// If there are no functions, there's no reason to set this up (and not make that network call)
|
|
1528
|
+
cfFetch: false,
|
|
1391
1529
|
// TODO: The fact that these request/response hacks are necessary is ridiculous.
|
|
1392
1530
|
// We need to eliminate them from env.ASSETS.fetch (not sure if just local or prod as well)
|
|
1393
1531
|
script: `
|
|
@@ -1449,7 +1587,7 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1449
1587
|
|
|
1450
1588
|
// env.ASSETS.fetch
|
|
1451
1589
|
serviceBindings: {
|
|
1452
|
-
async ASSETS(request:
|
|
1590
|
+
async ASSETS(request: MiniflareRequest) {
|
|
1453
1591
|
if (proxyPort) {
|
|
1454
1592
|
try {
|
|
1455
1593
|
const url = new URL(request.url);
|
|
@@ -1569,6 +1707,16 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1569
1707
|
default: false,
|
|
1570
1708
|
description: "Build a plugin rather than a Worker script",
|
|
1571
1709
|
},
|
|
1710
|
+
"build-output-directory": {
|
|
1711
|
+
type: "string",
|
|
1712
|
+
description: "The directory to output static assets to",
|
|
1713
|
+
},
|
|
1714
|
+
"node-compat": {
|
|
1715
|
+
describe: "Enable node.js compatibility",
|
|
1716
|
+
default: false,
|
|
1717
|
+
type: "boolean",
|
|
1718
|
+
hidden: true,
|
|
1719
|
+
},
|
|
1572
1720
|
})
|
|
1573
1721
|
.epilogue(pagesBetaWarning),
|
|
1574
1722
|
async ({
|
|
@@ -1580,9 +1728,21 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1580
1728
|
fallbackService,
|
|
1581
1729
|
watch,
|
|
1582
1730
|
plugin,
|
|
1731
|
+
"build-output-directory": buildOutputDirectory,
|
|
1732
|
+
"node-compat": nodeCompat,
|
|
1583
1733
|
}) => {
|
|
1584
|
-
|
|
1585
|
-
|
|
1734
|
+
if (!isInPagesCI) {
|
|
1735
|
+
// Beta message for `wrangler pages <commands>` usage
|
|
1736
|
+
logger.log(pagesBetaWarning);
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
if (nodeCompat) {
|
|
1740
|
+
console.warn(
|
|
1741
|
+
"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."
|
|
1742
|
+
);
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
buildOutputDirectory ??= dirname(outfile);
|
|
1586
1746
|
|
|
1587
1747
|
await buildFunctions({
|
|
1588
1748
|
outfile,
|
|
@@ -1593,7 +1753,8 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1593
1753
|
fallbackService,
|
|
1594
1754
|
watch,
|
|
1595
1755
|
plugin,
|
|
1596
|
-
buildOutputDirectory
|
|
1756
|
+
buildOutputDirectory,
|
|
1757
|
+
nodeCompat,
|
|
1597
1758
|
});
|
|
1598
1759
|
}
|
|
1599
1760
|
)
|
|
@@ -1818,7 +1979,7 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1818
1979
|
} as CommandModule);
|
|
1819
1980
|
};
|
|
1820
1981
|
|
|
1821
|
-
const invalidAssetsFetch: typeof
|
|
1982
|
+
const invalidAssetsFetch: typeof miniflareFetch = () => {
|
|
1822
1983
|
throw new Error(
|
|
1823
1984
|
"Trying to fetch assets directly when there is no `directory` option specified, and not in `local` mode."
|
|
1824
1985
|
);
|