wrangler 0.0.0-e29a241 → 0.0.0-e2f9bb2

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 (88) hide show
  1. package/README.md +25 -14
  2. package/bin/wrangler.js +13 -1
  3. package/miniflare-dist/index.mjs +4828 -21
  4. package/package.json +29 -24
  5. package/pages/functions/buildPlugin.ts +92 -0
  6. package/pages/functions/buildWorker.ts +80 -4
  7. package/pages/functions/filepath-routing.test.ts +11 -0
  8. package/pages/functions/filepath-routing.ts +8 -0
  9. package/pages/functions/routes.ts +6 -1
  10. package/pages/functions/template-plugin.ts +146 -0
  11. package/pages/functions/template-worker.ts +21 -8
  12. package/src/__tests__/config-cache.test.ts +36 -0
  13. package/src/__tests__/configuration.test.ts +692 -321
  14. package/src/__tests__/dev.test.tsx +284 -18
  15. package/src/__tests__/guess-worker-format.test.ts +28 -1
  16. package/src/__tests__/helpers/mock-cfetch.ts +5 -1
  17. package/src/__tests__/helpers/mock-console.ts +20 -3
  18. package/src/__tests__/helpers/mock-dialogs.ts +24 -0
  19. package/src/__tests__/helpers/mock-http-server.ts +46 -0
  20. package/src/__tests__/helpers/mock-istty.ts +27 -0
  21. package/src/__tests__/helpers/mock-oauth-flow.ts +255 -0
  22. package/src/__tests__/helpers/run-in-tmp.ts +7 -0
  23. package/src/__tests__/helpers/write-worker-source.ts +31 -0
  24. package/src/__tests__/https-options.test.ts +5 -2
  25. package/src/__tests__/index.test.ts +27 -769
  26. package/src/__tests__/init.test.ts +1042 -0
  27. package/src/__tests__/jest.setup.ts +4 -4
  28. package/src/__tests__/kv.test.ts +133 -61
  29. package/src/__tests__/logger.test.ts +115 -0
  30. package/src/__tests__/pages.test.ts +311 -26
  31. package/src/__tests__/publish.test.ts +2789 -618
  32. package/src/__tests__/r2.test.ts +26 -10
  33. package/src/__tests__/route.test.ts +4 -4
  34. package/src/__tests__/secret.test.ts +55 -34
  35. package/src/__tests__/tail.test.ts +146 -20
  36. package/src/__tests__/user.test.ts +131 -0
  37. package/src/__tests__/whoami.test.tsx +14 -9
  38. package/src/bundle.ts +30 -3
  39. package/src/cfetch/internal.ts +0 -2
  40. package/src/cli.ts +5 -11
  41. package/src/config/README.md +1 -1
  42. package/src/config/config.ts +13 -53
  43. package/src/config/environment.ts +88 -4
  44. package/src/config/index.ts +11 -4
  45. package/src/config/validation-helpers.ts +96 -7
  46. package/src/config/validation.ts +407 -79
  47. package/src/config-cache.ts +45 -0
  48. package/src/create-worker-preview.ts +22 -13
  49. package/src/create-worker-upload-form.ts +32 -9
  50. package/src/dev/dev-vars.ts +36 -0
  51. package/src/dev/dev.tsx +132 -78
  52. package/src/dev/local.tsx +115 -29
  53. package/src/dev/remote.tsx +23 -13
  54. package/src/dev/use-esbuild.ts +23 -5
  55. package/src/dialogs.tsx +16 -6
  56. package/src/entry.ts +83 -24
  57. package/src/environment-variables.ts +3 -1
  58. package/src/errors.ts +11 -0
  59. package/src/https-options.ts +3 -2
  60. package/src/index.tsx +619 -377
  61. package/src/inspect.ts +17 -11
  62. package/src/intl-polyfill.d.ts +139 -0
  63. package/src/logger.ts +66 -0
  64. package/src/miniflare-cli/index.ts +4 -0
  65. package/src/miniflare-cli/request-context.ts +40 -0
  66. package/src/module-collection.ts +7 -7
  67. package/src/open-in-browser.ts +5 -4
  68. package/src/package-manager.ts +9 -8
  69. package/src/pages.tsx +883 -62
  70. package/src/parse.ts +1 -1
  71. package/src/proxy.ts +25 -11
  72. package/src/publish.ts +263 -141
  73. package/src/sites.tsx +27 -14
  74. package/src/tail/index.ts +2 -2
  75. package/src/tail/printing.ts +13 -8
  76. package/src/update-check.ts +19 -0
  77. package/src/user.tsx +116 -41
  78. package/src/whoami.tsx +2 -1
  79. package/src/worker.ts +15 -6
  80. package/templates/gitignore +171 -0
  81. package/templates/static-asset-facade.js +1 -1
  82. package/wrangler-dist/cli.js +21167 -50229
  83. package/miniflare-dist/index.mjs.map +0 -7
  84. package/src/__tests__/dev2.test.tsx +0 -154
  85. package/src/__tests__/logout.test.ts +0 -52
  86. package/src/__tests__/sentry.test.ts +0 -146
  87. package/src/reporting.ts +0 -131
  88. package/wrangler-dist/cli.js.map +0 -7
