@nimbalyst/extension-sdk 0.1.0

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +151 -0
  3. package/dist/MaterialSymbol.d.ts +2 -0
  4. package/dist/MaterialSymbol.d.ts.map +1 -0
  5. package/dist/MaterialSymbol.js +1 -0
  6. package/dist/clipboard.d.ts +14 -0
  7. package/dist/clipboard.d.ts.map +1 -0
  8. package/dist/clipboard.js +27 -0
  9. package/dist/createReadOnlyHost.d.ts +45 -0
  10. package/dist/createReadOnlyHost.d.ts.map +1 -0
  11. package/dist/createReadOnlyHost.js +81 -0
  12. package/dist/documentPath.d.ts +6 -0
  13. package/dist/documentPath.d.ts.map +1 -0
  14. package/dist/documentPath.js +4 -0
  15. package/dist/externals.d.ts +23 -0
  16. package/dist/externals.d.ts.map +1 -0
  17. package/dist/externals.js +42 -0
  18. package/dist/externalsPlugin.d.ts +39 -0
  19. package/dist/externalsPlugin.d.ts.map +1 -0
  20. package/dist/externalsPlugin.js +251 -0
  21. package/dist/index.d.ts +33 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +39 -0
  24. package/dist/tailwind.d.ts +112 -0
  25. package/dist/tailwind.d.ts.map +1 -0
  26. package/dist/tailwind.js +129 -0
  27. package/dist/testing.d.ts +122 -0
  28. package/dist/testing.d.ts.map +1 -0
  29. package/dist/testing.js +199 -0
  30. package/dist/types/editor.d.ts +385 -0
  31. package/dist/types/editor.d.ts.map +1 -0
  32. package/dist/types/editor.js +15 -0
  33. package/dist/types/editors.d.ts +56 -0
  34. package/dist/types/editors.d.ts.map +1 -0
  35. package/dist/types/editors.js +19 -0
  36. package/dist/types/extension.d.ts +744 -0
  37. package/dist/types/extension.d.ts.map +1 -0
  38. package/dist/types/extension.js +4 -0
  39. package/dist/types/index.d.ts +9 -0
  40. package/dist/types/index.d.ts.map +1 -0
  41. package/dist/types/index.js +8 -0
  42. package/dist/types/panel.d.ts +575 -0
  43. package/dist/types/panel.d.ts.map +1 -0
  44. package/dist/types/panel.js +14 -0
  45. package/dist/types/theme.d.ts +148 -0
  46. package/dist/types/theme.d.ts.map +1 -0
  47. package/dist/types/theme.js +7 -0
  48. package/dist/useEditorLifecycle.d.ts +166 -0
  49. package/dist/useEditorLifecycle.d.ts.map +1 -0
  50. package/dist/useEditorLifecycle.js +327 -0
  51. package/dist/validate.d.ts +31 -0
  52. package/dist/validate.d.ts.map +1 -0
  53. package/dist/validate.js +128 -0
  54. package/dist/vite.d.ts +92 -0
  55. package/dist/vite.d.ts.map +1 -0
  56. package/dist/vite.js +179 -0
  57. package/package.json +91 -0
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Extension Testing Utilities
3
+ *
4
+ * Helpers for testing Nimbalyst extensions using Playwright against the running app.
5
+ * Extensions connect to the live Nimbalyst instance via CDP (Chrome DevTools Protocol).
6
+ *
7
+ * ## Quick Start
8
+ *
9
+ * ```ts
10
+ * // tests/my-extension.spec.ts
11
+ * import { test, expect, extensionEditor } from '@nimbalyst/extension-sdk/testing';
12
+ *
13
+ * test('loads CSV data', async ({ page }) => {
14
+ * const editor = extensionEditor(page, 'com.nimbalyst.csv-spreadsheet');
15
+ * await expect(editor.locator('.header-row')).toBeVisible();
16
+ * await expect(editor.locator('.data-row')).toHaveCount(10);
17
+ * });
18
+ * ```
19
+ *
20
+ * ## Setup
21
+ *
22
+ * Requires Nimbalyst running in dev mode (`npm run dev`), which enables CDP on port 9222.
23
+ *
24
+ * ## Multi-Window Support
25
+ *
26
+ * When multiple Nimbalyst windows are open, the fixture finds the correct window
27
+ * by checking which window's workspacePath is an ancestor of the test file's directory.
28
+ * This works automatically — no configuration needed.
29
+ *
30
+ * @packageDocumentation
31
+ */
32
+ import { expect } from '@playwright/test';
33
+ import type { Page, Locator } from 'playwright';
34
+ /**
35
+ * Playwright test fixture that connects to the running Nimbalyst instance via CDP.
36
+ *
37
+ * Unlike standard Playwright tests that launch a browser, this fixture attaches
38
+ * to the already-running Electron app. When multiple windows are open, the fixture
39
+ * finds the window whose workspace contains the test file (via __dirname matching).
40
+ *
41
+ * ```ts
42
+ * import { test, expect } from '@nimbalyst/extension-sdk/testing';
43
+ *
44
+ * test('my test', async ({ page }) => {
45
+ * // page is the Nimbalyst window whose workspace contains this test file
46
+ * await page.locator('.my-element').click();
47
+ * });
48
+ * ```
49
+ */
50
+ export declare const test: import("playwright/test").TestType<import("playwright/test").PlaywrightTestArgs & import("playwright/test").PlaywrightTestOptions & {
51
+ page: Page;
52
+ }, import("playwright/test").PlaywrightWorkerArgs & import("playwright/test").PlaywrightWorkerOptions>;
53
+ export { expect };
54
+ /**
55
+ * Create a locator scoped to an extension's custom editor container.
56
+ *
57
+ * Uses the `data-extension-id` and `data-file-path` attributes set by Nimbalyst's
58
+ * TabEditor on extension editor containers.
59
+ *
60
+ * ```ts
61
+ * const editor = extensionEditor(page, 'com.nimbalyst.csv-spreadsheet', '/path/to/data.csv');
62
+ * await editor.locator('.cell').first().click();
63
+ * await expect(editor.locator('.header')).toHaveText('Name');
64
+ * ```
65
+ *
66
+ * @param page - The Playwright page connected to Nimbalyst
67
+ * @param extensionId - The extension's manifest ID (e.g., 'com.nimbalyst.csv-spreadsheet')
68
+ * @param filePath - Optional: scope to a specific file's editor (when multiple files are open)
69
+ */
70
+ export declare function extensionEditor(page: Page, extensionId: string, filePath?: string): Locator;
71
+ /**
72
+ * Create a locator scoped to an extension's panel container.
73
+ *
74
+ * Uses the `data-extension-id` and `data-panel` attributes set by Nimbalyst's
75
+ * PanelContainer on extension panel containers.
76
+ *
77
+ * ```ts
78
+ * const panel = extensionPanel(page, 'com.nimbalyst.git', 'git-log');
79
+ * await panel.locator('.commit-row').first().click();
80
+ * ```
81
+ *
82
+ * @param page - The Playwright page connected to Nimbalyst
83
+ * @param extensionId - The extension's manifest ID
84
+ * @param panelId - The panel ID from the manifest
85
+ */
86
+ export declare function extensionPanel(page: Page, extensionId: string, panelId: string): Locator;
87
+ /**
88
+ * Call an extension's AI tool handler directly via the renderer's tool bridge.
89
+ * Useful for testing that tools return correct data without full MCP round-trips.
90
+ *
91
+ * ```ts
92
+ * const result = await callExtensionTool(page, 'excalidraw.get_elements', {});
93
+ * expect(result.success).toBe(true);
94
+ * expect(result.data.elements).toHaveLength(3);
95
+ * ```
96
+ *
97
+ * @param page - The Playwright page connected to Nimbalyst
98
+ * @param toolName - Fully qualified tool name (e.g., 'excalidraw.get_elements')
99
+ * @param args - Arguments to pass to the tool handler
100
+ */
101
+ export declare function callExtensionTool(page: Page, toolName: string, args?: Record<string, unknown>): Promise<{
102
+ success: boolean;
103
+ message?: string;
104
+ data?: unknown;
105
+ error?: string;
106
+ }>;
107
+ /**
108
+ * List all MCP tool definitions registered by extensions.
109
+ * Useful for verifying that an extension's tools are properly registered.
110
+ *
111
+ * ```ts
112
+ * const tools = await listExtensionTools(page);
113
+ * const myTool = tools.find(t => t.name === 'csv.export');
114
+ * expect(myTool).toBeDefined();
115
+ * ```
116
+ */
117
+ export declare function listExtensionTools(page: Page): Promise<Array<{
118
+ name: string;
119
+ description: string;
120
+ inputSchema?: unknown;
121
+ }>>;
122
+ //# sourceMappingURL=testing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAgB,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAKhD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,IAAI;UAAuB,IAAI;sGAqE1C,CAAC;AAEH,OAAO,EAAE,MAAM,EAAE,CAAC;AAElB;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAST;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,GACd,OAAO,CAIT;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACjC,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAWjF;AAED;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,IAAI,GACT,OAAO,CAAC,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC,CAM9E"}
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Extension Testing Utilities
3
+ *
4
+ * Helpers for testing Nimbalyst extensions using Playwright against the running app.
5
+ * Extensions connect to the live Nimbalyst instance via CDP (Chrome DevTools Protocol).
6
+ *
7
+ * ## Quick Start
8
+ *
9
+ * ```ts
10
+ * // tests/my-extension.spec.ts
11
+ * import { test, expect, extensionEditor } from '@nimbalyst/extension-sdk/testing';
12
+ *
13
+ * test('loads CSV data', async ({ page }) => {
14
+ * const editor = extensionEditor(page, 'com.nimbalyst.csv-spreadsheet');
15
+ * await expect(editor.locator('.header-row')).toBeVisible();
16
+ * await expect(editor.locator('.data-row')).toHaveCount(10);
17
+ * });
18
+ * ```
19
+ *
20
+ * ## Setup
21
+ *
22
+ * Requires Nimbalyst running in dev mode (`npm run dev`), which enables CDP on port 9222.
23
+ *
24
+ * ## Multi-Window Support
25
+ *
26
+ * When multiple Nimbalyst windows are open, the fixture finds the correct window
27
+ * by checking which window's workspacePath is an ancestor of the test file's directory.
28
+ * This works automatically — no configuration needed.
29
+ *
30
+ * @packageDocumentation
31
+ */
32
+ import { test as base, expect } from '@playwright/test';
33
+ import { chromium } from 'playwright';
34
+ const CDP_PORT = process.env.NIMBALYST_CDP_PORT || '9222';
35
+ const CDP_ENDPOINT = `http://localhost:${CDP_PORT}`;
36
+ /**
37
+ * Playwright test fixture that connects to the running Nimbalyst instance via CDP.
38
+ *
39
+ * Unlike standard Playwright tests that launch a browser, this fixture attaches
40
+ * to the already-running Electron app. When multiple windows are open, the fixture
41
+ * finds the window whose workspace contains the test file (via __dirname matching).
42
+ *
43
+ * ```ts
44
+ * import { test, expect } from '@nimbalyst/extension-sdk/testing';
45
+ *
46
+ * test('my test', async ({ page }) => {
47
+ * // page is the Nimbalyst window whose workspace contains this test file
48
+ * await page.locator('.my-element').click();
49
+ * });
50
+ * ```
51
+ */
52
+ export const test = base.extend({
53
+ page: async ({}, use, testInfo) => {
54
+ let browser;
55
+ try {
56
+ browser = await chromium.connectOverCDP(CDP_ENDPOINT);
57
+ }
58
+ catch (error) {
59
+ throw new Error(`Could not connect to Nimbalyst via CDP at ${CDP_ENDPOINT}.\n` +
60
+ `Make sure Nimbalyst is running in dev mode (npm run dev).\n` +
61
+ `Original error: ${error instanceof Error ? error.message : String(error)}`);
62
+ }
63
+ // Use the test file's directory to find the matching workspace window.
64
+ // The test file lives inside the workspace, so the window whose
65
+ // workspacePath is a prefix of the test file path is the correct one.
66
+ const testFileDir = testInfo.file
67
+ ? testInfo.file.substring(0, testInfo.file.lastIndexOf('/'))
68
+ : undefined;
69
+ let target;
70
+ for (const ctx of browser.contexts()) {
71
+ for (const p of ctx.pages()) {
72
+ const url = p.url();
73
+ if (url.startsWith('devtools://') || url.includes('mode=capture'))
74
+ continue;
75
+ try {
76
+ const ws = await p.evaluate(async () => (await window.electronAPI.getInitialState?.())?.workspacePath);
77
+ if (ws && testFileDir && testFileDir.startsWith(ws)) {
78
+ target = p;
79
+ break;
80
+ }
81
+ }
82
+ catch { }
83
+ }
84
+ if (target)
85
+ break;
86
+ }
87
+ // Fallback: if no workspace match (e.g. inline scripts in temp dirs),
88
+ // find the first non-devtools, non-capture page
89
+ if (!target) {
90
+ for (const ctx of browser.contexts()) {
91
+ for (const p of ctx.pages()) {
92
+ const url = p.url();
93
+ if (url.startsWith('devtools://') || url.includes('mode=capture'))
94
+ continue;
95
+ if (url.includes('theme=')) {
96
+ target = p;
97
+ break;
98
+ }
99
+ }
100
+ if (target)
101
+ break;
102
+ }
103
+ }
104
+ if (!target) {
105
+ throw new Error(`No Nimbalyst window found via CDP.\n` +
106
+ (testFileDir
107
+ ? `Looking for a window whose workspace contains: ${testFileDir}\n`
108
+ : '') +
109
+ `Make sure a Nimbalyst window is open with the correct project.`);
110
+ }
111
+ await use(target);
112
+ // Don't close the browser -- it's the user's running app
113
+ browser.close();
114
+ },
115
+ });
116
+ export { expect };
117
+ /**
118
+ * Create a locator scoped to an extension's custom editor container.
119
+ *
120
+ * Uses the `data-extension-id` and `data-file-path` attributes set by Nimbalyst's
121
+ * TabEditor on extension editor containers.
122
+ *
123
+ * ```ts
124
+ * const editor = extensionEditor(page, 'com.nimbalyst.csv-spreadsheet', '/path/to/data.csv');
125
+ * await editor.locator('.cell').first().click();
126
+ * await expect(editor.locator('.header')).toHaveText('Name');
127
+ * ```
128
+ *
129
+ * @param page - The Playwright page connected to Nimbalyst
130
+ * @param extensionId - The extension's manifest ID (e.g., 'com.nimbalyst.csv-spreadsheet')
131
+ * @param filePath - Optional: scope to a specific file's editor (when multiple files are open)
132
+ */
133
+ export function extensionEditor(page, extensionId, filePath) {
134
+ if (filePath) {
135
+ // Escape special CSS selector characters in file paths
136
+ const escapedPath = filePath.replace(/([\\/"'[\](){}|^$*+?.])/g, '\\$1');
137
+ return page.locator(`[data-extension-id="${extensionId}"][data-file-path="${escapedPath}"]`);
138
+ }
139
+ return page.locator(`[data-extension-id="${extensionId}"]`).first();
140
+ }
141
+ /**
142
+ * Create a locator scoped to an extension's panel container.
143
+ *
144
+ * Uses the `data-extension-id` and `data-panel` attributes set by Nimbalyst's
145
+ * PanelContainer on extension panel containers.
146
+ *
147
+ * ```ts
148
+ * const panel = extensionPanel(page, 'com.nimbalyst.git', 'git-log');
149
+ * await panel.locator('.commit-row').first().click();
150
+ * ```
151
+ *
152
+ * @param page - The Playwright page connected to Nimbalyst
153
+ * @param extensionId - The extension's manifest ID
154
+ * @param panelId - The panel ID from the manifest
155
+ */
156
+ export function extensionPanel(page, extensionId, panelId) {
157
+ return page.locator(`[data-extension-id="${extensionId}"][data-panel="${panelId}"]`);
158
+ }
159
+ /**
160
+ * Call an extension's AI tool handler directly via the renderer's tool bridge.
161
+ * Useful for testing that tools return correct data without full MCP round-trips.
162
+ *
163
+ * ```ts
164
+ * const result = await callExtensionTool(page, 'excalidraw.get_elements', {});
165
+ * expect(result.success).toBe(true);
166
+ * expect(result.data.elements).toHaveLength(3);
167
+ * ```
168
+ *
169
+ * @param page - The Playwright page connected to Nimbalyst
170
+ * @param toolName - Fully qualified tool name (e.g., 'excalidraw.get_elements')
171
+ * @param args - Arguments to pass to the tool handler
172
+ */
173
+ export async function callExtensionTool(page, toolName, args = {}) {
174
+ return await page.evaluate(async ({ toolName, args }) => {
175
+ const bridge = window.__nimbalyst_extension_tools__;
176
+ if (!bridge) {
177
+ return { success: false, error: '__nimbalyst_extension_tools__ not found. Is Nimbalyst running in dev mode?' };
178
+ }
179
+ return await bridge.executeExtensionTool(toolName, args, {});
180
+ }, { toolName, args });
181
+ }
182
+ /**
183
+ * List all MCP tool definitions registered by extensions.
184
+ * Useful for verifying that an extension's tools are properly registered.
185
+ *
186
+ * ```ts
187
+ * const tools = await listExtensionTools(page);
188
+ * const myTool = tools.find(t => t.name === 'csv.export');
189
+ * expect(myTool).toBeDefined();
190
+ * ```
191
+ */
192
+ export async function listExtensionTools(page) {
193
+ return await page.evaluate(async () => {
194
+ const bridge = window.__nimbalyst_extension_tools__;
195
+ if (!bridge)
196
+ return [];
197
+ return await bridge.getMCPToolDefinitions();
198
+ });
199
+ }