diff --git a/.changeset/wet-beds-attack.md b/.changeset/wet-beds-attack.md new file mode 100644 index 000000000000..94fb53bbe8a9 --- /dev/null +++ b/.changeset/wet-beds-attack.md @@ -0,0 +1,5 @@ +--- +"cloudflare-workers-bindings-extension": patch +--- + +Add documentation links to binding created notification and bindings tree view diff --git a/packages/cloudflare-workers-bindings-extension/package.json b/packages/cloudflare-workers-bindings-extension/package.json index 2b128e6bb190..195967c03107 100644 --- a/packages/cloudflare-workers-bindings-extension/package.json +++ b/packages/cloudflare-workers-bindings-extension/package.json @@ -36,7 +36,13 @@ { "command": "cloudflare-workers-bindings.addBinding", "title": "Cloudflare Workers: Add binding", - "icon": "$(add)" + "icon": "$(add)", + "enablement": "!ext.unsupportedWrangler" + }, + { + "command": "cloudflare-workers-bindings.openDocs", + "title": "Cloudflare Workers: Open Documentation", + "icon": "$(book)" } ], "jsonValidation": [ @@ -57,6 +63,13 @@ "when": "view == cloudflare-workers-bindings", "group": "navigation" } + ], + "view/item/context": [ + { + "command": "cloudflare-workers-bindings.openDocs", + "when": "view == cloudflare-workers-bindings && viewItem == binding", + "group": "inline" + } ] }, "views": { @@ -81,7 +94,13 @@ "viewsWelcome": [ { "view": "cloudflare-workers-bindings", - "contents": "Welcome to Cloudflare Workers! [Learn more](https://workers.cloudflare.com).\n[Add a binding](command:cloudflare-workers-bindings.addBinding)" + "contents": "Welcome to Cloudflare Workers! [Learn more](https://workers.cloudflare.com).\n[Add a binding](command:cloudflare-workers-bindings.addBinding)", + "when": "!ext.unsupportedWrangler" + }, + { + "view": "cloudflare-workers-bindings", + "contents": "Please upgrade Wrangler to at least 3.99.0 in order to use the Cloudflare Workers Extension. You can install the latest Wrangler version by running `npm i wrangler@latest`", + "when": "ext.unsupportedWrangler" } ] }, diff --git a/packages/cloudflare-workers-bindings-extension/resources/icons/ai.svg b/packages/cloudflare-workers-bindings-extension/resources/icons/ai.svg new file mode 100644 index 000000000000..72d8509c2d18 --- /dev/null +++ b/packages/cloudflare-workers-bindings-extension/resources/icons/ai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/cloudflare-workers-bindings-extension/resources/icons/analytics_engine_datasets.svg b/packages/cloudflare-workers-bindings-extension/resources/icons/analytics_engine_datasets.svg new file mode 100644 index 000000000000..0bba62fd3f91 --- /dev/null +++ b/packages/cloudflare-workers-bindings-extension/resources/icons/analytics_engine_datasets.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/cloudflare-workers-bindings-extension/resources/icons/assets.svg b/packages/cloudflare-workers-bindings-extension/resources/icons/assets.svg new file mode 100644 index 000000000000..bab10670a729 --- /dev/null +++ b/packages/cloudflare-workers-bindings-extension/resources/icons/assets.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/cloudflare-workers-bindings-extension/resources/icons/browser.svg b/packages/cloudflare-workers-bindings-extension/resources/icons/browser.svg new file mode 100644 index 000000000000..926b743027d4 --- /dev/null +++ b/packages/cloudflare-workers-bindings-extension/resources/icons/browser.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/packages/cloudflare-workers-bindings-extension/resources/icons/d1.svg b/packages/cloudflare-workers-bindings-extension/resources/icons/d1_databases.svg similarity index 100% rename from packages/cloudflare-workers-bindings-extension/resources/icons/d1.svg rename to packages/cloudflare-workers-bindings-extension/resources/icons/d1_databases.svg diff --git a/packages/cloudflare-workers-bindings-extension/resources/icons/dispatch_namespaces.svg b/packages/cloudflare-workers-bindings-extension/resources/icons/dispatch_namespaces.svg new file mode 100644 index 000000000000..b80871b92122 --- /dev/null +++ b/packages/cloudflare-workers-bindings-extension/resources/icons/dispatch_namespaces.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/cloudflare-workers-bindings-extension/resources/icons/durable_objects.svg b/packages/cloudflare-workers-bindings-extension/resources/icons/durable_objects.svg new file mode 100644 index 000000000000..0df07d2e33c5 --- /dev/null +++ b/packages/cloudflare-workers-bindings-extension/resources/icons/durable_objects.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/cloudflare-workers-bindings-extension/resources/icons/hyperdrive.svg b/packages/cloudflare-workers-bindings-extension/resources/icons/hyperdrive.svg new file mode 100644 index 000000000000..1d3e70b71a1a --- /dev/null +++ b/packages/cloudflare-workers-bindings-extension/resources/icons/hyperdrive.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/packages/cloudflare-workers-bindings-extension/resources/icons/kv.svg b/packages/cloudflare-workers-bindings-extension/resources/icons/kv_namespaces.svg similarity index 100% rename from packages/cloudflare-workers-bindings-extension/resources/icons/kv.svg rename to packages/cloudflare-workers-bindings-extension/resources/icons/kv_namespaces.svg diff --git a/packages/cloudflare-workers-bindings-extension/resources/icons/logfwdr.svg b/packages/cloudflare-workers-bindings-extension/resources/icons/logfwdr.svg new file mode 100644 index 000000000000..bad89037e9ad --- /dev/null +++ b/packages/cloudflare-workers-bindings-extension/resources/icons/logfwdr.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/cloudflare-workers-bindings-extension/resources/icons/mtls_certificates.svg b/packages/cloudflare-workers-bindings-extension/resources/icons/mtls_certificates.svg new file mode 100644 index 000000000000..b38e5af9c6bf --- /dev/null +++ b/packages/cloudflare-workers-bindings-extension/resources/icons/mtls_certificates.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/cloudflare-workers-bindings-extension/resources/icons/pipelines.svg b/packages/cloudflare-workers-bindings-extension/resources/icons/pipelines.svg new file mode 100644 index 000000000000..dc1c9f0f7c0f --- /dev/null +++ b/packages/cloudflare-workers-bindings-extension/resources/icons/pipelines.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/cloudflare-workers-bindings-extension/resources/icons/queues.svg b/packages/cloudflare-workers-bindings-extension/resources/icons/queues.svg new file mode 100644 index 000000000000..651f4745dcc8 --- /dev/null +++ b/packages/cloudflare-workers-bindings-extension/resources/icons/queues.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/packages/cloudflare-workers-bindings-extension/resources/icons/r2.svg b/packages/cloudflare-workers-bindings-extension/resources/icons/r2_buckets.svg similarity index 100% rename from packages/cloudflare-workers-bindings-extension/resources/icons/r2.svg rename to packages/cloudflare-workers-bindings-extension/resources/icons/r2_buckets.svg diff --git a/packages/cloudflare-workers-bindings-extension/resources/icons/send_email.svg b/packages/cloudflare-workers-bindings-extension/resources/icons/send_email.svg new file mode 100644 index 000000000000..5fd62ba6037d --- /dev/null +++ b/packages/cloudflare-workers-bindings-extension/resources/icons/send_email.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/packages/cloudflare-workers-bindings-extension/resources/icons/services.svg b/packages/cloudflare-workers-bindings-extension/resources/icons/services.svg new file mode 100644 index 000000000000..bab10670a729 --- /dev/null +++ b/packages/cloudflare-workers-bindings-extension/resources/icons/services.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/cloudflare-workers-bindings-extension/resources/icons/tail_consumers.svg b/packages/cloudflare-workers-bindings-extension/resources/icons/tail_consumers.svg new file mode 100644 index 000000000000..bab10670a729 --- /dev/null +++ b/packages/cloudflare-workers-bindings-extension/resources/icons/tail_consumers.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/cloudflare-workers-bindings-extension/resources/icons/unsafe.svg b/packages/cloudflare-workers-bindings-extension/resources/icons/unsafe.svg new file mode 100644 index 000000000000..58a678755095 --- /dev/null +++ b/packages/cloudflare-workers-bindings-extension/resources/icons/unsafe.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/cloudflare-workers-bindings-extension/resources/icons/vars.svg b/packages/cloudflare-workers-bindings-extension/resources/icons/vars.svg new file mode 100644 index 000000000000..8c5e623511fd --- /dev/null +++ b/packages/cloudflare-workers-bindings-extension/resources/icons/vars.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/cloudflare-workers-bindings-extension/resources/icons/vectorize.svg b/packages/cloudflare-workers-bindings-extension/resources/icons/vectorize.svg new file mode 100644 index 000000000000..4393ddee1780 --- /dev/null +++ b/packages/cloudflare-workers-bindings-extension/resources/icons/vectorize.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/cloudflare-workers-bindings-extension/resources/icons/version_metadata.svg b/packages/cloudflare-workers-bindings-extension/resources/icons/version_metadata.svg new file mode 100644 index 000000000000..9363094a5aa7 --- /dev/null +++ b/packages/cloudflare-workers-bindings-extension/resources/icons/version_metadata.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/cloudflare-workers-bindings-extension/resources/icons/workflows.svg b/packages/cloudflare-workers-bindings-extension/resources/icons/workflows.svg new file mode 100644 index 000000000000..b6a33b8248a2 --- /dev/null +++ b/packages/cloudflare-workers-bindings-extension/resources/icons/workflows.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/cloudflare-workers-bindings-extension/src/add-binding.ts b/packages/cloudflare-workers-bindings-extension/src/add-binding.ts index cbe4d3af2657..4e013fb9592d 100644 --- a/packages/cloudflare-workers-bindings-extension/src/add-binding.ts +++ b/packages/cloudflare-workers-bindings-extension/src/add-binding.ts @@ -1,3 +1,4 @@ +import assert from "node:assert"; import { Disposable, env, @@ -18,9 +19,10 @@ import { } from "./show-bindings"; import { importWrangler } from "./wrangler"; +type BindingLabel = "KV" | "R2" | "D1"; class BindingType implements QuickPickItem { constructor( - public label: string, + public label: BindingLabel, public configKey?: string, public detail?: string, public iconPath?: Uri @@ -33,63 +35,77 @@ export async function addBindingFlow(context: ExtensionContext) { "KV", "kv_namespaces", "Global, low-latency, key-value data storage", - Uri.file(context.asAbsolutePath("resources/icons/kv.svg")) + Uri.file(context.asAbsolutePath("resources/icons/kv_namespaces.svg")) ), new BindingType( "R2", "r2_buckets", "Object storage for all your data", - Uri.file(context.asAbsolutePath("resources/icons/r2.svg")) + Uri.file(context.asAbsolutePath("resources/icons/r2_buckets.svg")) ), new BindingType( "D1", "d1_databases", "Serverless SQL databases", - Uri.file(context.asAbsolutePath("resources/icons/d1.svg")) + Uri.file(context.asAbsolutePath("resources/icons/d1_databases.svg")) ), ]; interface State { - title: string; - step: number; - totalSteps: number; - bindingType: BindingType; - name: string; - runtime: QuickPickItem; - id: string; + config?: Config; + configUri: Uri; + bindingType?: BindingType; + name?: string; } + const title = "Add binding"; + async function collectInputs() { - const state = {} as Partial; + const configUri = await getConfigUri(); + if (!configUri) { + const docs = await window.showErrorMessage( + "Unable to locate Wrangler configuration file — please open or create a project with a wrangler.json(c) or wrangler.toml file. You can run `npx create cloudflare@latest` to get started with a template.", + "Learn more" + ); + if (docs) { + env.openExternal( + Uri.parse( + "https://developers.cloudflare.com/workers/wrangler/configuration/" + ) + ); + } + return; + } + let config: Config | undefined; + try { + config = await getWranglerConfig(); + } catch {} + if (!config) { + window.showErrorMessage( + "Please update wrangler to at least 3.99.0 to use this extension." + ); + return; + } + + const state = { config, configUri }; await MultiStepInput.run((input) => pickBindingType(input, state)); - return state as State; + return state; } - const title = "Add binding"; - - async function pickBindingType(input: MultiStepInput, state: Partial) { + async function pickBindingType(input: MultiStepInput, state: State) { const pick = await input.showQuickPick({ title, step: 1, totalSteps: 2, placeholder: "Choose a binding type", items: bindingTypes, - activeItem: - typeof state.bindingType !== "string" ? state.bindingType : undefined, }); state.bindingType = pick as BindingType; return (input: MultiStepInput) => inputBindingName(input, state); } - async function inputBindingName( - input: MultiStepInput, - state: Partial - ) { - const config = await getWranglerConfig(); - if (!config) { - throw new Error("No config found"); - } - const allBindingNames = getAllBindingNames(config); + async function inputBindingName(input: MultiStepInput, state: State) { + const allBindingNames = getAllBindingNames(state.config ?? {}); let name = await input.showInputBox({ title, @@ -107,17 +123,9 @@ export async function addBindingFlow(context: ExtensionContext) { return () => addToConfig(state); } - 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); + async function addToConfig(state: State) { + assert(state.bindingType?.configKey && state.name); + const workspaceFolder = workspace.getWorkspaceFolder(state.configUri); if (!workspaceFolder) { return null; @@ -125,25 +133,39 @@ export async function addBindingFlow(context: ExtensionContext) { const wrangler = importWrangler(workspaceFolder.uri.fsPath); - workspace.openTextDocument(configUri).then((doc) => { - window.showTextDocument(doc); + const doc = await workspace.openTextDocument(state.configUri); + window.showTextDocument(doc); + let openDocs: string | undefined; + let useClipboard = false; + if (!wrangler) { + useClipboard = true; + } else { try { - wrangler.experimental_patchConfig(configUri.path, { - [state.bindingType?.configKey!]: [{ binding: state.name! }], + const configFileName = state.configUri.path.split("/").at(-1); + wrangler.experimental_patchConfig(state.configUri.path, { + [state.bindingType.configKey]: [{ binding: state.name }], }); - window.showInformationMessage(`Created binding '${state.name}'`); - } catch { - window.showErrorMessage( - `Unable to directly add binding to config file. A snippet has been copied to clipboard - please paste this into your config file.` + openDocs = await window.showInformationMessage( + `🎉 The ${state.bindingType.label} binding '${state.name}' has been added to your ${configFileName}`, + `Open ${state.bindingType.label} documentation` ); - - const patch = `[[${state.bindingType?.configKey!}]] -binding = "${state.name}" -`; - - env.clipboard.writeText(patch); + } catch { + useClipboard = true; } - }); + } + if (useClipboard) { + const patch = `[[${state.bindingType?.configKey!}]] + binding = "${state.name}" + `; + env.clipboard.writeText(patch); + openDocs = await window.showInformationMessage( + `✨ A snippet has been copied to clipboard - please paste this into your wrangler.toml`, + `Open ${state.bindingType.label} documentation` + ); + } + if (openDocs) { + env.openExternal(Uri.parse(bindingDocs[state.bindingType.label])); + } } async function validateNameIsUnique(name: string, allBindingNames: string[]) { @@ -170,7 +192,6 @@ interface QuickPickParameters { step: number; totalSteps: number; items: T[]; - activeItem?: T; ignoreFocusOut?: boolean; placeholder: string; buttons?: QuickInputButton[]; @@ -238,7 +259,6 @@ export class MultiStepInput { step, totalSteps, items, - activeItem, ignoreFocusOut, placeholder, buttons, @@ -255,9 +275,6 @@ export class MultiStepInput { input.ignoreFocusOut = ignoreFocusOut ?? false; input.placeholder = placeholder; input.items = items; - if (activeItem) { - input.activeItems = [activeItem]; - } input.buttons = [ ...(this.steps.length > 1 ? [QuickInputButtons.Back] : []), ...(buttons || []), @@ -408,3 +425,9 @@ const isRecord = ( value: unknown ): value is Record => typeof value === "object" && value !== null && !Array.isArray(value); + +const bindingDocs: Record = { + KV: "https://developers.cloudflare.com/kv/get-started/#5-access-your-kv-namespace-from-your-worker", + D1: "https://developers.cloudflare.com/d1/get-started/#write-queries-within-your-worker", + R2: "https://developers.cloudflare.com/r2/api/workers/workers-api-usage/#4-access-your-r2-bucket-from-your-worker", +}; diff --git a/packages/cloudflare-workers-bindings-extension/src/extension.ts b/packages/cloudflare-workers-bindings-extension/src/extension.ts index d10f1dfc6742..0a60de576c55 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 { addBindingFlow } from "./add-binding"; -import { BindingsProvider } from "./show-bindings"; +import { BindingsProvider, Node } from "./show-bindings"; export type Result = { bindingsProvider: BindingsProvider; @@ -40,12 +40,28 @@ export async function activate( await addBindingFlow(context); } ); - // Cleanup when the extension is deactivated + + const openDocsCommand = vscode.commands.registerCommand( + "cloudflare-workers-bindings.openDocs", + async (node: Node) => { + const docs: Record = { + d1_databases: "https://developers.cloudflare.com/d1/", + r2_buckets: "https://developers.cloudflare.com/r2/", + kv_namespaces: "https://developers.cloudflare.com/kv/", + }; + if (node.type === "binding") { + vscode.env.openExternal(vscode.Uri.parse(docs[node.name])); + } + } + ); + + // Cleanup when the extension is deactivated context.subscriptions.push( bindingsView, watcher, refreshCommand, - addBindingCommand + addBindingCommand, + openDocsCommand ); return { diff --git a/packages/cloudflare-workers-bindings-extension/src/show-bindings.ts b/packages/cloudflare-workers-bindings-extension/src/show-bindings.ts index a5e449539a9b..1dd73b1256b3 100644 --- a/packages/cloudflare-workers-bindings-extension/src/show-bindings.ts +++ b/packages/cloudflare-workers-bindings-extension/src/show-bindings.ts @@ -1,8 +1,9 @@ +import path from "path"; import * as vscode from "vscode"; import { importWrangler } from "./wrangler"; export type Config = ReturnType< - ReturnType["experimental_readRawConfig"] + NonNullable>["experimental_readRawConfig"] >["rawConfig"]; export type Environment = Required["env"][string]; @@ -39,7 +40,7 @@ type EnvNode = { name: string | null; }; -type BindingNode = Exclude< +export type BindingNode = Exclude< { [Name in BindingType]: { type: "binding"; @@ -56,7 +57,7 @@ type ResourceNode = { description?: string; }; -type Node = EnvNode | BindingNode | ResourceNode; +export type Node = EnvNode | BindingNode | ResourceNode; export class BindingsProvider implements vscode.TreeDataProvider { // Event emitter for refreshing the tree @@ -83,11 +84,24 @@ export class BindingsProvider implements vscode.TreeDataProvider { return item; } + // this is the header case "binding": { - return new vscode.TreeItem( + const item = new vscode.TreeItem( friendlyBindingNames[node.name], vscode.TreeItemCollapsibleState.Expanded ); + const enabledBindings = ["kv_namespaces", "r2_buckets", "d1_databases"]; + if (enabledBindings.includes(node.name)) { + item.contextValue = "binding"; + } + item.iconPath = path.join( + __dirname, + "../", + "resources", + "icons", + `${node.name}.svg` + ); + return item; } case "resource": { const item = new vscode.TreeItem( @@ -600,26 +614,29 @@ function hasBinding | Array>( } // Finds the first wrangler config file in the workspace and parse it -export async function getWranglerConfig(): Promise { +export async function getWranglerConfig(): Promise { const configUri = await getConfigUri(); if (!configUri) { - return null; + return; } const workspaceFolder = vscode.workspace.getWorkspaceFolder(configUri); if (!workspaceFolder) { - return null; + return; } const wrangler = importWrangler(workspaceFolder.uri.fsPath); - const { rawConfig } = wrangler.experimental_readRawConfig({ - config: configUri.fsPath, - }); - - return rawConfig; + if (!wrangler) { + return; + } else { + const { rawConfig } = wrangler.experimental_readRawConfig({ + config: configUri.fsPath, + }); + return rawConfig; + } } -export async function getConfigUri(): Promise { +export async function getConfigUri(): Promise { const [configUri] = await vscode.workspace.findFiles( "wrangler.{toml,jsonc,json}", null, diff --git a/packages/cloudflare-workers-bindings-extension/src/test/suite/extension.test.ts b/packages/cloudflare-workers-bindings-extension/src/test/suite/extension.test.ts index 0d3b4cc9437f..93c40f6c2570 100644 --- a/packages/cloudflare-workers-bindings-extension/src/test/suite/extension.test.ts +++ b/packages/cloudflare-workers-bindings-extension/src/test/suite/extension.test.ts @@ -18,6 +18,93 @@ describe("Extension Test Suite", () => { }); describe("BindingsProvider", () => { + it("should not return wrangler if the version of wrangler is too old", async () => { + const pkgJsonPath = path + .resolve(__dirname, "..", "..", "..", "..", "wrangler", "package.json") + .toString(); + + const originalPkgJson = JSON.parse( + await fs.readFile(pkgJsonPath, "utf8") + ); + + const cleanup = await seed({ + "wrangler.json": generateWranglerConfig({ + r2_buckets: [ + { + binding: "r2", + bucket_name: "something else", + }, + ], + }), + }); + + try { + const extension = getExtension(); + const { bindingsProvider } = await extension.activate(); + + await fs.writeFile( + pkgJsonPath, + JSON.stringify({ ...originalPkgJson, version: "3.98.0" }) + ); + // bindingsProvider uses wrangler to read config. + // If that doesn't work, but we have provided a wrangler.json, + // this means wrangler wasn't imported. Which should be + // because its an old version. (not the best test :/) + let children = await bindingsProvider.getChildren(); + assert.deepEqual(children, []); + + await fs.writeFile( + pkgJsonPath, + JSON.stringify({ ...originalPkgJson, version: "3.99.0" }) + ); + children = await bindingsProvider.getChildren(); + assert.deepEqual(children, [ + { + config: [ + { + binding: "r2", + bucket_name: "something else", + }, + ], + name: "r2_buckets", + type: "binding", + }, + ]); + + await fs.writeFile( + pkgJsonPath, + JSON.stringify({ ...originalPkgJson, version: "2.99.0" }) + ); + children = await bindingsProvider.getChildren(); + assert.deepEqual(children, []); + + // pre-releases should work + await fs.writeFile( + pkgJsonPath, + JSON.stringify({ ...originalPkgJson, version: "0.0.0-something" }) + ); + children = await bindingsProvider.getChildren(); + assert.deepEqual(children, [ + { + config: [ + { + binding: "r2", + bucket_name: "something else", + }, + ], + name: "r2_buckets", + type: "binding", + }, + ]); + } finally { + await cleanup(); + await fs.writeFile( + pkgJsonPath, + JSON.stringify(originalPkgJson, null, "\t") + "\n" + ); + } + }); + it("shows no bindings if there is no wrangler config", async () => { const extension = getExtension(); const { bindingsProvider } = await extension.activate(); @@ -176,86 +263,173 @@ describe("Extension Test Suite", () => { { label: friendlyBindingNames.kv_namespaces, collapsibleState: vscode.TreeItemCollapsibleState.Expanded, + contextValue: "binding", + iconPath: path.join( + __filename, + "../../../../resources/icons/kv_namespaces.svg" + ), }, { label: friendlyBindingNames.r2_buckets, collapsibleState: vscode.TreeItemCollapsibleState.Expanded, + contextValue: "binding", + iconPath: path.join( + __filename, + "../../../../resources/icons/r2_buckets.svg" + ), }, { label: friendlyBindingNames.d1_databases, collapsibleState: vscode.TreeItemCollapsibleState.Expanded, + contextValue: "binding", + iconPath: path.join( + __filename, + "../../../../resources/icons/d1_databases.svg" + ), }, { label: friendlyBindingNames.durable_objects, collapsibleState: vscode.TreeItemCollapsibleState.Expanded, + iconPath: path.join( + __filename, + "../../../../resources/icons/durable_objects.svg" + ), }, { label: friendlyBindingNames.ai, collapsibleState: vscode.TreeItemCollapsibleState.Expanded, + iconPath: path.join( + __filename, + "../../../../resources/icons/ai.svg" + ), }, { label: friendlyBindingNames.analytics_engine_datasets, collapsibleState: vscode.TreeItemCollapsibleState.Expanded, + iconPath: path.join( + __filename, + "../../../../resources/icons/analytics_engine_datasets.svg" + ), }, { label: friendlyBindingNames.browser, collapsibleState: vscode.TreeItemCollapsibleState.Expanded, + iconPath: path.join( + __filename, + "../../../../resources/icons/browser.svg" + ), }, { label: friendlyBindingNames.hyperdrive, collapsibleState: vscode.TreeItemCollapsibleState.Expanded, + iconPath: path.join( + __filename, + "../../../../resources/icons/hyperdrive.svg" + ), }, { label: friendlyBindingNames.mtls_certificates, collapsibleState: vscode.TreeItemCollapsibleState.Expanded, + iconPath: path.join( + __filename, + "../../../../resources/icons/mtls_certificates.svg" + ), }, { label: friendlyBindingNames.services, collapsibleState: vscode.TreeItemCollapsibleState.Expanded, + iconPath: path.join( + __filename, + "../../../../resources/icons/services.svg" + ), }, { label: friendlyBindingNames.assets, collapsibleState: vscode.TreeItemCollapsibleState.Expanded, + iconPath: path.join( + __filename, + "../../../../resources/icons/assets.svg" + ), }, { label: friendlyBindingNames.vectorize, collapsibleState: vscode.TreeItemCollapsibleState.Expanded, + iconPath: path.join( + __filename, + "../../../../resources/icons/vectorize.svg" + ), }, { label: friendlyBindingNames.version_metadata, collapsibleState: vscode.TreeItemCollapsibleState.Expanded, + iconPath: path.join( + __filename, + "../../../../resources/icons/version_metadata.svg" + ), }, { label: friendlyBindingNames.dispatch_namespaces, collapsibleState: vscode.TreeItemCollapsibleState.Expanded, + iconPath: path.join( + __filename, + "../../../../resources/icons/dispatch_namespaces.svg" + ), }, { label: friendlyBindingNames.queues, collapsibleState: vscode.TreeItemCollapsibleState.Expanded, + iconPath: path.join( + __filename, + "../../../../resources/icons/queues.svg" + ), }, { label: friendlyBindingNames.workflows, collapsibleState: vscode.TreeItemCollapsibleState.Expanded, + iconPath: path.join( + __filename, + "../../../../resources/icons/workflows.svg" + ), }, { label: friendlyBindingNames.send_email, collapsibleState: vscode.TreeItemCollapsibleState.Expanded, + iconPath: path.join( + __filename, + "../../../../resources/icons/send_email.svg" + ), }, { label: friendlyBindingNames.logfwdr, collapsibleState: vscode.TreeItemCollapsibleState.Expanded, + iconPath: path.join( + __filename, + "../../../../resources/icons/logfwdr.svg" + ), }, { label: friendlyBindingNames.pipelines, collapsibleState: vscode.TreeItemCollapsibleState.Expanded, + iconPath: path.join( + __filename, + "../../../../resources/icons/pipelines.svg" + ), }, { label: friendlyBindingNames.vars, collapsibleState: vscode.TreeItemCollapsibleState.Expanded, + iconPath: path.join( + __filename, + "../../../../resources/icons/vars.svg" + ), }, { label: friendlyBindingNames.unsafe, collapsibleState: vscode.TreeItemCollapsibleState.Expanded, + iconPath: path.join( + __filename, + "../../../../resources/icons/unsafe.svg" + ), }, ] ); @@ -528,6 +702,11 @@ describe("Extension Test Suite", () => { { label: friendlyBindingNames.d1_databases, collapsibleState: vscode.TreeItemCollapsibleState.Expanded, + contextValue: "binding", + iconPath: path.join( + __filename, + "../../../../resources/icons/d1_databases.svg" + ), }, ] ); @@ -539,6 +718,11 @@ describe("Extension Test Suite", () => { { label: friendlyBindingNames.kv_namespaces, collapsibleState: vscode.TreeItemCollapsibleState.Expanded, + contextValue: "binding", + iconPath: path.join( + __filename, + "../../../../resources/icons/kv_namespaces.svg" + ), }, ] ); @@ -553,6 +737,11 @@ describe("Extension Test Suite", () => { { label: friendlyBindingNames.r2_buckets, collapsibleState: vscode.TreeItemCollapsibleState.Expanded, + contextValue: "binding", + iconPath: path.join( + __filename, + "../../../../resources/icons/r2_buckets.svg" + ), }, ] ); @@ -703,6 +892,8 @@ async function symlinkWranglerNodeModule() { "wrangler" ); + console.log("wranglerNodeModulePath", wranglerNodeModulePath); + console.log("nodeModulesPath", nodeModulesPath); await fs.mkdir(nodeModulesPath, { recursive: true }); await fs.symlink( wranglerNodeModulePath, diff --git a/packages/cloudflare-workers-bindings-extension/src/wrangler.ts b/packages/cloudflare-workers-bindings-extension/src/wrangler.ts index 2e35d08288e2..a4fbcc4e0146 100644 --- a/packages/cloudflare-workers-bindings-extension/src/wrangler.ts +++ b/packages/cloudflare-workers-bindings-extension/src/wrangler.ts @@ -1,8 +1,10 @@ +import { existsSync, readFileSync } from "fs"; import * as path from "path"; +import * as vscode from "vscode"; export function importWrangler( workspaceRoot: string -): typeof import("wrangler") { +): typeof import("wrangler") | undefined { const wrangler = path.join( workspaceRoot, "node_modules", @@ -10,6 +12,38 @@ export function importWrangler( "wrangler-dist", "cli.js" ); + if (!existsSync(wrangler)) { + vscode.window.showErrorMessage( + "Cannot find Wrangler. Have you run `npm install` in your project directory?" + ); + return; + } + const packageJsonPath = path.join( + workspaceRoot, + "node_modules", + "wrangler", + "package.json" + ); + const wranglerVersion = JSON.parse(readFileSync(packageJsonPath, "utf8")) + .version as string; + const isPreRelease = wranglerVersion.startsWith("0.0.0"); + const major = parseInt(wranglerVersion.split(".")[0]); + const minor = parseInt(wranglerVersion.split(".")[1]); + // min version is 3.99.0 (and there were no patches for 3.99.0) + // will probably need to update this to whatever one --x-provision is released on :') + if ((major < 3 && !isPreRelease) || (major === 3 && minor < 99)) { + vscode.commands.executeCommand( + "setContext", + "ext.unsupportedWrangler", + true + ); + return; + } + vscode.commands.executeCommand( + "setContext", + "ext.unsupportedWrangler", + false + ); return require(wrangler); }