@frockbot/plugin-desktop-directory-picker 0.0.0 → 0.1.1

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/frockbot.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "schemaVersion": 3,
3
+ "id": "desktop-directory-picker",
4
+ "displayName": "Desktop Directory Picker",
5
+ "version": "0.0.1",
6
+ "compatibility": { "frockbot": ">=0.0.1" },
7
+ "contributions": {
8
+ "desktop": {
9
+ "entry": "./desktop",
10
+ "execution": "trusted-main",
11
+ "commands": []
12
+ }
13
+ },
14
+ "permissions": ["desktop:directory-picker"]
15
+ }
package/package.json CHANGED
@@ -1,14 +1,36 @@
1
1
  {
2
2
  "name": "@frockbot/plugin-desktop-directory-picker",
3
- "version": "0.0.0",
4
- "description": "Placeholder reserving this name for trusted publishing. Superseded by the first release.",
5
- "license": "UNLICENSED",
3
+ "version": "0.1.1",
4
+ "private": false,
5
+ "type": "module",
6
+ "exports": {
7
+ ".": "./src/index.ts",
8
+ "./desktop": "./src/desktop.ts",
9
+ "./manifest": "./src/manifest.ts",
10
+ "./frockbot.json": "./frockbot.json",
11
+ "./package.json": "./package.json"
12
+ },
13
+ "frockbot": {
14
+ "manifest": "./frockbot.json"
15
+ },
16
+ "scripts": {
17
+ "test": "bun test src",
18
+ "typecheck": "tsc --noEmit -p tsconfig.json"
19
+ },
20
+ "dependencies": {
21
+ "@frockbot/desktop-core": "0.1.1",
22
+ "cordis": "4.0.0-rc.8"
23
+ },
24
+ "devDependencies": {
25
+ "@types/bun": "1.4.0",
26
+ "typescript": "^7.0.2"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
6
31
  "repository": {
7
32
  "type": "git",
8
33
  "url": "git+https://github.com/timoconnellaus/frockbot.git",
9
34
  "directory": "packages/plugin-desktop-directory-picker"
10
- },
11
- "publishConfig": {
12
- "access": "public"
13
35
  }
14
36
  }
@@ -0,0 +1,122 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+ import {
3
+ DesktopCommandRegistry,
4
+ DesktopDirectoryPickerCapability,
5
+ type DesktopDirectoryPickerRequest,
6
+ type DesktopDirectoryPickerResult,
7
+ } from "@frockbot/desktop-core";
8
+ import { Context } from "cordis";
9
+ import {
10
+ desktopDirectoryPickerPlugin,
11
+ PICK_DIRECTORY_COMMAND,
12
+ } from "./desktop.js";
13
+
14
+ class FakeDirectoryPicker extends DesktopDirectoryPickerCapability {
15
+ requests: DesktopDirectoryPickerRequest[] = [];
16
+ result: DesktopDirectoryPickerResult = {
17
+ paths: ["/tmp/frockbot"],
18
+ cancelled: false,
19
+ };
20
+
21
+ pick(
22
+ request: DesktopDirectoryPickerRequest,
23
+ signal: AbortSignal,
24
+ ): Promise<DesktopDirectoryPickerResult> {
25
+ signal.throwIfAborted();
26
+ this.requests.push(request);
27
+ return Promise.resolve(this.result);
28
+ }
29
+ }
30
+
31
+ const roots: Context[] = [];
32
+
33
+ async function createHost(): Promise<{
34
+ root: Context;
35
+ picker: FakeDirectoryPicker;
36
+ }> {
37
+ const root = new Context();
38
+ roots.push(root);
39
+ await root.plugin(DesktopCommandRegistry);
40
+ await root.plugin(FakeDirectoryPicker);
41
+ return {
42
+ root,
43
+ picker: root.desktopDirectoryPicker as FakeDirectoryPicker,
44
+ };
45
+ }
46
+
47
+ afterEach(async () => {
48
+ await Promise.all(roots.splice(0).map((root) => root.fiber.dispose()));
49
+ });
50
+
51
+ async function expectFailure(
52
+ promise: Promise<unknown>,
53
+ message: string,
54
+ ): Promise<void> {
55
+ let failure: unknown;
56
+ try {
57
+ await promise;
58
+ } catch (error) {
59
+ failure = error;
60
+ }
61
+ expect(failure).toBeInstanceOf(Error);
62
+ expect(failure instanceof Error ? failure.message : "").toContain(message);
63
+ }
64
+
65
+ describe("desktop directory picker plugin", () => {
66
+ test("registers a directory command backed by the granted capability", async () => {
67
+ const { root, picker } = await createHost();
68
+ await root.plugin(desktopDirectoryPickerPlugin);
69
+
70
+ expect(
71
+ await root.desktopCommands.invoke<DesktopDirectoryPickerResult>(
72
+ PICK_DIRECTORY_COMMAND,
73
+ { title: " Choose a workspace ", multiple: true },
74
+ ),
75
+ ).toEqual({ paths: ["/tmp/frockbot"], cancelled: false });
76
+ expect(picker.requests).toEqual([
77
+ {
78
+ mode: "directory",
79
+ title: "Choose a workspace",
80
+ multiple: true,
81
+ },
82
+ ]);
83
+ });
84
+
85
+ test("preserves cancellation as an ordinary capability result", async () => {
86
+ const { root, picker } = await createHost();
87
+ picker.result = { paths: [], cancelled: true };
88
+ await root.plugin(desktopDirectoryPickerPlugin);
89
+
90
+ expect(
91
+ await root.desktopCommands.invoke<DesktopDirectoryPickerResult>(
92
+ PICK_DIRECTORY_COMMAND,
93
+ {},
94
+ ),
95
+ ).toEqual({ paths: [], cancelled: true });
96
+ });
97
+
98
+ test("unregisters its command on disposal", async () => {
99
+ const { root } = await createHost();
100
+ const fiber = await root.plugin(desktopDirectoryPickerPlugin);
101
+
102
+ await fiber.dispose();
103
+
104
+ await expectFailure(
105
+ root.desktopCommands.invoke(PICK_DIRECTORY_COMMAND, {}),
106
+ "is unavailable",
107
+ );
108
+ });
109
+
110
+ test("rejects malformed input before invoking the capability", async () => {
111
+ const { root, picker } = await createHost();
112
+ await root.plugin(desktopDirectoryPickerPlugin);
113
+
114
+ await expectFailure(
115
+ root.desktopCommands.invoke(PICK_DIRECTORY_COMMAND, {
116
+ multiple: "yes",
117
+ }),
118
+ "directory picker multiple must be a boolean",
119
+ );
120
+ expect(picker.requests).toEqual([]);
121
+ });
122
+ });
package/src/desktop.ts ADDED
@@ -0,0 +1,60 @@
1
+ import type {
2
+ DesktopCommand,
3
+ DesktopDirectoryPickerResult,
4
+ } from "@frockbot/desktop-core";
5
+ import type { Plugin } from "cordis";
6
+
7
+ export const PICK_DIRECTORY_COMMAND = "desktop.directory.pick";
8
+
9
+ export interface PickDirectoryInput {
10
+ title?: string;
11
+ multiple?: boolean;
12
+ }
13
+
14
+ export function decodePickDirectoryInput(input: unknown): PickDirectoryInput {
15
+ if (typeof input !== "object" || input === null || Array.isArray(input)) {
16
+ throw new Error("directory picker input must be an object");
17
+ }
18
+ const record = input as Record<string, unknown>;
19
+ let title: string | undefined;
20
+ if (record.title !== undefined) {
21
+ if (typeof record.title !== "string") {
22
+ throw new Error("directory picker title must be a string");
23
+ }
24
+ title = record.title.trim() || undefined;
25
+ if (title && title.length > 200) {
26
+ throw new Error("directory picker title must be at most 200 characters");
27
+ }
28
+ }
29
+ const multiple = record.multiple ?? false;
30
+ if (typeof multiple !== "boolean") {
31
+ throw new Error("directory picker multiple must be a boolean");
32
+ }
33
+ return { title, multiple };
34
+ }
35
+
36
+ export const desktopDirectoryPickerPlugin: Plugin.Function = (ctx) => {
37
+ const command: DesktopCommand<
38
+ PickDirectoryInput,
39
+ DesktopDirectoryPickerResult
40
+ > = {
41
+ id: PICK_DIRECTORY_COMMAND,
42
+ decode: decodePickDirectoryInput,
43
+ execute: (input, context) =>
44
+ ctx.desktopDirectoryPicker.pick(
45
+ {
46
+ mode: "directory",
47
+ title: input.title,
48
+ multiple: input.multiple ?? false,
49
+ },
50
+ context.signal,
51
+ ),
52
+ };
53
+ return ctx.desktopCommands.register(command);
54
+ };
55
+ desktopDirectoryPickerPlugin.inject = [
56
+ "desktopCommands",
57
+ "desktopDirectoryPicker",
58
+ ];
59
+
60
+ export default desktopDirectoryPickerPlugin;
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./desktop.js";
2
+ export { default as desktopDirectoryPickerManifest } from "./manifest.js";
@@ -0,0 +1,3 @@
1
+ import manifest from "../frockbot.json" with { type: "json" };
2
+
3
+ export default manifest;
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2023",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "resolveJsonModule": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "skipLibCheck": true,
10
+ "lib": ["ES2023", "DOM"],
11
+ "types": ["bun"]
12
+ },
13
+ "include": ["src/**/*.ts"]
14
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # @frockbot/plugin-desktop-directory-picker
2
-
3
- Placeholder reserving this name. See https://github.com/timoconnellaus/frockbot.