wrangler 2.0.7 → 2.0.11
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 +1 -1
- package/bin/wrangler.js +16 -4
- package/package.json +2 -2
- package/src/__tests__/configuration.test.ts +165 -70
- package/src/__tests__/dev.test.tsx +158 -66
- package/src/__tests__/helpers/mock-dialogs.ts +41 -1
- package/src/__tests__/init.test.ts +191 -111
- package/src/__tests__/kv.test.ts +8 -8
- package/src/__tests__/package-manager.test.ts +154 -7
- package/src/__tests__/pages.test.ts +115 -18
- package/src/__tests__/publish.test.ts +431 -140
- package/src/__tests__/secret.test.ts +4 -4
- package/src/__tests__/whoami.test.tsx +34 -0
- package/src/cfetch/index.ts +17 -2
- package/src/cfetch/internal.ts +12 -9
- package/src/config/config.ts +1 -1
- package/src/config/validation-helpers.ts +10 -1
- package/src/config/validation.ts +59 -33
- package/src/create-worker-preview.ts +15 -15
- package/src/dev/dev.tsx +4 -15
- package/src/dev/remote.tsx +26 -16
- package/src/dialogs.tsx +48 -0
- package/src/index.tsx +181 -167
- package/src/package-manager.ts +50 -3
- package/src/pages.tsx +298 -228
- package/src/publish.ts +148 -15
- package/src/sites.tsx +52 -14
- package/src/user.tsx +12 -1
- package/src/whoami.tsx +3 -2
- package/src/worker.ts +2 -1
- package/src/zones.ts +73 -0
- package/templates/new-worker-scheduled.js +17 -0
- package/templates/new-worker-scheduled.ts +32 -0
- package/wrangler-dist/cli.js +707 -407
package/src/pages.tsx
CHANGED
|
@@ -821,6 +821,299 @@ interface CreateDeploymentArgs {
|
|
|
821
821
|
commitDirty?: boolean;
|
|
822
822
|
}
|
|
823
823
|
|
|
824
|
+
const upload = async ({
|
|
825
|
+
directory,
|
|
826
|
+
accountId,
|
|
827
|
+
projectName,
|
|
828
|
+
}: {
|
|
829
|
+
directory: string;
|
|
830
|
+
accountId: string;
|
|
831
|
+
projectName: string;
|
|
832
|
+
}) => {
|
|
833
|
+
type FileContainer = {
|
|
834
|
+
content: string;
|
|
835
|
+
contentType: string;
|
|
836
|
+
sizeInBytes: number;
|
|
837
|
+
hash: string;
|
|
838
|
+
};
|
|
839
|
+
|
|
840
|
+
const IGNORE_LIST = [
|
|
841
|
+
"_worker.js",
|
|
842
|
+
"_redirects",
|
|
843
|
+
"_headers",
|
|
844
|
+
".DS_Store",
|
|
845
|
+
"node_modules",
|
|
846
|
+
".git",
|
|
847
|
+
];
|
|
848
|
+
|
|
849
|
+
const walk = async (
|
|
850
|
+
dir: string,
|
|
851
|
+
fileMap: Map<string, FileContainer> = new Map(),
|
|
852
|
+
depth = 0
|
|
853
|
+
) => {
|
|
854
|
+
const files = await readdir(dir);
|
|
855
|
+
|
|
856
|
+
await Promise.all(
|
|
857
|
+
files.map(async (file) => {
|
|
858
|
+
const filepath = join(dir, file);
|
|
859
|
+
const filestat = await stat(filepath);
|
|
860
|
+
|
|
861
|
+
if (IGNORE_LIST.includes(file)) {
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
if (filestat.isSymbolicLink()) {
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
if (filestat.isDirectory()) {
|
|
870
|
+
fileMap = await walk(filepath, fileMap, depth + 1);
|
|
871
|
+
} else {
|
|
872
|
+
let name;
|
|
873
|
+
if (depth) {
|
|
874
|
+
name = filepath.split(sep).slice(1).join("/");
|
|
875
|
+
} else {
|
|
876
|
+
name = file;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// TODO: Move this to later so we don't hold as much in memory
|
|
880
|
+
const fileContent = await readFile(filepath);
|
|
881
|
+
|
|
882
|
+
const base64Content = fileContent.toString("base64");
|
|
883
|
+
const extension = extname(basename(name)).substring(1);
|
|
884
|
+
|
|
885
|
+
if (filestat.size > 25 * 1024 * 1024) {
|
|
886
|
+
throw new Error(
|
|
887
|
+
`Error: Pages only supports files up to ${prettyBytes(
|
|
888
|
+
25 * 1024 * 1024
|
|
889
|
+
)} in size\n${name} is ${prettyBytes(filestat.size)} in size`
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
fileMap.set(name, {
|
|
894
|
+
content: base64Content,
|
|
895
|
+
contentType: getType(name) || "application/octet-stream",
|
|
896
|
+
sizeInBytes: filestat.size,
|
|
897
|
+
hash: hash(base64Content + extension)
|
|
898
|
+
.toString("hex")
|
|
899
|
+
.slice(0, 32),
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
})
|
|
903
|
+
);
|
|
904
|
+
|
|
905
|
+
return fileMap;
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
const fileMap = await walk(directory);
|
|
909
|
+
|
|
910
|
+
if (fileMap.size > 20000) {
|
|
911
|
+
throw new FatalError(
|
|
912
|
+
`Error: Pages only supports up to 20,000 files in a deployment. Ensure you have specified your build output directory correctly.`,
|
|
913
|
+
1
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
const files = [...fileMap.values()];
|
|
918
|
+
|
|
919
|
+
async function fetchJwt(): Promise<string> {
|
|
920
|
+
return (
|
|
921
|
+
await fetchResult<{ jwt: string }>(
|
|
922
|
+
`/accounts/${accountId}/pages/projects/${projectName}/upload-token`
|
|
923
|
+
)
|
|
924
|
+
).jwt;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
let jwt = await fetchJwt();
|
|
928
|
+
|
|
929
|
+
const start = Date.now();
|
|
930
|
+
|
|
931
|
+
const missingHashes = await fetchResult<string[]>(
|
|
932
|
+
`/pages/assets/check-missing`,
|
|
933
|
+
{
|
|
934
|
+
method: "POST",
|
|
935
|
+
headers: {
|
|
936
|
+
"Content-Type": "application/json",
|
|
937
|
+
Authorization: `Bearer ${jwt}`,
|
|
938
|
+
},
|
|
939
|
+
body: JSON.stringify({
|
|
940
|
+
hashes: files.map(({ hash }) => hash),
|
|
941
|
+
}),
|
|
942
|
+
}
|
|
943
|
+
);
|
|
944
|
+
|
|
945
|
+
const sortedFiles = files
|
|
946
|
+
.filter((file) => missingHashes.includes(file.hash))
|
|
947
|
+
.sort((a, b) => b.sizeInBytes - a.sizeInBytes);
|
|
948
|
+
|
|
949
|
+
// Start with a few buckets so small projects still get
|
|
950
|
+
// the benefit of multiple upload streams
|
|
951
|
+
const buckets: {
|
|
952
|
+
files: FileContainer[];
|
|
953
|
+
remainingSize: number;
|
|
954
|
+
}[] = new Array(BULK_UPLOAD_CONCURRENCY).fill(null).map(() => ({
|
|
955
|
+
files: [],
|
|
956
|
+
remainingSize: MAX_BUCKET_SIZE,
|
|
957
|
+
}));
|
|
958
|
+
|
|
959
|
+
let bucketOffset = 0;
|
|
960
|
+
for (const file of sortedFiles) {
|
|
961
|
+
let inserted = false;
|
|
962
|
+
|
|
963
|
+
for (let i = 0; i < buckets.length; i++) {
|
|
964
|
+
// Start at a different bucket for each new file
|
|
965
|
+
const bucket = buckets[(i + bucketOffset) % buckets.length];
|
|
966
|
+
if (
|
|
967
|
+
bucket.remainingSize >= file.sizeInBytes &&
|
|
968
|
+
bucket.files.length < MAX_BUCKET_FILE_COUNT
|
|
969
|
+
) {
|
|
970
|
+
bucket.files.push(file);
|
|
971
|
+
bucket.remainingSize -= file.sizeInBytes;
|
|
972
|
+
inserted = true;
|
|
973
|
+
break;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
if (!inserted) {
|
|
978
|
+
buckets.push({
|
|
979
|
+
files: [file],
|
|
980
|
+
remainingSize: MAX_BUCKET_SIZE - file.sizeInBytes,
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
bucketOffset++;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
let counter = fileMap.size - sortedFiles.length;
|
|
987
|
+
const { rerender, unmount } = render(
|
|
988
|
+
<Progress done={counter} total={fileMap.size} />
|
|
989
|
+
);
|
|
990
|
+
|
|
991
|
+
const queue = new PQueue({ concurrency: BULK_UPLOAD_CONCURRENCY });
|
|
992
|
+
|
|
993
|
+
for (const bucket of buckets) {
|
|
994
|
+
// Don't upload empty buckets (can happen for tiny projects)
|
|
995
|
+
if (bucket.files.length === 0) continue;
|
|
996
|
+
|
|
997
|
+
const payload: UploadPayloadFile[] = bucket.files.map((file) => ({
|
|
998
|
+
key: file.hash,
|
|
999
|
+
value: file.content,
|
|
1000
|
+
metadata: {
|
|
1001
|
+
contentType: file.contentType,
|
|
1002
|
+
},
|
|
1003
|
+
base64: true,
|
|
1004
|
+
}));
|
|
1005
|
+
|
|
1006
|
+
let attempts = 0;
|
|
1007
|
+
const doUpload = async (): Promise<void> => {
|
|
1008
|
+
try {
|
|
1009
|
+
return await fetchResult(`/pages/assets/upload`, {
|
|
1010
|
+
method: "POST",
|
|
1011
|
+
headers: {
|
|
1012
|
+
"Content-Type": "application/json",
|
|
1013
|
+
Authorization: `Bearer ${jwt}`,
|
|
1014
|
+
},
|
|
1015
|
+
body: JSON.stringify(payload),
|
|
1016
|
+
});
|
|
1017
|
+
} catch (e) {
|
|
1018
|
+
if (attempts < MAX_UPLOAD_ATTEMPTS) {
|
|
1019
|
+
// Linear backoff, 0 second first time, then 1 second etc.
|
|
1020
|
+
await new Promise((resolve) =>
|
|
1021
|
+
setTimeout(resolve, attempts++ * 1000)
|
|
1022
|
+
);
|
|
1023
|
+
|
|
1024
|
+
if ((e as { code: number }).code === 8000013) {
|
|
1025
|
+
// Looks like the JWT expired, fetch another one
|
|
1026
|
+
jwt = await fetchJwt();
|
|
1027
|
+
}
|
|
1028
|
+
return doUpload();
|
|
1029
|
+
} else {
|
|
1030
|
+
throw e;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
};
|
|
1034
|
+
|
|
1035
|
+
queue.add(() =>
|
|
1036
|
+
doUpload().then(
|
|
1037
|
+
() => {
|
|
1038
|
+
counter += bucket.files.length;
|
|
1039
|
+
rerender(<Progress done={counter} total={fileMap.size} />);
|
|
1040
|
+
},
|
|
1041
|
+
(error) => {
|
|
1042
|
+
return Promise.reject(
|
|
1043
|
+
new FatalError(
|
|
1044
|
+
"Failed to upload files. Please try again.",
|
|
1045
|
+
error.code || 1
|
|
1046
|
+
)
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
)
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
await queue.onIdle();
|
|
1054
|
+
|
|
1055
|
+
unmount();
|
|
1056
|
+
|
|
1057
|
+
const uploadMs = Date.now() - start;
|
|
1058
|
+
|
|
1059
|
+
const skipped = fileMap.size - missingHashes.length;
|
|
1060
|
+
const skippedMessage = skipped > 0 ? `(${skipped} already uploaded) ` : "";
|
|
1061
|
+
|
|
1062
|
+
logger.log(
|
|
1063
|
+
`✨ Success! Uploaded ${
|
|
1064
|
+
sortedFiles.length
|
|
1065
|
+
} files ${skippedMessage}${formatTime(uploadMs)}\n`
|
|
1066
|
+
);
|
|
1067
|
+
|
|
1068
|
+
const doUpsertHashes = async (): Promise<void> => {
|
|
1069
|
+
try {
|
|
1070
|
+
return await fetchResult(`/pages/assets/upsert-hashes`, {
|
|
1071
|
+
method: "POST",
|
|
1072
|
+
headers: {
|
|
1073
|
+
"Content-Type": "application/json",
|
|
1074
|
+
Authorization: `Bearer ${jwt}`,
|
|
1075
|
+
},
|
|
1076
|
+
body: JSON.stringify({
|
|
1077
|
+
hashes: files.map(({ hash }) => hash),
|
|
1078
|
+
}),
|
|
1079
|
+
});
|
|
1080
|
+
} catch (e) {
|
|
1081
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1082
|
+
|
|
1083
|
+
if ((e as { code: number }).code === 8000013) {
|
|
1084
|
+
// Looks like the JWT expired, fetch another one
|
|
1085
|
+
jwt = await fetchJwt();
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
return await fetchResult(`/pages/assets/upsert-hashes`, {
|
|
1089
|
+
method: "POST",
|
|
1090
|
+
headers: {
|
|
1091
|
+
"Content-Type": "application/json",
|
|
1092
|
+
Authorization: `Bearer ${jwt}`,
|
|
1093
|
+
},
|
|
1094
|
+
body: JSON.stringify({
|
|
1095
|
+
hashes: files.map(({ hash }) => hash),
|
|
1096
|
+
}),
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1100
|
+
|
|
1101
|
+
try {
|
|
1102
|
+
await doUpsertHashes();
|
|
1103
|
+
} catch {
|
|
1104
|
+
logger.warn(
|
|
1105
|
+
"Failed to update file hashes. Every upload appeared to succeed for this deployment, but you might need to re-upload for future deployments. This shouldn't have any impact other than slowing the upload speed of your next deployment."
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
return Object.fromEntries(
|
|
1110
|
+
[...fileMap.entries()].map(([fileName, file]) => [
|
|
1111
|
+
`/${fileName}`,
|
|
1112
|
+
file.hash,
|
|
1113
|
+
])
|
|
1114
|
+
);
|
|
1115
|
+
};
|
|
1116
|
+
|
|
824
1117
|
const createDeployment: CommandModule<
|
|
825
1118
|
CreateDeploymentArgs,
|
|
826
1119
|
CreateDeploymentArgs
|
|
@@ -1053,237 +1346,11 @@ const createDeployment: CommandModule<
|
|
|
1053
1346
|
builtFunctions = readFileSync(outfile, "utf-8");
|
|
1054
1347
|
}
|
|
1055
1348
|
|
|
1056
|
-
|
|
1057
|
-
content: string;
|
|
1058
|
-
contentType: string;
|
|
1059
|
-
sizeInBytes: number;
|
|
1060
|
-
hash: string;
|
|
1061
|
-
};
|
|
1062
|
-
|
|
1063
|
-
const IGNORE_LIST = [
|
|
1064
|
-
"_worker.js",
|
|
1065
|
-
"_redirects",
|
|
1066
|
-
"_headers",
|
|
1067
|
-
".DS_Store",
|
|
1068
|
-
"node_modules",
|
|
1069
|
-
];
|
|
1070
|
-
|
|
1071
|
-
const walk = async (
|
|
1072
|
-
dir: string,
|
|
1073
|
-
fileMap: Map<string, FileContainer> = new Map(),
|
|
1074
|
-
depth = 0
|
|
1075
|
-
) => {
|
|
1076
|
-
const files = await readdir(dir);
|
|
1077
|
-
|
|
1078
|
-
await Promise.all(
|
|
1079
|
-
files.map(async (file) => {
|
|
1080
|
-
const filepath = join(dir, file);
|
|
1081
|
-
const filestat = await stat(filepath);
|
|
1082
|
-
|
|
1083
|
-
if (IGNORE_LIST.includes(file)) {
|
|
1084
|
-
return;
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
if (filestat.isSymbolicLink()) {
|
|
1088
|
-
return;
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
if (filestat.isDirectory()) {
|
|
1092
|
-
fileMap = await walk(filepath, fileMap, depth + 1);
|
|
1093
|
-
} else {
|
|
1094
|
-
let name;
|
|
1095
|
-
if (depth) {
|
|
1096
|
-
name = filepath.split(sep).slice(1).join("/");
|
|
1097
|
-
} else {
|
|
1098
|
-
name = file;
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
// TODO: Move this to later so we don't hold as much in memory
|
|
1102
|
-
const fileContent = await readFile(filepath);
|
|
1103
|
-
|
|
1104
|
-
const base64Content = fileContent.toString("base64");
|
|
1105
|
-
const extension = extname(basename(name)).substring(1);
|
|
1106
|
-
|
|
1107
|
-
if (filestat.size > 25 * 1024 * 1024) {
|
|
1108
|
-
throw new Error(
|
|
1109
|
-
`Error: Pages only supports files up to ${prettyBytes(
|
|
1110
|
-
25 * 1024 * 1024
|
|
1111
|
-
)} in size\n${name} is ${prettyBytes(filestat.size)} in size`
|
|
1112
|
-
);
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
fileMap.set(name, {
|
|
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),
|
|
1122
|
-
});
|
|
1123
|
-
}
|
|
1124
|
-
})
|
|
1125
|
-
);
|
|
1126
|
-
|
|
1127
|
-
return fileMap;
|
|
1128
|
-
};
|
|
1129
|
-
|
|
1130
|
-
const fileMap = await walk(directory);
|
|
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
|
-
|
|
1145
|
-
const start = Date.now();
|
|
1146
|
-
|
|
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
|
-
);
|
|
1160
|
-
|
|
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
|
-
}
|
|
1192
|
-
|
|
1193
|
-
if (!inserted) {
|
|
1194
|
-
buckets.push({
|
|
1195
|
-
files: [file],
|
|
1196
|
-
remainingSize: MAX_BUCKET_SIZE - file.sizeInBytes,
|
|
1197
|
-
});
|
|
1198
|
-
}
|
|
1199
|
-
bucketOffset++;
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
let counter = fileMap.size - sortedFiles.length;
|
|
1203
|
-
const { rerender, unmount } = render(
|
|
1204
|
-
<Progress done={counter} total={fileMap.size} />
|
|
1205
|
-
);
|
|
1206
|
-
|
|
1207
|
-
const queue = new PQueue({ concurrency: BULK_UPLOAD_CONCURRENCY });
|
|
1208
|
-
|
|
1209
|
-
for (const bucket of buckets) {
|
|
1210
|
-
// Don't upload empty buckets (can happen for tiny projects)
|
|
1211
|
-
if (bucket.files.length === 0) continue;
|
|
1212
|
-
|
|
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
|
-
}
|
|
1243
|
-
}
|
|
1244
|
-
};
|
|
1245
|
-
|
|
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
|
-
}
|
|
1263
|
-
|
|
1264
|
-
await queue.onIdle();
|
|
1265
|
-
|
|
1266
|
-
unmount();
|
|
1267
|
-
|
|
1268
|
-
const uploadMs = Date.now() - start;
|
|
1269
|
-
|
|
1270
|
-
logger.log(
|
|
1271
|
-
`✨ Success! Uploaded ${fileMap.size} files ${formatTime(uploadMs)}\n`
|
|
1272
|
-
);
|
|
1349
|
+
const manifest = await upload({ directory, accountId, projectName });
|
|
1273
1350
|
|
|
1274
1351
|
const formData = new FormData();
|
|
1275
1352
|
|
|
1276
|
-
formData.append(
|
|
1277
|
-
"manifest",
|
|
1278
|
-
JSON.stringify(
|
|
1279
|
-
Object.fromEntries(
|
|
1280
|
-
[...fileMap.entries()].map(([fileName, file]) => [
|
|
1281
|
-
`/${fileName}`,
|
|
1282
|
-
file.hash,
|
|
1283
|
-
])
|
|
1284
|
-
)
|
|
1285
|
-
)
|
|
1286
|
-
);
|
|
1353
|
+
formData.append("manifest", JSON.stringify(manifest));
|
|
1287
1354
|
|
|
1288
1355
|
if (branch) {
|
|
1289
1356
|
formData.append("branch", branch);
|
|
@@ -1522,6 +1589,9 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1522
1589
|
} else {
|
|
1523
1590
|
logger.log("No functions. Shimming...");
|
|
1524
1591
|
miniflareArgs = {
|
|
1592
|
+
// cfFetch sets the `cf` object that a function could expect
|
|
1593
|
+
// If there are no functions, there's no reason to set this up (and not make that network call)
|
|
1594
|
+
cfFetch: false,
|
|
1525
1595
|
// TODO: The fact that these request/response hacks are necessary is ridiculous.
|
|
1526
1596
|
// We need to eliminate them from env.ASSETS.fetch (not sure if just local or prod as well)
|
|
1527
1597
|
script: `
|