Skip to content

Commit

Permalink
changeset
Browse files Browse the repository at this point in the history
  • Loading branch information
emily-shen committed Dec 17, 2024
1 parent 0786502 commit 0e561fa
Show file tree
Hide file tree
Showing 2 changed files with 388 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .changeset/blue-laws-bathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"wrangler": patch
---

feat: add experimental_readRawConfig()

Adds a Wrangler API to find and read a config file
381 changes: 381 additions & 0 deletions packages/cloudflare-workers-bindings-extension/src/add-binding.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,381 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import crypto from "crypto";
import path from "path";
import {
Disposable,
ExtensionContext,
QuickInput,
QuickInputButton,
QuickInputButtons,
QuickPickItem,
ThemeIcon,
Uri,
window,
workspace,
} from "vscode";
import { importWrangler } from "./wrangler";

const encoder = new TextEncoder();
const kvApiResponse = {
result: [],
success: true,
errors: [],
messages: [],
result_info: {
page: 1,
per_page: 20,
count: 20,
total_count: 101,
total_pages: 5,
},
};
class BindingType implements QuickPickItem {
constructor(
public label: string,
public description?: string,
public detail?: string,
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
) {
const bindingTypes: BindingType[] = [
new BindingType(
"KV",
"kv_namespaces",
"Global, low-latency, key-value data storage",
Uri.file(context.asAbsolutePath("resources/icons/kv.svg"))
),
new BindingType(
"R2",
"r2_buckets",
"Object storage for all your data",
Uri.file(context.asAbsolutePath("resources/icons/r2.svg"))
),
new BindingType(
"D1",
"d1_databases",
"Serverless SQL databases",
Uri.file(context.asAbsolutePath("resources/icons/d1.svg"))
),
];

interface State {
title: string;
step: number;
totalSteps: number;
bindingType: BindingType;
name: string;
runtime: QuickPickItem;
id: string;
}

async function collectInputs() {
const state = {} as Partial<State>;
await MultiStepInput.run((input) => pickResourceGroup(input, state));
return state as State;
}

const title = "Add binding";

async function pickResourceGroup(
input: MultiStepInput,
state: Partial<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,
// shouldResume,
});
state.bindingType = pick as BindingType;
return (input: MultiStepInput) => inputName(input, state);
}

async function inputName(input: MultiStepInput, state: Partial<State>) {
// TODO: Remember current value when navigating back.

let name = await input.showInputBox({
title,
step: 2,
totalSteps: 2,
value: state.name || "",
prompt: "Choose a binding name",
validate: validateNameIsUnique,
placeholder: `e.g. MY_BINDING`,
// shouldResume,
});
state.name = name;
return (input: MultiStepInput) => addToToml(input, state);
}

async function addToToml(input: MultiStepInput, state: Partial<State>) {
console.log("pretend we added to toml");

// const wrangler = importWrangler(rootPath);
// const config = wrangler.experimental_readRawConfig({ config: rootPath });
// use patch to add and write to config
//

// await workspace
// .openTextDocument(Uri.file(path.join(rootPath!, "wrangler.toml")))
// .then((doc) => {
// window.showTextDocument(doc);
// let text = doc.getText();

// if (state.bindingType?.description === "r2_buckets") {
// text += `
// [[r2_buckets]]
// binding = "${state.name}"`;
// } else if (state.bindingType?.description === "kv_namespaces") {
// text += `
// [[kv_namespaces]]
// binding = "${state.name}"`;
// } else if (state.bindingType?.description === "d1_databases") {
// text += `
// [[d1_databases]]
// binding = "${state.name}"`;
// }

// workspace.fs.writeFile(doc.uri, encoder.encode(text));
// });
}

async function validateNameIsUnique(name: string) {
// TODO: actually validate uniqueness
// read wrangler config
// check all
return name === "SOME_KV_BINDING" ? "Name not unique" : undefined;
}

const state = await collectInputs();
window.showInformationMessage(`Creating Application Service '${state.name}'`);
}

// -------------------------------------------------------
// Helper code that wraps the API for the multi-step case.
// -------------------------------------------------------

class InputFlowAction {
static back = new InputFlowAction();
static cancel = new InputFlowAction();
static resume = new InputFlowAction();
}

type InputStep = (input: MultiStepInput) => Thenable<InputStep | void>;

interface QuickPickParameters<T extends QuickPickItem> {
title: string;
step: number;
totalSteps: number;
items: T[];
activeItem?: T;
ignoreFocusOut?: boolean;
placeholder: string;
buttons?: QuickInputButton[];
}

interface InputBoxParameters {
title: string;
step: number;
totalSteps: number;
value: string;
prompt: string;
validate: (value: string) => Promise<string | undefined>;
buttons?: QuickInputButton[];
ignoreFocusOut?: boolean;
placeholder?: string;
// shouldResume: () => Thenable<boolean>;
}

export class MultiStepInput {
static async run<T>(start: InputStep) {
const input = new MultiStepInput();
return input.stepThrough(start);
}

private current?: QuickInput;
private steps: InputStep[] = [];

private async stepThrough<T>(start: InputStep) {
let step: InputStep | void = start;
while (step) {
this.steps.push(step);
if (this.current) {
this.current.enabled = false;
this.current.busy = true;
}
try {
step = await step(this);
} catch (err) {
if (err === InputFlowAction.back) {
this.steps.pop();
step = this.steps.pop();
} else if (err === InputFlowAction.resume) {
step = this.steps.pop();
} else if (err === InputFlowAction.cancel) {
step = undefined;
} else {
throw err;
}
}
}
if (this.current) {
this.current.dispose();
}
}

async showQuickPick<
T extends QuickPickItem,
P extends QuickPickParameters<T>,
>({
title,
step,
totalSteps,
items,
activeItem,
ignoreFocusOut,
placeholder,
buttons,
// shouldResume,
}: P) {
const disposables: Disposable[] = [];
try {
return await new Promise<
T | (P extends { buttons: (infer I)[] } ? I : never)
>((resolve, reject) => {
const input = window.createQuickPick<T>();
input.title = title;
input.step = step;
input.totalSteps = totalSteps;
input.ignoreFocusOut = ignoreFocusOut ?? false;
input.placeholder = placeholder;
input.items = items;
if (activeItem) {
input.activeItems = [activeItem];
}
input.buttons = [
...(this.steps.length > 1 ? [QuickInputButtons.Back] : []),
...(buttons || []),
];
disposables.push(
input.onDidTriggerButton((item) => {
if (item === QuickInputButtons.Back) {
reject(InputFlowAction.back);
} else {
resolve(<any>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<P extends InputBoxParameters>({
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(<any>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());
}
}
}

0 comments on commit 0e561fa

Please sign in to comment.