wrangler 0.0.0-e64812e → 0.0.0-e8e684c7

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.
Files changed (214) hide show
  1. package/README.md +27 -3
  2. package/bin/wrangler.js +150 -62
  3. package/kv-asset-handler.js +1 -0
  4. package/miniflare-dist/index.mjs +663 -7
  5. package/package.json +173 -152
  6. package/src/__tests__/config-cache-without-cache-dir.test.ts +38 -0
  7. package/src/__tests__/config-cache.test.ts +30 -24
  8. package/src/__tests__/configuration.test.ts +4015 -3363
  9. package/src/__tests__/dev.test.tsx +1290 -952
  10. package/src/__tests__/generate.test.ts +91 -0
  11. package/src/__tests__/guess-worker-format.test.ts +68 -68
  12. package/src/__tests__/helpers/cmd-shim.d.ts +6 -6
  13. package/src/__tests__/helpers/mock-account-id.ts +24 -24
  14. package/src/__tests__/helpers/mock-bin.ts +20 -20
  15. package/src/__tests__/helpers/mock-cfetch.ts +179 -94
  16. package/src/__tests__/helpers/mock-console.ts +49 -39
  17. package/src/__tests__/helpers/mock-dialogs.ts +94 -71
  18. package/src/__tests__/helpers/mock-get-zone-from-host.ts +8 -0
  19. package/src/__tests__/helpers/mock-http-server.ts +30 -30
  20. package/src/__tests__/helpers/mock-istty.ts +65 -18
  21. package/src/__tests__/helpers/mock-known-routes.ts +7 -0
  22. package/src/__tests__/helpers/mock-kv.ts +26 -26
  23. package/src/__tests__/helpers/mock-oauth-flow.ts +223 -228
  24. package/src/__tests__/helpers/mock-process.ts +39 -0
  25. package/src/__tests__/helpers/mock-stdin.ts +82 -77
  26. package/src/__tests__/helpers/mock-web-socket.ts +21 -21
  27. package/src/__tests__/helpers/run-in-tmp.ts +27 -28
  28. package/src/__tests__/helpers/run-wrangler.ts +8 -8
  29. package/src/__tests__/helpers/write-worker-source.ts +16 -16
  30. package/src/__tests__/helpers/write-wrangler-toml.ts +9 -9
  31. package/src/__tests__/https-options.test.ts +142 -109
  32. package/src/__tests__/index.test.ts +222 -233
  33. package/src/__tests__/init.test.ts +2139 -1603
  34. package/src/__tests__/jest.setup.ts +114 -34
  35. package/src/__tests__/kv.test.ts +1222 -1032
  36. package/src/__tests__/logger.test.ts +100 -74
  37. package/src/__tests__/metrics.test.ts +450 -0
  38. package/src/__tests__/package-manager.test.ts +303 -303
  39. package/src/__tests__/pages.test.ts +1173 -652
  40. package/src/__tests__/parse.test.ts +252 -252
  41. package/src/__tests__/publish.test.ts +6788 -5301
  42. package/src/__tests__/pubsub.test.ts +367 -0
  43. package/src/__tests__/r2.test.ts +328 -225
  44. package/src/__tests__/route.test.ts +18 -18
  45. package/src/__tests__/secret.test.ts +470 -459
  46. package/src/__tests__/tail.test.ts +676 -564
  47. package/src/__tests__/test-old-node-version.js +31 -0
  48. package/src/__tests__/user.test.ts +136 -112
  49. package/src/__tests__/validate-dev-props.test.ts +56 -0
  50. package/src/__tests__/version.test.ts +35 -0
  51. package/src/__tests__/whoami.test.tsx +267 -83
  52. package/src/__tests__/worker-namespace.test.ts +329 -0
  53. package/src/abort.d.ts +1 -1
  54. package/src/api/dev.ts +93 -0
  55. package/src/api/index.ts +1 -0
  56. package/src/bundle-reporter.tsx +29 -0
  57. package/src/bundle.ts +430 -157
  58. package/src/cfetch/index.ts +80 -80
  59. package/src/cfetch/internal.ts +244 -91
  60. package/src/cli.ts +21 -7
  61. package/src/config/config.ts +224 -195
  62. package/src/config/diagnostics.ts +61 -61
  63. package/src/config/environment.ts +422 -357
  64. package/src/config/index.ts +219 -193
  65. package/src/config/validation-helpers.ts +379 -357
  66. package/src/config/validation.ts +1690 -1367
  67. package/src/config-cache.ts +81 -41
  68. package/src/create-worker-preview.ts +208 -136
  69. package/src/create-worker-upload-form.ts +275 -238
  70. package/src/dev/dev-vars.ts +13 -13
  71. package/src/dev/dev.tsx +477 -351
  72. package/src/dev/local.tsx +426 -295
  73. package/src/dev/remote.tsx +406 -211
  74. package/src/dev/use-esbuild.ts +154 -94
  75. package/src/dev/validate-dev-props.ts +31 -0
  76. package/src/dev-registry.tsx +160 -0
  77. package/src/dev.tsx +696 -0
  78. package/src/dialogs.tsx +97 -97
  79. package/src/dispatch-namespace.ts +206 -0
  80. package/src/durable.ts +87 -87
  81. package/src/entry.ts +234 -228
  82. package/src/environment-variables.ts +23 -23
  83. package/src/errors.ts +6 -6
  84. package/src/faye-websocket.d.ts +6 -0
  85. package/src/generate.ts +131 -0
  86. package/src/git-client.ts +42 -0
  87. package/src/global-wrangler-config-path.ts +26 -0
  88. package/src/https-options.ts +84 -80
  89. package/src/index.tsx +2164 -2768
  90. package/src/init.ts +622 -0
  91. package/src/inspect.ts +678 -593
  92. package/src/intl-polyfill.d.ts +123 -123
  93. package/src/is-ci.ts +14 -0
  94. package/src/is-interactive.ts +12 -0
  95. package/src/kv.ts +299 -277
  96. package/src/logger.ts +46 -39
  97. package/src/metrics/index.ts +5 -0
  98. package/src/metrics/metrics-config.ts +239 -0
  99. package/src/metrics/metrics-dispatcher.ts +96 -0
  100. package/src/metrics/metrics-usage-headers.ts +24 -0
  101. package/src/metrics/send-event.ts +92 -0
  102. package/src/miniflare-cli/assets.ts +554 -0
  103. package/src/miniflare-cli/enum-keys.ts +8 -8
  104. package/src/miniflare-cli/index.ts +198 -32
  105. package/src/miniflare-cli/request-context.ts +18 -18
  106. package/src/module-collection.ts +212 -212
  107. package/src/open-in-browser.ts +4 -6
  108. package/src/package-manager.ts +173 -128
  109. package/src/pages/build.tsx +238 -0
  110. package/src/pages/constants.ts +12 -0
  111. package/src/pages/deployments.tsx +106 -0
  112. package/src/pages/dev.tsx +528 -0
  113. package/src/pages/errors.ts +22 -0
  114. package/src/pages/functions/buildPlugin.ts +109 -0
  115. package/src/pages/functions/buildWorker.ts +155 -0
  116. package/{pages → src/pages}/functions/filepath-routing.test.ts +113 -113
  117. package/src/pages/functions/filepath-routing.ts +189 -0
  118. package/src/pages/functions/identifiers.ts +78 -0
  119. package/src/pages/functions/routes-consolidation.test.ts +250 -0
  120. package/src/pages/functions/routes-consolidation.ts +73 -0
  121. package/src/pages/functions/routes-transformation.test.ts +271 -0
  122. package/src/pages/functions/routes-transformation.ts +122 -0
  123. package/src/pages/functions/routes.ts +151 -0
  124. package/src/pages/functions.tsx +96 -0
  125. package/src/pages/index.tsx +94 -0
  126. package/src/pages/projects.tsx +169 -0
  127. package/src/pages/publish.tsx +391 -0
  128. package/src/pages/types.ts +49 -0
  129. package/src/pages/upload.tsx +413 -0
  130. package/src/pages/utils.ts +12 -0
  131. package/src/parse.ts +209 -125
  132. package/src/paths.ts +6 -6
  133. package/src/preview.ts +31 -0
  134. package/src/proxy.ts +410 -389
  135. package/src/publish.ts +773 -534
  136. package/src/pubsub/index.ts +286 -0
  137. package/src/pubsub/pubsub-commands.tsx +623 -0
  138. package/src/r2.ts +100 -19
  139. package/src/selfsigned.d.ts +23 -23
  140. package/src/sites.tsx +278 -201
  141. package/src/tail/filters.ts +111 -109
  142. package/src/tail/index.ts +230 -217
  143. package/src/tail/printing.ts +86 -46
  144. package/src/update-check.ts +11 -11
  145. package/src/user/choose-account.tsx +69 -0
  146. package/src/user/env-vars.ts +46 -0
  147. package/src/user/generate-auth-url.ts +33 -0
  148. package/src/user/generate-random-state.ts +16 -0
  149. package/src/user/index.ts +3 -0
  150. package/src/user/user.tsx +1191 -0
  151. package/src/whoami.tsx +141 -42
  152. package/src/worker.ts +123 -99
  153. package/src/zones.ts +167 -0
  154. package/templates/checked-fetch.js +17 -0
  155. package/templates/first-party-worker-module-facade.ts +18 -0
  156. package/templates/format-dev-errors.ts +32 -0
  157. package/templates/new-worker-scheduled.js +3 -3
  158. package/templates/new-worker-scheduled.ts +15 -15
  159. package/templates/new-worker.js +3 -3
  160. package/templates/new-worker.ts +15 -15
  161. package/templates/no-op-worker.js +10 -0
  162. package/templates/pages-shim.ts +9 -0
  163. package/templates/pages-template-plugin.ts +167 -0
  164. package/templates/pages-template-worker.ts +172 -0
  165. package/templates/serve-static-assets.ts +63 -0
  166. package/templates/service-bindings-module-facade.js +54 -0
  167. package/templates/service-bindings-sw-facade.js +42 -0
  168. package/templates/tsconfig.json +95 -95
  169. package/wrangler-dist/cli.d.ts +338 -0
  170. package/wrangler-dist/cli.js +95449 -67524
  171. package/pages/functions/buildPlugin.ts +0 -105
  172. package/pages/functions/buildWorker.ts +0 -151
  173. package/pages/functions/filepath-routing.ts +0 -189
  174. package/pages/functions/identifiers.ts +0 -78
  175. package/pages/functions/routes.ts +0 -156
  176. package/pages/functions/template-plugin.ts +0 -147
  177. package/pages/functions/template-worker.ts +0 -143
  178. package/src/__tests__/helpers/faye-websocket.d.ts +0 -6
  179. package/src/pages.tsx +0 -2038
  180. package/src/user.tsx +0 -1203
  181. package/templates/static-asset-facade.js +0 -47
  182. package/vendor/@cloudflare/kv-asset-handler/CHANGELOG.md +0 -332
  183. package/vendor/@cloudflare/kv-asset-handler/LICENSE_APACHE +0 -176
  184. package/vendor/@cloudflare/kv-asset-handler/LICENSE_MIT +0 -25
  185. package/vendor/@cloudflare/kv-asset-handler/README.md +0 -245
  186. package/vendor/@cloudflare/kv-asset-handler/dist/index.d.ts +0 -32
  187. package/vendor/@cloudflare/kv-asset-handler/dist/index.js +0 -354
  188. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.d.ts +0 -13
  189. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.js +0 -148
  190. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.d.ts +0 -1
  191. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.js +0 -436
  192. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.d.ts +0 -1
  193. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.js +0 -40
  194. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.d.ts +0 -1
  195. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.js +0 -42
  196. package/vendor/@cloudflare/kv-asset-handler/dist/types.d.ts +0 -26
  197. package/vendor/@cloudflare/kv-asset-handler/dist/types.js +0 -31
  198. package/vendor/@cloudflare/kv-asset-handler/package.json +0 -52
  199. package/vendor/@cloudflare/kv-asset-handler/src/index.ts +0 -296
  200. package/vendor/@cloudflare/kv-asset-handler/src/mocks.ts +0 -136
  201. package/vendor/@cloudflare/kv-asset-handler/src/test/getAssetFromKV.ts +0 -464
  202. package/vendor/@cloudflare/kv-asset-handler/src/test/mapRequestToAsset.ts +0 -33
  203. package/vendor/@cloudflare/kv-asset-handler/src/test/serveSinglePageApp.ts +0 -42
  204. package/vendor/@cloudflare/kv-asset-handler/src/types.ts +0 -39
  205. package/vendor/wrangler-mime/CHANGELOG.md +0 -289
  206. package/vendor/wrangler-mime/LICENSE +0 -21
  207. package/vendor/wrangler-mime/Mime.js +0 -97
  208. package/vendor/wrangler-mime/README.md +0 -187
  209. package/vendor/wrangler-mime/cli.js +0 -46
  210. package/vendor/wrangler-mime/index.js +0 -4
  211. package/vendor/wrangler-mime/lite.js +0 -4
  212. package/vendor/wrangler-mime/package.json +0 -52
  213. package/vendor/wrangler-mime/types/other.js +0 -1
  214. package/vendor/wrangler-mime/types/standard.js +0 -1
