Skip to content

Commit 0e561fa

Browse files
committed
changeset
1 parent 0786502 commit 0e561fa

File tree

2 files changed

+388
-0
lines changed

2 files changed

+388
-0
lines changed

.changeset/blue-laws-bathe.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
feat: add experimental_readRawConfig()
6+
7+
Adds a Wrangler API to find and read a config file
Lines changed: 381 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,381 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
import crypto from "crypto";
6+
import path from "path";
7+
import {
8+
Disposable,
9+
ExtensionContext,
10+
QuickInput,
11+
QuickInputButton,
12+
QuickInputButtons,
13+
QuickPickItem,
14+
ThemeIcon,
15+
Uri,
16+
window,
17+
workspace,
18+
} from "vscode";
19+
import { importWrangler } from "./wrangler";
20+
21+
const encoder = new TextEncoder();
22+
const kvApiResponse = {
23+
result: [],
24+
success: true,
25+
errors: [],
26+
messages: [],
27+
result_info: {
28+
page: 1,
29+
per_page: 20,
30+
count: 20,
31+
total_count: 101,
32+
total_pages: 5,
33+
},
34+
};
35+
class BindingType implements QuickPickItem {
36+
constructor(
37+
public label: string,
38+
public description?: string,
39+
public detail?: string,
40+
public iconPath?: Uri
41+
) {}
42+
}
43+
/**
44+
* A multi-step input using window.createQuickPick() and window.createInputBox().
45+
*
46+
* This first part uses the helper class `MultiStepInput` that wraps the API for the multi-step case.
47+
*/
48+
export async function multiStepInput(
49+
context: ExtensionContext,
50+
rootPath: string
51+
) {
52+
const bindingTypes: BindingType[] = [
53+
new BindingType(
54+
"KV",
55+
"kv_namespaces",
56+
"Global, low-latency, key-value data storage",
57+
Uri.file(context.asAbsolutePath("resources/icons/kv.svg"))
58+
),
59+
new BindingType(
60+
"R2",
61+
"r2_buckets",
62+
"Object storage for all your data",
63+
Uri.file(context.asAbsolutePath("resources/icons/r2.svg"))
64+
),
65+
new BindingType(
66+
"D1",
67+
"d1_databases",
68+
"Serverless SQL databases",
69+
Uri.file(context.asAbsolutePath("resources/icons/d1.svg"))
70+
),
71+
];
72+
73+
interface State {
74+
title: string;
75+
step: number;
76+
totalSteps: number;
77+
bindingType: BindingType;
78+
name: string;
79+
runtime: QuickPickItem;
80+
id: string;
81+
}
82+
83+
async function collectInputs() {
84+
const state = {} as Partial<State>;
85+
await MultiStepInput.run((input) => pickResourceGroup(input, state));
86+
return state as State;
87+
}
88+
89+
const title = "Add binding";
90+
91+
async function pickResourceGroup(
92+
input: MultiStepInput,
93+
state: Partial<State>
94+
) {
95+
const pick = await input.showQuickPick({
96+
title,
97+
step: 1,
98+
totalSteps: 2,
99+
placeholder: "Choose a binding type",
100+
items: bindingTypes,
101+
activeItem:
102+
typeof state.bindingType !== "string" ? state.bindingType : undefined,
103+
// shouldResume,
104+
});
105+
state.bindingType = pick as BindingType;
106+
return (input: MultiStepInput) => inputName(input, state);
107+
}
108+
109+
async function inputName(input: MultiStepInput, state: Partial<State>) {
110+
// TODO: Remember current value when navigating back.
111+
112+
let name = await input.showInputBox({
113+
title,
114+
step: 2,
115+
totalSteps: 2,
116+
value: state.name || "",
117+
prompt: "Choose a binding name",
118+
validate: validateNameIsUnique,
119+
placeholder: `e.g. MY_BINDING`,
120+
// shouldResume,
121+
});
122+
state.name = name;
123+
return (input: MultiStepInput) => addToToml(input, state);
124+
}
125+
126+
async function addToToml(input: MultiStepInput, state: Partial<State>) {
127+
console.log("pretend we added to toml");
128+
129+
// const wrangler = importWrangler(rootPath);
130+
// const config = wrangler.experimental_readRawConfig({ config: rootPath });
131+
// use patch to add and write to config
132+
//
133+
134+
// await workspace
135+
// .openTextDocument(Uri.file(path.join(rootPath!, "wrangler.toml")))
136+
// .then((doc) => {
137+
// window.showTextDocument(doc);
138+
// let text = doc.getText();
139+
140+
// if (state.bindingType?.description === "r2_buckets") {
141+
// text += `
142+
// [[r2_buckets]]
143+
// binding = "${state.name}"`;
144+
// } else if (state.bindingType?.description === "kv_namespaces") {
145+
// text += `
146+
// [[kv_namespaces]]
147+
// binding = "${state.name}"`;
148+
// } else if (state.bindingType?.description === "d1_databases") {
149+
// text += `
150+
// [[d1_databases]]
151+
// binding = "${state.name}"`;
152+
// }
153+
154+
// workspace.fs.writeFile(doc.uri, encoder.encode(text));
155+
// });
156+
}
157+
158+
async function validateNameIsUnique(name: string) {
159+
// TODO: actually validate uniqueness
160+
// read wrangler config
161+
// check all
162+
return name === "SOME_KV_BINDING" ? "Name not unique" : undefined;
163+
}
164+
165+
const state = await collectInputs();
166+
window.showInformationMessage(`Creating Application Service '${state.name}'`);
167+
}
168+
169+
// -------------------------------------------------------
170+
// Helper code that wraps the API for the multi-step case.
171+
// -------------------------------------------------------
172+
173+
class InputFlowAction {
174+
static back = new InputFlowAction();
175+
static cancel = new InputFlowAction();
176+
static resume = new InputFlowAction();
177+
}
178+
179+
type InputStep = (input: MultiStepInput) => Thenable<InputStep | void>;
180+
181+
interface QuickPickParameters<T extends QuickPickItem> {
182+
title: string;
183+
step: number;
184+
totalSteps: number;
185+
items: T[];
186+
activeItem?: T;
187+
ignoreFocusOut?: boolean;
188+
placeholder: string;
189+
buttons?: QuickInputButton[];
190+
}
191+
192+
interface InputBoxParameters {
193+
title: string;
194+
step: number;
195+
totalSteps: number;
196+
value: string;
197+
prompt: string;
198+
validate: (value: string) => Promise<string | undefined>;
199+
buttons?: QuickInputButton[];
200+
ignoreFocusOut?: boolean;
201+
placeholder?: string;
202+
// shouldResume: () => Thenable<boolean>;
203+
}
204+
205+
export class MultiStepInput {
206+
static async run<T>(start: InputStep) {
207+
const input = new MultiStepInput();
208+
return input.stepThrough(start);
209+
}
210+
211+
private current?: QuickInput;
212+
private steps: InputStep[] = [];
213+
214+
private async stepThrough<T>(start: InputStep) {
215+
let step: InputStep | void = start;
216+
while (step) {
217+
this.steps.push(step);
218+
if (this.current) {
219+
this.current.enabled = false;
220+
this.current.busy = true;
221+
}
222+
try {
223+
step = await step(this);
224+
} catch (err) {
225+
if (err === InputFlowAction.back) {
226+
this.steps.pop();
227+
step = this.steps.pop();
228+
} else if (err === InputFlowAction.resume) {
229+
step = this.steps.pop();
230+
} else if (err === InputFlowAction.cancel) {
231+
step = undefined;
232+
} else {
233+
throw err;
234+
}
235+
}
236+
}
237+
if (this.current) {
238+
this.current.dispose();
239+
}
240+
}
241+
242+
async showQuickPick<
243+
T extends QuickPickItem,
244+
P extends QuickPickParameters<T>,
245+
>({
246+
title,
247+
step,
248+
totalSteps,
249+
items,
250+
activeItem,
251+
ignoreFocusOut,
252+
placeholder,
253+
buttons,
254+
// shouldResume,
255+
}: P) {
256+
const disposables: Disposable[] = [];
257+
try {
258+
return await new Promise<
259+
T | (P extends { buttons: (infer I)[] } ? I : never)
260+
>((resolve, reject) => {
261+
const input = window.createQuickPick<T>();
262+
input.title = title;
263+
input.step = step;
264+
input.totalSteps = totalSteps;
265+
input.ignoreFocusOut = ignoreFocusOut ?? false;
266+
input.placeholder = placeholder;
267+
input.items = items;
268+
if (activeItem) {
269+
input.activeItems = [activeItem];
270+
}
271+
input.buttons = [
272+
...(this.steps.length > 1 ? [QuickInputButtons.Back] : []),
273+
...(buttons || []),
274+
];
275+
disposables.push(
276+
input.onDidTriggerButton((item) => {
277+
if (item === QuickInputButtons.Back) {
278+
reject(InputFlowAction.back);
279+
} else {
280+
resolve(<any>item);
281+
}
282+
}),
283+
input.onDidChangeSelection((items) => resolve(items[0]))
284+
// input.onDidHide(() => {
285+
// (async () => {
286+
// reject(
287+
// shouldResume && (await shouldResume())
288+
// ? InputFlowAction.resume
289+
// : InputFlowAction.cancel
290+
// );
291+
// })().catch(reject);
292+
// })
293+
);
294+
if (this.current) {
295+
this.current.dispose();
296+
}
297+
this.current = input;
298+
this.current.show();
299+
});
300+
} finally {
301+
disposables.forEach((d) => d.dispose());
302+
}
303+
}
304+
305+
async showInputBox<P extends InputBoxParameters>({
306+
title,
307+
step,
308+
totalSteps,
309+
value,
310+
prompt,
311+
validate,
312+
buttons,
313+
ignoreFocusOut,
314+
placeholder,
315+
// shouldResume,
316+
}: P) {
317+
const disposables: Disposable[] = [];
318+
try {
319+
return await new Promise<
320+
string | (P extends { buttons: (infer I)[] } ? I : never)
321+
>((resolve, reject) => {
322+
const input = window.createInputBox();
323+
input.title = title;
324+
input.step = step;
325+
input.totalSteps = totalSteps;
326+
input.value = value || "";
327+
input.prompt = prompt;
328+
input.ignoreFocusOut = ignoreFocusOut ?? false;
329+
input.placeholder = placeholder;
330+
input.buttons = [
331+
...(this.steps.length > 1 ? [QuickInputButtons.Back] : []),
332+
...(buttons || []),
333+
];
334+
let validating = validate("");
335+
disposables.push(
336+
input.onDidTriggerButton((item) => {
337+
if (item === QuickInputButtons.Back) {
338+
reject(InputFlowAction.back);
339+
} else {
340+
resolve(<any>item);
341+
}
342+
}),
343+
input.onDidAccept(async () => {
344+
const value = input.value;
345+
input.enabled = false;
346+
input.busy = true;
347+
if (!(await validate(value))) {
348+
resolve(value);
349+
}
350+
input.enabled = true;
351+
input.busy = false;
352+
}),
353+
input.onDidChangeValue(async (text) => {
354+
const current = validate(text);
355+
validating = current;
356+
const validationMessage = await current;
357+
if (current === validating) {
358+
input.validationMessage = validationMessage;
359+
}
360+
})
361+
// input.onDidHide(() => {
362+
// (async () => {
363+
// reject(
364+
// shouldResume && (await shouldResume())
365+
// ? InputFlowAction.resume
366+
// : InputFlowAction.cancel
367+
// );
368+
// })().catch(reject);
369+
// })
370+
);
371+
if (this.current) {
372+
this.current.dispose();
373+
}
374+
this.current = input;
375+
this.current.show();
376+
});
377+
} finally {
378+
disposables.forEach((d) => d.dispose());
379+
}
380+
}
381+
}

0 commit comments

Comments
 (0)