package/src/dev/local.tsx CHANGED
@@ -5,6 +5,7 @@ import path from "node:path";
5
5
  import { useState, useEffect, useRef } from "react";
6
6
  import onExit from "signal-exit";
7
7
  import useInspector from "../inspect";
8
+ import { logger } from "../logger";
8
9
  import { DEFAULT_MODULE_RULES } from "../module-collection";
9
10
  import { waitForPortToBeAvailable } from "../proxy";
10
11
  import type { Config } from "../config";
@@ -14,19 +15,20 @@ import type { EsbuildBundle } from "./use-esbuild";
14
15
  import type { MiniflareOptions } from "miniflare";
15
16
 
16
17
  interface LocalProps {
17
- name: undefined | string;
18
+ name: string | undefined;
18
19
  bundle: EsbuildBundle | undefined;
19
20
  format: CfScriptFormat | undefined;
20
- compatibilityDate: string | undefined;
21
- compatibilityFlags: undefined | string[];
21
+ compatibilityDate: string;
22
+ compatibilityFlags: string[] | undefined;
22
23
  bindings: CfWorkerInit["bindings"];
23
- assetPaths: undefined | AssetPaths;
24
- public: undefined | string;
24
+ assetPaths: AssetPaths | undefined;
25
+ public: string | undefined;
25
26
  port: number;
26
27
  ip: string;
27
28
  rules: Config["rules"];
28
29
  inspectorPort: number;
29
30
  enableLocalPersistence: boolean;
31
+ crons: Config["triggers"]["crons"];
30
32
  }
31
33
 
