Skip to content

Commit

Permalink
feat(cloudflare-workers-bindings-extensions): list bindings on the si…
Browse files Browse the repository at this point in the history
…debar (#7582)
  • Loading branch information
edmundhung authored Dec 19, 2024
1 parent 48e7e10 commit ab0ac94
Show file tree
Hide file tree
Showing 10 changed files with 794 additions and 210 deletions.
5 changes: 5 additions & 0 deletions .changeset/warm-squids-visit.md
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`)

This file was deleted.

44 changes: 30 additions & 14 deletions packages/cloudflare-workers-bindings-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"package": "pnpm run check:type && pnpm run check:lint && node esbuild.js --production",
"compile-tests": "tsc -p . --outDir out",
"watch-tests": "tsc -p . -w --outDir out",
"test": "node ./out/test/runTest.js",
"check:type": "tsc --noEmit",
"check:lint": "eslint src --ext ts",
"build": "vsce package",
Expand All @@ -27,46 +28,61 @@
"contributes": {
"commands": [
{
"command": "cloudflare-workers-bindings-extension.testCommand",
"title": "Test command",
"icon": {
"light": "resources/light/edit.svg",
"dark": "resources/dark/edit.svg"
}
"command": "cloudflare-workers-bindings.refresh",
"title": "Cloudflare Workers: Refresh bindings",
"icon": "$(refresh)"
}
],
"menus": {
"view/title": [
{
"command": "cloudflare-workers-bindings.refresh",
"when": "view == cloudflare-workers-bindings",
"group": "navigation"
}
]
},
"views": {
"cloudflare-workers-bindings": [
"cloudflare-workers": [
{
"id": "cloudflare-workers-bindings-extension",
"id": "cloudflare-workers-bindings",
"name": "Bindings",
"icon": "resources/icons/cf-workers-logo.svg"
"icon": "media/cf-workers-logo.svg",
"contextualTitle": "Cloudflare Workers Bindings"
}
]
},
"viewsContainers": {
"activitybar": [
{
"id": "cloudflare-workers-bindings",
"id": "cloudflare-workers",
"title": "Cloudflare Workers",
"icon": "media/cf-workers-logo.svg"
}
]
}
},
"viewsWelcome": [
{
"view": "cloudflare-workers-bindings",
"contents": "Welcome to Cloudflare Workers! [Learn more](https://workers.cloudflare.com).\n[Refresh Bindings](command:cloudflare-workers-bindings.refresh)"
}
]
},
"activationEvents": [
"workspaceContains:{**/wrangler.json,**/wrangler.jsonc,**/wrangler.toml}"
"workspaceContains:**/wrangler.{json,jsonc,toml}"
],
"devDependencies": {
"@types/glob": "^7.1.1",
"@types/mocha": "^10.0.7",
"@types/node": "20.x",
"@types/vscode": "^1.92.0",
"@typescript-eslint/eslint-plugin": "^7.14.1",
"@typescript-eslint/parser": "^7.11.0",
"@vscode/test-cli": "^0.0.9",
"@vscode/test-electron": "^2.4.0",
"@vscode/test-electron": "^2.4.1",
"esbuild": "^0.21.5",
"eslint": "^8.57.0",
"glob": "^7.1.4",
"mocha": "^10.2.0",
"npm-run-all": "^4.1.5",
"typescript": "^5.4.5",
"vsce": "^2.15.0",
Expand Down
225 changes: 225 additions & 0 deletions packages/cloudflare-workers-bindings-extension/src/bindings.ts
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 packages/cloudflare-workers-bindings-extension/src/extension.ts
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,
};
}
Loading

0 comments on commit ab0ac94

Please sign in to comment.