@@ -0,0 +1,94 @@
1
+ /* eslint-disable no-shadow */
2
+
3
+ import * as Build from "./build";
4
+ import * as Deployments from "./deployments";
5
+ import * as Dev from "./dev";
6
+ import * as Functions from "./functions";
7
+ import * as Projects from "./projects";
8
+ import * as Publish from "./publish";
9
+ import * as Upload from "./upload";
10
+ import { CLEANUP, pagesBetaWarning } from "./utils";
11
+ import type { BuilderCallback } from "yargs";
12
+
13
+ process.on("SIGINT", () => {
14
+ CLEANUP();
15
+ process.exit();
16
+ });
17
+ process.on("SIGTERM", () => {
18
+ CLEANUP();
19
+ process.exit();
20
+ });
21
+
22
+ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
23
+ return (
24
+ yargs
25
+ .command(
26
+ "dev [directory] [-- command..]",
27
+ "🧑‍💻 Develop your full-stack Pages application locally",
28
+ Dev.Options,
29
+ Dev.Handler
30
+ )
31
+ /**
32
+ * `wrangler pages functions` is meant for internal use only for now,
33
+ * so let's hide this command from the help output
34
+ */
35
+ .command("functions", false, (yargs) =>
36
+ yargs
37
+ .command(
38
+ "build [directory]",
39
+ "Compile a folder of Cloudflare Pages Functions into a single Worker",
40
+ Build.Options,
41
+ Build.Handler
42
+ )
43
+ .command(
44
+ "optimize-routes [routesPath] [outputRoutesPath]",
45
+ "Consolidate and optimize the route paths declared in _routes.json",
46
+ Functions.OptimizeRoutesOptions,
47
+ Functions.OptimizeRoutesHandler
48
+ )
49
+ )
50
+ .command("project", "⚡️ Interact with your Pages projects", (yargs) =>
51
+ yargs
52
+ .command(
53
+ "list",
54
+ "List your Cloudflare Pages projects",
55
+ Projects.ListOptions,
56
+ Projects.ListHandler
57
+ )
58
+ .command(
59
+ "create [project-name]",
60
+ "Create a new Cloudflare Pages project",
61
+ Projects.CreateOptions,
62
+ Projects.CreateHandler
63
+ )
64
+ .command("upload [directory]", false, Upload.Options, Upload.Handler)
65
+ .epilogue(pagesBetaWarning)
66
+ )
67
+ .command(
68
+ "deployment",
69
+ "🚀 Interact with the deployments of a project",
70
+ (yargs) =>
71
+ yargs
72
+ .command(
73
+ "list",
74
+ "List deployments in your Cloudflare Pages project",
75
+ Deployments.ListOptions,
76
+ Deployments.ListHandler
77
+ )
78
+ .command(
79
+ "create [directory]",
80
+ "🆙 Publish a directory of static assets as a Pages deployment",
81
+ Publish.Options,
82
+ Publish.Handler
83
+ )
84
+ .epilogue(pagesBetaWarning)
85
+ )
86
+ .command(
87
+ "publish [directory]",
88
+ "🆙 Publish a directory of static assets as a Pages deployment",
89
+ Publish.Options,
90
+ Publish.Handler
91
+ )
92
+ .epilogue(pagesBetaWarning)
93
+ );
94
+ };
@@ -0,0 +1,169 @@
1
+ import { execSync } from "node:child_process";
2
+ import { render } from "ink";
3
+ import Table from "ink-table";
4
+ import React from "react";
5
+ import { format as timeagoFormat } from "timeago.js";
6
+ import { fetchResult } from "../cfetch";
7
+ import { getConfigCache, saveToConfigCache } from "../config-cache";
8
+ import { prompt } from "../dialogs";
9
+ import { FatalError } from "../errors";
10
+ import { logger } from "../logger";
11
+ import * as metrics from "../metrics";
12
+ import { requireAuth } from "../user";
13
+ import { PAGES_CONFIG_CACHE_FILENAME } from "./constants";
14
+ import { pagesBetaWarning } from "./utils";
15
+ import type { PagesConfigCache, Project } from "./types";
16
+ import type { ArgumentsCamelCase, Argv } from "yargs";
17
+
18
+ // Empty object, as per eslint
19
+ type ListArgs = Record<string, never>;
20
+
21
+ export function ListOptions(yargs: Argv): Argv<ListArgs> {
22
+ return yargs.epilogue(pagesBetaWarning);
23
+ }
24
+
25
+ export async function ListHandler() {
26
+ const config = getConfigCache<PagesConfigCache>(PAGES_CONFIG_CACHE_FILENAME);
27
+
28
+ const accountId = await requireAuth(config);
29
+
30
+ const projects: Array<Project> = await listProjects({ accountId });
31
+
32
+ const data = projects.map((project) => {
33
+ return {
34
+ "Project Name": project.name,
35
+ "Project Domains": `${project.domains.join(", ")}`,
36
+ "Git Provider": project.source ? "Yes" : "No",
37
+ "Last Modified": project.latest_deployment
38
+ ? timeagoFormat(project.latest_deployment.modified_on)
39
+ : timeagoFormat(project.created_on),
40
+ };
41
+ });
42
+
43
+ saveToConfigCache<PagesConfigCache>(PAGES_CONFIG_CACHE_FILENAME, {
44
+ account_id: accountId,
45
+ });
46
+
47
+ const { unmount } = render(<Table data={data}></Table>, {
48
+ patchConsole: false,
49
+ });
50
+ unmount();
51
+ await metrics.sendMetricsEvent("list pages projects");
52
+ }
53
+
54
+ export const listProjects = async ({
55
+ accountId,
56
+ }: {
57
+ accountId: string;
58
+ }): Promise<Array<Project>> => {
59
+ const pageSize = 10;
60
+ let page = 1;
61
+ const results = [];
62
+ while (results.length % pageSize === 0) {
63
+ const json: Array<Project> = await fetchResult(
64
+ `/accounts/${accountId}/pages/projects`,
65
+ {},
66
+ new URLSearchParams({
67
+ per_page: pageSize.toString(),
68
+ page: page.toString(),
69
+ })
70
+ );
71
+ page++;
72
+ results.push(...json);
73
+ if (json.length < pageSize) {
74
+ break;
75
+ }
76
+ }
77
+ return results;
78
+ };
79
+
80
+ type CreateArgs = {
81
+ "project-name": string;
82
+ "production-branch"?: string;
83
+ };
84
+
85
+ export function CreateOptions(yargs: Argv): Argv<CreateArgs> {
86
+ return yargs
87
+ .positional("project-name", {
88
+ type: "string",
89
+ demandOption: true,
90
+ description: "The name of your Pages project",
91
+ })
92
+ .options({
93
+ "production-branch": {
94
+ type: "string",
95
+ description: "The name of the production branch of your project",
96
+ },
97
+ })
98
+ .epilogue(pagesBetaWarning);
99
+ }
100
+
101
+ export async function CreateHandler({
102
+ productionBranch,
103
+ projectName,
104
+ }: ArgumentsCamelCase<CreateArgs>) {
105
+ const config = getConfigCache<PagesConfigCache>(PAGES_CONFIG_CACHE_FILENAME);
106
+ const accountId = await requireAuth(config);
107
+
108
+ const isInteractive = process.stdin.isTTY;
109
+ if (!projectName && isInteractive) {
110
+ projectName = await prompt("Enter the name of your new project:");
111
+ }
112
+
113
+ if (!projectName) {
114
+ throw new FatalError("Must specify a project name.", 1);
115
+ }
116
+
117
+ if (!productionBranch && isInteractive) {
118
+ let isGitDir = true;
119
+ try {
120
+ execSync(`git rev-parse --is-inside-work-tree`, {
121
+ stdio: "ignore",
122
+ });
123
+ } catch (err) {
124
+ isGitDir = false;
125
+ }
126
+
127
+ if (isGitDir) {
128
+ try {
129
+ productionBranch = execSync(`git rev-parse --abbrev-ref HEAD`)
130
+ .toString()
131
+ .trim();
132
+ } catch (err) {}
133
+ }
134
+
135
+ productionBranch = await prompt(
136
+ "Enter the production branch name:",
137
+ "text",
138
+ productionBranch ?? "production"
139
+ );
140
+ }
141
+
142
+ if (!productionBranch) {
143
+ throw new FatalError("Must specify a production branch.", 1);
144
+ }
145
+
146
+ const { subdomain } = await fetchResult<Project>(
147
+ `/accounts/${accountId}/pages/projects`,
148
+ {
149
+ method: "POST",
150
+ body: JSON.stringify({
151
+ name: projectName,
152
+ production_branch: productionBranch,
153
+ }),
154
+ }
155
+ );
156
+
157
+ saveToConfigCache<PagesConfigCache>(PAGES_CONFIG_CACHE_FILENAME, {
158
+ account_id: accountId,
159
+ project_name: projectName,
160
+ });
161
+
162
+ logger.log(
163
+ `✨ Successfully created the '${projectName}' project. It will be available at https://${subdomain}/ once you create your first deployment.`
164
+ );
165
+ logger.log(
166
+ `To deploy a folder of assets, run 'wrangler pages publish [directory]'.`
167
+ );
168
+ await metrics.sendMetricsEvent("create pages project");
169
+ }
@@ -0,0 +1,391 @@
1
+ import { execSync } from "node:child_process";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ import { cwd } from "node:process";
6
+ import { render, Text } from "ink";
7
+ import SelectInput from "ink-select-input";
8
+ import React from "react";
9
+ import { File, FormData } from "undici";
10
+ import { fetchResult } from "../cfetch";
11
+ import { getConfigCache, saveToConfigCache } from "../config-cache";
12
+ import { prompt } from "../dialogs";
13
+ import { FatalError } from "../errors";
14
+ import { logger } from "../logger";
15
+ import * as metrics from "../metrics";
16
+ import { requireAuth } from "../user";
17
+ import { buildFunctions } from "./build";
18
+ import { PAGES_CONFIG_CACHE_FILENAME } from "./constants";
19
+ import { FunctionsNoRoutesError, getFunctionsNoRoutesWarning } from "./errors";
20
+ import {
21
+ isRoutesJSONSpec,
22
+ optimizeRoutesJSONSpec,
23
+ } from "./functions/routes-transformation";
24
+ import { listProjects } from "./projects";
25
+ import { upload } from "./upload";
26
+ import { pagesBetaWarning } from "./utils";
27
+ import type {
28
+ Deployment,
29
+ PagesConfigCache,
30
+ Project,
31
+ YargsOptionsToInterface,
32
+ } from "./types";
33
+ import type { Argv } from "yargs";
34
+
35
+ type PublishArgs = YargsOptionsToInterface<typeof Options>;
36
+
37
+ export function Options(yargs: Argv) {
38
+ return yargs
39
+ .positional("directory", {
40
+ type: "string",
41
+ demandOption: true,
42
+ description: "The directory of static files to upload",
43
+ })
44
+ .options({
45
+ "project-name": {
46
+ type: "string",
47
+ description: "The name of the project you want to deploy to",
48
+ },
49
+ branch: {
50
+ type: "string",
51
+ description: "The name of the branch you want to deploy to",
52
+ },
53
+ "commit-hash": {
54
+ type: "string",
55
+ description: "The SHA to attach to this deployment",
56
+ },
57
+ "commit-message": {
58
+ type: "string",
59
+ description: "The commit message to attach to this deployment",
60
+ },
61
+ "commit-dirty": {
62
+ type: "boolean",
63
+ description:
64
+ "Whether or not the workspace should be considered dirty for this deployment",
65
+ },
66
+ config: {
67
+ describe: "Pages does not support wrangler.toml",
68
+ type: "string",
69
+ hidden: true,
70
+ },
71
+ })
72
+ .epilogue(pagesBetaWarning);
73
+ }
74
+
75
+ export const Handler = async ({
76
+ directory,
77
+ projectName,
78
+ branch,
79
+ commitHash,
80
+ commitMessage,
81
+ commitDirty,
82
+ config: wranglerConfig,
83
+ }: PublishArgs) => {
84
+ if (wranglerConfig) {
85
+ throw new FatalError("Pages does not support wrangler.toml", 1);
86
+ }
87
+
88
+ if (!directory) {
89
+ throw new FatalError("Must specify a directory.", 1);
90
+ }
91
+
92
+ const config = getConfigCache<PagesConfigCache>(PAGES_CONFIG_CACHE_FILENAME);
93
+ const accountId = await requireAuth(config);
94
+
95
+ projectName ??= config.project_name;
96
+
97
+ const isInteractive = process.stdin.isTTY;
98
+ if (!projectName && isInteractive) {
99
+ const projects = (await listProjects({ accountId })).filter(
100
+ (project) => !project.source
101
+ );
102
+
103
+ let existingOrNew: "existing" | "new" = "new";
104
+
105
+ if (projects.length > 0) {
106
+ existingOrNew = await new Promise<"new" | "existing">((resolve) => {
107
+ const { unmount } = render(
108
+ <>
109
+ <Text>
110
+ No project selected. Would you like to create one or use an
111
+ existing project?
112
+ </Text>
113
+ <SelectInput
114
+ items={[
115
+ {
116
+ key: "new",
117
+ label: "Create a new project",
118
+ value: "new",
119
+ },
120
+ {
121
+ key: "existing",
122
+ label: "Use an existing project",
123
+ value: "existing",
124
+ },
125
+ ]}
126
+ onSelect={async (selected) => {
127
+ resolve(selected.value as "new" | "existing");
128
+ unmount();
129
+ }}
130
+ />
131
+ </>
132
+ );
133
+ });
134
+ }
135
+
136
+ switch (existingOrNew) {
137
+ case "existing": {
138
+ projectName = await new Promise((resolve) => {
139
+ const { unmount } = render(
140
+ <>
141
+ <Text>Select a project:</Text>
142
+ <SelectInput
143
+ items={projects.map((project) => ({
144
+ key: project.name,
145
+ label: project.name,
146
+ value: project,
147
+ }))}
148
+ onSelect={async (selected) => {
149
+ resolve(selected.value.name);
150
+ unmount();
151
+ }}
152
+ />
153
+ </>
154
+ );
155
+ });
156
+ break;
157
+ }
158
+ case "new": {
159
+ projectName = await prompt("Enter the name of your new project:");
160
+
161
+ if (!projectName) {
162
+ throw new FatalError("Must specify a project name.", 1);
163
+ }
164
+
165
+ let isGitDir = true;
166
+ try {
167
+ execSync(`git rev-parse --is-inside-work-tree`, {
168
+ stdio: "ignore",
169
+ });
170
+ } catch (err) {
171
+ isGitDir = false;
172
+ }
173
+
174
+ const productionBranch = await prompt(
175
+ "Enter the production branch name:",
176
+ "text",
177
+ isGitDir
178
+ ? execSync(`git rev-parse --abbrev-ref HEAD`).toString().trim()
179
+ : "production"
180
+ );
181
+
182
+ if (!productionBranch) {
183
+ throw new FatalError("Must specify a production branch.", 1);
184
+ }
185
+
186
+ await fetchResult<Project>(`/accounts/${accountId}/pages/projects`, {
187
+ method: "POST",
188
+ body: JSON.stringify({
189
+ name: projectName,
190
+ production_branch: productionBranch,
191
+ }),
192
+ });
193
+
194
+ saveToConfigCache<PagesConfigCache>(PAGES_CONFIG_CACHE_FILENAME, {
195
+ account_id: accountId,
196
+ project_name: projectName,
197
+ });
198
+
199
+ logger.log(`✨ Successfully created the '${projectName}' project.`);
200
+ await metrics.sendMetricsEvent("create pages project");
201
+ break;
202
+ }
203
+ }
204
+ }
205
+
206
+ if (!projectName) {
207
+ throw new FatalError("Must specify a project name.", 1);
208
+ }
209
+
210
+ // We infer git info by default is not passed in
211
+
212
+ let isGitDir = true;
213
+ try {
214
+ execSync(`git rev-parse --is-inside-work-tree`, {
215
+ stdio: "ignore",
216
+ });
217
+ } catch (err) {
218
+ isGitDir = false;
219
+ }
220
+
221
+ let isGitDirty = false;
222
+
223
+ if (isGitDir) {
224
+ try {
225
+ isGitDirty = Boolean(
226
+ execSync(`git status --porcelain`).toString().length
227
+ );
228
+
229
+ if (!branch) {
230
+ branch = execSync(`git rev-parse --abbrev-ref HEAD`).toString().trim();
231
+ }
232
+
233
+ if (!commitHash) {
234
+ commitHash = execSync(`git rev-parse HEAD`).toString().trim();
235
+ }
236
+
237
+ if (!commitMessage) {
238
+ commitMessage = execSync(`git show -s --format=%B ${commitHash}`)
239
+ .toString()
240
+ .trim();
241
+ }
242
+ } catch (err) {}
243
+
244
+ if (isGitDirty && !commitDirty) {
245
+ logger.warn(
246
+ `Warning: Your working directory is a git repo and has uncommitted changes\nTo silence this warning, pass in --commit-dirty=true`
247
+ );
248
+ }
249
+
250
+ if (commitDirty === undefined) {
251
+ commitDirty = isGitDirty;
252
+ }
253
+ }
254
+
255
+ let builtFunctions: string | undefined = undefined;
256
+ const functionsDirectory = join(cwd(), "functions");
257
+ const routesOutputPath = join(tmpdir(), `_routes-${Math.random()}.json`);
258
+ if (existsSync(functionsDirectory)) {
259
+ const outfile = join(tmpdir(), `./functionsWorker-${Math.random()}.js`);
260
+ try {
261
+ await buildFunctions({
262
+ outfile,
263
+ functionsDirectory,
264
+ onEnd: () => {},
265
+ buildOutputDirectory: dirname(outfile),
266
+ routesOutputPath,
267
+ });
268
+ builtFunctions = readFileSync(outfile, "utf-8");
269
+ } catch (e) {
270
+ if (e instanceof FunctionsNoRoutesError) {
271
+ logger.warn(
272
+ getFunctionsNoRoutesWarning(functionsDirectory, "skipping")
273
+ );
274
+ } else {
275
+ throw e;
276
+ }
277
+ }
278
+ }
279
+
280
+ const manifest = await upload({ directory, accountId, projectName });
281
+
282
+ const formData = new FormData();
283
+
284
+ formData.append("manifest", JSON.stringify(manifest));
285
+
286
+ if (branch) {
287
+ formData.append("branch", branch);
288
+ }
289
+
290
+ if (commitMessage) {
291
+ formData.append("commit_message", commitMessage);
292
+ }
293
+
294
+ if (commitHash) {
295
+ formData.append("commit_hash", commitHash);
296
+ }
297
+
298
+ if (commitDirty !== undefined) {
299
+ formData.append("commit_dirty", commitDirty);
300
+ }
301
+
302
+ let _headers: string | undefined,
303
+ _redirects: string | undefined,
304
+ _routes: string | undefined,
305
+ _workerJS: string | undefined;
306
+
307
+ try {
308
+ _headers = readFileSync(join(directory, "_headers"), "utf-8");
309
+ } catch {}
310
+
311
+ try {
312
+ _redirects = readFileSync(join(directory, "_redirects"), "utf-8");
313
+ } catch {}
314
+
315
+ try {
316
+ _workerJS = readFileSync(join(directory, "_worker.js"), "utf-8");
317
+ } catch {}
318
+
319
+ if (_headers) {
320
+ formData.append("_headers", new File([_headers], "_headers"));
321
+ logger.log(`✨ Uploading _headers`);
322
+ }
323
+
324
+ if (_redirects) {
325
+ formData.append("_redirects", new File([_redirects], "_redirects"));
326
+ logger.log(`✨ Uploading _redirects`);
327
+ }
328
+
329
+ if (builtFunctions) {
330
+ formData.append("_worker.js", new File([builtFunctions], "_worker.js"));
331
+ logger.log(`✨ Uploading Functions`);
332
+ try {
333
+ _routes = readFileSync(routesOutputPath, "utf-8");
334
+ if (_routes) {
335
+ formData.append("_routes.json", new File([_routes], "_routes.json"));
336
+ }
337
+ } catch {}
338
+ } else if (_workerJS) {
339
+ // Advanced Mode
340
+ // https://developers.cloudflare.com/pages/platform/functions/#advanced-mode
341
+ formData.append("_worker.js", new File([_workerJS], "_worker.js"));
342
+ logger.log(`✨ Uploading _worker.js`);
343
+
344
+ try {
345
+ // In advanced mode, developers can specify a custom _routes.json
346
+ // file. In which case, we need to run it through optimization
347
+ // to potentially reduce the overall worker pipeline size
348
+ const routesPath = join(directory, "_routes.json");
349
+ const advancedModeRoutesString = readFileSync(routesPath, "utf-8");
350
+ const advancedModeRoutes = JSON.parse(advancedModeRoutesString);
351
+
352
+ if (!isRoutesJSONSpec(advancedModeRoutes)) {
353
+ throw new FatalError(
354
+ "Invalid _routes.json file found at:" + routesPath,
355
+ 1
356
+ );
357
+ }
358
+
359
+ _routes = JSON.stringify(optimizeRoutesJSONSpec(advancedModeRoutes));
360
+ formData.append("_routes.json", new File([_routes], "_routes.json"));
361
+ logger.log(`✨ Uploading _routes.json`);
362
+
363
+ logger.warn(
364
+ `🚨 _routes.json is an experimental feature and is subject to change. Don't use unless you really must!`
365
+ );
366
+ } catch (e) {
367
+ // Ignore file not existing errors for _routes.json but forward the potential
368
+ // FatalError from an invalid spec
369
+ if (e instanceof FatalError) {
370
+ throw e;
371
+ }
372
+ }
373
+ }
374
+ const deploymentResponse = await fetchResult<Deployment>(
375
+ `/accounts/${accountId}/pages/projects/${projectName}/deployments`,
376
+ {
377
+ method: "POST",
378
+ body: formData,
379
+ }
380
+ );
381
+
382
+ saveToConfigCache<PagesConfigCache>(PAGES_CONFIG_CACHE_FILENAME, {
383
+ account_id: accountId,
384
+ project_name: projectName,
385
+ });
386
+
387
+ logger.log(
388
+ `✨ Deployment complete! Take a peek over at ${deploymentResponse.url}`
389
+ );
390
+ await metrics.sendMetricsEvent("create pages deployment");
391
+ };
@@ -0,0 +1,49 @@
1
+ import type { ArgumentsCamelCase, Argv } from "yargs";
2
+
3
+ /**
4
+ * Given some Yargs Options function factory, extract the interface
5
+ * that corresponds to the yargs arguments
6
+ */
7
+ export type YargsOptionsToInterface<T extends (yargs: Argv) => Argv> =
8
+ T extends (yargs: Argv) => Argv<infer P> ? ArgumentsCamelCase<P> : never;
9
+
10
+ export type Project = {
11
+ name: string;
12
+ subdomain: string;
13
+ domains: Array<string>;
14
+ source?: {
15
+ type: string;
16
+ };
17
+ latest_deployment?: {
18
+ modified_on: string;
19
+ };
20
+ created_on: string;
21
+ production_branch: string;
22
+ };
23
+ export type Deployment = {
24
+ id: string;
25
+ environment: string;
26
+ deployment_trigger: {
27
+ metadata: {
28
+ commit_hash: string;
29
+ branch: string;
30
+ };
31
+ };
32
+ url: string;
33
+ latest_stage: {
34
+ status: string;
35
+ ended_on: string;
36
+ };
37
+ project_name: string;
38
+ };
39
+ export type UploadPayloadFile = {
40
+ key: string;
41
+ value: string;
42
+ metadata: { contentType: string };
43
+ base64: boolean;
44
+ };
45
+
46
+ export interface PagesConfigCache {
47
+ account_id?: string;
48
+ project_name?: string;
49
+ }