wrangler 0.0.0-e978986 → 0.0.0-e9e9872

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 (186) hide show
  1. package/README.md +7 -1
  2. package/bin/wrangler.js +114 -57
  3. package/kv-asset-handler.js +1 -0
  4. package/miniflare-dist/index.mjs +10 -2
  5. package/package.json +159 -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 +3935 -3363
  9. package/src/__tests__/dev.test.tsx +1128 -949
  10. package/src/__tests__/guess-worker-format.test.ts +68 -68
  11. package/src/__tests__/helpers/cmd-shim.d.ts +6 -6
  12. package/src/__tests__/helpers/mock-account-id.ts +24 -24
  13. package/src/__tests__/helpers/mock-bin.ts +20 -20
  14. package/src/__tests__/helpers/mock-cfetch.ts +92 -92
  15. package/src/__tests__/helpers/mock-console.ts +49 -39
  16. package/src/__tests__/helpers/mock-dialogs.ts +94 -71
  17. package/src/__tests__/helpers/mock-http-server.ts +30 -30
  18. package/src/__tests__/helpers/mock-istty.ts +65 -18
  19. package/src/__tests__/helpers/mock-kv.ts +26 -26
  20. package/src/__tests__/helpers/mock-oauth-flow.ts +223 -228
  21. package/src/__tests__/helpers/mock-process.ts +39 -0
  22. package/src/__tests__/helpers/mock-stdin.ts +82 -77
  23. package/src/__tests__/helpers/mock-web-socket.ts +21 -21
  24. package/src/__tests__/helpers/run-in-tmp.ts +27 -28
  25. package/src/__tests__/helpers/run-wrangler.ts +8 -8
  26. package/src/__tests__/helpers/write-worker-source.ts +16 -16
  27. package/src/__tests__/helpers/write-wrangler-toml.ts +9 -9
  28. package/src/__tests__/https-options.test.ts +142 -109
  29. package/src/__tests__/index.test.ts +239 -233
  30. package/src/__tests__/init.test.ts +1605 -1180
  31. package/src/__tests__/jest.setup.ts +76 -33
  32. package/src/__tests__/kv.test.ts +1160 -991
  33. package/src/__tests__/logger.test.ts +100 -74
  34. package/src/__tests__/package-manager.test.ts +303 -303
  35. package/src/__tests__/pages.test.ts +1160 -563
  36. package/src/__tests__/parse.test.ts +252 -252
  37. package/src/__tests__/publish.test.ts +6426 -5299
  38. package/src/__tests__/pubsub.test.ts +367 -0
  39. package/src/__tests__/r2.test.ts +133 -133
  40. package/src/__tests__/route.test.ts +18 -18
  41. package/src/__tests__/secret.test.ts +470 -459
  42. package/src/__tests__/tail.test.ts +538 -530
  43. package/src/__tests__/user.test.ts +124 -112
  44. package/src/__tests__/whoami.test.tsx +208 -83
  45. package/src/__tests__/worker-namespace.test.ts +327 -0
  46. package/src/abort.d.ts +1 -1
  47. package/src/api/dev.ts +52 -0
  48. package/src/api/index.ts +1 -0
  49. package/src/bundle-reporter.tsx +29 -0
  50. package/src/bundle.ts +158 -146
  51. package/src/cfetch/index.ts +80 -80
  52. package/src/cfetch/internal.ts +92 -88
  53. package/src/cli.ts +21 -7
  54. package/src/config/config.ts +204 -195
  55. package/src/config/diagnostics.ts +61 -61
  56. package/src/config/environment.ts +390 -357
  57. package/src/config/index.ts +206 -193
  58. package/src/config/validation-helpers.ts +366 -357
  59. package/src/config/validation.ts +1573 -1367
  60. package/src/config-cache.ts +79 -41
  61. package/src/create-worker-preview.ts +206 -136
  62. package/src/create-worker-upload-form.ts +247 -238
  63. package/src/dev/dev-vars.ts +13 -13
  64. package/src/dev/dev.tsx +334 -336
  65. package/src/dev/local.tsx +310 -274
  66. package/src/dev/remote.tsx +366 -211
  67. package/src/dev/use-esbuild.ts +126 -94
  68. package/src/dev.tsx +538 -0
  69. package/src/dialogs.tsx +97 -97
  70. package/src/durable.ts +87 -87
  71. package/src/entry.ts +234 -228
  72. package/src/environment-variables.ts +23 -23
  73. package/src/errors.ts +6 -6
  74. package/src/faye-websocket.d.ts +6 -0
  75. package/src/generate.ts +33 -0
  76. package/src/git-client.ts +42 -0
  77. package/src/global-wrangler-config-path.ts +26 -0
  78. package/src/https-options.ts +84 -80
  79. package/src/index.tsx +1800 -2752
  80. package/src/init.ts +549 -0
  81. package/src/inspect.ts +593 -593
  82. package/src/intl-polyfill.d.ts +123 -123
  83. package/src/is-interactive.ts +12 -0
  84. package/src/kv.ts +299 -277
  85. package/src/logger.ts +46 -39
  86. package/src/miniflare-cli/enum-keys.ts +8 -8
  87. package/src/miniflare-cli/index.ts +43 -31
  88. package/src/miniflare-cli/request-context.ts +18 -18
  89. package/src/module-collection.ts +212 -212
  90. package/src/open-in-browser.ts +4 -6
  91. package/src/package-manager.ts +173 -128
  92. package/src/pages/build.tsx +202 -0
  93. package/src/pages/constants.ts +7 -0
  94. package/src/pages/deployments.tsx +101 -0
  95. package/src/pages/dev.tsx +964 -0
  96. package/src/pages/functions/buildPlugin.ts +105 -0
  97. package/src/pages/functions/buildWorker.ts +151 -0
  98. package/{pages → src/pages}/functions/filepath-routing.test.ts +113 -113
  99. package/src/pages/functions/filepath-routing.ts +189 -0
  100. package/src/pages/functions/identifiers.ts +78 -0
  101. package/src/pages/functions/routes.ts +151 -0
  102. package/src/pages/index.tsx +84 -0
  103. package/src/pages/projects.tsx +157 -0
  104. package/src/pages/publish.tsx +335 -0
  105. package/src/pages/types.ts +40 -0
  106. package/src/pages/upload.tsx +396 -0
  107. package/src/pages/utils.ts +12 -0
  108. package/src/parse.ts +209 -125
  109. package/src/paths.ts +6 -6
  110. package/src/preview.ts +31 -0
  111. package/src/proxy.ts +400 -389
  112. package/src/publish.ts +702 -505
  113. package/src/pubsub/index.ts +286 -0
  114. package/src/pubsub/pubsub-commands.tsx +577 -0
  115. package/src/r2.ts +19 -19
  116. package/src/selfsigned.d.ts +23 -23
  117. package/src/sites.tsx +278 -201
  118. package/src/tail/filters.ts +108 -108
  119. package/src/tail/index.ts +217 -217
  120. package/src/tail/printing.ts +45 -45
  121. package/src/update-check.ts +11 -11
  122. package/src/user/choose-account.tsx +60 -0
  123. package/src/user/env-vars.ts +46 -0
  124. package/src/user/generate-auth-url.ts +33 -0
  125. package/src/user/generate-random-state.ts +16 -0
  126. package/src/user/index.ts +3 -0
  127. package/src/user/user.tsx +1170 -0
  128. package/src/whoami.tsx +61 -41
  129. package/src/worker-namespace.ts +190 -0
  130. package/src/worker.ts +110 -99
  131. package/src/zones.ts +76 -0
  132. package/templates/checked-fetch.js +17 -0
  133. package/templates/new-worker-scheduled.js +4 -4
  134. package/templates/new-worker-scheduled.ts +16 -16
  135. package/templates/new-worker.js +3 -3
  136. package/templates/new-worker.ts +15 -15
  137. package/templates/no-op-worker.js +10 -0
  138. package/templates/pages-template-plugin.ts +155 -0
  139. package/templates/pages-template-worker.ts +161 -0
  140. package/templates/static-asset-facade.js +38 -36
  141. package/templates/tsconfig.json +95 -95
  142. package/wrangler-dist/cli.d.ts +298 -0
  143. package/wrangler-dist/cli.js +33496 -31370
  144. package/pages/functions/buildPlugin.ts +0 -105
  145. package/pages/functions/buildWorker.ts +0 -151
  146. package/pages/functions/filepath-routing.ts +0 -189
  147. package/pages/functions/identifiers.ts +0 -78
  148. package/pages/functions/routes.ts +0 -156
  149. package/pages/functions/template-plugin.ts +0 -147
  150. package/pages/functions/template-worker.ts +0 -143
  151. package/src/__tests__/helpers/faye-websocket.d.ts +0 -6
  152. package/src/pages.tsx +0 -2027
  153. package/src/user.tsx +0 -1203
  154. package/vendor/@cloudflare/kv-asset-handler/CHANGELOG.md +0 -332
  155. package/vendor/@cloudflare/kv-asset-handler/LICENSE_APACHE +0 -176
  156. package/vendor/@cloudflare/kv-asset-handler/LICENSE_MIT +0 -25
  157. package/vendor/@cloudflare/kv-asset-handler/README.md +0 -245
  158. package/vendor/@cloudflare/kv-asset-handler/dist/index.d.ts +0 -32
  159. package/vendor/@cloudflare/kv-asset-handler/dist/index.js +0 -354
  160. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.d.ts +0 -13
  161. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.js +0 -148
  162. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.d.ts +0 -1
  163. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.js +0 -436
  164. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.d.ts +0 -1
  165. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.js +0 -40
  166. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.d.ts +0 -1
  167. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.js +0 -42
  168. package/vendor/@cloudflare/kv-asset-handler/dist/types.d.ts +0 -26
  169. package/vendor/@cloudflare/kv-asset-handler/dist/types.js +0 -31
  170. package/vendor/@cloudflare/kv-asset-handler/package.json +0 -52
  171. package/vendor/@cloudflare/kv-asset-handler/src/index.ts +0 -296
  172. package/vendor/@cloudflare/kv-asset-handler/src/mocks.ts +0 -136
  173. package/vendor/@cloudflare/kv-asset-handler/src/test/getAssetFromKV.ts +0 -464
  174. package/vendor/@cloudflare/kv-asset-handler/src/test/mapRequestToAsset.ts +0 -33
  175. package/vendor/@cloudflare/kv-asset-handler/src/test/serveSinglePageApp.ts +0 -42
  176. package/vendor/@cloudflare/kv-asset-handler/src/types.ts +0 -39
  177. package/vendor/wrangler-mime/CHANGELOG.md +0 -289
  178. package/vendor/wrangler-mime/LICENSE +0 -21
  179. package/vendor/wrangler-mime/Mime.js +0 -97
  180. package/vendor/wrangler-mime/README.md +0 -187
  181. package/vendor/wrangler-mime/cli.js +0 -46
  182. package/vendor/wrangler-mime/index.js +0 -4
  183. package/vendor/wrangler-mime/lite.js +0 -4
  184. package/vendor/wrangler-mime/package.json +0 -52
  185. package/vendor/wrangler-mime/types/other.js +0 -1
  186. package/vendor/wrangler-mime/types/standard.js +0 -1
