item);
+ }
+ }),
+ input.onDidChangeSelection((items) => resolve(items[0]))
+ // input.onDidHide(() => {
+ // (async () => {
+ // reject(
+ // shouldResume && (await shouldResume())
+ // ? InputFlowAction.resume
+ // : InputFlowAction.cancel
+ // );
+ // })().catch(reject);
+ // })
+ );
+ if (this.current) {
+ this.current.dispose();
+ }
+ this.current = input;
+ this.current.show();
+ });
+ } finally {
+ disposables.forEach((d) => d.dispose());
+ }
+ }
+
+ async showInputBox({
+ title,
+ step,
+ totalSteps,
+ value,
+ prompt,
+ validate,
+ buttons,
+ ignoreFocusOut,
+ placeholder,
+ // shouldResume,
+ }: P) {
+ const disposables: Disposable[] = [];
+ try {
+ return await new Promise<
+ string | (P extends { buttons: (infer I)[] } ? I : never)
+ >((resolve, reject) => {
+ const input = window.createInputBox();
+ input.title = title;
+ input.step = step;
+ input.totalSteps = totalSteps;
+ input.value = value || "";
+ input.prompt = prompt;
+ input.ignoreFocusOut = ignoreFocusOut ?? false;
+ input.placeholder = placeholder;
+ input.buttons = [
+ ...(this.steps.length > 1 ? [QuickInputButtons.Back] : []),
+ ...(buttons || []),
+ ];
+ let validating = validate("");
+ disposables.push(
+ input.onDidTriggerButton((item) => {
+ if (item === QuickInputButtons.Back) {
+ reject(InputFlowAction.back);
+ } else {
+ resolve(item);
+ }
+ }),
+ input.onDidAccept(async () => {
+ const value = input.value;
+ input.enabled = false;
+ input.busy = true;
+ if (!(await validate(value))) {
+ resolve(value);
+ }
+ input.enabled = true;
+ input.busy = false;
+ }),
+ input.onDidChangeValue(async (text) => {
+ const current = validate(text);
+ validating = current;
+ const validationMessage = await current;
+ if (current === validating) {
+ input.validationMessage = validationMessage;
+ }
+ })
+ // input.onDidHide(() => {
+ // (async () => {
+ // reject(
+ // shouldResume && (await shouldResume())
+ // ? InputFlowAction.resume
+ // : InputFlowAction.cancel
+ // );
+ // })().catch(reject);
+ // })
+ );
+ if (this.current) {
+ this.current.dispose();
+ }
+ this.current = input;
+ this.current.show();
+ });
+ } finally {
+ disposables.forEach((d) => d.dispose());
+ }
+ }
+}
diff --git a/packages/cloudflare-workers-bindings-extension/src/bindings.ts b/packages/cloudflare-workers-bindings-extension/src/bindings.ts
index 817b39fdebbd..5e4c7f715daf 100644
--- a/packages/cloudflare-workers-bindings-extension/src/bindings.ts
+++ b/packages/cloudflare-workers-bindings-extension/src/bindings.ts
@@ -200,26 +200,29 @@ export class BindingsProvider implements vscode.TreeDataProvider {
// Finds the first wrangler config file in the workspace and parse it
export async function getWranglerConfig(): Promise {
- const [configUri] = await vscode.workspace.findFiles(
- "wrangler.{toml,jsonc,json}",
- null,
- 1
- );
-
+ const configUri = await getConfigUri();
if (!configUri) {
return null;
}
-
const workspaceFolder = vscode.workspace.getWorkspaceFolder(configUri);
if (!workspaceFolder) {
return null;
}
- const wrangler = await importWrangler(workspaceFolder.uri.fsPath);
+ const wrangler = importWrangler(workspaceFolder.uri.fsPath);
const { rawConfig } = wrangler.experimental_readRawConfig({
config: configUri.fsPath,
});
return rawConfig;
}
+
+export async function getConfigUri(): Promise {
+ const [configUri] = await vscode.workspace.findFiles(
+ "wrangler.{toml,jsonc,json}",
+ null,
+ 1
+ );
+ return configUri;
+}
diff --git a/packages/cloudflare-workers-bindings-extension/src/extension.ts b/packages/cloudflare-workers-bindings-extension/src/extension.ts
index 9da94758ee44..d91a7e71ac4b 100644
--- a/packages/cloudflare-workers-bindings-extension/src/extension.ts
+++ b/packages/cloudflare-workers-bindings-extension/src/extension.ts
@@ -1,4 +1,5 @@
import * as vscode from "vscode";
+import { multiStepInput } from "./add-binding";
import { BindingsProvider } from "./bindings";
export type Result = {
@@ -31,7 +32,12 @@ export async function activate(
"cloudflare-workers-bindings.refresh",
() => bindingsProvider.refresh()
);
-
+ vscode.commands.registerCommand(
+ "cloudflare-workers-bindings.addEntry",
+ async () => {
+ await multiStepInput(context);
+ }
+ );
// Cleanup when the extension is deactivated
context.subscriptions.push(bindingsView, watcher, refreshCommand);
diff --git a/packages/cloudflare-workers-bindings-extension/src/wrangler.ts b/packages/cloudflare-workers-bindings-extension/src/wrangler.ts
index 2e35d08288e2..3f23a1f4e6a9 100644
--- a/packages/cloudflare-workers-bindings-extension/src/wrangler.ts
+++ b/packages/cloudflare-workers-bindings-extension/src/wrangler.ts
@@ -1,4 +1,5 @@
import * as path from "path";
+import * as vscode from "vscode";
export function importWrangler(
workspaceRoot: string
From f2c7493f26974ea0ffd4df24240d250ac0ef5119 Mon Sep 17 00:00:00 2001
From: emily-shen <69125074+emily-shen@users.noreply.github.com>
Date: Thu, 19 Dec 2024 11:22:37 +0000
Subject: [PATCH 2/5] cleanup
---
.../package.json | 6 ++--
.../src/add-binding.ts | 36 ++-----------------
.../src/extension.ts | 4 ++-
3 files changed, 8 insertions(+), 38 deletions(-)
diff --git a/packages/cloudflare-workers-bindings-extension/package.json b/packages/cloudflare-workers-bindings-extension/package.json
index ac5ab7d280d0..1891f9ff2625 100644
--- a/packages/cloudflare-workers-bindings-extension/package.json
+++ b/packages/cloudflare-workers-bindings-extension/package.json
@@ -33,7 +33,7 @@
"icon": "$(refresh)"
},
{
- "command": "cloudflare-workers-bindings.addEntry",
+ "command": "cloudflare-workers-bindings.addBinding",
"title": "Cloudflare Workers: Add binding",
"icon": "$(add)"
}
@@ -46,7 +46,7 @@
"group": "navigation"
},
{
- "command": "cloudflare-workers-bindings.addEntry",
+ "command": "cloudflare-workers-bindings.addBinding",
"when": "view == cloudflare-workers-bindings",
"group": "navigation"
}
@@ -74,7 +74,7 @@
"viewsWelcome": [
{
"view": "cloudflare-workers-bindings",
- "contents": "Welcome to Cloudflare Workers! [Learn more](https://workers.cloudflare.com).\n[Add a binding](command:cloudflare-workers-bindings.addEntry)"
+ "contents": "Welcome to Cloudflare Workers! [Learn more](https://workers.cloudflare.com).\n[Add a binding](command:cloudflare-workers-bindings.addBinding)"
}
]
},
diff --git a/packages/cloudflare-workers-bindings-extension/src/add-binding.ts b/packages/cloudflare-workers-bindings-extension/src/add-binding.ts
index d7900ba30baa..450ccdee31e9 100644
--- a/packages/cloudflare-workers-bindings-extension/src/add-binding.ts
+++ b/packages/cloudflare-workers-bindings-extension/src/add-binding.ts
@@ -21,15 +21,8 @@ class BindingType implements QuickPickItem {
public iconPath?: Uri
) {}
}
-/**
- * A multi-step input using window.createQuickPick() and window.createInputBox().
- *
- * This first part uses the helper class `MultiStepInput` that wraps the API for the multi-step case.
- */
-export async function multiStepInput(
- context: ExtensionContext
- // rootPath: string
-) {
+
+export async function multiStepInput(context: ExtensionContext) {
const bindingTypes: BindingType[] = [
new BindingType(
"KV",
@@ -81,15 +74,12 @@ export async function multiStepInput(
items: bindingTypes,
activeItem:
typeof state.bindingType !== "string" ? state.bindingType : undefined,
- // shouldResume,
});
state.bindingType = pick as BindingType;
return (input: MultiStepInput) => inputName(input, state);
}
async function inputName(input: MultiStepInput, state: Partial) {
- // TODO: Remember current value when navigating back.
-
let name = await input.showInputBox({
title,
step: 2,
@@ -98,7 +88,6 @@ export async function multiStepInput(
prompt: "Choose a binding name",
validate: validateNameIsUnique,
placeholder: `e.g. MY_BINDING`,
- // shouldResume,
});
state.name = name;
return () => addToToml(state);
@@ -179,7 +168,6 @@ interface InputBoxParameters {
buttons?: QuickInputButton[];
ignoreFocusOut?: boolean;
placeholder?: string;
- // shouldResume: () => Thenable;
}
export class MultiStepInput {
@@ -231,7 +219,6 @@ export class MultiStepInput {
ignoreFocusOut,
placeholder,
buttons,
- // shouldResume,
}: P) {
const disposables: Disposable[] = [];
try {
@@ -261,15 +248,6 @@ export class MultiStepInput {
}
}),
input.onDidChangeSelection((items) => resolve(items[0]))
- // input.onDidHide(() => {
- // (async () => {
- // reject(
- // shouldResume && (await shouldResume())
- // ? InputFlowAction.resume
- // : InputFlowAction.cancel
- // );
- // })().catch(reject);
- // })
);
if (this.current) {
this.current.dispose();
@@ -292,7 +270,6 @@ export class MultiStepInput {
buttons,
ignoreFocusOut,
placeholder,
- // shouldResume,
}: P) {
const disposables: Disposable[] = [];
try {
@@ -338,15 +315,6 @@ export class MultiStepInput {
input.validationMessage = validationMessage;
}
})
- // input.onDidHide(() => {
- // (async () => {
- // reject(
- // shouldResume && (await shouldResume())
- // ? InputFlowAction.resume
- // : InputFlowAction.cancel
- // );
- // })().catch(reject);
- // })
);
if (this.current) {
this.current.dispose();
diff --git a/packages/cloudflare-workers-bindings-extension/src/extension.ts b/packages/cloudflare-workers-bindings-extension/src/extension.ts
index d91a7e71ac4b..3ce99f903cfb 100644
--- a/packages/cloudflare-workers-bindings-extension/src/extension.ts
+++ b/packages/cloudflare-workers-bindings-extension/src/extension.ts
@@ -32,8 +32,10 @@ export async function activate(
"cloudflare-workers-bindings.refresh",
() => bindingsProvider.refresh()
);
+
+ // Register the add bindings command
vscode.commands.registerCommand(
- "cloudflare-workers-bindings.addEntry",
+ "cloudflare-workers-bindings.addBinding",
async () => {
await multiStepInput(context);
}
From e9d1018587c83156742216d3bb3f5f8d7f8ecf28 Mon Sep 17 00:00:00 2001
From: emily-shen <69125074+emily-shen@users.noreply.github.com>
Date: Thu, 19 Dec 2024 12:08:32 +0000
Subject: [PATCH 3/5] changeset
---
.changeset/slimy-dots-hope.md | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 .changeset/slimy-dots-hope.md
diff --git a/.changeset/slimy-dots-hope.md b/.changeset/slimy-dots-hope.md
new file mode 100644
index 000000000000..f1a05aca9503
--- /dev/null
+++ b/.changeset/slimy-dots-hope.md
@@ -0,0 +1,5 @@
+---
+"cloudflare-workers-bindings-extension": minor
+---
+
+feat: add ui to add a binding via the extension
From cc97bed3cb7af655d8f05c2e2441b60cda85c1c3 Mon Sep 17 00:00:00 2001
From: emily-shen <69125074+emily-shen@users.noreply.github.com>
Date: Thu, 19 Dec 2024 13:19:57 +0000
Subject: [PATCH 4/5] pr feedback
---
.../src/add-binding.ts | 31 +++++++++++--------
.../src/extension.ts | 15 ++++++---
.../src/{bindings.ts => show-bindings.ts} | 0
.../src/wrangler.ts | 1 -
4 files changed, 28 insertions(+), 19 deletions(-)
rename packages/cloudflare-workers-bindings-extension/src/{bindings.ts => show-bindings.ts} (100%)
diff --git a/packages/cloudflare-workers-bindings-extension/src/add-binding.ts b/packages/cloudflare-workers-bindings-extension/src/add-binding.ts
index 450ccdee31e9..861ee0c840fa 100644
--- a/packages/cloudflare-workers-bindings-extension/src/add-binding.ts
+++ b/packages/cloudflare-workers-bindings-extension/src/add-binding.ts
@@ -10,7 +10,7 @@ import {
window,
workspace,
} from "vscode";
-import { getConfigUri } from "./bindings";
+import { getConfigUri } from "./show-bindings";
import { importWrangler } from "./wrangler";
class BindingType implements QuickPickItem {
@@ -22,7 +22,7 @@ class BindingType implements QuickPickItem {
) {}
}
-export async function multiStepInput(context: ExtensionContext) {
+export async function addBindingFlow(context: ExtensionContext) {
const bindingTypes: BindingType[] = [
new BindingType(
"KV",
@@ -56,16 +56,13 @@ export async function multiStepInput(context: ExtensionContext) {
async function collectInputs() {
const state = {} as Partial;
- await MultiStepInput.run((input) => pickResourceGroup(input, state));
+ await MultiStepInput.run((input) => pickBindingType(input, state));
return state as State;
}
const title = "Add binding";
- async function pickResourceGroup(
- input: MultiStepInput,
- state: Partial
- ) {
+ async function pickBindingType(input: MultiStepInput, state: Partial) {
const pick = await input.showQuickPick({
title,
step: 1,
@@ -76,10 +73,13 @@ export async function multiStepInput(context: ExtensionContext) {
typeof state.bindingType !== "string" ? state.bindingType : undefined,
});
state.bindingType = pick as BindingType;
- return (input: MultiStepInput) => inputName(input, state);
+ return (input: MultiStepInput) => inputBindingName(input, state);
}
- async function inputName(input: MultiStepInput, state: Partial) {
+ async function inputBindingName(
+ input: MultiStepInput,
+ state: Partial
+ ) {
let name = await input.showInputBox({
title,
step: 2,
@@ -90,12 +90,17 @@ export async function multiStepInput(context: ExtensionContext) {
placeholder: `e.g. MY_BINDING`,
});
state.name = name;
- return () => addToToml(state);
+ return () => addToConfig(state);
}
- async function addToToml(state: Partial) {
+ async function addToConfig(state: Partial) {
const configUri = await getConfigUri();
if (!configUri) {
+ // for some reason, if we just throw an error it doesn't surface properly when triggered by the button in the welcome view
+ window.showErrorMessage(
+ "Unable to locate Wrangler configuration file — have you opened a project with a wrangler.json(c) or wrangler.toml file?",
+ {}
+ );
return null;
}
const workspaceFolder = workspace.getWorkspaceFolder(configUri);
@@ -106,7 +111,7 @@ export async function multiStepInput(context: ExtensionContext) {
const wrangler = importWrangler(workspaceFolder.uri.fsPath);
- await workspace.openTextDocument(configUri).then((doc) => {
+ workspace.openTextDocument(configUri).then((doc) => {
window.showTextDocument(doc);
try {
wrangler.experimental_patchConfig(configUri.path, {
@@ -132,7 +137,7 @@ binding = "${state.name}"
return name === "SOME_KV_BINDING" ? "Name not unique" : undefined;
}
- const state = await collectInputs();
+ await collectInputs();
}
// -------------------------------------------------------
diff --git a/packages/cloudflare-workers-bindings-extension/src/extension.ts b/packages/cloudflare-workers-bindings-extension/src/extension.ts
index 3ce99f903cfb..d10f1dfc6742 100644
--- a/packages/cloudflare-workers-bindings-extension/src/extension.ts
+++ b/packages/cloudflare-workers-bindings-extension/src/extension.ts
@@ -1,6 +1,6 @@
import * as vscode from "vscode";
-import { multiStepInput } from "./add-binding";
-import { BindingsProvider } from "./bindings";
+import { addBindingFlow } from "./add-binding";
+import { BindingsProvider } from "./show-bindings";
export type Result = {
bindingsProvider: BindingsProvider;
@@ -34,14 +34,19 @@ export async function activate(
);
// Register the add bindings command
- vscode.commands.registerCommand(
+ const addBindingCommand = vscode.commands.registerCommand(
"cloudflare-workers-bindings.addBinding",
async () => {
- await multiStepInput(context);
+ await addBindingFlow(context);
}
);
// Cleanup when the extension is deactivated
- context.subscriptions.push(bindingsView, watcher, refreshCommand);
+ context.subscriptions.push(
+ bindingsView,
+ watcher,
+ refreshCommand,
+ addBindingCommand
+ );
return {
bindingsProvider,
diff --git a/packages/cloudflare-workers-bindings-extension/src/bindings.ts b/packages/cloudflare-workers-bindings-extension/src/show-bindings.ts
similarity index 100%
rename from packages/cloudflare-workers-bindings-extension/src/bindings.ts
rename to packages/cloudflare-workers-bindings-extension/src/show-bindings.ts
diff --git a/packages/cloudflare-workers-bindings-extension/src/wrangler.ts b/packages/cloudflare-workers-bindings-extension/src/wrangler.ts
index 3f23a1f4e6a9..2e35d08288e2 100644
--- a/packages/cloudflare-workers-bindings-extension/src/wrangler.ts
+++ b/packages/cloudflare-workers-bindings-extension/src/wrangler.ts
@@ -1,5 +1,4 @@
import * as path from "path";
-import * as vscode from "vscode";
export function importWrangler(
workspaceRoot: string
From d587c3013c921c52c702c7a73cb7294bc24802a8 Mon Sep 17 00:00:00 2001
From: emily-shen <69125074+emily-shen@users.noreply.github.com>
Date: Thu, 19 Dec 2024 13:21:05 +0000
Subject: [PATCH 5/5] fixup
---
.../src/add-binding.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/cloudflare-workers-bindings-extension/src/add-binding.ts b/packages/cloudflare-workers-bindings-extension/src/add-binding.ts
index 861ee0c840fa..ec3e387c5561 100644
--- a/packages/cloudflare-workers-bindings-extension/src/add-binding.ts
+++ b/packages/cloudflare-workers-bindings-extension/src/add-binding.ts
@@ -16,7 +16,7 @@ import { importWrangler } from "./wrangler";
class BindingType implements QuickPickItem {
constructor(
public label: string,
- public description?: string,
+ public configKey?: string,
public detail?: string,
public iconPath?: Uri
) {}
@@ -115,7 +115,7 @@ export async function addBindingFlow(context: ExtensionContext) {
window.showTextDocument(doc);
try {
wrangler.experimental_patchConfig(configUri.path, {
- [state.bindingType?.description!]: [{ binding: state.name! }],
+ [state.bindingType?.configKey!]: [{ binding: state.name! }],
});
window.showInformationMessage(`Created binding '${state.name}'`);
} catch {
@@ -123,7 +123,7 @@ export async function addBindingFlow(context: ExtensionContext) {
`Unable to directly add binding to config file. A snippet has been copied to clipboard - please paste this into your config file.`
);
- const patch = `[[${state.bindingType?.description!}]]
+ const patch = `[[${state.bindingType?.configKey!}]]
binding = "${state.name}"
`;