@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.
- package/CHANGELOG.md +39 -0
- package/README.md +151 -0
- package/dist/MaterialSymbol.d.ts +2 -0
- package/dist/MaterialSymbol.d.ts.map +1 -0
- package/dist/MaterialSymbol.js +1 -0
- package/dist/clipboard.d.ts +14 -0
- package/dist/clipboard.d.ts.map +1 -0
- package/dist/clipboard.js +27 -0
- package/dist/createReadOnlyHost.d.ts +45 -0
- package/dist/createReadOnlyHost.d.ts.map +1 -0
- package/dist/createReadOnlyHost.js +81 -0
- package/dist/documentPath.d.ts +6 -0
- package/dist/documentPath.d.ts.map +1 -0
- package/dist/documentPath.js +4 -0
- package/dist/externals.d.ts +23 -0
- package/dist/externals.d.ts.map +1 -0
- package/dist/externals.js +42 -0
- package/dist/externalsPlugin.d.ts +39 -0
- package/dist/externalsPlugin.d.ts.map +1 -0
- package/dist/externalsPlugin.js +251 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/tailwind.d.ts +112 -0
- package/dist/tailwind.d.ts.map +1 -0
- package/dist/tailwind.js +129 -0
- package/dist/testing.d.ts +122 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +199 -0
- package/dist/types/editor.d.ts +385 -0
- package/dist/types/editor.d.ts.map +1 -0
- package/dist/types/editor.js +15 -0
- package/dist/types/editors.d.ts +56 -0
- package/dist/types/editors.d.ts.map +1 -0
- package/dist/types/editors.js +19 -0
- package/dist/types/extension.d.ts +744 -0
- package/dist/types/extension.d.ts.map +1 -0
- package/dist/types/extension.js +4 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +8 -0
- package/dist/types/panel.d.ts +575 -0
- package/dist/types/panel.d.ts.map +1 -0
- package/dist/types/panel.js +14 -0
- package/dist/types/theme.d.ts +148 -0
- package/dist/types/theme.d.ts.map +1 -0
- package/dist/types/theme.js +7 -0
- package/dist/useEditorLifecycle.d.ts +166 -0
- package/dist/useEditorLifecycle.d.ts.map +1 -0
- package/dist/useEditorLifecycle.js +327 -0
- package/dist/validate.d.ts +31 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +128 -0
- package/dist/vite.d.ts +92 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +179 -0
- 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"}
|
package/dist/testing.js
ADDED
|
@@ -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
|
+
}
|