@@ -0,0 +1,189 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { build } from "esbuild";
4
+ import { toUrlPath } from "../../paths";
5
+ import type { UrlPath } from "../../paths";
6
+ import type { HTTPMethod, RouteConfig } from "./routes";
7
+
8
+ export async function generateConfigFromFileTree({
9
+ baseDir,
10
+ baseURL,
11
+ }: {
12
+ baseDir: string;
13
+ baseURL: UrlPath;
14
+ }) {
15
+ let routeEntries: RouteConfig[] = [];
16
+
17
+ if (!baseURL.startsWith("/")) {
18
+ baseURL = `/${baseURL}` as UrlPath;
19
+ }
20
+
21
+ if (baseURL.endsWith("/")) {
22
+ baseURL = baseURL.slice(0, -1) as UrlPath;
23
+ }
24
+
25
+ await forEachFile(baseDir, async (filepath) => {
26
+ const ext = path.extname(filepath);
27
+ if (/^\.(mjs|js|ts|tsx|jsx)$/.test(ext)) {
28
+ const { metafile } = await build({
29
+ metafile: true,
30
+ write: false,
31
+ bundle: false,
32
+ entryPoints: [path.resolve(filepath)],
33
+ });
34
+ const exportNames: string[] = [];
35
+ if (metafile) {
36
+ for (const output in metafile?.outputs) {
37
+ exportNames.push(...metafile.outputs[output].exports);
38
+ }
39
+ }
40
+ for (const exportName of exportNames) {
41
+ const [match, method = ""] = (exportName.match(
42
+ /^onRequest(Get|Post|Put|Patch|Delete|Options|Head)?$/
43
+ ) ?? []) as (string | undefined)[];
44
+
45
+ if (match) {
46
+ const basename = path.basename(filepath).slice(0, -ext.length);
47
+
48
+ const isIndexFile = basename === "index";
49
+ // TODO: deprecate _middleware_ in favor of _middleware
50
+ const isMiddlewareFile =
51
+ basename === "_middleware" || basename === "_middleware_";
52
+
53
+ let routePath = path
54
+ .relative(baseDir, filepath)
55
+ .slice(0, -ext.length);
56
+ let mountPath = path.dirname(routePath);
57
+
58
+ if (isIndexFile || isMiddlewareFile) {
59
+ routePath = path.dirname(routePath);
60
+ }
61
+
62
+ if (routePath === ".") {
63
+ routePath = "";
64
+ }
65
+ if (mountPath === ".") {
66
+ mountPath = "";
67
+ }
68
+
69
+ routePath = `${baseURL}/${routePath}`;
70
+ mountPath = `${baseURL}/${mountPath}`;
71
+
72
+ routePath = routePath.replace(/\[\[([^\]]+)\]\]/g, ":$1*"); // transform [[id]] => :id*
73
+ routePath = routePath.replaceAll(/\[([^\]]+)\]/g, ":$1"); // transform [id] => :id
74
+ mountPath = mountPath.replace(/\[\[([^\]]+)\]\]/g, ":$1*"); // transform [[id]] => :id*
75
+ mountPath = mountPath.replaceAll(/\[([^\]]+)\]/g, ":$1"); // transform [id] => :id
76
+
77
+ // These are used as module specifiers so UrlPaths are okay to use even on Windows
78
+ const modulePath = toUrlPath(path.relative(baseDir, filepath));
79
+
80
+ const routeEntry: RouteConfig = {
81
+ routePath: toUrlPath(routePath),
82
+ mountPath: toUrlPath(mountPath),
83
+ method: method.toUpperCase() as HTTPMethod,
84
+ [isMiddlewareFile ? "middleware" : "module"]: [
85
+ `${modulePath}:${exportName}`,
86
+ ],
87
+ };
88
+
89
+ routeEntries.push(routeEntry);
90
+ }
91
+ }
92
+ }
93
+ });
94
+ // Combine together any routes (index routes) which contain both a module and a middleware
95
+ routeEntries = routeEntries.reduce(
96
+ (acc: typeof routeEntries, { routePath, ...rest }) => {
97
+ const existingRouteEntry = acc.find(
98
+ (routeEntry) =>
99
+ routeEntry.routePath === routePath &&
100
+ routeEntry.method === rest.method
101
+ );
102
+ if (existingRouteEntry !== undefined) {
103
+ Object.assign(existingRouteEntry, rest);
104
+ } else {
105
+ acc.push({ routePath, ...rest });
106
+ }
107
+ return acc;
108
+ },
109
+ []
110
+ );
111
+
112
+ routeEntries.sort((a, b) => compareRoutes(a, b));
113
+
114
+ return {
115
+ routes: routeEntries,
116
+ };
117
+ }
118
+
119
+ // Ensure routes are produced in order of precedence so that
120
+ // more specific routes aren't occluded from matching due to
121
+ // less specific routes appearing first in the route list.
122
+ export function compareRoutes(
123
+ { routePath: routePathA, method: methodA }: RouteConfig,
124
+ { routePath: routePathB, method: methodB }: RouteConfig
125
+ ) {
126
+ function parseRoutePath(routePath: UrlPath): string[] {
127
+ return routePath.slice(1).split("/").filter(Boolean);
128
+ }
129
+
130
+ const segmentsA = parseRoutePath(routePathA);
131
+ const segmentsB = parseRoutePath(routePathB);
132
+
133
+ // sort routes with fewer segments after those with more segments
134
+ if (segmentsA.length !== segmentsB.length) {
135
+ return segmentsB.length - segmentsA.length;
136
+ }
137
+
138
+ for (let i = 0; i < segmentsA.length; i++) {
139
+ const isWildcardA = segmentsA[i].includes("*");
140
+ const isWildcardB = segmentsB[i].includes("*");
141
+ const isParamA = segmentsA[i].includes(":");
142
+ const isParamB = segmentsB[i].includes(":");
143
+
144
+ // sort wildcard segments after non-wildcard segments
145
+ if (isWildcardA && !isWildcardB) return 1;
146
+ if (!isWildcardA && isWildcardB) return -1;
147
+
148
+ // sort dynamic param segments after non-param segments
149
+ if (isParamA && !isParamB) return 1;
150
+ if (!isParamA && isParamB) return -1;
151
+ }
152
+
153
+ // sort routes that specify an HTTP before those that don't
154
+ if (methodA && !methodB) return -1;
155
+ if (!methodA && methodB) return 1;
156
+
157
+ // all else equal, just sort the paths lexicographically
158
+ return routePathA.localeCompare(routePathB);
159
+ }
160
+
161
+ async function forEachFile<T>(
162
+ baseDir: string,
163
+ fn: (filepath: string) => T | Promise<T>
164
+ ) {
165
+ const searchPaths = [baseDir];
166
+ const returnValues: T[] = [];
167
+
168
+ while (isNotEmpty(searchPaths)) {
169
+ const cwd = searchPaths.shift();
170
+ const dir = await fs.readdir(cwd, { withFileTypes: true });
171
+ for (const entry of dir) {
172
+ const pathname = path.join(cwd, entry.name);
173
+ if (entry.isDirectory()) {
174
+ searchPaths.push(pathname);
175
+ } else if (entry.isFile()) {
176
+ returnValues.push(await fn(pathname));
177
+ }
178
+ }
179
+ }
180
+
181
+ return returnValues;
182
+ }
183
+
184
+ interface NonEmptyArray<T> extends Array<T> {
185
+ shift(): T;
186
+ }
187
+ function isNotEmpty<T>(array: T[]): array is NonEmptyArray<T> {
188
+ return array.length > 0;
189
+ }
@@ -0,0 +1,78 @@
1
+ export const RESERVED_KEYWORDS = [
2
+ "do",
3
+ "if",
4
+ "in",
5
+ "for",
6
+ "let",
7
+ "new",
8
+ "try",
9
+ "var",
10
+ "case",
11
+ "else",
12
+ "enum",
13
+ "eval",
14
+ "null",
15
+ "this",
16
+ "true",
17
+ "void",
18
+ "with",
19
+ "await",
20
+ "break",
21
+ "catch",
22
+ "class",
23
+ "const",
24
+ "false",
25
+ "super",
26
+ "throw",
27
+ "while",
28
+ "yield",
29
+ "delete",
30
+ "export",
31
+ "import",
32
+ "public",
33
+ "return",
34
+ "static",
35
+ "switch",
36
+ "typeof",
37
+ "default",
38
+ "extends",
39
+ "finally",
40
+ "package",
41
+ "private",
42
+ "continue",
43
+ "debugger",
44
+ "function",
45
+ "arguments",
46
+ "interface",
47
+ "protected",
48
+ "implements",
49
+ "instanceof",
50
+ "NaN",
51
+ "Infinity",
52
+ "undefined",
53
+ ];
54
+
55
+ export const reservedKeywordRegex = new RegExp(
56
+ `^${RESERVED_KEYWORDS.join("|")}$`
57
+ );
58
+
59
+ export const identifierStartRegex = /[$_\p{ID_Start}]/u;
60
+
61
+ export const identifierPartRegex = /[$_\u200C\u200D\p{ID_Continue}]/u;
62
+
63
+ export const identifierNameRegex =
64
+ /^(?:[$_\p{ID_Start}])(?:[$_\u200C\u200D\p{ID_Continue}])*$/u;
65
+
66
+ export const validIdentifierRegex = new RegExp(
67
+ `(?!(${reservedKeywordRegex.source})$)${identifierNameRegex.source}`,
68
+ "u"
69
+ );
70
+
71
+ export const isValidIdentifier = (identifier: string) =>
72
+ validIdentifierRegex.test(identifier);
73
+
74
+ export const normalizeIdentifier = (identifier: string) =>
75
+ identifier.replace(
76
+ /(?:^[^$_\p{ID_Start}])|[^$_\u200C\u200D\p{ID_Continue}]/gu,
77
+ "_"
78
+ );
@@ -0,0 +1,151 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { isValidIdentifier, normalizeIdentifier } from "./identifiers";
4
+ import type { UrlPath } from "../../paths";
5
+
6
+ export const HTTP_METHODS = [
7
+ "HEAD",
8
+ "OPTIONS",
9
+ "GET",
10
+ "POST",
11
+ "PUT",
12
+ "PATCH",
13
+ "DELETE",
14
+ ] as const;
15
+ export type HTTPMethod = typeof HTTP_METHODS[number];
16
+
17
+ export type RoutesCollection = Array<{
18
+ routePath: UrlPath;
19
+ mountPath: UrlPath;
20
+ method?: HTTPMethod;
21
+ modules: string[];
22
+ middlewares: string[];
23
+ }>;
24
+
25
+ export type Config = {
26
+ routes?: RouteConfig[];
27
+ schedules?: unknown;
28
+ };
29
+
30
+ export type RouteConfig = {
31
+ routePath: UrlPath;
32
+ mountPath: UrlPath;
33
+ method?: HTTPMethod;
34
+ middleware?: string | string[];
35
+ module?: string | string[];
36
+ };
37
+
38
+ type ImportMap = Map<
39
+ string,
40
+ {
41
+ filepath: string;
42
+ name: string;
43
+ identifier: string;
44
+ }
45
+ >;
46
+
47
+ type Arguments = {
48
+ config: Config;
49
+ outfile: string;
50
+ srcDir: string;
51
+ };
52
+
53
+ export async function writeRoutesModule({
54
+ config,
55
+ srcDir,
56
+ outfile = "_routes.js",
57
+ }: Arguments) {
58
+ const { importMap, routes } = parseConfig(config, srcDir);
59
+ const routesModule = generateRoutesModule(importMap, routes);
60
+
61
+ await fs.writeFile(outfile, routesModule);
62
+
63
+ return outfile;
64
+ }
65
+
66
+ export function parseConfig(config: Config, baseDir: string) {
67
+ const routes: RoutesCollection = [];
68
+ const importMap: ImportMap = new Map();
69
+ const identifierCount = new Map<string, number>(); // to keep track of identifier collisions
70
+
71
+ function parseModuleIdentifiers(paths: string | string[] | undefined) {
72
+ if (typeof paths === "undefined") {
73
+ paths = [];
74
+ }
75
+
76
+ if (typeof paths === "string") {
77
+ paths = [paths];
78
+ }
79
+
80
+ return paths.map((modulePath) => {
81
+ const [filepath, name = "default"] = modulePath.split(":");
82
+ let { identifier } = importMap.get(modulePath) ?? {};
83
+
84
+ const resolvedPath = path.resolve(baseDir, filepath);
85
+
86
+ // ensure the filepath isn't attempting to resolve to anything outside of the project
87
+ if (path.relative(baseDir, resolvedPath).startsWith("..")) {
88
+ throw new Error(`Invalid module path "${filepath}"`);
89
+ }
90
+
91
+ // ensure the module name (if provided) is a valid identifier to guard against injection attacks
92
+ if (name !== "default" && !isValidIdentifier(name)) {
93
+ throw new Error(`Invalid module identifier "${name}"`);
94
+ }
95
+
96
+ if (!identifier) {
97
+ identifier = normalizeIdentifier(`__${filepath}_${name}`);
98
+
99
+ let count = identifierCount.get(identifier) ?? 0;
100
+ identifierCount.set(identifier, ++count);
101
+
102
+ if (count > 1) {
103
+ identifier += `_${count}`;
104
+ }
105
+
106
+ importMap.set(modulePath, { filepath: resolvedPath, name, identifier });
107
+ }
108
+
109
+ return identifier;
110
+ });
111
+ }
112
+
113
+ for (const { routePath, mountPath, method, ...props } of config.routes ??
114
+ []) {
115
+ routes.push({
116
+ routePath,
117
+ mountPath,
118
+ method,
119
+ middlewares: parseModuleIdentifiers(props.middleware),
120
+ modules: parseModuleIdentifiers(props.module),
121
+ });
122
+ }
123
+
124
+ return { routes, importMap };
125
+ }
126
+
127
+ export function generateRoutesModule(
128
+ importMap: ImportMap,
129
+ routes: RoutesCollection
130
+ ) {
131
+ return `${[...importMap.values()]
132
+ .map(
133
+ ({ filepath, name, identifier }) =>
134
+ `import { ${name} as ${identifier} } from ${JSON.stringify(filepath)}`
135
+ )
136
+ .join("\n")}
137
+
138
+ export const routes = [
139
+ ${routes
140
+ .map(
141
+ (route) => ` {
142
+ routePath: "${route.routePath}",
143
+ mountPath: "${route.mountPath}",
144
+ method: "${route.method}",
145
+ middlewares: [${route.middlewares.join(", ")}],
146
+ modules: [${route.modules.join(", ")}],
147
+ },`
148
+ )
149
+ .join("\n")}
150
+ ]`;
151
+ }
@@ -0,0 +1,84 @@
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 Projects from "./projects";
7
+ import * as Publish from "./publish";
8
+ import * as Upload from "./upload";
9
+ import { CLEANUP, pagesBetaWarning } from "./utils";
10
+ import type { BuilderCallback } from "yargs";
11
+
12
+ process.on("SIGINT", () => {
13
+ CLEANUP();
14
+ process.exit();
15
+ });
16
+ process.on("SIGTERM", () => {
17
+ CLEANUP();
18
+ process.exit();
19
+ });
20
+
21
+ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
22
+ return yargs
23
+ .command(
24
+ "dev [directory] [-- command..]",
25
+ "🧑‍💻 Develop your full-stack Pages application locally",
26
+ Dev.Options,
27
+ Dev.Handler
28
+ )
29
+ .command("functions", false, (yargs) =>
30
+ // we hide this command from help output because
31
+ // it's not meant to be used directly right now
32
+ {
33
+ return yargs.command(
34
+ "build [directory]",
35
+ "Compile a folder of Cloudflare Pages Functions into a single Worker",
36
+ Build.Options,
37
+ Build.Handler
38
+ );
39
+ }
40
+ )
41
+ .command("project", "⚡️ Interact with your Pages projects", (yargs) =>
42
+ yargs
43
+ .command(
44
+ "list",
45
+ "List your Cloudflare Pages projects",
46
+ Projects.ListOptions,
47
+ Projects.ListHandler
48
+ )
49
+ .command(
50
+ "create [project-name]",
51
+ "Create a new Cloudflare Pages project",
52
+ Projects.CreateOptions,
53
+ Projects.CreateHandler
54
+ )
55
+ .command("upload [directory]", false, Upload.Options, Upload.Handler)
56
+ .epilogue(pagesBetaWarning)
57
+ )
58
+ .command(
59
+ "deployment",
60
+ "🚀 Interact with the deployments of a project",
61
+ (yargs) =>
62
+ yargs
63
+ .command(
64
+ "list",
65
+ "List deployments in your Cloudflare Pages project",
66
+ Deployments.ListOptions,
67
+ Deployments.ListHandler
68
+ )
69
+ .command(
70
+ "create [directory]",
71
+ "🆙 Publish a directory of static assets as a Pages deployment",
72
+ Publish.Options,
73
+ Publish.Handler
74
+ )
75
+ .epilogue(pagesBetaWarning)
76
+ )
77
+ .command(
78
+ "publish [directory]",
79
+ "🆙 Publish a directory of static assets as a Pages deployment",
80
+ Publish.Options,
81
+ Publish.Handler
82
+ )
83
+ .epilogue(pagesBetaWarning);
84
+ };
@@ -0,0 +1,157 @@
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 { requireAuth } from "../user";
12
+ import { PAGES_CONFIG_CACHE_FILENAME } from "./constants";
13
+ import { pagesBetaWarning } from "./utils";
14
+ import type { PagesConfigCache, Project } from "./types";
15
+ import type { ArgumentsCamelCase, Argv } from "yargs";
16
+
17
+ // Empty object, as per eslint
18
+ type ListArgs = Record<string, never>;
19
+
20
+ export function ListOptions(yargs: Argv): Argv<ListArgs> {
21
+ return yargs.epilogue(pagesBetaWarning);
22
+ }
23
+
24
+ export async function ListHandler() {
25
+ const config = getConfigCache<PagesConfigCache>(PAGES_CONFIG_CACHE_FILENAME);
26
+
27
+ const accountId = await requireAuth(config);
28
+
29
+ const projects: Array<Project> = await listProjects({ accountId });
30
+
31
+ const data = projects.map((project) => {
32
+ return {
33
+ "Project Name": project.name,
34
+ "Project Domains": `${project.domains.join(", ")}`,
35
+ "Git Provider": project.source ? "Yes" : "No",
36
+ "Last Modified": project.latest_deployment
37
+ ? timeagoFormat(project.latest_deployment.modified_on)
38
+ : timeagoFormat(project.created_on),
39
+ };
40
+ });
41
+
42
+ saveToConfigCache<PagesConfigCache>(PAGES_CONFIG_CACHE_FILENAME, {
43
+ account_id: accountId,
44
+ });
45
+
46
+ render(<Table data={data}></Table>, { patchConsole: false });
47
+ }
48
+
49
+ export const listProjects = async ({
50
+ accountId,
51
+ }: {
52
+ accountId: string;
53
+ }): Promise<Array<Project>> => {
54
+ const pageSize = 10;
55
+ let page = 1;
56
+ const results = [];
57
+ while (results.length % pageSize === 0) {
58
+ const json: Array<Project> = await fetchResult(
59
+ `/accounts/${accountId}/pages/projects`,
60
+ {},
61
+ new URLSearchParams({
62
+ per_page: pageSize.toString(),
63
+ page: page.toString(),
64
+ })
65
+ );
66
+ page++;
67
+ results.push(...json);
68
+ if (json.length < pageSize) {
69
+ break;
70
+ }
71
+ }
72
+ return results;
73
+ };
74
+
75
+ type CreateArgs = {
76
+ "project-name": string;
77
+ "production-branch"?: string;
78
+ };
79
+
80
+ export function CreateOptions(yargs: Argv): Argv<CreateArgs> {
81
+ return yargs
82
+ .positional("project-name", {
83
+ type: "string",
84
+ demandOption: true,
85
+ description: "The name of your Pages project",
86
+ })
87
+ .options({
88
+ "production-branch": {
89
+ type: "string",
90
+ description: "The name of the production branch of your project",
91
+ },
92
+ })
93
+ .epilogue(pagesBetaWarning);
94
+ }
95
+
96
+ export async function CreateHandler({
97
+ productionBranch,
98
+ projectName,
99
+ }: ArgumentsCamelCase<CreateArgs>) {
100
+ const config = getConfigCache<PagesConfigCache>(PAGES_CONFIG_CACHE_FILENAME);
101
+ const accountId = await requireAuth(config);
102
+
103
+ const isInteractive = process.stdin.isTTY;
104
+ if (!projectName && isInteractive) {
105
+ projectName = await prompt("Enter the name of your new project:");
106
+ }
107
+
108
+ if (!projectName) {
109
+ throw new FatalError("Must specify a project name.", 1);
110
+ }
111
+
112
+ if (!productionBranch && isInteractive) {
113
+ let isGitDir = true;
114
+ try {
115
+ execSync(`git rev-parse --is-inside-work-tree`, {
116
+ stdio: "ignore",
117
+ });
118
+ } catch (err) {
119
+ isGitDir = false;
120
+ }
121
+
122
+ productionBranch = await prompt(
123
+ "Enter the production branch name:",
124
+ "text",
125
+ isGitDir
126
+ ? execSync(`git rev-parse --abbrev-ref HEAD`).toString().trim()
127
+ : "production"
128
+ );
129
+ }
130
+
131
+ if (!productionBranch) {
132
+ throw new FatalError("Must specify a production branch.", 1);
133
+ }
134
+
135
+ const { subdomain } = await fetchResult<Project>(
136
+ `/accounts/${accountId}/pages/projects`,
137
+ {
138
+ method: "POST",
139
+ body: JSON.stringify({
140
+ name: projectName,
141
+ production_branch: productionBranch,
142
+ }),
143
+ }
144
+ );
145
+
146
+ saveToConfigCache<PagesConfigCache>(PAGES_CONFIG_CACHE_FILENAME, {
147
+ account_id: accountId,
148
+ project_name: projectName,
149
+ });
150
+
151
+ logger.log(
152
+ `✨ Successfully created the '${projectName}' project. It will be available at https://${subdomain}/ once you create your first deployment.`
153
+ );
154
+ logger.log(
155
+ `To deploy a folder of assets, run 'wrangler pages publish [directory]'.`
156
+ );
157
+ }