@dolphy-app/extension-sdk 0.2.0 → 0.4.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/README.md +25 -8
- package/dist/answer-element-BOQcuxYh.js +114 -0
- package/dist/answer-view-D6wnyThb.d.ts +28 -0
- package/dist/index.d.ts +107 -27
- package/dist/index.js +48 -117
- package/dist/runtime.d.ts +17 -0
- package/dist/runtime.js +24 -0
- package/dist/testing.d.ts +330 -11
- package/dist/testing.js +722 -11
- package/docs/debugging.md +210 -0
- package/docs/no-build.md +99 -0
- package/docs/quick-start.md +172 -0
- package/docs/recipe-command-panel.md +211 -0
- package/docs/recipe-event-storage.md +266 -0
- package/docs/recipe-exercise-type.md +379 -0
- package/docs/recipe-import-export.md +241 -0
- package/docs/recipe-settings.md +161 -0
- package/docs/recipe-theme.md +116 -0
- package/docs/recipe-ui-kit.md +172 -0
- package/docs/recipe-when-dependencies.md +158 -0
- package/package.json +10 -4
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Debugging
|
|
2
|
+
|
|
3
|
+
Where to look when an extension does not do what you expect, from the cheapest
|
|
4
|
+
check to the running app. Commands are those of a generated project; see the
|
|
5
|
+
[quick start](quick-start.md).
|
|
6
|
+
|
|
7
|
+
## 1. Reproduce it in a test
|
|
8
|
+
|
|
9
|
+
The helpers of `@dolphy-app/extension-sdk/testing` run your handlers in plain
|
|
10
|
+
Node, with a debugger and `console.log` available. Most mistakes (wrong result
|
|
11
|
+
shape, a setting read before it is set, a handler that throws) show up there.
|
|
12
|
+
|
|
13
|
+
- `loadCommands`, `loadEvents`, `loadExerciseType` and `loadGradePolicy` take a
|
|
14
|
+
`logger` option: pass an object with `debug`, `info`, `warn` and `error`
|
|
15
|
+
methods to see what `ctx.logger` receives.
|
|
16
|
+
- The helpers check what the host checks: the shape of a verdict, a grade of
|
|
17
|
+
1–5 or `null`, a command result (`notify` text of 1–500 characters, a JSON
|
|
18
|
+
value of at most 64 KiB), a declared id. A message from a helper usually names
|
|
19
|
+
the rule.
|
|
20
|
+
- They do not swallow a handler's failure and do not count the host's time
|
|
21
|
+
limits, so a test that passes does not prove a handler is fast enough: an
|
|
22
|
+
event handler gets 2 seconds, `activate` gets 10.
|
|
23
|
+
- They do not restrict permissions (see section 5).
|
|
24
|
+
|
|
25
|
+
## 2. Run the checks
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
pnpm typecheck # ids against extension.json: a misspelt or missing id
|
|
29
|
+
pnpm validate # the manifest the way the app parses it
|
|
30
|
+
pnpm lint # metadata and bundle findings the catalog review also sees
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`pnpm validate` parses the built manifest with the same code as the app and
|
|
34
|
+
exits with code 1 on a problem.
|
|
35
|
+
`pnpm lint` prints `warning <id> <RULE> <field>: <message>` lines; a rule that
|
|
36
|
+
looks wrong for your code (for example `eval` in a bundled dependency) is a hint
|
|
37
|
+
for the reviewer, not a failure.
|
|
38
|
+
|
|
39
|
+
## 3. The app: development loop
|
|
40
|
+
|
|
41
|
+
`dolphy-ext dev` builds the project in watch mode and starts the installed
|
|
42
|
+
Dolphy app on the result:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
pnpm exec dolphy-ext dev # the project in the current directory
|
|
46
|
+
pnpm exec dolphy-ext dev ../my-ext # another project
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
It prints the path of the app it started and the directory it passes to it
|
|
50
|
+
(`DOLPHY_DEV_EXTENSIONS=<project>/dist-ext`). Press Ctrl+C to stop the build and
|
|
51
|
+
the app; the exit code is 0. The app is looked up in this order:
|
|
52
|
+
|
|
53
|
+
1. `--app <path>`: a macOS `.app` bundle or an executable file.
|
|
54
|
+
2. The `DOLPHY_APP` environment variable (same kind of path).
|
|
55
|
+
3. The standard place of your system: macOS `/Applications/Dolphy.app`, then
|
|
56
|
+
`~/Applications/Dolphy.app`; Windows
|
|
57
|
+
`%LOCALAPPDATA%\Programs\Dolphy\Dolphy.exe`; Linux the newest
|
|
58
|
+
`~/Applications/Dolphy-Linux-*.AppImage` (Linux has no fixed place: keep the
|
|
59
|
+
AppImage there or use `--app`).
|
|
60
|
+
|
|
61
|
+
When none exists the command exits with code 2 and lists the places it looked
|
|
62
|
+
at. A path given with `--app` or `DOLPHY_APP` that does not exist is an error as
|
|
63
|
+
well, not a silent fallback.
|
|
64
|
+
|
|
65
|
+
If you prefer to run the two parts yourself, start `pnpm dev`
|
|
66
|
+
(`dolphy-ext build --watch`) and start Dolphy with
|
|
67
|
+
`DOLPHY_DEV_EXTENSIONS=<project>/dist-ext`; from a checkout of the Dolphy
|
|
68
|
+
repository that is `DOLPHY_DEV_EXTENSIONS=<project>/dist-ext pnpm dev`. The app
|
|
69
|
+
rereads the extensions on every file change and applies the change live. Things
|
|
70
|
+
to know:
|
|
71
|
+
|
|
72
|
+
- The app has one instance. If Dolphy is already running, a second start exits
|
|
73
|
+
at once and the variable is lost. `dolphy-ext dev` notices an app that quits
|
|
74
|
+
within 5 seconds and prints "Dolphy is probably already running: quit it and
|
|
75
|
+
run again" (exit code 1): quit the running app and start again.
|
|
76
|
+
- The extension appears in Settings → Extensions with the origin
|
|
77
|
+
"Development". A manifest or load problem is shown there next to the
|
|
78
|
+
extension instead of the extension working: start with that text.
|
|
79
|
+
- If the id is also in the bundled set or installed from the catalog, the
|
|
80
|
+
development copy wins.
|
|
81
|
+
- Answer inputs of extensions in development are recreated on a change, so
|
|
82
|
+
their state can be lost.
|
|
83
|
+
|
|
84
|
+
## 4. DevTools
|
|
85
|
+
|
|
86
|
+
While `DOLPHY_DEV_EXTENSIONS` is set (so under `dolphy-ext dev` too), in any
|
|
87
|
+
build of the app, including an installed one, these keys toggle the DevTools of
|
|
88
|
+
the main window: `F12`, `Cmd+Alt+I` (macOS) and `Ctrl+Shift+I`. Without the
|
|
89
|
+
variable the keys do nothing and an installed app has no DevTools.
|
|
90
|
+
|
|
91
|
+
- Views, panels and markdown blocks of an extension run in frames with the
|
|
92
|
+
address `dolphy-ext://<id>/__dolphy/frame.html`. In the console, pick that
|
|
93
|
+
frame in the context drop-down (the one that says "top") to evaluate code in
|
|
94
|
+
your view; in "Elements" the frame is an `<iframe>` with that address.
|
|
95
|
+
- `dolphy-ext dev` and `dolphy-ext build --watch` put an inline source map
|
|
96
|
+
(`//# sourceMappingURL=data:application/json…`) into every bundle, so
|
|
97
|
+
"Sources" shows your TypeScript (`src/index.ts` and the files it imports) for
|
|
98
|
+
views, panels and renderers: set a breakpoint there, `debugger;` works too.
|
|
99
|
+
A plain `dolphy-ext build` (`pnpm build`) and the catalog build never write
|
|
100
|
+
source maps, and the catalog check rejects a submission that has one.
|
|
101
|
+
- The code that runs in the extension process (`main.mjs`: commands, events,
|
|
102
|
+
`activate`) is not in this window: see section 7 and the log.
|
|
103
|
+
|
|
104
|
+
## 5. The log
|
|
105
|
+
|
|
106
|
+
`ctx.logger` has `debug`, `info`, `warn` and `error`; each takes an object of
|
|
107
|
+
fields and an optional message:
|
|
108
|
+
|
|
109
|
+
```text
|
|
110
|
+
ctx.logger.info({ id: change.id, value: change.value }, 'setting changed');
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The output goes to the log of the app, not to a console of the window. Log
|
|
114
|
+
structured fields, not secrets or the learner's answers. The host writes to the
|
|
115
|
+
same log: an exception in a handler, a handler that outlives its time limit, an
|
|
116
|
+
event dropped from a full queue, and a warning after activation about an id the
|
|
117
|
+
manifest declares but the code did not register.
|
|
118
|
+
|
|
119
|
+
### Reading the log in the app
|
|
120
|
+
|
|
121
|
+
1. Open Settings → Extensions. In the "Diagnostics" block press "Log" to see
|
|
122
|
+
everything, or press "Log" in the row of your extension to see only its
|
|
123
|
+
entries (the dialog opens with the id of the extension in the filter).
|
|
124
|
+
2. The dialog lists the last 500 entries, the newest at the bottom. Each entry
|
|
125
|
+
shows the time, the level, the source (`main`, `engine` or `ext-host`), the
|
|
126
|
+
id of the extension that wrote it and the message. "Details" opens the other
|
|
127
|
+
fields of the entry as JSON.
|
|
128
|
+
3. Filter by extension: type or pick an id in "Extension" (an id that is not in
|
|
129
|
+
the list is accepted). Filter by level: "Minimum level" hides entries below
|
|
130
|
+
it; the default, "Debug", shows all of them.
|
|
131
|
+
4. The dialog does not update by itself: press "Refresh" after you triggered
|
|
132
|
+
the code you are watching. "No entries match the filters." means the filters
|
|
133
|
+
hide everything, or nothing was logged yet.
|
|
134
|
+
|
|
135
|
+
The labels the dialog shows, with the keys of the app's messages (ru and en):
|
|
136
|
+
|
|
137
|
+
| Where | English | Русский | Message key |
|
|
138
|
+
| -------------------------- | -------------------------------- | ----------------------------- | ------------------------------------------ |
|
|
139
|
+
| Settings section | Extensions | Расширения | `settings.extensions.title` |
|
|
140
|
+
| Origin of a dev extension | Development | Разработка | `settings.extensions.origin.dev` |
|
|
141
|
+
| Trust switch | Trust (no isolation) | Доверять (без изоляции) | `settings.extensions.trustLabel` |
|
|
142
|
+
| Block with the log button | Diagnostics | Диагностика | `settings.extensions.support.title` |
|
|
143
|
+
| Button of the block | Log | Журнал | `settings.extensions.support.openLog` |
|
|
144
|
+
| Action in an extension row | Log | Журнал | `settings.extensions.log.rowAction` |
|
|
145
|
+
| Dialog title | Log | Журнал | `settings.extensions.log.title` |
|
|
146
|
+
| Extension filter | Extension | Расширение | `settings.extensions.log.filterExtension` |
|
|
147
|
+
| Extension filter, empty | All extensions | Все расширения | `settings.extensions.log.filterExtensionHint` |
|
|
148
|
+
| Level filter | Minimum level | Минимальный уровень | `settings.extensions.log.filterLevel` |
|
|
149
|
+
| Level | Debug | Отладка | `settings.extensions.log.level.debug` |
|
|
150
|
+
| Level | Info | Инфо | `settings.extensions.log.level.info` |
|
|
151
|
+
| Level | Warning | Предупреждение | `settings.extensions.log.level.warn` |
|
|
152
|
+
| Level | Error | Ошибка | `settings.extensions.log.level.error` |
|
|
153
|
+
| Other fields of an entry | Details | Подробности | `settings.extensions.log.details` |
|
|
154
|
+
| Reread the log | Refresh | Обновить | `settings.extensions.log.refresh` |
|
|
155
|
+
| Filters match nothing | No entries match the filters. | Нет записей, подходящих под условия. | `settings.extensions.log.empty` |
|
|
156
|
+
|
|
157
|
+
### Output of the restricted process
|
|
158
|
+
|
|
159
|
+
An extension that is not trusted runs in a restricted process (section 6). What
|
|
160
|
+
it prints with `console.log`, `console.error` or an uncaught error goes to the
|
|
161
|
+
log as `warn` entries with the extension id and `stream` (`stdout` or `stderr`)
|
|
162
|
+
in "Details". Use `ctx.logger` for anything you want to filter by level; use
|
|
163
|
+
`console` only for a quick look. The output is limited so that one extension
|
|
164
|
+
cannot flood the log: at most 64 KiB in 60 seconds per extension; the rest is
|
|
165
|
+
dropped and one entry "output truncated" with `droppedBytes` closes the window.
|
|
166
|
+
A process that sends a message larger than 1 MiB or more than 200 messages in
|
|
167
|
+
a second is stopped, with an `error` entry whose reason is `ipc-size` or
|
|
168
|
+
`ipc-rate`.
|
|
169
|
+
|
|
170
|
+
### The file
|
|
171
|
+
|
|
172
|
+
The entries are also in `logs/dolphy-YYYY-MM-DD.log` in the app's data folder,
|
|
173
|
+
one JSON object per line (`level`, `source`, `message`, `at` and the other
|
|
174
|
+
fields). A new file starts every day and at 2 MiB; files older than 7 days are
|
|
175
|
+
removed, and the oldest go first while the folder is over 10 MiB. Attach the
|
|
176
|
+
relevant lines, or the text of "Copy diagnostics" in the "Diagnostics" block (it
|
|
177
|
+
has no paths of your home folder, library content or learning data), to a bug
|
|
178
|
+
report.
|
|
179
|
+
|
|
180
|
+
## 6. Permissions and the restricted process
|
|
181
|
+
|
|
182
|
+
An extension that is not bundled with the app and not trusted runs in a
|
|
183
|
+
restricted process, and what its `permissions` do not declare is unavailable:
|
|
184
|
+
`ctx.library` throws `PermissionError` without `library.read`; spawning a
|
|
185
|
+
process, a worker thread or a native module fails with `ERR_ACCESS_DENIED`.
|
|
186
|
+
Test helpers do not reproduce this, so a feature that needs a permission must be
|
|
187
|
+
tried in the app. Extensions in development get the permissions their manifest
|
|
188
|
+
declares. To debug without the restrictions, turn on "Trust (no isolation)" for
|
|
189
|
+
the extension in Settings → Extensions; turn it off again before you release,
|
|
190
|
+
because your users will not have it on.
|
|
191
|
+
|
|
192
|
+
## 7. Reading a stack trace
|
|
193
|
+
|
|
194
|
+
The code of the extension process is `dist-ext/<id>/main.mjs`, readable and not
|
|
195
|
+
minified. A watch build (`dolphy-ext dev`, `dolphy-ext build --watch`) appends
|
|
196
|
+
an inline source map to it, but the app does not turn on source maps for that
|
|
197
|
+
process, so a stack trace in the log still points to lines of `main.mjs`: open
|
|
198
|
+
that file to find the place and search it for the name from the trace. The
|
|
199
|
+
map is for the browser files of section 4. A plain `dolphy-ext build` writes no
|
|
200
|
+
maps at all.
|
|
201
|
+
|
|
202
|
+
## Which limit did I hit?
|
|
203
|
+
|
|
204
|
+
| Symptom | Limit |
|
|
205
|
+
| ----------------------------------------------- | -------------------------------------------------------- |
|
|
206
|
+
| activation fails with `activation-timeout` | `activate` must finish in 10 seconds |
|
|
207
|
+
| an event handler stops mid-way | 2 seconds per event; the queue holds 100 events |
|
|
208
|
+
| `StorageQuotaError` | key 128 characters, value 64 KiB, 256 keys, 1 MiB total |
|
|
209
|
+
| a command result is rejected | `notify` text 1–500 characters; a result up to 64 KiB |
|
|
210
|
+
| a panel cannot load an image or open a socket | the frame loads only from its own extension, no network |
|
package/docs/no-build.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Without a build
|
|
2
|
+
|
|
3
|
+
An extension is a directory with an `extension.json` and, if it has code, an ES
|
|
4
|
+
module. You do not need TypeScript, a `package.json` or `dolphy-ext` to write
|
|
5
|
+
one. This page shows the smallest case: one palette command, two hand-written
|
|
6
|
+
files. Use it for a quick experiment or when the code is a few lines; switch to
|
|
7
|
+
the [quick start](quick-start.md) layout when you want typed ids, tests and
|
|
8
|
+
bundling.
|
|
9
|
+
|
|
10
|
+
## The files
|
|
11
|
+
|
|
12
|
+
File `extension.json` (no build):
|
|
13
|
+
|
|
14
|
+
```json
|
|
15
|
+
{
|
|
16
|
+
"id": "acme.plain",
|
|
17
|
+
"version": "0.1.0",
|
|
18
|
+
"apiVersion": 1,
|
|
19
|
+
"name": "Plain hello",
|
|
20
|
+
"description": "A palette command written by hand, without a build step.",
|
|
21
|
+
"author": "your-github-login",
|
|
22
|
+
"tags": ["productivity"],
|
|
23
|
+
"contributes": {
|
|
24
|
+
"commands": [{ "id": "acme.plain.hello", "title": "Say hello" }]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
File `main.mjs` (no build):
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
export default {
|
|
33
|
+
activate(ctx) {
|
|
34
|
+
ctx.commands.register('acme.plain.hello', () => ({
|
|
35
|
+
notify: `Hello from ${ctx.extensionId}!`,
|
|
36
|
+
}));
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- `main` is left out: for an extension with commands it defaults to `./main.mjs`,
|
|
42
|
+
so the module sits next to the manifest under that name.
|
|
43
|
+
- `export default { activate(ctx) }` is the whole contract of the module. `ctx`
|
|
44
|
+
is the same context as in the typed projects (`commands`, `settings`,
|
|
45
|
+
`storage`, `events`, `logger`, `library`); `deactivate()` is optional.
|
|
46
|
+
- There are no helpers, so a command returns the result object itself:
|
|
47
|
+
`{ notify: text }` is what `notify(text)` makes (the SDK helper does nothing
|
|
48
|
+
more). Every id must be
|
|
49
|
+
declared in the manifest, and a declared id nobody registers produces a
|
|
50
|
+
warning in the log.
|
|
51
|
+
- The module must be plain JavaScript that Node runs as it is: no TypeScript, no
|
|
52
|
+
bare imports of packages (nothing is installed next to it).
|
|
53
|
+
|
|
54
|
+
## Check and try
|
|
55
|
+
|
|
56
|
+
Put both files into a directory named exactly as the extension id,
|
|
57
|
+
`acme.plain/` (the app takes the id from the directory name and rejects a
|
|
58
|
+
manifest that disagrees), and check it:
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
npx --package @dolphy-app/extension-tools dolphy-ext validate ./acme.plain
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`validate` parses the manifest the way the app does and exits with code 1 on a
|
|
65
|
+
problem. To try the extension, put the directory into a folder and start the
|
|
66
|
+
app with `DOLPHY_DEV_EXTENSIONS` pointing at that folder (the folder, not the
|
|
67
|
+
extension directory: it holds one directory per extension), or copy the
|
|
68
|
+
directory to `<userData>/extensions/` and restart the app. Edits to the files are
|
|
69
|
+
picked up without a restart when you use `DOLPHY_DEV_EXTENSIONS`.
|
|
70
|
+
|
|
71
|
+
To test the module without the app, activate it with the SDK helper:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
npm install --save-dev @dolphy-app/extension-sdk vitest
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
<!-- fragment -->
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
import { loadCommands } from '@dolphy-app/extension-sdk/testing';
|
|
81
|
+
import extension from './main.mjs';
|
|
82
|
+
|
|
83
|
+
const commands = await loadCommands(extension, {
|
|
84
|
+
declaredCommands: ['acme.plain.hello'],
|
|
85
|
+
});
|
|
86
|
+
console.log(await commands.run('acme.plain.hello'));
|
|
87
|
+
// { kind: 'notify', text: 'Hello from …!' }
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`loadCommands` accepts the default export as it is, because the module has the
|
|
91
|
+
shape of a host module (`activate`, `deactivate`).
|
|
92
|
+
|
|
93
|
+
## Publishing
|
|
94
|
+
|
|
95
|
+
The extension catalog reviews a project, not a bare directory: it requires a
|
|
96
|
+
`package.json` with a lock file and a `README.md` that explains what each
|
|
97
|
+
permission is for (`dolphy-ext catalog check`). A hand-written directory is for
|
|
98
|
+
yourself or for handing a folder to someone. To publish, generate a project with
|
|
99
|
+
`create-dolphy-extension` and move the code into it.
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Quick start
|
|
2
|
+
|
|
3
|
+
This guide takes you from nothing to a working Dolphy extension: a command in
|
|
4
|
+
the command palette that shows a notification. It is the smallest project the
|
|
5
|
+
generator makes (`--template blank`); the recipes next to this file build on the
|
|
6
|
+
same layout:
|
|
7
|
+
|
|
8
|
+
- [Exercise type](recipe-exercise-type.md): a new kind of task with its own answer input.
|
|
9
|
+
- [Theme](recipe-theme.md): colors, no code.
|
|
10
|
+
- [Command and panel](recipe-command-panel.md): palette commands and a screen in an isolated frame.
|
|
11
|
+
- [Events and storage](recipe-event-storage.md): react to learning events and keep data.
|
|
12
|
+
- [Settings](recipe-settings.md): let the user configure the extension.
|
|
13
|
+
- [Without a build](no-build.md): a hand-written `extension.json` and `main.mjs`, no TypeScript.
|
|
14
|
+
- [Debugging](debugging.md): where to look when something does not work.
|
|
15
|
+
|
|
16
|
+
## What you need
|
|
17
|
+
|
|
18
|
+
- Node.js 22.12 or newer and pnpm.
|
|
19
|
+
- The Dolphy app, to see the extension work. Everything else (build, type
|
|
20
|
+
check, tests) runs without it.
|
|
21
|
+
|
|
22
|
+
## 1. Create the project
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
npx @dolphy-app/create-extension my-extension --id acme.hello --template blank
|
|
26
|
+
cd my-extension
|
|
27
|
+
pnpm install
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`--id` is the extension id: lowercase letters, digits, dots and hyphens. Every
|
|
31
|
+
id the extension declares (commands, panels, settings…) starts with it, so pick
|
|
32
|
+
one that is yours (a publisher prefix, then a name). Without `--id` the id is the
|
|
33
|
+
directory name in kebab-case. The `--template` values:
|
|
34
|
+
|
|
35
|
+
| Template | What you get |
|
|
36
|
+
| --------------- | ------------------------------------------------------------------------------ |
|
|
37
|
+
| `exercise` | a task type with an answer input and a setting (the default) |
|
|
38
|
+
| `theme` | a color theme, no code |
|
|
39
|
+
| `command-panel` | palette commands and a panel |
|
|
40
|
+
| `events` | a learning event handler, storage, commands and a panel |
|
|
41
|
+
| `blank` | one palette command |
|
|
42
|
+
|
|
43
|
+
An unknown name exits with code 2 and lists the available ones.
|
|
44
|
+
|
|
45
|
+
## 2. The files
|
|
46
|
+
|
|
47
|
+
The project is two files that matter. The manifest declares what the extension
|
|
48
|
+
adds, the code implements it.
|
|
49
|
+
|
|
50
|
+
File `extension.json` (quick start):
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"$schema": "./node_modules/@dolphy-app/extension-api/dist/extension.schema.json",
|
|
55
|
+
"id": "acme.hello",
|
|
56
|
+
"version": "0.1.0",
|
|
57
|
+
"apiVersion": 1,
|
|
58
|
+
"name": "Hello command",
|
|
59
|
+
"description": "A command-palette command that shows a notification.",
|
|
60
|
+
"author": "your-github-login",
|
|
61
|
+
"tags": ["productivity"],
|
|
62
|
+
"contributes": {
|
|
63
|
+
"commands": [{ "id": "acme.hello.hello", "title": "Say hello" }]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`$schema` gives editors completion and checking. `id` is the identity of the
|
|
69
|
+
extension in the app and the catalog; it never changes after the first release.
|
|
70
|
+
Replace `your-github-login` in `author` with your GitHub login before you
|
|
71
|
+
publish. `contributes.commands` declares the command that appears in the palette.
|
|
72
|
+
|
|
73
|
+
File `src/index.ts` (quick start):
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { defineExtension, notify } from '@dolphy-app/extension-sdk';
|
|
77
|
+
|
|
78
|
+
// extension code: runs in the extension process of the app
|
|
79
|
+
// the command id comes from extension.json: a misspelt id or a declared id
|
|
80
|
+
// without a handler fails `pnpm typecheck`
|
|
81
|
+
export const host = defineExtension({
|
|
82
|
+
commands: {
|
|
83
|
+
'acme.hello.hello': () => notify('Hello from acme.hello!'),
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`host` is the code that runs in the extension process of the app.
|
|
89
|
+
`defineExtension` takes a handler for every command the manifest declares:
|
|
90
|
+
`notify` returns a notification to the app. The ids are types: `pnpm typecheck`
|
|
91
|
+
(and every build) writes `.dolphy/ids.d.ts` from `extension.json`, so a misspelt
|
|
92
|
+
id, or a declared command without a handler, does not compile.
|
|
93
|
+
|
|
94
|
+
File `test/index.test.ts` (quick start):
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { loadCommands } from '@dolphy-app/extension-sdk/testing';
|
|
98
|
+
import { expect, it } from 'vitest';
|
|
99
|
+
import { host } from '../src/index.ts';
|
|
100
|
+
|
|
101
|
+
it('the hello command notifies', async () => {
|
|
102
|
+
const commands = await loadCommands(host, {
|
|
103
|
+
declaredCommands: ['acme.hello.hello'],
|
|
104
|
+
});
|
|
105
|
+
expect(await commands.run('acme.hello.hello')).toEqual({
|
|
106
|
+
kind: 'notify',
|
|
107
|
+
text: 'Hello from acme.hello!',
|
|
108
|
+
});
|
|
109
|
+
await commands.dispose();
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The test runs the command the way the host does, without the app:
|
|
114
|
+
`@dolphy-app/extension-sdk/testing` activates `host` with in-memory storage,
|
|
115
|
+
settings and commands.
|
|
116
|
+
|
|
117
|
+
## 3. Build, check, test
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
pnpm build # dolphy-ext build: writes dist-ext/acme.hello
|
|
121
|
+
pnpm validate # parses the built manifest the way the app does
|
|
122
|
+
pnpm lint # metadata and bundle checks the catalog review also runs
|
|
123
|
+
pnpm typecheck # dolphy-ext types, then tsc
|
|
124
|
+
pnpm test # vitest
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`pnpm build` turns `src/index.ts` into the files the app loads. With this
|
|
128
|
+
template that is `dist-ext/acme.hello/extension.json` and `main.mjs`. Keep
|
|
129
|
+
`dist-ext` and `.dolphy` out of git; the generated `.gitignore` does it.
|
|
130
|
+
|
|
131
|
+
## 4. Try it in the app
|
|
132
|
+
|
|
133
|
+
One command builds the project in watch mode and starts the installed Dolphy
|
|
134
|
+
app on the result:
|
|
135
|
+
|
|
136
|
+
```sh
|
|
137
|
+
pnpm exec dolphy-ext dev
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The extension appears in Settings → Extensions with the origin "development",
|
|
141
|
+
and the command appears in the command palette. Press Ctrl+C to stop the build
|
|
142
|
+
and the app. Quit a running Dolphy first: the app has one instance, a second
|
|
143
|
+
start does not pick up the extension directory. Where the app is looked up and
|
|
144
|
+
what to do when it is not found: [debugging](debugging.md), section 3.
|
|
145
|
+
|
|
146
|
+
To run the two parts yourself, start the build in watch mode with `pnpm dev`
|
|
147
|
+
(`dolphy-ext build --watch`) and start Dolphy with the variable
|
|
148
|
+
`DOLPHY_DEV_EXTENSIONS` set to the absolute path of `dist-ext` of your project.
|
|
149
|
+
It adds a developer root with the highest priority.
|
|
150
|
+
|
|
151
|
+
```sh
|
|
152
|
+
# from a checkout of the Dolphy repository
|
|
153
|
+
DOLPHY_DEV_EXTENSIONS=/path/to/my-extension/dist-ext pnpm dev
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Save a file: the build writes the new files and the app applies them without a
|
|
157
|
+
restart. The window does not reload; answer inputs of extensions in development
|
|
158
|
+
are recreated, so their state can be lost.
|
|
159
|
+
|
|
160
|
+
An extension that is not bundled with the app and not trusted runs in a
|
|
161
|
+
restricted process: what the `permissions` of the manifest do not declare is
|
|
162
|
+
unavailable. The test helpers do not reproduce that; try permission-dependent
|
|
163
|
+
code in the app.
|
|
164
|
+
|
|
165
|
+
## 5. Next
|
|
166
|
+
|
|
167
|
+
- Pick the recipe closest to your task and generate that template.
|
|
168
|
+
- Before a pull request to the extension catalog run `pnpm build`,
|
|
169
|
+
`pnpm validate`, `pnpm lint`, `pnpm typecheck` and `pnpm test`: all must pass.
|
|
170
|
+
The generated `.github/workflows/ci.yml` runs the same steps on every push.
|
|
171
|
+
- Write in `README.md` what the extension does and what each permission is for;
|
|
172
|
+
the catalog review reads it.
|