-
Notifications
You must be signed in to change notification settings - Fork 762
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(cloudflare-workers-bindings-extensions): list bindings on the si…
…debar (#7582)
- Loading branch information
1 parent
48e7e10
commit ab0ac94
Showing
10 changed files
with
794 additions
and
210 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"cloudflare-workers-bindings-extension": patch | ||
--- | ||
|
||
Introduce a bindings view that lists all the KV, D1 and R2 bindings on the wrangler config (e.g. `wrangler.toml`, `wrangler.jsonc`) |
5 changes: 0 additions & 5 deletions
5
packages/cloudflare-workers-bindings-extension/.vscode-test.mjs
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
225 changes: 225 additions & 0 deletions
225
packages/cloudflare-workers-bindings-extension/src/bindings.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
import * as vscode from "vscode"; | ||
import { importWrangler } from "./wrangler"; | ||
|
||
type Config = ReturnType< | ||
ReturnType<typeof importWrangler>["experimental_readRawConfig"] | ||
>["rawConfig"]; | ||
|
||
type Node = | ||
| { | ||
type: "env"; | ||
config: Config; | ||
env: string | null; | ||
} | ||
| { | ||
type: "binding"; | ||
config: Config; | ||
env: string | null; | ||
binding: string; | ||
} | ||
| { | ||
type: "resource"; | ||
config: Config; | ||
env: string | null; | ||
binding: string; | ||
name: string; | ||
description?: string; | ||
}; | ||
|
||
export class BindingsProvider implements vscode.TreeDataProvider<Node> { | ||
// Event emitter for refreshing the tree | ||
private _onDidChangeTreeData: vscode.EventEmitter< | ||
Node | undefined | null | void | ||
> = new vscode.EventEmitter<Node | undefined | null | void>(); | ||
|
||
// To notify the TreeView that the tree data has changed | ||
readonly onDidChangeTreeData: vscode.Event<Node | undefined | null | void> = | ||
this._onDidChangeTreeData.event; | ||
|
||
// To manually refresh the tree | ||
refresh(): void { | ||
this._onDidChangeTreeData.fire(); | ||
} | ||
|
||
getTreeItem(node: Node): vscode.TreeItem { | ||
switch (node.type) { | ||
case "env": { | ||
const item = new vscode.TreeItem( | ||
node.env ?? "Top-level env", | ||
vscode.TreeItemCollapsibleState.Expanded | ||
); | ||
|
||
return item; | ||
} | ||
case "binding": { | ||
return new vscode.TreeItem( | ||
node.binding, | ||
vscode.TreeItemCollapsibleState.Expanded | ||
); | ||
} | ||
case "resource": { | ||
const item = new vscode.TreeItem( | ||
node.name, | ||
vscode.TreeItemCollapsibleState.None | ||
); | ||
|
||
if (node.description) { | ||
item.description = node.description; | ||
} | ||
|
||
return item; | ||
} | ||
} | ||
} | ||
|
||
async getChildren(node?: Node): Promise<Node[]> { | ||
if (!node) { | ||
const config = await getWranglerConfig(); | ||
|
||
if (!config) { | ||
return []; | ||
} | ||
|
||
const topLevelEnvNode: Node = { | ||
type: "env", | ||
config, | ||
env: null, | ||
}; | ||
const children: Node[] = []; | ||
|
||
for (const env of Object.keys(config.env ?? {})) { | ||
const node: Node = { | ||
...topLevelEnvNode, | ||
env, | ||
}; | ||
const grandChildren = await this.getChildren(node); | ||
|
||
// Include the environment only if it has any bindings | ||
if (grandChildren.length > 0) { | ||
children.push({ | ||
...topLevelEnvNode, | ||
env, | ||
}); | ||
} | ||
} | ||
|
||
const topLevelEnvChildren = await this.getChildren(topLevelEnvNode); | ||
|
||
if (children.length > 0) { | ||
// Include top level env only if it has any bindings too | ||
if (topLevelEnvChildren.length > 0) { | ||
children.unshift(topLevelEnvNode); | ||
} | ||
|
||
return children; | ||
} | ||
|
||
// Skip the top level env if there are no environments | ||
return topLevelEnvChildren; | ||
} | ||
|
||
switch (node.type) { | ||
case "env": { | ||
const children: Node[] = []; | ||
const env = node.env ? node.config.env?.[node.env] : node.config; | ||
|
||
if (env?.kv_namespaces && env.kv_namespaces.length > 0) { | ||
children.push({ | ||
...node, | ||
type: "binding", | ||
binding: "KV Namespaces", | ||
}); | ||
} | ||
|
||
if (env?.r2_buckets && env.r2_buckets.length > 0) { | ||
children.push({ | ||
...node, | ||
type: "binding", | ||
binding: "R2 Buckets", | ||
}); | ||
} | ||
|
||
if (env?.d1_databases && env.d1_databases.length > 0) { | ||
children.push({ | ||
...node, | ||
type: "binding", | ||
binding: "D1 Databases", | ||
}); | ||
} | ||
|
||
return children; | ||
} | ||
case "binding": { | ||
const children: Node[] = []; | ||
const env = node.env ? node.config.env?.[node.env] : node.config; | ||
|
||
switch (node.binding) { | ||
case "KV Namespaces": { | ||
for (const kv of env?.kv_namespaces ?? []) { | ||
children.push({ | ||
...node, | ||
type: "resource", | ||
name: kv.binding, | ||
description: kv.id, | ||
}); | ||
} | ||
break; | ||
} | ||
case "R2 Buckets": { | ||
for (const r2 of env?.r2_buckets ?? []) { | ||
children.push({ | ||
...node, | ||
type: "resource", | ||
name: r2.binding, | ||
description: r2.bucket_name, | ||
}); | ||
} | ||
|
||
break; | ||
} | ||
case "D1 Databases": { | ||
for (const d1 of env?.d1_databases ?? []) { | ||
children.push({ | ||
...node, | ||
type: "resource", | ||
name: d1.binding, | ||
description: d1.database_id, | ||
}); | ||
} | ||
break; | ||
} | ||
} | ||
|
||
return children; | ||
} | ||
case "resource": | ||
return []; | ||
} | ||
} | ||
} | ||
|
||
// Finds the first wrangler config file in the workspace and parse it | ||
export async function getWranglerConfig(): Promise<Config | null> { | ||
const [configUri] = await vscode.workspace.findFiles( | ||
"wrangler.{toml,jsonc,json}", | ||
null, | ||
1 | ||
); | ||
|
||
if (!configUri) { | ||
return null; | ||
} | ||
|
||
const workspaceFolder = vscode.workspace.getWorkspaceFolder(configUri); | ||
|
||
if (!workspaceFolder) { | ||
return null; | ||
} | ||
|
||
const wrangler = await importWrangler(workspaceFolder.uri.fsPath); | ||
const { rawConfig } = wrangler.experimental_readRawConfig({ | ||
config: configUri.fsPath, | ||
}); | ||
|
||
return rawConfig; | ||
} |
49 changes: 33 additions & 16 deletions
49
packages/cloudflare-workers-bindings-extension/src/extension.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,41 @@ | ||
import * as vscode from "vscode"; | ||
import { importWrangler } from "./wrangler"; | ||
import { BindingsProvider } from "./bindings"; | ||
|
||
export async function activate(context: vscode.ExtensionContext) { | ||
vscode.commands.registerCommand( | ||
"cloudflare-workers-bindings-extension.testCommand", | ||
() => | ||
vscode.window.showInformationMessage(`Successfully called test command.`) | ||
export type Result = { | ||
bindingsProvider: BindingsProvider; | ||
}; | ||
|
||
export async function activate( | ||
context: vscode.ExtensionContext | ||
): Promise<Result> { | ||
// A tree data provider that returns all the bindings data from the workspace | ||
const bindingsProvider = new BindingsProvider(); | ||
// Register the tree view to list bindings | ||
const bindingsView = vscode.window.registerTreeDataProvider( | ||
"cloudflare-workers-bindings", | ||
bindingsProvider | ||
); | ||
|
||
// Watch for config file changes | ||
const watcher = vscode.workspace.createFileSystemWatcher( | ||
"**/wrangler.{toml,jsonc,json}" | ||
); | ||
|
||
const rootPath = | ||
vscode.workspace.workspaceFolders && | ||
vscode.workspace.workspaceFolders.length > 0 | ||
? vscode.workspace.workspaceFolders[0].uri.fsPath | ||
: undefined; | ||
// Refresh the bindings when the wrangler config file changes | ||
watcher.onDidChange(() => bindingsProvider.refresh()); | ||
watcher.onDidCreate(() => bindingsProvider.refresh()); | ||
watcher.onDidDelete(() => bindingsProvider.refresh()); | ||
|
||
if (!rootPath) { | ||
return; | ||
} | ||
// Register the refresh command, which is also used by the bindings view | ||
const refreshCommand = vscode.commands.registerCommand( | ||
"cloudflare-workers-bindings.refresh", | ||
() => bindingsProvider.refresh() | ||
); | ||
|
||
const wrangler = importWrangler(rootPath); | ||
// Cleanup when the extension is deactivated | ||
context.subscriptions.push(bindingsView, watcher, refreshCommand); | ||
|
||
// Do stuff with Wrangler | ||
return { | ||
bindingsProvider, | ||
}; | ||
} |
Oops, something went wrong.