32
34
  export function Local(props: LocalProps) {
@@ -52,17 +54,33 @@ function useLocalWorker({
52
54
  rules,
53
55
  enableLocalPersistence,
54
56
  ip,
57
+ crons,
55
58
  }: LocalProps) {
56
59
  // TODO: pass vars via command line
57
60
  const local = useRef<ReturnType<typeof spawn>>();
58
61
  const removeSignalExitListener = useRef<() => void>();
59
62
  const [inspectorUrl, setInspectorUrl] = useState<string | undefined>();
63
+ // if we're using local persistence for data, we should use the cwd
64
+ // as an explicit path, or else it'll use the temp dir
65
+ // which disappears when dev ends
66
+ const localPersistencePath = enableLocalPersistence
67
+ ? // Maybe we could make the path configurable as well?
68
+ path.join(process.cwd(), "wrangler-local-state")
69
+ : // We otherwise choose null, but choose true later
70
+ // so that it's persisted in the temp dir across a dev session
71
+ // even when we change source and reload
72
+ null;
60
73
  useEffect(() => {
74
+ const abortController = new AbortController();
61
75
  async function startLocalWorker() {
62
76
  if (!bundle || !format) return;
63
77
 
64
78
  // port for the worker
65
- await waitForPortToBeAvailable(port, { retryPeriod: 200, timeout: 2000 });
79
+ await waitForPortToBeAvailable(port, {
80
+ retryPeriod: 200,
81
+ timeout: 2000,
82
+ abortSignal: abortController.signal,
83
+ });
66
84
 
67
85
  if (publicDirectory) {
68
86
  throw new Error(
@@ -81,7 +99,35 @@ function useLocalWorker({
81
99
 
82
100
  const scriptPath = realpathSync(bundle.path);
83
101
 
84
- const wasmBindings = { ...bindings.wasm_modules };
102
+ // the wasm_modules/text_blobs/data_blobs bindings are
103
+ // relative to process.cwd(), but the actual worker bundle
104
+ // is in the temp output directory; so we rewrite the paths to be absolute,
105
+ // letting miniflare resolve them correctly
106
+
107
+ // wasm
108
+ const wasmBindings: Record<string, string> = {};
109
+ for (const [name, filePath] of Object.entries(
110
+ bindings.wasm_modules || {}
111
+ )) {
112
+ wasmBindings[name] = path.join(process.cwd(), filePath);
113
+ }
114
+
115
+ // text
116
+ const textBlobBindings: Record<string, string> = {};
117
+ for (const [name, filePath] of Object.entries(
118
+ bindings.text_blobs || {}
119
+ )) {
120
+ textBlobBindings[name] = path.join(process.cwd(), filePath);
121
+ }
122
+
123
+ // data
124
+ const dataBlobBindings: Record<string, string> = {};
125
+ for (const [name, filePath] of Object.entries(
126
+ bindings.data_blobs || {}
127
+ )) {
128
+ dataBlobBindings[name] = path.join(process.cwd(), filePath);
129
+ }
130
+
85
131
  if (format === "service-worker") {
86
132
  for (const { type, name } of bundle.modules) {
87
133
  if (type === "compiled-wasm") {
@@ -91,6 +137,20 @@ function useLocalWorker({
91
137
  // characters with an underscore.
92
138
  const identifier = name.replace(/[^a-zA-Z0-9_$]/g, "_");
93
139
  wasmBindings[identifier] = name;
140
+ } else if (type === "text") {
141
+ // In service-worker format, text modules are referenced by global identifiers,
142
+ // so we convert it here.
143
+ // This identifier has to be a valid JS identifier, so we replace all non alphanumeric
144
+ // characters with an underscore.
145
+ const identifier = name.replace(/[^a-zA-Z0-9_$]/g, "_");
146
+ textBlobBindings[identifier] = name;
147
+ } else if (type === "buffer") {
148
+ // In service-worker format, data blobs are referenced by global identifiers,
149
+ // so we convert it here.
150
+ // This identifier has to be a valid JS identifier, so we replace all non alphanumeric
151
+ // characters with an underscore.
152
+ const identifier = name.replace(/[^a-zA-Z0-9_$]/g, "_");
153
+ dataBlobBindings[identifier] = name;
94
154
  }
95
155
  }
96
156
  }
@@ -111,14 +171,28 @@ function useLocalWorker({
111
171
  compatibilityDate,
112
172
  compatibilityFlags,
113
173
  kvNamespaces: bindings.kv_namespaces?.map((kv) => kv.binding),
114
- kvPersist: enableLocalPersistence,
115
174
  durableObjects: Object.fromEntries(
116
175
  (bindings.durable_objects?.bindings ?? []).map<[string, string]>(
117
176
  (value) => [value.name, value.class_name]
118
177
  )
119
178
  ),
120
- durableObjectsPersist: enableLocalPersistence,
121
- cachePersist: enableLocalPersistence,
179
+ ...(localPersistencePath
180
+ ? {
181
+ kvPersist: path.join(localPersistencePath, "kv"),
182
+ durableObjectsPersist: path.join(localPersistencePath, "do"),
183
+ cachePersist: path.join(localPersistencePath, "cache"),
184
+ }
185
+ : {
186
+ // We mark these as true, so that they'll
187
+ // persist in the temp directory.
188
+ // This means they'll persist across a dev session,
189
+ // even if we change source and reload,
190
+ // and be deleted when the dev session ends
191
+ durableObjectsPersist: true,
192
+ cachePersist: true,
193
+ kvPersist: true,
194
+ }),
195
+
122
196
  sitePath: assetPaths?.assetDirectory
123
197
  ? path.join(assetPaths.baseDirectory, assetPaths.assetDirectory)
124
198
  : undefined,
@@ -130,8 +204,11 @@ function useLocalWorker({
130
204
  : undefined,
131
205
  bindings: bindings.vars,
132
206
  wasmBindings,
207
+ textBlobBindings,
208
+ dataBlobBindings,
133
209
  sourceMap: true,
134
210
  logUnhandledRejections: true,
211
+ crons,
135
212
  };
136
213
 
137
214
  // The path to the Miniflare CLI assumes that this file is being run from
@@ -143,28 +220,33 @@ function useLocalWorker({
143
220
  );
144
221
  const optionsArg = JSON.stringify(options, null);
145
222
 
146
- console.log("⎔ Starting a local server...");
147
- local.current = spawn("node", [
148
- "--experimental-vm-modules", // ensures that Miniflare can run ESM Workers
149
- "--no-warnings", // hide annoying Node warnings
150
- "--inspect", // start Miniflare listening for a debugger to attach
151
- miniflareCLIPath,
152
- optionsArg,
153
- // "--log=VERBOSE", // uncomment this to Miniflare to log "everything"!
154
- ]);
223
+ logger.log("⎔ Starting a local server...");
224
+ local.current = spawn(
225
+ "node",
226
+ [
227
+ "--experimental-vm-modules", // ensures that Miniflare can run ESM Workers
228
+ "--no-warnings", // hide annoying Node warnings
229
+ "--inspect", // start Miniflare listening for a debugger to attach
230
+ miniflareCLIPath,
231
+ optionsArg,
232
+ // "--log=VERBOSE", // uncomment this to Miniflare to log "everything"!
233
+ ],
234
+ {
235
+ cwd: path.dirname(scriptPath),
236
+ }
237
+ );
155
238
 
156
239
  local.current.on("close", (code) => {
157
240
  if (code) {
158
- console.log(`Miniflare process exited with code ${code}`);
241
+ logger.log(`Miniflare process exited with code ${code}`);
159
242
  }
160
243
  });
161
244
 
162
245
  local.current.stdout?.on("data", (data: Buffer) => {
163
- console.log(`${data.toString()}`);
246
+ process.stdout.write(data);
164
247
  });
165
248
 
166
249
  local.current.stderr?.on("data", (data: Buffer) => {
167
- console.error(`${data.toString()}`);
168
250
  const matches =
169
251
  /Debugger listening on (ws:\/\/127\.0\.0\.1:\d+\/[A-Za-z0-9-]+)/.exec(
170
252
  data.toString()
@@ -176,29 +258,30 @@ function useLocalWorker({
176
258
 
177
259
  local.current.on("exit", (code) => {
178
260
  if (code) {
179
- console.error(`Miniflare process exited with code ${code}`);
261
+ logger.error(`Miniflare process exited with code ${code}`);
180
262
  }
181
263
  });
182
264
 
183
265
  local.current.on("error", (error: Error) => {
184
- console.error(`Miniflare process failed to spawn`);
185
- console.error(error);
266
+ logger.error(`Miniflare process failed to spawn`);
267
+ logger.error(error);
186
268
  });
187
269
 
188
270
  removeSignalExitListener.current = onExit((_code, _signal) => {
189
- console.log("⎔ Shutting down local server.");
271
+ logger.log("⎔ Shutting down local server.");
190
272
  local.current?.kill();
191
273
  local.current = undefined;
192
274
  });
193
275
  }
194
276
 
195
277
  startLocalWorker().catch((err) => {
196
- console.error("local worker:", err);
278
+ logger.error("local worker:", err);
197
279
  });
198
280
 
199
281
  return () => {
282
+ abortController.abort();
200
283
  if (local.current) {
201
- console.log("⎔ Shutting down local server.");
284
+ logger.log("⎔ Shutting down local server.");
202
285
  local.current?.kill();
203
286
  local.current = undefined;
204
287
  removeSignalExitListener.current && removeSignalExitListener.current();
@@ -216,11 +299,14 @@ function useLocalWorker({
216
299
  bindings.vars,
217
300
  compatibilityDate,
218
301
  compatibilityFlags,
219
- enableLocalPersistence,
302
+ localPersistencePath,
220
303
  assetPaths,
221
304
  publicDirectory,
222
305
  rules,
223
306
  bindings.wasm_modules,
307
+ bindings.text_blobs,
308
+ bindings.data_blobs,
309
+ crons,
224
310
  ]);
225
311
  return { inspectorUrl };
226
312
  }
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import { useState, useEffect, useRef } from "react";
5
5
  import { createWorkerPreview } from "../create-worker-preview";
6
6
  import useInspector from "../inspect";
7
+ import { logger } from "../logger";
7
8
  import { usePreviewServer } from "../proxy";
8
9
  import { syncAssets } from "../sites";
9
10
  import type { CfPreviewToken } from "../create-worker-preview";
@@ -15,18 +16,18 @@ export function Remote(props: {
15
16
  name: string | undefined;
16
17
  bundle: EsbuildBundle | undefined;
17
18
  format: CfScriptFormat | undefined;
18
- public: undefined | string;
19
- assetPaths: undefined | AssetPaths;
19
+ public: string | undefined;
20
+ assetPaths: AssetPaths | undefined;
20
21
  port: number;
21
22
  ip: string;
22
23
  localProtocol: "https" | "http";
23
24
  inspectorPort: number;
24
- accountId: undefined | string;
25
- apiToken: undefined | string;
25
+ accountId: string | undefined;
26
+ apiToken: string | undefined;
26
27
  bindings: CfWorkerInit["bindings"];
27
- compatibilityDate: string | undefined;
28
- compatibilityFlags: undefined | string[];
29
- usageModel: undefined | "bundled" | "unbound";
28
+ compatibilityDate: string;
29
+ compatibilityFlags: string[] | undefined;
30
+ usageModel: "bundled" | "unbound" | undefined;
30
31
  env: string | undefined;
31
32
  legacyEnv: boolean | undefined;
32
33
  zone: { id: string; host: string } | undefined;
@@ -75,11 +76,11 @@ export function useWorker(props: {
75
76
  accountId: string;
76
77
  apiToken: string;
77
78
  bindings: CfWorkerInit["bindings"];
78
- assetPaths: undefined | AssetPaths;
79
+ assetPaths: AssetPaths | undefined;
79
80
  port: number;
80
81
  compatibilityDate: string | undefined;
81
82
  compatibilityFlags: string[] | undefined;
82
- usageModel: undefined | "bundled" | "unbound";
83
+ usageModel: "bundled" | "unbound" | undefined;
83
84
  env: string | undefined;
84
85
  legacyEnv: boolean | undefined;
85
86
  zone: { id: string; host: string } | undefined;
@@ -106,6 +107,7 @@ export function useWorker(props: {
106
107
  const startedRef = useRef(false);
107
108
 
108
109
  useEffect(() => {
110
+ const abortController = new AbortController();
109
111
  async function start() {
110
112
  setToken(undefined); // reset token in case we're re-running
111
113
 
@@ -114,7 +116,7 @@ export function useWorker(props: {
114
116
  if (!startedRef.current) {
115
117
  startedRef.current = true;
116
118
  } else {
117
- console.log("⎔ Detected changes, restarted server.");
119
+ logger.log("⎔ Detected changes, restarted server.");
118
120
  }
119
121
 
120
122
  const assets = await syncAssets(
@@ -125,7 +127,8 @@ export function useWorker(props: {
125
127
  // concept of service environments for kv namespaces yet).
126
128
  name + (!props.legacyEnv && props.env ? `-${props.env}` : ""),
127
129
  assetPaths,
128
- true
130
+ true,
131
+ false
129
132
  ); // TODO: cancellable?
130
133
 
131
134
  const content = await readFile(bundle.path, "utf-8");
@@ -173,15 +176,22 @@ export function useWorker(props: {
173
176
  accountId,
174
177
  apiToken,
175
178
  },
176
- { env: props.env, legacyEnv: props.legacyEnv, zone: props.zone }
179
+ { env: props.env, legacyEnv: props.legacyEnv, zone: props.zone },
180
+ abortController.signal
177
181
  )
178
182
  );
179
183
  }
180
184
  start().catch((err) => {
181
185
  // we want to log the error, but not end the process
182
186
  // since it could recover after the developer fixes whatever's wrong
183
- console.error("remote worker:", err);
187
+ if ((err as { code: string }).code !== "ABORT_ERR") {
188
+ logger.error("Error on remote worker:", err);
189
+ }
184
190
  });
191
+
192
+ return () => {
193
+ abortController.abort();
194
+ };
185
195
  }, [
186
196
  name,
187
197
  bundle,
@@ -1,6 +1,8 @@
1
1
  import assert from "node:assert";
2
+ import { useApp } from "ink";
2
3
  import { useState, useEffect } from "react";
3
4
  import { bundleWorker } from "../bundle";
5
+ import { logger } from "../logger";
4
6
  import type { Config } from "../config";
5
7
  import type { Entry } from "../entry";
6
8
  import type { CfModule } from "../worker";
@@ -23,22 +25,29 @@ export function useEsbuild({
23
25
  jsxFragment,
24
26
  rules,
25
27
  serveAssetsFromWorker,
28
+ tsconfig,
29
+ minify,
30
+ nodeCompat,
26
31
  }: {
27
32
  entry: Entry;
28
33
  destination: string | undefined;
29
- staticRoot: undefined | string;
34
+ staticRoot: string | undefined;
30
35
  jsxFactory: string | undefined;
31
36
  jsxFragment: string | undefined;
32
37
  rules: Config["rules"];
33
38
  serveAssetsFromWorker: boolean;
39
+ tsconfig: string | undefined;
40
+ minify: boolean | undefined;
41
+ nodeCompat: boolean | undefined;
34
42
  }): EsbuildBundle | undefined {
35
43
  const [bundle, setBundle] = useState<EsbuildBundle>();
44
+ const { exit } = useApp();
36
45
  useEffect(() => {
37
46
  let stopWatching: (() => void) | undefined = undefined;
38
47
 
39
48
  const watchMode: WatchMode = {
40
49
  async onRebuild(error) {
41
- if (error) console.error("watch build failed:", error);
50
+ if (error) logger.error("Watch build failed:", error);
42
51
  else {
43
52
  // nothing really changes here, so let's increment the id
44
53
  // to change the return object's identity
@@ -64,6 +73,9 @@ export function useEsbuild({
64
73
  jsxFragment,
65
74
  rules,
66
75
  watch: watchMode,
76
+ tsconfig,
77
+ minify,
78
+ nodeCompat,
67
79
  });
68
80
 
69
81
  // Capture the `stop()` method to use as the `useEffect()` destructor.
@@ -79,9 +91,11 @@ export function useEsbuild({
79
91
  });
80
92
  }
81
93
 
82
- build().catch(() => {
83
- // esbuild already logs errors to stderr and we don't want to end the process
84
- // on build errors anyway so this is a no-op error handler
94
+ build().catch((err) => {
95
+ // If esbuild fails on first run, we want to quit the process
96
+ // since we can't recover from here
97
+ // related: https://github.com/evanw/esbuild/issues/1037
98
+ exit(err);
85
99
  });
86
100
 
87
101
  return () => {
@@ -95,6 +109,10 @@ export function useEsbuild({
95
109
  jsxFragment,
96
110
  serveAssetsFromWorker,
97
111
  rules,
112
+ tsconfig,
113
+ exit,
114
+ minify,
115
+ nodeCompat,
98
116
  ]);
99
117
  return bundle;
100
118
  }
package/src/dialogs.tsx CHANGED
@@ -1,24 +1,28 @@
1
+ import chalk from "chalk";
1
2
  import { Box, Text, useInput, render } from "ink";
2
3
  import TextInput from "ink-text-input";
3
4
  import * as React from "react";
4
5
  import { useState } from "react";
6
+ import { logger } from "./logger";
5
7
  type ConfirmProps = {
6
8
  text: string;
7
9
  onConfirm: (answer: boolean) => void;
8
10
  };
9
11
  function Confirm(props: ConfirmProps) {
10
- useInput((input: string) => {
11
- if (input === "y") {
12
+ useInput((input: string, key) => {
13
+ if (input === "y" || key.return === true) {
12
14
  props.onConfirm(true);
13
15
  } else if (input === "n") {
14
16
  props.onConfirm(false);
15
17
  } else {
16
- console.log("Unrecognised input");
18
+ logger.warn("Unrecognised input:", input);
17
19
  }
18
20
  });
19
21
  return (
20
22
  <Box>
21
- <Text>{props.text} (y/n) </Text>
23
+ <Text>
24
+ {props.text} ({chalk.bold("y")}/n)
25
+ </Text>
22
26
  </Box>
23
27
  );
24
28
  }
@@ -39,12 +43,13 @@ export function confirm(text: string): Promise<boolean> {
39
43
 
40
44
  type PromptProps = {
41
45
  text: string;
46
+ defaultValue?: string;
42
47
  type?: "text" | "password";
43
48
  onSubmit: (text: string) => void;
44
49
  };
45
50
 
46
51
  function Prompt(props: PromptProps) {
47
- const [value, setValue] = useState("");
52
+ const [value, setValue] = useState(props.defaultValue || "");
48
53
 
49
54
  return (
50
55
  <Box>
@@ -61,11 +66,16 @@ function Prompt(props: PromptProps) {
61
66
  );
62
67
  }
63
68
 
64
- export async function prompt(text: string, type: "text" | "password" = "text") {
69
+ export async function prompt(
70
+ text: string,
71
+ type: "text" | "password" = "text",
72
+ defaultValue?: string
73
+ ): Promise<string> {
65
74
  return new Promise((resolve) => {
66
75
  const { unmount } = render(
67
76
  <Prompt
68
77
  text={text}
78
+ defaultValue={defaultValue}
69
79
  type={type}
70
80
  onSubmit={(inputText) => {
71
81
  unmount();