pi-profiles-manager 1.1.4 → 1.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/extension.test.ts +46 -17
- package/src/extension.ts +21 -10
package/package.json
CHANGED
package/src/extension.test.ts
CHANGED
|
@@ -34,6 +34,7 @@ vi.mock("@earendil-works/pi-tui", () => ({
|
|
|
34
34
|
},
|
|
35
35
|
Text: class {
|
|
36
36
|
constructor(...args: unknown[]) { tuiState.texts.push(args); }
|
|
37
|
+
setText(text: string) { tuiState.texts.push([text, 1, 0]); }
|
|
37
38
|
},
|
|
38
39
|
matchesKey: (data: string, key: string) => data === key,
|
|
39
40
|
}));
|
|
@@ -266,7 +267,7 @@ describe("package extension", () => {
|
|
|
266
267
|
});
|
|
267
268
|
|
|
268
269
|
it.each(["return", "escape"])(
|
|
269
|
-
"keeps export
|
|
270
|
+
"keeps export string visible until %s dismisses it",
|
|
270
271
|
async (dismissKey) => {
|
|
271
272
|
const syncFs = await import("node:fs");
|
|
272
273
|
vi.mocked(syncFs.readFileSync).mockReturnValue(JSON.stringify({
|
|
@@ -290,7 +291,7 @@ describe("package extension", () => {
|
|
|
290
291
|
.fn()
|
|
291
292
|
.mockResolvedValueOnce("alpha")
|
|
292
293
|
.mockResolvedValueOnce("export")
|
|
293
|
-
.mockImplementationOnce(async (factory:
|
|
294
|
+
.mockImplementationOnce(async (factory: (tui: { requestRender: () => void }, theme: { fg: (color: string, text: string) => string; bold: (text: string) => string }, kb: unknown, done: (value: unknown) => void) => { handleInput(data: string): void }, options: unknown) => {
|
|
294
295
|
confirmationOptions = options;
|
|
295
296
|
return await new Promise((resolve) => {
|
|
296
297
|
finishConfirmation = resolve;
|
|
@@ -320,7 +321,12 @@ describe("package extension", () => {
|
|
|
320
321
|
"piprofile:1:eyJfdHlwZSI6InBpcHJvZmlsZSIsInZlcnNpb24iOjEsInByb2ZpbGUiOnsibmFtZSI6ImFscGhhIiwib3JkZXIiOjAsImZhdm9yaXRlIjpmYWxzZX19",
|
|
321
322
|
);
|
|
322
323
|
expect(tuiState.texts).toContainEqual([
|
|
323
|
-
"
|
|
324
|
+
"piprofile:1:eyJfdHlwZSI6InBpcHJvZmlsZSIsInZlcnNpb24iOjEsInByb2ZpbGUiOnsibmFtZSI6ImFscGhhIiwib3JkZXIiOjAsImZhdm9yaXRlIjpmYWxzZX19",
|
|
325
|
+
1,
|
|
326
|
+
0,
|
|
327
|
+
]);
|
|
328
|
+
expect(tuiState.texts).toContainEqual([
|
|
329
|
+
expect.stringContaining("Copied profile 'alpha' to clipboard"),
|
|
324
330
|
1,
|
|
325
331
|
0,
|
|
326
332
|
]);
|
|
@@ -331,7 +337,7 @@ describe("package extension", () => {
|
|
|
331
337
|
expect(confirmationView).toBeDefined();
|
|
332
338
|
expect(handlerSettled).toBe(false);
|
|
333
339
|
|
|
334
|
-
confirmationView
|
|
340
|
+
confirmationView?.handleInput(dismissKey);
|
|
335
341
|
await handling;
|
|
336
342
|
|
|
337
343
|
expect(finishConfirmation).toBeDefined();
|
|
@@ -343,7 +349,7 @@ describe("package extension", () => {
|
|
|
343
349
|
},
|
|
344
350
|
);
|
|
345
351
|
|
|
346
|
-
it("
|
|
352
|
+
it("keeps export string visible and shows error if clipboard fails", async () => {
|
|
347
353
|
const syncFs = await import("node:fs");
|
|
348
354
|
vi.mocked(syncFs.readFileSync).mockReturnValue(JSON.stringify({
|
|
349
355
|
version: 1,
|
|
@@ -358,10 +364,27 @@ describe("package extension", () => {
|
|
|
358
364
|
const handler = (vi.mocked(pi.registerCommand).mock.calls as any[]).find(
|
|
359
365
|
([name]: [string]) => name === "profiles",
|
|
360
366
|
)[1].handler as (args: string, ctx: any) => Promise<void>;
|
|
367
|
+
|
|
368
|
+
let confirmationView: { handleInput(data: string): void } | undefined;
|
|
369
|
+
let finishConfirmation: ((value: unknown) => void) | undefined;
|
|
361
370
|
const custom = vi
|
|
362
371
|
.fn()
|
|
363
372
|
.mockResolvedValueOnce("alpha")
|
|
364
373
|
.mockResolvedValueOnce("export")
|
|
374
|
+
.mockImplementationOnce(async (factory: (tui: { requestRender: () => void }, theme: { fg: (color: string, text: string) => string; bold: (text: string) => string }, kb: unknown, done: (value: unknown) => void) => { handleInput(data: string): void }, _options: unknown) => {
|
|
375
|
+
return await new Promise((resolve) => {
|
|
376
|
+
finishConfirmation = resolve;
|
|
377
|
+
confirmationView = factory(
|
|
378
|
+
{ requestRender: vi.fn() },
|
|
379
|
+
{
|
|
380
|
+
fg: (_color: string, text: string) => text,
|
|
381
|
+
bold: (text: string) => text,
|
|
382
|
+
},
|
|
383
|
+
{},
|
|
384
|
+
resolve,
|
|
385
|
+
);
|
|
386
|
+
});
|
|
387
|
+
})
|
|
365
388
|
.mockResolvedValueOnce(null);
|
|
366
389
|
const ctx = {
|
|
367
390
|
sessionManager: { getSessionId: () => "session-1" },
|
|
@@ -369,22 +392,28 @@ describe("package extension", () => {
|
|
|
369
392
|
ui: { custom, notify: vi.fn(), setStatus: vi.fn() },
|
|
370
393
|
};
|
|
371
394
|
|
|
372
|
-
|
|
395
|
+
let handlerSettled = false;
|
|
396
|
+
const handling = handler("", ctx).then(() => { handlerSettled = true; });
|
|
397
|
+
await vi.waitFor(() => expect(custom).toHaveBeenCalledTimes(3));
|
|
373
398
|
|
|
374
|
-
expect(
|
|
375
|
-
"
|
|
376
|
-
"error",
|
|
377
|
-
);
|
|
378
|
-
expect(custom).toHaveBeenCalledTimes(3);
|
|
379
|
-
expect(tuiState.texts).not.toContainEqual([
|
|
380
|
-
"Copied profile 'alpha' to clipboard.",
|
|
399
|
+
expect(tuiState.texts).toContainEqual([
|
|
400
|
+
"piprofile:1:eyJfdHlwZSI6InBpcHJvZmlsZSIsInZlcnNpb24iOjEsInByb2ZpbGUiOnsibmFtZSI6ImFscGhhIiwib3JkZXIiOjAsImZhdm9yaXRlIjpmYWxzZX19",
|
|
381
401
|
1,
|
|
382
402
|
0,
|
|
383
403
|
]);
|
|
384
|
-
expect(
|
|
385
|
-
"
|
|
386
|
-
|
|
387
|
-
|
|
404
|
+
expect(tuiState.texts).toContainEqual([
|
|
405
|
+
expect.stringContaining("Failed to copy profile 'alpha' to clipboard"),
|
|
406
|
+
1,
|
|
407
|
+
0,
|
|
408
|
+
]);
|
|
409
|
+
expect(confirmationView).toBeDefined();
|
|
410
|
+
expect(handlerSettled).toBe(false);
|
|
411
|
+
|
|
412
|
+
confirmationView?.handleInput("return");
|
|
413
|
+
await handling;
|
|
414
|
+
|
|
415
|
+
expect(finishConfirmation).toBeDefined();
|
|
416
|
+
expect(handlerSettled).toBe(true);
|
|
388
417
|
});
|
|
389
418
|
|
|
390
419
|
it("imports typed profile strings into the authoritative config without legacy storage", async () => {
|
package/src/extension.ts
CHANGED
|
@@ -174,12 +174,26 @@ function prompt(ctx: ContextLike, title: string, initial = ""): Promise<string |
|
|
|
174
174
|
}, { overlay: true });
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
function
|
|
178
|
-
return (ctx.ui.custom as
|
|
179
|
-
const container = new Container() as
|
|
177
|
+
function showExportDialog(ctx: ContextLike, profileName: string, exportString: string, copyPromise: Promise<void>): Promise<void> {
|
|
178
|
+
return (ctx.ui.custom as (factory: unknown, options?: unknown) => Promise<void>)((tui: { requestRender: () => void }, theme: { fg: (color: string, text: string) => string; bold: (text: string) => string }, _kb: unknown, done: (value: void) => void) => {
|
|
179
|
+
const container = new Container() as { addChild: (child: unknown) => void; render: (width: number) => unknown; invalidate: () => void };
|
|
180
|
+
const statusText = new Text(theme.fg("accent", theme.bold(`Copying profile '${profileName}' to clipboard...`)), 1, 0);
|
|
181
|
+
|
|
180
182
|
container.addChild(new DynamicBorder((value: string) => theme.fg("accent", value)));
|
|
181
|
-
container.addChild(
|
|
183
|
+
container.addChild(statusText);
|
|
184
|
+
container.addChild(new Text(theme.fg("accent", exportString), 1, 0));
|
|
182
185
|
container.addChild(new DynamicBorder((value: string) => theme.fg("accent", value)));
|
|
186
|
+
|
|
187
|
+
copyPromise.then(() => {
|
|
188
|
+
statusText.setText(theme.fg("accent", theme.bold(`Copied profile '${profileName}' to clipboard.`)));
|
|
189
|
+
container.invalidate();
|
|
190
|
+
tui.requestRender();
|
|
191
|
+
}).catch((error) => {
|
|
192
|
+
statusText.setText(theme.fg("error", theme.bold(`Failed to copy profile '${profileName}' to clipboard: ${error instanceof Error ? error.message : String(error)}`)));
|
|
193
|
+
container.invalidate();
|
|
194
|
+
tui.requestRender();
|
|
195
|
+
});
|
|
196
|
+
|
|
183
197
|
return {
|
|
184
198
|
render: (width: number) => container.render(width),
|
|
185
199
|
invalidate: () => container.invalidate(),
|
|
@@ -509,12 +523,9 @@ export default function extension(pi: PiLike) {
|
|
|
509
523
|
favorite: selected === manager.config.defaultProfile,
|
|
510
524
|
},
|
|
511
525
|
})).toString("base64");
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
} catch (error) {
|
|
516
|
-
ctx.ui.notify(`Failed to copy profile '${selected}' to clipboard: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
517
|
-
}
|
|
526
|
+
const exportString = `piprofile:1:${encoded}`;
|
|
527
|
+
const copyPromise = copyToClipboard(exportString);
|
|
528
|
+
await showExportDialog(ctx, selected, exportString, copyPromise);
|
|
518
529
|
continue;
|
|
519
530
|
}
|
|
520
531
|
if (action === "delete") {
|