wrangler 0.0.0-e043479 → 0.0.0-e0a0509
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 +27 -14
- package/bin/wrangler.js +13 -1
- package/miniflare-dist/index.mjs +4886 -0
- package/package.json +38 -25
- package/pages/functions/buildPlugin.ts +92 -0
- package/pages/functions/buildWorker.ts +80 -4
- package/pages/functions/filepath-routing.test.ts +136 -25
- package/pages/functions/filepath-routing.ts +14 -3
- package/pages/functions/routes.ts +6 -1
- package/pages/functions/template-plugin.ts +146 -0
- package/pages/functions/template-worker.ts +21 -8
- package/src/__tests__/config-cache.test.ts +36 -0
- package/src/__tests__/configuration.test.ts +1294 -355
- package/src/__tests__/dev.test.tsx +592 -12
- package/src/__tests__/guess-worker-format.test.ts +37 -18
- package/src/__tests__/helpers/mock-account-id.ts +4 -0
- package/src/__tests__/helpers/mock-cfetch.ts +8 -4
- package/src/__tests__/helpers/mock-console.ts +20 -3
- package/src/__tests__/helpers/mock-dialogs.ts +26 -1
- package/src/__tests__/helpers/mock-http-server.ts +46 -0
- package/src/__tests__/helpers/mock-istty.ts +27 -0
- package/src/__tests__/helpers/mock-kv.ts +9 -16
- package/src/__tests__/helpers/mock-oauth-flow.ts +255 -0
- package/src/__tests__/helpers/run-in-tmp.ts +10 -0
- package/src/__tests__/helpers/write-worker-source.ts +31 -0
- package/src/__tests__/https-options.test.ts +130 -0
- package/src/__tests__/index.test.ts +129 -753
- package/src/__tests__/init.test.ts +1516 -0
- package/src/__tests__/jest.setup.ts +13 -5
- package/src/__tests__/kv.test.ts +208 -174
- package/src/__tests__/logger.test.ts +115 -0
- package/src/__tests__/pages.test.ts +327 -0
- package/src/__tests__/parse.test.ts +4 -0
- package/src/__tests__/publish.test.ts +3313 -505
- package/src/__tests__/r2.test.ts +38 -26
- package/src/__tests__/route.test.ts +45 -0
- package/src/__tests__/secret.test.ts +55 -34
- package/src/__tests__/tail.test.ts +146 -20
- package/src/__tests__/user.test.ts +131 -0
- package/src/__tests__/whoami.test.tsx +26 -7
- package/src/bundle.ts +43 -7
- package/src/cfetch/internal.ts +9 -5
- package/src/cli.ts +5 -11
- package/src/config/README.md +7 -7
- package/src/config/config.ts +69 -105
- package/src/config/environment.ts +93 -4
- package/src/config/index.ts +15 -4
- package/src/config/validation-helpers.ts +228 -23
- package/src/config/validation.ts +559 -154
- package/src/config-cache.ts +45 -0
- package/src/{api/preview.ts → create-worker-preview.ts} +41 -13
- package/src/{api/form_data.ts → create-worker-upload-form.ts} +33 -10
- package/src/dev/dev-vars.ts +36 -0
- package/src/dev/dev.tsx +418 -0
- package/src/dev/local.tsx +312 -0
- package/src/dev/remote.tsx +213 -0
- package/src/dev/use-esbuild.ts +118 -0
- package/src/dialogs.tsx +16 -6
- package/src/entry.ts +222 -12
- package/src/environment-variables.ts +3 -1
- package/src/errors.ts +11 -0
- package/src/https-options.ts +123 -0
- package/src/index.tsx +878 -482
- package/src/inspect.ts +25 -11
- package/src/intl-polyfill.d.ts +139 -0
- package/src/{kv.tsx → kv.ts} +7 -27
- package/src/logger.ts +66 -0
- package/src/miniflare-cli/README.md +30 -0
- package/src/miniflare-cli/enum-keys.ts +17 -0
- package/src/miniflare-cli/index.ts +41 -0
- package/src/miniflare-cli/request-context.ts +40 -0
- package/src/module-collection.ts +94 -6
- package/src/open-in-browser.ts +18 -5
- package/src/package-manager.ts +9 -8
- package/src/pages.tsx +892 -62
- package/src/parse.ts +4 -2
- package/src/proxy.ts +128 -55
- package/src/publish.ts +305 -199
- package/src/selfsigned.d.ts +29 -0
- package/src/sites.tsx +88 -33
- package/src/tail/index.ts +2 -2
- package/src/tail/printing.ts +13 -8
- package/src/update-check.ts +19 -0
- package/src/user.tsx +244 -104
- package/src/whoami.tsx +2 -1
- package/src/{api/worker.ts → worker.ts} +15 -29
- package/templates/gitignore +171 -0
- package/templates/static-asset-facade.js +1 -1
- package/wrangler-dist/cli.js +23568 -50489
- package/src/__tests__/helpers/mock-user.ts +0 -27
- package/src/__tests__/logout.test.ts +0 -50
- package/src/__tests__/sentry.test.ts +0 -146
- package/src/dev.tsx +0 -960
- package/src/guess-worker-format.ts +0 -68
- package/src/reporting.ts +0 -115
- package/wrangler-dist/cli.js.map +0 -7
package/src/pages.tsx
CHANGED
|
@@ -2,36 +2,102 @@
|
|
|
2
2
|
|
|
3
3
|
import { execSync, spawn } from "node:child_process";
|
|
4
4
|
import { existsSync, lstatSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
5
6
|
import { tmpdir } from "node:os";
|
|
6
|
-
import { join } from "node:path";
|
|
7
|
+
import { dirname, join, sep } from "node:path";
|
|
8
|
+
import { cwd } from "node:process";
|
|
7
9
|
import { URL } from "node:url";
|
|
10
|
+
import { hash } from "blake3-wasm";
|
|
8
11
|
import { watch } from "chokidar";
|
|
12
|
+
import { render, Text } from "ink";
|
|
13
|
+
import SelectInput from "ink-select-input";
|
|
14
|
+
import Spinner from "ink-spinner";
|
|
15
|
+
import Table from "ink-table";
|
|
9
16
|
import { getType } from "mime";
|
|
17
|
+
import prettyBytes from "pretty-bytes";
|
|
18
|
+
import React from "react";
|
|
19
|
+
import { format as timeagoFormat } from "timeago.js";
|
|
20
|
+
import { File, FormData } from "undici";
|
|
21
|
+
import { buildPlugin } from "../pages/functions/buildPlugin";
|
|
10
22
|
import { buildWorker } from "../pages/functions/buildWorker";
|
|
11
23
|
import { generateConfigFromFileTree } from "../pages/functions/filepath-routing";
|
|
12
24
|
import { writeRoutesModule } from "../pages/functions/routes";
|
|
25
|
+
import { fetchResult } from "./cfetch";
|
|
26
|
+
import { getConfigCache, saveToConfigCache } from "./config-cache";
|
|
27
|
+
import { prompt } from "./dialogs";
|
|
28
|
+
import { FatalError } from "./errors";
|
|
29
|
+
import { logger } from "./logger";
|
|
30
|
+
import { getRequestContextCheckOptions } from "./miniflare-cli/request-context";
|
|
13
31
|
import openInBrowser from "./open-in-browser";
|
|
14
32
|
import { toUrlPath } from "./paths";
|
|
33
|
+
import { requireAuth } from "./user";
|
|
15
34
|
import type { Config } from "../pages/functions/routes";
|
|
16
35
|
import type { Headers, Request, fetch } from "@miniflare/core";
|
|
17
36
|
import type { BuildResult } from "esbuild";
|
|
18
37
|
import type { MiniflareOptions } from "miniflare";
|
|
19
|
-
import type { BuilderCallback } from "yargs";
|
|
38
|
+
import type { BuilderCallback, CommandModule } from "yargs";
|
|
39
|
+
|
|
40
|
+
export type Project = {
|
|
41
|
+
name: string;
|
|
42
|
+
subdomain: string;
|
|
43
|
+
domains: Array<string>;
|
|
44
|
+
source?: {
|
|
45
|
+
type: string;
|
|
46
|
+
};
|
|
47
|
+
latest_deployment?: {
|
|
48
|
+
modified_on: string;
|
|
49
|
+
};
|
|
50
|
+
created_on: string;
|
|
51
|
+
production_branch: string;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type Deployment = {
|
|
55
|
+
id: string;
|
|
56
|
+
environment: string;
|
|
57
|
+
deployment_trigger: {
|
|
58
|
+
metadata: {
|
|
59
|
+
commit_hash: string;
|
|
60
|
+
branch: string;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
url: string;
|
|
64
|
+
latest_stage: {
|
|
65
|
+
status: string;
|
|
66
|
+
ended_on: string;
|
|
67
|
+
};
|
|
68
|
+
project_name: string;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
interface PagesConfigCache {
|
|
72
|
+
account_id?: string;
|
|
73
|
+
project_name?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const PAGES_CONFIG_CACHE_FILENAME = "pages.json";
|
|
20
77
|
|
|
21
78
|
// Defer importing miniflare until we really need it. This takes ~0.5s
|
|
22
79
|
// and also modifies some `stream/web` and `undici` prototypes, so we
|
|
23
80
|
// don't want to do this if pages commands aren't being called.
|
|
24
81
|
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
82
|
+
export const pagesBetaWarning =
|
|
83
|
+
"🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose";
|
|
84
|
+
|
|
85
|
+
const isInPagesCI = !!process.env.CF_PAGES;
|
|
86
|
+
|
|
87
|
+
const CLEANUP_CALLBACKS: (() => void)[] = [];
|
|
88
|
+
const CLEANUP = () => {
|
|
89
|
+
CLEANUP_CALLBACKS.forEach((callback) => callback());
|
|
90
|
+
RUNNING_BUILDERS.forEach((builder) => builder.stop?.());
|
|
31
91
|
};
|
|
32
92
|
|
|
33
|
-
process.on("SIGINT", () =>
|
|
34
|
-
|
|
93
|
+
process.on("SIGINT", () => {
|
|
94
|
+
CLEANUP();
|
|
95
|
+
process.exit();
|
|
96
|
+
});
|
|
97
|
+
process.on("SIGTERM", () => {
|
|
98
|
+
CLEANUP();
|
|
99
|
+
process.exit();
|
|
100
|
+
});
|
|
35
101
|
|
|
36
102
|
function isWindows() {
|
|
37
103
|
return process.platform === "win32";
|
|
@@ -91,7 +157,7 @@ function getPort(pid: number) {
|
|
|
91
157
|
const match = matches[0];
|
|
92
158
|
if (match) return parseInt(match[1]);
|
|
93
159
|
} catch (thrown) {
|
|
94
|
-
|
|
160
|
+
logger.error(
|
|
95
161
|
`Error scanning for ports of process with PID ${pid}: ${thrown}`
|
|
96
162
|
);
|
|
97
163
|
}
|
|
@@ -104,13 +170,15 @@ async function spawnProxyProcess({
|
|
|
104
170
|
port?: number;
|
|
105
171
|
command: (string | number)[];
|
|
106
172
|
}): Promise<void | number> {
|
|
107
|
-
if (command.length === 0)
|
|
108
|
-
|
|
173
|
+
if (command.length === 0) {
|
|
174
|
+
CLEANUP();
|
|
175
|
+
throw new FatalError(
|
|
109
176
|
"Must specify a directory of static assets to serve or a command to run.",
|
|
110
177
|
1
|
|
111
178
|
);
|
|
179
|
+
}
|
|
112
180
|
|
|
113
|
-
|
|
181
|
+
logger.log(`Running ${command.join(" ")}...`);
|
|
114
182
|
const proxy = spawn(
|
|
115
183
|
command[0].toString(),
|
|
116
184
|
command.slice(1).map((value) => value.toString()),
|
|
@@ -122,30 +190,30 @@ async function spawnProxyProcess({
|
|
|
122
190
|
},
|
|
123
191
|
}
|
|
124
192
|
);
|
|
125
|
-
|
|
193
|
+
CLEANUP_CALLBACKS.push(() => {
|
|
126
194
|
proxy.kill();
|
|
127
195
|
});
|
|
128
196
|
|
|
129
197
|
proxy.stdout.on("data", (data) => {
|
|
130
|
-
|
|
198
|
+
logger.log(`[proxy]: ${data}`);
|
|
131
199
|
});
|
|
132
200
|
|
|
133
201
|
proxy.stderr.on("data", (data) => {
|
|
134
|
-
|
|
202
|
+
logger.error(`[proxy]: ${data}`);
|
|
135
203
|
});
|
|
136
204
|
|
|
137
205
|
proxy.on("close", (code) => {
|
|
138
|
-
|
|
206
|
+
logger.error(`Proxy exited with status ${code}.`);
|
|
139
207
|
});
|
|
140
208
|
|
|
141
209
|
// Wait for proxy process to start...
|
|
142
210
|
while (!proxy.pid) {}
|
|
143
211
|
|
|
144
212
|
if (port === undefined) {
|
|
145
|
-
|
|
213
|
+
logger.log(
|
|
146
214
|
`Sleeping ${SECONDS_TO_WAIT_FOR_PROXY} seconds to allow proxy process to start before attempting to automatically determine port...`
|
|
147
215
|
);
|
|
148
|
-
|
|
216
|
+
logger.log("To skip, specify the proxy port with --proxy.");
|
|
149
217
|
await sleep(SECONDS_TO_WAIT_FOR_PROXY * 1000);
|
|
150
218
|
|
|
151
219
|
port = getPids(proxy.pid)
|
|
@@ -153,12 +221,13 @@ async function spawnProxyProcess({
|
|
|
153
221
|
.filter((port) => port !== undefined)[0];
|
|
154
222
|
|
|
155
223
|
if (port === undefined) {
|
|
156
|
-
|
|
224
|
+
CLEANUP();
|
|
225
|
+
throw new FatalError(
|
|
157
226
|
"Could not automatically determine proxy port. Please specify the proxy port with --proxy.",
|
|
158
227
|
1
|
|
159
228
|
);
|
|
160
229
|
} else {
|
|
161
|
-
|
|
230
|
+
logger.log(`Automatically determined the proxy port to be ${port}.`);
|
|
162
231
|
}
|
|
163
232
|
}
|
|
164
233
|
|
|
@@ -425,12 +494,12 @@ async function generateAssetsFetch(directory: string): Promise<typeof fetch> {
|
|
|
425
494
|
}).on("change", (path) => {
|
|
426
495
|
switch (path) {
|
|
427
496
|
case headersFile: {
|
|
428
|
-
|
|
497
|
+
logger.log("_headers modified. Re-evaluating...");
|
|
429
498
|
headersMatcher = generateHeadersMatcher(headersFile);
|
|
430
499
|
break;
|
|
431
500
|
}
|
|
432
501
|
case redirectsFile: {
|
|
433
|
-
|
|
502
|
+
logger.log("_redirects modified. Re-evaluating...");
|
|
434
503
|
redirectsMatcher = generateRedirectsMatcher(redirectsFile);
|
|
435
504
|
break;
|
|
436
505
|
}
|
|
@@ -643,7 +712,7 @@ async function generateAssetsFetch(directory: string): Promise<typeof fetch> {
|
|
|
643
712
|
const RUNNING_BUILDERS: BuildResult[] = [];
|
|
644
713
|
|
|
645
714
|
async function buildFunctions({
|
|
646
|
-
|
|
715
|
+
outfile,
|
|
647
716
|
outputConfigPath,
|
|
648
717
|
functionsDirectory,
|
|
649
718
|
minify = false,
|
|
@@ -651,8 +720,10 @@ async function buildFunctions({
|
|
|
651
720
|
fallbackService = "ASSETS",
|
|
652
721
|
watch = false,
|
|
653
722
|
onEnd,
|
|
723
|
+
plugin = false,
|
|
724
|
+
buildOutputDirectory,
|
|
654
725
|
}: {
|
|
655
|
-
|
|
726
|
+
outfile: string;
|
|
656
727
|
outputConfigPath?: string;
|
|
657
728
|
functionsDirectory: string;
|
|
658
729
|
minify?: boolean;
|
|
@@ -660,6 +731,8 @@ async function buildFunctions({
|
|
|
660
731
|
fallbackService?: string;
|
|
661
732
|
watch?: boolean;
|
|
662
733
|
onEnd?: () => void;
|
|
734
|
+
plugin?: boolean;
|
|
735
|
+
buildOutputDirectory?: string;
|
|
663
736
|
}) {
|
|
664
737
|
RUNNING_BUILDERS.forEach(
|
|
665
738
|
(runningBuilder) => runningBuilder.stop && runningBuilder.stop()
|
|
@@ -686,19 +759,488 @@ async function buildFunctions({
|
|
|
686
759
|
outfile: routesModule,
|
|
687
760
|
});
|
|
688
761
|
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
762
|
+
if (plugin) {
|
|
763
|
+
RUNNING_BUILDERS.push(
|
|
764
|
+
await buildPlugin({
|
|
765
|
+
routesModule,
|
|
766
|
+
outfile,
|
|
767
|
+
minify,
|
|
768
|
+
sourcemap,
|
|
769
|
+
watch,
|
|
770
|
+
onEnd,
|
|
771
|
+
})
|
|
772
|
+
);
|
|
773
|
+
} else {
|
|
774
|
+
RUNNING_BUILDERS.push(
|
|
775
|
+
await buildWorker({
|
|
776
|
+
routesModule,
|
|
777
|
+
outfile,
|
|
778
|
+
minify,
|
|
779
|
+
sourcemap,
|
|
780
|
+
fallbackService,
|
|
781
|
+
watch,
|
|
782
|
+
onEnd,
|
|
783
|
+
buildOutputDirectory,
|
|
784
|
+
})
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
interface CreateDeploymentArgs {
|
|
790
|
+
directory: string;
|
|
791
|
+
projectName?: string;
|
|
792
|
+
branch?: string;
|
|
793
|
+
commitHash?: string;
|
|
794
|
+
commitMessage?: string;
|
|
795
|
+
commitDirty?: boolean;
|
|
700
796
|
}
|
|
701
797
|
|
|
798
|
+
const createDeployment: CommandModule<
|
|
799
|
+
CreateDeploymentArgs,
|
|
800
|
+
CreateDeploymentArgs
|
|
801
|
+
> = {
|
|
802
|
+
describe: "🆙 Publish a directory of static assets as a Pages deployment",
|
|
803
|
+
builder: (yargs) => {
|
|
804
|
+
return yargs
|
|
805
|
+
.positional("directory", {
|
|
806
|
+
type: "string",
|
|
807
|
+
demandOption: true,
|
|
808
|
+
description: "The directory of static files to upload",
|
|
809
|
+
})
|
|
810
|
+
.options({
|
|
811
|
+
"project-name": {
|
|
812
|
+
type: "string",
|
|
813
|
+
description: "The name of the project you want to deploy to",
|
|
814
|
+
},
|
|
815
|
+
branch: {
|
|
816
|
+
type: "string",
|
|
817
|
+
description: "The name of the branch you want to deploy to",
|
|
818
|
+
},
|
|
819
|
+
"commit-hash": {
|
|
820
|
+
type: "string",
|
|
821
|
+
description: "The SHA to attach to this deployment",
|
|
822
|
+
},
|
|
823
|
+
"commit-message": {
|
|
824
|
+
type: "string",
|
|
825
|
+
description: "The commit message to attach to this deployment",
|
|
826
|
+
},
|
|
827
|
+
"commit-dirty": {
|
|
828
|
+
type: "boolean",
|
|
829
|
+
description:
|
|
830
|
+
"Whether or not the workspace should be considered dirty for this deployment",
|
|
831
|
+
},
|
|
832
|
+
})
|
|
833
|
+
.epilogue(pagesBetaWarning);
|
|
834
|
+
},
|
|
835
|
+
handler: async ({
|
|
836
|
+
directory,
|
|
837
|
+
projectName,
|
|
838
|
+
branch,
|
|
839
|
+
commitHash,
|
|
840
|
+
commitMessage,
|
|
841
|
+
commitDirty,
|
|
842
|
+
}) => {
|
|
843
|
+
if (!directory) {
|
|
844
|
+
throw new FatalError("Must specify a directory.", 1);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
const config = getConfigCache<PagesConfigCache>(
|
|
848
|
+
PAGES_CONFIG_CACHE_FILENAME
|
|
849
|
+
);
|
|
850
|
+
const accountId = await requireAuth(config);
|
|
851
|
+
|
|
852
|
+
projectName ??= config.project_name;
|
|
853
|
+
|
|
854
|
+
const isInteractive = process.stdin.isTTY;
|
|
855
|
+
if (!projectName && isInteractive) {
|
|
856
|
+
const projects = (await listProjects({ accountId })).filter(
|
|
857
|
+
(project) => !project.source
|
|
858
|
+
);
|
|
859
|
+
|
|
860
|
+
let existingOrNew: "existing" | "new" = "new";
|
|
861
|
+
|
|
862
|
+
if (projects.length > 0) {
|
|
863
|
+
existingOrNew = await new Promise<"new" | "existing">((resolve) => {
|
|
864
|
+
const { unmount } = render(
|
|
865
|
+
<>
|
|
866
|
+
<Text>
|
|
867
|
+
No project selected. Would you like to create one or use an
|
|
868
|
+
existing project?
|
|
869
|
+
</Text>
|
|
870
|
+
<SelectInput
|
|
871
|
+
items={[
|
|
872
|
+
{
|
|
873
|
+
key: "new",
|
|
874
|
+
label: "Create a new project",
|
|
875
|
+
value: "new",
|
|
876
|
+
},
|
|
877
|
+
{
|
|
878
|
+
key: "existing",
|
|
879
|
+
label: "Use an existing project",
|
|
880
|
+
value: "existing",
|
|
881
|
+
},
|
|
882
|
+
]}
|
|
883
|
+
onSelect={async (selected) => {
|
|
884
|
+
resolve(selected.value as "new" | "existing");
|
|
885
|
+
unmount();
|
|
886
|
+
}}
|
|
887
|
+
/>
|
|
888
|
+
</>
|
|
889
|
+
);
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
switch (existingOrNew) {
|
|
894
|
+
case "existing": {
|
|
895
|
+
projectName = await new Promise((resolve) => {
|
|
896
|
+
const { unmount } = render(
|
|
897
|
+
<>
|
|
898
|
+
<Text>Select a project:</Text>
|
|
899
|
+
<SelectInput
|
|
900
|
+
items={projects.map((project) => ({
|
|
901
|
+
key: project.name,
|
|
902
|
+
label: project.name,
|
|
903
|
+
value: project,
|
|
904
|
+
}))}
|
|
905
|
+
onSelect={async (selected) => {
|
|
906
|
+
resolve(selected.value.name);
|
|
907
|
+
unmount();
|
|
908
|
+
}}
|
|
909
|
+
/>
|
|
910
|
+
</>
|
|
911
|
+
);
|
|
912
|
+
});
|
|
913
|
+
break;
|
|
914
|
+
}
|
|
915
|
+
case "new": {
|
|
916
|
+
projectName = await prompt("Enter the name of your new project:");
|
|
917
|
+
|
|
918
|
+
if (!projectName) {
|
|
919
|
+
throw new FatalError("Must specify a project name.", 1);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
let isGitDir = true;
|
|
923
|
+
try {
|
|
924
|
+
execSync(`git rev-parse --is-inside-work-tree`, {
|
|
925
|
+
stdio: "ignore",
|
|
926
|
+
});
|
|
927
|
+
} catch (err) {
|
|
928
|
+
isGitDir = false;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
const productionBranch = await prompt(
|
|
932
|
+
"Enter the production branch name:",
|
|
933
|
+
"text",
|
|
934
|
+
isGitDir
|
|
935
|
+
? execSync(`git rev-parse --abbrev-ref HEAD`).toString().trim()
|
|
936
|
+
: "production"
|
|
937
|
+
);
|
|
938
|
+
|
|
939
|
+
if (!productionBranch) {
|
|
940
|
+
throw new FatalError("Must specify a production branch.", 1);
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
await fetchResult<Project>(`/accounts/${accountId}/pages/projects`, {
|
|
944
|
+
method: "POST",
|
|
945
|
+
body: JSON.stringify({
|
|
946
|
+
name: projectName,
|
|
947
|
+
production_branch: productionBranch,
|
|
948
|
+
}),
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
saveToConfigCache<PagesConfigCache>(PAGES_CONFIG_CACHE_FILENAME, {
|
|
952
|
+
account_id: accountId,
|
|
953
|
+
project_name: projectName,
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
logger.log(`✨ Successfully created the '${projectName}' project.`);
|
|
957
|
+
break;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
if (!projectName) {
|
|
963
|
+
throw new FatalError("Must specify a project name.", 1);
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// We infer git info by default is not passed in
|
|
967
|
+
|
|
968
|
+
let isGitDir = true;
|
|
969
|
+
try {
|
|
970
|
+
execSync(`git rev-parse --is-inside-work-tree`, {
|
|
971
|
+
stdio: "ignore",
|
|
972
|
+
});
|
|
973
|
+
} catch (err) {
|
|
974
|
+
isGitDir = false;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
let isGitDirty = false;
|
|
978
|
+
|
|
979
|
+
if (isGitDir) {
|
|
980
|
+
try {
|
|
981
|
+
isGitDirty = Boolean(
|
|
982
|
+
execSync(`git status --porcelain`).toString().length
|
|
983
|
+
);
|
|
984
|
+
|
|
985
|
+
if (!branch) {
|
|
986
|
+
branch = execSync(`git rev-parse --abbrev-ref HEAD`)
|
|
987
|
+
.toString()
|
|
988
|
+
.trim();
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
if (!commitHash) {
|
|
992
|
+
commitHash = execSync(`git rev-parse HEAD`).toString().trim();
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
if (!commitMessage) {
|
|
996
|
+
commitMessage = execSync(`git show -s --format=%B ${commitHash}`)
|
|
997
|
+
.toString()
|
|
998
|
+
.trim();
|
|
999
|
+
}
|
|
1000
|
+
} catch (err) {}
|
|
1001
|
+
|
|
1002
|
+
if (isGitDirty && !commitDirty) {
|
|
1003
|
+
logger.warn(
|
|
1004
|
+
`Warning: Your working directory is a git repo and has uncommitted changes\nTo silense this warning, pass in --commit-dirty=true`
|
|
1005
|
+
);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
if (commitDirty === undefined) {
|
|
1009
|
+
commitDirty = isGitDirty;
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
let builtFunctions: string | undefined = undefined;
|
|
1014
|
+
const functionsDirectory = join(cwd(), "functions");
|
|
1015
|
+
if (existsSync(functionsDirectory)) {
|
|
1016
|
+
const outfile = join(tmpdir(), "./functionsWorker.js");
|
|
1017
|
+
|
|
1018
|
+
await new Promise((resolve) =>
|
|
1019
|
+
buildFunctions({
|
|
1020
|
+
outfile,
|
|
1021
|
+
functionsDirectory,
|
|
1022
|
+
onEnd: () => resolve(null),
|
|
1023
|
+
buildOutputDirectory: dirname(outfile),
|
|
1024
|
+
})
|
|
1025
|
+
);
|
|
1026
|
+
|
|
1027
|
+
builtFunctions = readFileSync(outfile, "utf-8");
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
type File = {
|
|
1031
|
+
content: Buffer;
|
|
1032
|
+
metadata: Metadata;
|
|
1033
|
+
};
|
|
1034
|
+
|
|
1035
|
+
type Metadata = {
|
|
1036
|
+
sizeInBytes: number;
|
|
1037
|
+
hash: string;
|
|
1038
|
+
};
|
|
1039
|
+
|
|
1040
|
+
const IGNORE_LIST = [
|
|
1041
|
+
"_worker.js",
|
|
1042
|
+
"_redirects",
|
|
1043
|
+
"_headers",
|
|
1044
|
+
".DS_Store",
|
|
1045
|
+
"node_modules",
|
|
1046
|
+
];
|
|
1047
|
+
|
|
1048
|
+
const walk = async (
|
|
1049
|
+
dir: string,
|
|
1050
|
+
fileMap: Map<string, File> = new Map(),
|
|
1051
|
+
depth = 0
|
|
1052
|
+
) => {
|
|
1053
|
+
const files = await readdir(dir);
|
|
1054
|
+
|
|
1055
|
+
await Promise.all(
|
|
1056
|
+
files.map(async (file) => {
|
|
1057
|
+
const filepath = join(dir, file);
|
|
1058
|
+
const filestat = await stat(filepath);
|
|
1059
|
+
|
|
1060
|
+
if (IGNORE_LIST.includes(file)) {
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
if (filestat.isSymbolicLink()) {
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
if (filestat.isDirectory()) {
|
|
1069
|
+
fileMap = await walk(filepath, fileMap, depth + 1);
|
|
1070
|
+
} else {
|
|
1071
|
+
let name;
|
|
1072
|
+
if (depth) {
|
|
1073
|
+
name = filepath.split(sep).slice(1).join("/");
|
|
1074
|
+
} else {
|
|
1075
|
+
name = file;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// TODO: Move this to later so we don't hold as much in memory
|
|
1079
|
+
const fileContent = await readFile(filepath);
|
|
1080
|
+
|
|
1081
|
+
const base64Content = fileContent.toString("base64");
|
|
1082
|
+
const extension =
|
|
1083
|
+
name.split(".").length > 1 ? name.split(".").at(-1) || "" : "";
|
|
1084
|
+
|
|
1085
|
+
const content = base64Content + extension;
|
|
1086
|
+
|
|
1087
|
+
if (filestat.size > 25 * 1024 * 1024) {
|
|
1088
|
+
throw new Error(
|
|
1089
|
+
`Error: Pages only supports files up to ${prettyBytes(
|
|
1090
|
+
25 * 1024 * 1024
|
|
1091
|
+
)} in size\n${name} is ${prettyBytes(filestat.size)} in size`
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
fileMap.set(name, {
|
|
1096
|
+
content: fileContent,
|
|
1097
|
+
metadata: {
|
|
1098
|
+
sizeInBytes: filestat.size,
|
|
1099
|
+
hash: hash(content).toString("hex").slice(0, 32),
|
|
1100
|
+
},
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
})
|
|
1104
|
+
);
|
|
1105
|
+
|
|
1106
|
+
return fileMap;
|
|
1107
|
+
};
|
|
1108
|
+
|
|
1109
|
+
const fileMap = await walk(directory);
|
|
1110
|
+
|
|
1111
|
+
const start = Date.now();
|
|
1112
|
+
|
|
1113
|
+
const files: Array<Promise<void>> = [];
|
|
1114
|
+
|
|
1115
|
+
if (fileMap.size > 1000) {
|
|
1116
|
+
throw new Error(
|
|
1117
|
+
`Error: Pages only supports up to 1,000 files in a deployment at the moment.\nTry a smaller project perhaps?`
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
let counter = 0;
|
|
1122
|
+
|
|
1123
|
+
const { rerender, unmount } = render(
|
|
1124
|
+
<Progress done={counter} total={fileMap.size} />
|
|
1125
|
+
);
|
|
1126
|
+
|
|
1127
|
+
fileMap.forEach((file: File, name: string) => {
|
|
1128
|
+
const form = new FormData();
|
|
1129
|
+
form.append(
|
|
1130
|
+
"file",
|
|
1131
|
+
new File([new Uint8Array(file.content.buffer)], name)
|
|
1132
|
+
);
|
|
1133
|
+
|
|
1134
|
+
// TODO: Consider a retry
|
|
1135
|
+
|
|
1136
|
+
const promise = fetchResult<{ id: string }>(
|
|
1137
|
+
`/accounts/${accountId}/pages/projects/${projectName}/file`,
|
|
1138
|
+
{
|
|
1139
|
+
method: "POST",
|
|
1140
|
+
body: form,
|
|
1141
|
+
}
|
|
1142
|
+
).then((response) => {
|
|
1143
|
+
counter++;
|
|
1144
|
+
rerender(<Progress done={counter} total={fileMap.size} />);
|
|
1145
|
+
if (response.id != file.metadata.hash) {
|
|
1146
|
+
throw new Error(
|
|
1147
|
+
`Looks like there was an issue uploading '${name}'. Try again perhaps?`
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
files.push(promise);
|
|
1153
|
+
});
|
|
1154
|
+
|
|
1155
|
+
await Promise.all(files);
|
|
1156
|
+
|
|
1157
|
+
unmount();
|
|
1158
|
+
|
|
1159
|
+
const uploadMs = Date.now() - start;
|
|
1160
|
+
|
|
1161
|
+
logger.log(
|
|
1162
|
+
`✨ Success! Uploaded ${fileMap.size} files ${formatTime(uploadMs)}\n`
|
|
1163
|
+
);
|
|
1164
|
+
|
|
1165
|
+
const formData = new FormData();
|
|
1166
|
+
|
|
1167
|
+
formData.append(
|
|
1168
|
+
"manifest",
|
|
1169
|
+
JSON.stringify(
|
|
1170
|
+
Object.fromEntries(
|
|
1171
|
+
[...fileMap.entries()].map(([fileName, file]) => [
|
|
1172
|
+
`/${fileName}`,
|
|
1173
|
+
file.metadata.hash,
|
|
1174
|
+
])
|
|
1175
|
+
)
|
|
1176
|
+
)
|
|
1177
|
+
);
|
|
1178
|
+
|
|
1179
|
+
if (branch) {
|
|
1180
|
+
formData.append("branch", branch);
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
if (commitMessage) {
|
|
1184
|
+
formData.append("commit_message", commitMessage);
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
if (commitHash) {
|
|
1188
|
+
formData.append("commit_hash", commitHash);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
if (commitDirty !== undefined) {
|
|
1192
|
+
formData.append("commit_dirty", commitDirty);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
let _headers: string | undefined,
|
|
1196
|
+
_redirects: string | undefined,
|
|
1197
|
+
_workerJS: string | undefined;
|
|
1198
|
+
|
|
1199
|
+
try {
|
|
1200
|
+
_headers = readFileSync(join(directory, "_headers"), "utf-8");
|
|
1201
|
+
} catch {}
|
|
1202
|
+
|
|
1203
|
+
try {
|
|
1204
|
+
_redirects = readFileSync(join(directory, "_redirects"), "utf-8");
|
|
1205
|
+
} catch {}
|
|
1206
|
+
|
|
1207
|
+
try {
|
|
1208
|
+
_workerJS = readFileSync(join(directory, "_worker.js"), "utf-8");
|
|
1209
|
+
} catch {}
|
|
1210
|
+
|
|
1211
|
+
if (_headers) {
|
|
1212
|
+
formData.append("_headers", new File([_headers], "_headers"));
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
if (_redirects) {
|
|
1216
|
+
formData.append("_redirects", new File([_redirects], "_redirects"));
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
if (builtFunctions) {
|
|
1220
|
+
formData.append("_worker.js", new File([builtFunctions], "_worker.js"));
|
|
1221
|
+
} else if (_workerJS) {
|
|
1222
|
+
formData.append("_worker.js", new File([_workerJS], "_worker.js"));
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
const deploymentResponse = await fetchResult<Deployment>(
|
|
1226
|
+
`/accounts/${accountId}/pages/projects/${projectName}/deployments`,
|
|
1227
|
+
{
|
|
1228
|
+
method: "POST",
|
|
1229
|
+
body: formData,
|
|
1230
|
+
}
|
|
1231
|
+
);
|
|
1232
|
+
|
|
1233
|
+
saveToConfigCache<PagesConfigCache>(PAGES_CONFIG_CACHE_FILENAME, {
|
|
1234
|
+
account_id: accountId,
|
|
1235
|
+
project_name: projectName,
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
logger.log(
|
|
1239
|
+
`✨ Deployment complete! Take a peek over at ${deploymentResponse.url}`
|
|
1240
|
+
);
|
|
1241
|
+
},
|
|
1242
|
+
};
|
|
1243
|
+
|
|
702
1244
|
export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
703
1245
|
return yargs
|
|
704
1246
|
.command(
|
|
@@ -759,7 +1301,8 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
759
1301
|
description: "Auto reload HTML pages when change is detected",
|
|
760
1302
|
},
|
|
761
1303
|
// TODO: Miniflare user options
|
|
762
|
-
})
|
|
1304
|
+
})
|
|
1305
|
+
.epilogue(pagesBetaWarning);
|
|
763
1306
|
},
|
|
764
1307
|
async ({
|
|
765
1308
|
local,
|
|
@@ -773,8 +1316,11 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
773
1316
|
"live-reload": liveReload,
|
|
774
1317
|
_: [_pages, _dev, ...remaining],
|
|
775
1318
|
}) => {
|
|
1319
|
+
// Beta message for `wrangler pages <commands>` usage
|
|
1320
|
+
logger.log(pagesBetaWarning);
|
|
1321
|
+
|
|
776
1322
|
if (!local) {
|
|
777
|
-
|
|
1323
|
+
logger.error("Only local mode is supported at the moment.");
|
|
778
1324
|
return;
|
|
779
1325
|
}
|
|
780
1326
|
|
|
@@ -801,33 +1347,37 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
801
1347
|
);
|
|
802
1348
|
|
|
803
1349
|
if (usingFunctions) {
|
|
804
|
-
const
|
|
1350
|
+
const outfile = join(tmpdir(), "./functionsWorker.js");
|
|
805
1351
|
|
|
806
|
-
|
|
1352
|
+
logger.log(`Compiling worker to "${outfile}"...`);
|
|
807
1353
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
1354
|
+
try {
|
|
1355
|
+
await buildFunctions({
|
|
1356
|
+
outfile,
|
|
1357
|
+
functionsDirectory,
|
|
1358
|
+
sourcemap: true,
|
|
1359
|
+
watch: true,
|
|
1360
|
+
onEnd: () => scriptReadyResolve(),
|
|
1361
|
+
buildOutputDirectory: directory,
|
|
1362
|
+
});
|
|
1363
|
+
} catch {}
|
|
815
1364
|
|
|
816
1365
|
watch([functionsDirectory], {
|
|
817
1366
|
persistent: true,
|
|
818
1367
|
ignoreInitial: true,
|
|
819
1368
|
}).on("all", async () => {
|
|
820
1369
|
await buildFunctions({
|
|
821
|
-
|
|
1370
|
+
outfile,
|
|
822
1371
|
functionsDirectory,
|
|
823
1372
|
sourcemap: true,
|
|
824
1373
|
watch: true,
|
|
825
1374
|
onEnd: () => scriptReadyResolve(),
|
|
1375
|
+
buildOutputDirectory: directory,
|
|
826
1376
|
});
|
|
827
1377
|
});
|
|
828
1378
|
|
|
829
1379
|
miniflareArgs = {
|
|
830
|
-
scriptPath,
|
|
1380
|
+
scriptPath: outfile,
|
|
831
1381
|
};
|
|
832
1382
|
} else {
|
|
833
1383
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
@@ -843,7 +1393,7 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
843
1393
|
scriptPath,
|
|
844
1394
|
};
|
|
845
1395
|
} else {
|
|
846
|
-
|
|
1396
|
+
logger.log("No functions. Shimming...");
|
|
847
1397
|
miniflareArgs = {
|
|
848
1398
|
// TODO: The fact that these request/response hacks are necessary is ridiculous.
|
|
849
1399
|
// We need to eliminate them from env.ASSETS.fetch (not sure if just local or prod as well)
|
|
@@ -875,6 +1425,9 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
875
1425
|
? await generateAssetsFetch(directory)
|
|
876
1426
|
: invalidAssetsFetch;
|
|
877
1427
|
|
|
1428
|
+
const requestContextCheckOptions =
|
|
1429
|
+
await getRequestContextCheckOptions();
|
|
1430
|
+
|
|
878
1431
|
const miniflare = new Miniflare({
|
|
879
1432
|
port,
|
|
880
1433
|
watch: true,
|
|
@@ -895,7 +1448,9 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
895
1448
|
// User bindings
|
|
896
1449
|
bindings: {
|
|
897
1450
|
...Object.fromEntries(
|
|
898
|
-
bindings
|
|
1451
|
+
bindings
|
|
1452
|
+
.map((binding) => binding.toString().split("="))
|
|
1453
|
+
.map(([key, ...values]) => [key, values.join("=")])
|
|
899
1454
|
),
|
|
900
1455
|
},
|
|
901
1456
|
|
|
@@ -908,7 +1463,7 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
908
1463
|
url.host = `localhost:${proxyPort}`;
|
|
909
1464
|
return await fetch(url, request);
|
|
910
1465
|
} catch (thrown) {
|
|
911
|
-
|
|
1466
|
+
logger.error(`Could not proxy request: ${thrown}`);
|
|
912
1467
|
|
|
913
1468
|
// TODO: Pretty error page
|
|
914
1469
|
return new Response(
|
|
@@ -920,7 +1475,7 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
920
1475
|
try {
|
|
921
1476
|
return await assetsFetch(request);
|
|
922
1477
|
} catch (thrown) {
|
|
923
|
-
|
|
1478
|
+
logger.error(`Could not serve static asset: ${thrown}`);
|
|
924
1479
|
|
|
925
1480
|
// TODO: Pretty error page
|
|
926
1481
|
return new Response(
|
|
@@ -937,13 +1492,14 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
937
1492
|
cachePersist: true,
|
|
938
1493
|
liveReload,
|
|
939
1494
|
|
|
1495
|
+
...requestContextCheckOptions,
|
|
940
1496
|
...miniflareArgs,
|
|
941
1497
|
});
|
|
942
1498
|
|
|
943
1499
|
try {
|
|
944
1500
|
// `startServer` might throw if user code contains errors
|
|
945
1501
|
const server = await miniflare.startServer();
|
|
946
|
-
|
|
1502
|
+
logger.log(`Serving at http://localhost:${port}/`);
|
|
947
1503
|
|
|
948
1504
|
if (process.env.BROWSER !== "none") {
|
|
949
1505
|
await openInBrowser(`http://localhost:${port}/`);
|
|
@@ -958,13 +1514,14 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
958
1514
|
});
|
|
959
1515
|
}
|
|
960
1516
|
|
|
961
|
-
|
|
1517
|
+
CLEANUP_CALLBACKS.push(() => {
|
|
962
1518
|
server.close();
|
|
963
1519
|
miniflare.dispose().catch((err) => miniflare.log.error(err));
|
|
964
1520
|
});
|
|
965
1521
|
} catch (e) {
|
|
966
1522
|
miniflare.log.error(e as Error);
|
|
967
|
-
|
|
1523
|
+
CLEANUP();
|
|
1524
|
+
throw new FatalError("Could not start Miniflare.", 1);
|
|
968
1525
|
}
|
|
969
1526
|
}
|
|
970
1527
|
)
|
|
@@ -982,7 +1539,7 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
982
1539
|
description: "The directory of Pages Functions",
|
|
983
1540
|
})
|
|
984
1541
|
.options({
|
|
985
|
-
|
|
1542
|
+
outfile: {
|
|
986
1543
|
type: "string",
|
|
987
1544
|
default: "_worker.js",
|
|
988
1545
|
description: "The location of the output Worker script",
|
|
@@ -1014,28 +1571,260 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
1014
1571
|
description:
|
|
1015
1572
|
"Watch for changes to the functions and automatically rebuild the Worker script",
|
|
1016
1573
|
},
|
|
1017
|
-
|
|
1574
|
+
plugin: {
|
|
1575
|
+
type: "boolean",
|
|
1576
|
+
default: false,
|
|
1577
|
+
description: "Build a plugin rather than a Worker script",
|
|
1578
|
+
},
|
|
1579
|
+
})
|
|
1580
|
+
.epilogue(pagesBetaWarning),
|
|
1018
1581
|
async ({
|
|
1019
1582
|
directory,
|
|
1020
|
-
|
|
1583
|
+
outfile,
|
|
1021
1584
|
"output-config-path": outputConfigPath,
|
|
1022
1585
|
minify,
|
|
1023
1586
|
sourcemap,
|
|
1024
1587
|
fallbackService,
|
|
1025
1588
|
watch,
|
|
1589
|
+
plugin,
|
|
1026
1590
|
}) => {
|
|
1591
|
+
if (!isInPagesCI) {
|
|
1592
|
+
// Beta message for `wrangler pages <commands>` usage
|
|
1593
|
+
logger.log(pagesBetaWarning);
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1027
1596
|
await buildFunctions({
|
|
1028
|
-
|
|
1597
|
+
outfile,
|
|
1029
1598
|
outputConfigPath,
|
|
1030
1599
|
functionsDirectory: directory,
|
|
1031
1600
|
minify,
|
|
1032
1601
|
sourcemap,
|
|
1033
1602
|
fallbackService,
|
|
1034
1603
|
watch,
|
|
1604
|
+
plugin,
|
|
1605
|
+
buildOutputDirectory: dirname(outfile),
|
|
1035
1606
|
});
|
|
1036
1607
|
}
|
|
1037
1608
|
)
|
|
1038
|
-
)
|
|
1609
|
+
)
|
|
1610
|
+
.command("project", "⚡️ Interact with your Pages projects", (yargs) =>
|
|
1611
|
+
yargs
|
|
1612
|
+
.command(
|
|
1613
|
+
"list",
|
|
1614
|
+
"List your Cloudflare Pages projects",
|
|
1615
|
+
(yargs) => yargs.epilogue(pagesBetaWarning),
|
|
1616
|
+
async () => {
|
|
1617
|
+
const config = getConfigCache<PagesConfigCache>(
|
|
1618
|
+
PAGES_CONFIG_CACHE_FILENAME
|
|
1619
|
+
);
|
|
1620
|
+
|
|
1621
|
+
const accountId = await requireAuth(config);
|
|
1622
|
+
|
|
1623
|
+
const projects: Array<Project> = await listProjects({ accountId });
|
|
1624
|
+
|
|
1625
|
+
const data = projects.map((project) => {
|
|
1626
|
+
return {
|
|
1627
|
+
"Project Name": project.name,
|
|
1628
|
+
"Project Domains": `${project.domains.join(", ")}`,
|
|
1629
|
+
"Git Provider": project.source ? "Yes" : "No",
|
|
1630
|
+
"Last Modified": project.latest_deployment
|
|
1631
|
+
? timeagoFormat(project.latest_deployment.modified_on)
|
|
1632
|
+
: timeagoFormat(project.created_on),
|
|
1633
|
+
};
|
|
1634
|
+
});
|
|
1635
|
+
|
|
1636
|
+
saveToConfigCache<PagesConfigCache>(PAGES_CONFIG_CACHE_FILENAME, {
|
|
1637
|
+
account_id: accountId,
|
|
1638
|
+
});
|
|
1639
|
+
|
|
1640
|
+
render(<Table data={data}></Table>);
|
|
1641
|
+
}
|
|
1642
|
+
)
|
|
1643
|
+
.command(
|
|
1644
|
+
"create [project-name]",
|
|
1645
|
+
"Create a new Cloudflare Pages project",
|
|
1646
|
+
(yargs) =>
|
|
1647
|
+
yargs
|
|
1648
|
+
.positional("project-name", {
|
|
1649
|
+
type: "string",
|
|
1650
|
+
demandOption: true,
|
|
1651
|
+
description: "The name of your Pages project",
|
|
1652
|
+
})
|
|
1653
|
+
.options({
|
|
1654
|
+
"production-branch": {
|
|
1655
|
+
type: "string",
|
|
1656
|
+
description:
|
|
1657
|
+
"The name of the production branch of your project",
|
|
1658
|
+
},
|
|
1659
|
+
})
|
|
1660
|
+
.epilogue(pagesBetaWarning),
|
|
1661
|
+
async ({ productionBranch, projectName }) => {
|
|
1662
|
+
const config = getConfigCache<PagesConfigCache>(
|
|
1663
|
+
PAGES_CONFIG_CACHE_FILENAME
|
|
1664
|
+
);
|
|
1665
|
+
const accountId = await requireAuth(config);
|
|
1666
|
+
|
|
1667
|
+
const isInteractive = process.stdin.isTTY;
|
|
1668
|
+
if (!projectName && isInteractive) {
|
|
1669
|
+
projectName = await prompt("Enter the name of your new project:");
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
if (!projectName) {
|
|
1673
|
+
throw new FatalError("Must specify a project name.", 1);
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
if (!productionBranch && isInteractive) {
|
|
1677
|
+
let isGitDir = true;
|
|
1678
|
+
try {
|
|
1679
|
+
execSync(`git rev-parse --is-inside-work-tree`, {
|
|
1680
|
+
stdio: "ignore",
|
|
1681
|
+
});
|
|
1682
|
+
} catch (err) {
|
|
1683
|
+
isGitDir = false;
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
productionBranch = await prompt(
|
|
1687
|
+
"Enter the production branch name:",
|
|
1688
|
+
"text",
|
|
1689
|
+
isGitDir
|
|
1690
|
+
? execSync(`git rev-parse --abbrev-ref HEAD`)
|
|
1691
|
+
.toString()
|
|
1692
|
+
.trim()
|
|
1693
|
+
: "production"
|
|
1694
|
+
);
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
if (!productionBranch) {
|
|
1698
|
+
throw new FatalError("Must specify a production branch.", 1);
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
const { subdomain } = await fetchResult<Project>(
|
|
1702
|
+
`/accounts/${accountId}/pages/projects`,
|
|
1703
|
+
{
|
|
1704
|
+
method: "POST",
|
|
1705
|
+
body: JSON.stringify({
|
|
1706
|
+
name: projectName,
|
|
1707
|
+
production_branch: productionBranch,
|
|
1708
|
+
}),
|
|
1709
|
+
}
|
|
1710
|
+
);
|
|
1711
|
+
|
|
1712
|
+
saveToConfigCache<PagesConfigCache>(PAGES_CONFIG_CACHE_FILENAME, {
|
|
1713
|
+
account_id: accountId,
|
|
1714
|
+
project_name: projectName,
|
|
1715
|
+
});
|
|
1716
|
+
|
|
1717
|
+
logger.log(
|
|
1718
|
+
`✨ Successfully created the '${projectName}' project. It will be available at https://${subdomain}/ once you create your first deployment.`
|
|
1719
|
+
);
|
|
1720
|
+
logger.log(
|
|
1721
|
+
`To deploy a folder of assets, run 'wrangler pages publish [directory]'.`
|
|
1722
|
+
);
|
|
1723
|
+
}
|
|
1724
|
+
)
|
|
1725
|
+
.epilogue(pagesBetaWarning)
|
|
1726
|
+
)
|
|
1727
|
+
.command(
|
|
1728
|
+
"deployment",
|
|
1729
|
+
"🚀 Interact with the deployments of a project",
|
|
1730
|
+
(yargs) =>
|
|
1731
|
+
yargs
|
|
1732
|
+
.command(
|
|
1733
|
+
"list",
|
|
1734
|
+
"List deployments in your Cloudflare Pages project",
|
|
1735
|
+
(yargs) =>
|
|
1736
|
+
yargs
|
|
1737
|
+
.options({
|
|
1738
|
+
"project-name": {
|
|
1739
|
+
type: "string",
|
|
1740
|
+
description:
|
|
1741
|
+
"The name of the project you would like to list deployments for",
|
|
1742
|
+
},
|
|
1743
|
+
})
|
|
1744
|
+
.epilogue(pagesBetaWarning),
|
|
1745
|
+
async ({ projectName }) => {
|
|
1746
|
+
const config = getConfigCache<PagesConfigCache>(
|
|
1747
|
+
PAGES_CONFIG_CACHE_FILENAME
|
|
1748
|
+
);
|
|
1749
|
+
const accountId = await requireAuth(config);
|
|
1750
|
+
|
|
1751
|
+
projectName ??= config.project_name;
|
|
1752
|
+
|
|
1753
|
+
const isInteractive = process.stdin.isTTY;
|
|
1754
|
+
if (!projectName && isInteractive) {
|
|
1755
|
+
const projects = await listProjects({ accountId });
|
|
1756
|
+
projectName = await new Promise((resolve) => {
|
|
1757
|
+
const { unmount } = render(
|
|
1758
|
+
<>
|
|
1759
|
+
<Text>Select a project:</Text>
|
|
1760
|
+
<SelectInput
|
|
1761
|
+
items={projects.map((project) => ({
|
|
1762
|
+
key: project.name,
|
|
1763
|
+
label: project.name,
|
|
1764
|
+
value: project,
|
|
1765
|
+
}))}
|
|
1766
|
+
onSelect={async (selected) => {
|
|
1767
|
+
resolve(selected.value.name);
|
|
1768
|
+
unmount();
|
|
1769
|
+
}}
|
|
1770
|
+
/>
|
|
1771
|
+
</>
|
|
1772
|
+
);
|
|
1773
|
+
});
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
if (!projectName) {
|
|
1777
|
+
throw new FatalError("Must specify a project name.", 1);
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
const deployments: Array<Deployment> = await fetchResult(
|
|
1781
|
+
`/accounts/${accountId}/pages/projects/${projectName}/deployments`
|
|
1782
|
+
);
|
|
1783
|
+
|
|
1784
|
+
const titleCase = (word: string) =>
|
|
1785
|
+
word.charAt(0).toUpperCase() + word.slice(1);
|
|
1786
|
+
|
|
1787
|
+
const shortSha = (sha: string) => sha.slice(0, 7);
|
|
1788
|
+
|
|
1789
|
+
const getStatus = (deployment: Deployment) => {
|
|
1790
|
+
// Return a pretty time since timestamp if successful otherwise the status
|
|
1791
|
+
if (deployment.latest_stage.status === `success`) {
|
|
1792
|
+
return timeagoFormat(deployment.latest_stage.ended_on);
|
|
1793
|
+
}
|
|
1794
|
+
return titleCase(deployment.latest_stage.status);
|
|
1795
|
+
};
|
|
1796
|
+
|
|
1797
|
+
const data = deployments.map((deployment) => {
|
|
1798
|
+
return {
|
|
1799
|
+
Environment: titleCase(deployment.environment),
|
|
1800
|
+
Branch: deployment.deployment_trigger.metadata.branch,
|
|
1801
|
+
Source: shortSha(
|
|
1802
|
+
deployment.deployment_trigger.metadata.commit_hash
|
|
1803
|
+
),
|
|
1804
|
+
Deployment: deployment.url,
|
|
1805
|
+
Status: getStatus(deployment),
|
|
1806
|
+
// TODO: Use a url shortener
|
|
1807
|
+
Build: `https://dash.cloudflare.com/${accountId}/pages/view/${deployment.project_name}/${deployment.id}`,
|
|
1808
|
+
};
|
|
1809
|
+
});
|
|
1810
|
+
|
|
1811
|
+
saveToConfigCache<PagesConfigCache>(PAGES_CONFIG_CACHE_FILENAME, {
|
|
1812
|
+
account_id: accountId,
|
|
1813
|
+
});
|
|
1814
|
+
|
|
1815
|
+
render(<Table data={data}></Table>);
|
|
1816
|
+
}
|
|
1817
|
+
)
|
|
1818
|
+
.command({
|
|
1819
|
+
command: "create [directory]",
|
|
1820
|
+
...createDeployment,
|
|
1821
|
+
} as CommandModule)
|
|
1822
|
+
.epilogue(pagesBetaWarning)
|
|
1823
|
+
)
|
|
1824
|
+
.command({
|
|
1825
|
+
command: "publish [directory]",
|
|
1826
|
+
...createDeployment,
|
|
1827
|
+
} as CommandModule);
|
|
1039
1828
|
};
|
|
1040
1829
|
|
|
1041
1830
|
const invalidAssetsFetch: typeof fetch = () => {
|
|
@@ -1043,3 +1832,44 @@ const invalidAssetsFetch: typeof fetch = () => {
|
|
|
1043
1832
|
"Trying to fetch assets directly when there is no `directory` option specified, and not in `local` mode."
|
|
1044
1833
|
);
|
|
1045
1834
|
};
|
|
1835
|
+
|
|
1836
|
+
const listProjects = async ({
|
|
1837
|
+
accountId,
|
|
1838
|
+
}: {
|
|
1839
|
+
accountId: string;
|
|
1840
|
+
}): Promise<Array<Project>> => {
|
|
1841
|
+
const pageSize = 10;
|
|
1842
|
+
let page = 1;
|
|
1843
|
+
const results = [];
|
|
1844
|
+
while (results.length % pageSize === 0) {
|
|
1845
|
+
const json: Array<Project> = await fetchResult(
|
|
1846
|
+
`/accounts/${accountId}/pages/projects`,
|
|
1847
|
+
{},
|
|
1848
|
+
new URLSearchParams({
|
|
1849
|
+
per_page: pageSize.toString(),
|
|
1850
|
+
page: page.toString(),
|
|
1851
|
+
})
|
|
1852
|
+
);
|
|
1853
|
+
page++;
|
|
1854
|
+
results.push(...json);
|
|
1855
|
+
if (json.length < pageSize) {
|
|
1856
|
+
break;
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
return results;
|
|
1860
|
+
};
|
|
1861
|
+
|
|
1862
|
+
function formatTime(duration: number) {
|
|
1863
|
+
return `(${(duration / 1000).toFixed(2)} sec)`;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
function Progress({ done, total }: { done: number; total: number }) {
|
|
1867
|
+
return (
|
|
1868
|
+
<>
|
|
1869
|
+
<Text>
|
|
1870
|
+
<Spinner type="earth" />
|
|
1871
|
+
{` Uploading... (${done}/${total})\n`}
|
|
1872
|
+
</Text>
|
|
1873
|
+
</>
|
|
1874
|
+
);
|
|
1875
|
+
}
|