@vestra-player/sdk 1.5.54 → 1.5.57
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 +23 -0
- package/dist/docs-tree.json.gz +0 -0
- package/dist/plugin/appearance.js +49 -2
- package/dist/plugin/packages.js +4 -1
- package/dist/plugin/resolvers.js +29 -4
- package/dist/plugin/testing.d.ts +34 -2
- package/dist/plugin/testing.js +45 -2
- package/dist/plugin/types.d.ts +80 -11
- package/dist/wire.d.ts +6 -1
- package/dist/wire.js +8 -1
- package/llms.txt +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.5.57
|
|
4
|
+
|
|
5
|
+
**Who must change: nobody.** The first npm release with 1.5.56's fix: no change to the TypeScript API or the wire, `apiVersion` stays `vestra/1`, no new capability, no player version needed.
|
|
6
|
+
|
|
7
|
+
- **1.5.56 never reached npm.** Its tag was pushed, but the release run (41502365359) skipped `publish` because GitHub never gave its two macOS jobs a hosted runner (capacity) and they were cancelled. 1.5.57 publishes the same code: `appearance.onChange` and `resolvers.onFileTypesChange` hear a change the player woke the plugin for, and the session-storage clearing docs, as described under [1.5.56](#1556). Install 1.5.57; there is no `@vestra-player/sdk@1.5.56` on npm.
|
|
8
|
+
- _Release process_: runner capacity no longer blocks a release (product-owner ruling, 2026-11-02). `release.yml` publishes once `verify`, `static`, `build`, `docs` and the Ubuntu and Windows tests and examples pass; macOS tests and examples still run on every release as `test-macos`, beside `publish` and without gating it. `_test.yml` takes an `os` input (a JSON array of runner labels, all three by default), so pull requests and `main` still test and gate on Ubuntu, Windows and macOS as before. [Maintaining › Releasing](docs/maintaining.md#releasing) says so.
|
|
9
|
+
|
|
10
|
+
## 1.5.56
|
|
11
|
+
|
|
12
|
+
**Who must change: nobody.** A fix: no change to the TypeScript API or the wire, `apiVersion` stays `vestra/1`, no new capability, no player version needed.
|
|
13
|
+
|
|
14
|
+
- **`appearance.onChange` and `resolvers.onFileTypesChange` hear a change the player woke the plugin for.** A plugin the player stopped and starts again to deliver a palette or file-type change ([Idle plugins are stopped](docs/explanation/plugins.md#idle-plugins-are-stopped)) read the new value with `window.state` at start; the held `appearance.changed` or `resolvers.fileTypes` that followed compared equal to it, so the listeners never ran. The SDK now compares an event with what the listeners last heard, not with the value read at start: they hear the change once, and the same value again is still not a change. The player sends `resolvers.fileTypes` only when the list changed. It sends `appearance.changed` when the theme or the language changed, both values each time; an event whose language differs from the last one the SDK saw is a language change, and as before does not call `onChange` unless the theme differs too (also for the echo of the plugin's own `setLanguage`). One case remains: a plugin the player woke for a language change alone cannot tell it from a theme change, since the event does not say what changed, and its `onChange` may hear the unchanged theme once. A field naming the change could be added to the event later without breaking anything. `appearance.theme`, `resolvers.fileTypes` and views read the same values as before. `packages.onChange` and `bindings.onChange` (the other two events that wake a plugin) never compared, and are unchanged.
|
|
15
|
+
- _Docs_: when session storage is cleared, corrected against the player: also when a store or another plugin with `plugins.manage` disables the plugin (`packages.setEnabled(id, false)`, or an update installed with `enabled: false`, player 0.6.4), and when an earlier installation kept for a later reinstall (after a `replace`) is dropped because the player keeps only so many; [How a plugin runs › Stopping](docs/explanation/plugins.md#stopping) names other plugins with `plugins.manage` too. `SessionStorage`, [How a plugin runs › Restarts](docs/explanation/plugins.md#restarts-activate-can-run-more-than-once) and [Store a plugin's data](docs/how-to/store-data.md) say so. `Appearance.onChange` and `Resolvers.onFileTypesChange` describe the wake.
|
|
16
|
+
|
|
17
|
+
## 1.5.55
|
|
18
|
+
|
|
19
|
+
**Who must change: nobody, but check `TestHost` tests that update a disabled plugin.** Additive: `InstallOptions` gains `enabled` and `TestHost` gains `installKeepsDisabled`, `apiVersion` stays `vestra/1`, no new capability, and existing code still compiles. Older players ignore the option, and an install there always enables the plugin. One `TestHost` behaviour change at run time: like player 0.6.4, it now keeps a `disabled` entry disabled when the plugin under test installs an update or reinstall over it without `enabled`, where it used to make it `running`. A test that expects the update to run (the player's Store test did) sets `host.installKeepsDisabled = false` to keep playing players up to 0.6.3, or asserts the new rule; a test that covers both runs once with each.
|
|
20
|
+
|
|
21
|
+
- **Choose whether an installed plugin runs** (`candidate.install({ enabled })`). Planned for player 0.6.4 (not released yet), where an update or reinstall (from any source, `replace` included) keeps the installed plugin's state when `enabled` is left out: a plugin the user or a store disabled stays disabled, and only its files are installed. A first install runs as before. `true` enables and starts it, for a store that disabled a revoked version and installs the update that fixes it (if the new version fails to start, the old one is restored, still disabled); `false` installs it disabled, stopping a running version and dropping its session values as `setEnabled(id, false)` does. Built-in plugins always run. The SDK sends `enabled` only when given and refuses anything but a boolean with `invalid` before sending. On players up to 0.6.3 the option is ignored and every install runs; call `packages.setEnabled` after the install is committed when the state matters there.
|
|
22
|
+
- **`TestHost`** models player 0.6.4 by default: a first install is `running`, an update of a `disabled` entry stays `disabled` unless `enabled: true`, `enabled: false` installs it `disabled`, and a non-boolean `enabled` on the wire is `invalid` (`null` reads as absent, as in the player). `installKeepsDisabled = false` plays players up to 0.6.3 instead: every install is `running` and `enabled` is ignored. `packageCalls` records `install`'s `enabled` when it was given.
|
|
23
|
+
- **Stop reasons, corrected** (SDK 1.5.54 docs, from the player's implementation for 0.6.4, still planned). `upgrade` also covers a new version stopped because its install was cancelled (the previous version comes back). `idle` comes only with lazy activation, which is off by default (`VESTRA_LAZY=1`), and also parks a plugin whose first run it judges lazy right after it is ready, not only after a while without use. `failed` is not sent for a full input queue: an own process is killed, and a member's full queue restarts the shared runner as a whole, as does an unreadable line from the runner itself. In those cases, and when an own process fails to activate or the process has already exited, the plugin gets no stop at all and its `onDispose` cleanups do not run. `host-lost` is not proof of a crash: player 0.6.4 sends `quit` on a normal exit, while an older player could close its pipes first when its window closed. [How a plugin runs › Restarts](docs/explanation/plugins.md#restarts-activate-can-run-more-than-once) now states as fact that plugins sharing one process are restarted together (player 0.5.6 and later). `StopReason`, [The plugin model](docs/explanation/plugins.md#stopping) and [Host and Node](docs/explanation/host-and-node.md#stopping-a-plugin) say so.
|
|
24
|
+
- _Docs_: `InstallOptions.enabled`, `PackageCandidate.install` (with an example; the `replace` example no longer promises a running plugin), `TestHost.candidates` and `TestHost.installKeepsDisabled` describe it.
|
|
25
|
+
|
|
3
26
|
## 1.5.54
|
|
4
27
|
|
|
5
28
|
**Who must change: nobody, but check cleanup functions passed directly to `onDispose`.** Additive: `Plugin.stopReason`, the types `StopReason` and `DisposeInfo`, an optional argument for `onDispose` cleanups, `TestHost.previousCrash`, an optional `code` for `TestHost.failVideoShaders` and `stop({ reason })` / `disconnect()` from `startPlugin`; `apiVersion` stays `vestra/1`, no new capability, and existing code still compiles. One runtime difference: with this SDK in the player (or in `TestHost`) an `onDispose` cleanup is called with one argument, the `DisposeInfo`. A function that takes optional parameters of its own and is passed directly now receives it as its first argument: `plugin.onDispose(controller.abort.bind(controller))` aborts with the `DisposeInfo` as the abort reason, `onDispose(save)` with `save(force?: boolean)` gets an object as `force`. Wrap such a function (`() => controller.abort()`). The stop reasons are planned for player 0.6.4; `previous-crash` comes from player 0.6.3.
|
package/dist/docs-tree.json.gz
CHANGED
|
Binary file
|
|
@@ -18,12 +18,34 @@ class AppearanceService {
|
|
|
18
18
|
/** The saved interface language (the next start's). */
|
|
19
19
|
language;
|
|
20
20
|
#listeners = new Set();
|
|
21
|
+
/**
|
|
22
|
+
* The theme listeners last heard (or the last event's), not the one read
|
|
23
|
+
* at start: a plugin woken from standby reads the new theme from
|
|
24
|
+
* `window.state`, then gets the `appearance.changed` the player held for
|
|
25
|
+
* it, and its listeners still hear that change. Left unset until the
|
|
26
|
+
* first event on purpose: set from the start-up read, it would hide that
|
|
27
|
+
* held change again.
|
|
28
|
+
*/
|
|
29
|
+
#notified;
|
|
30
|
+
/**
|
|
31
|
+
* The language as the start-up read and later events carried it (not
|
|
32
|
+
* `language`, which the plugin's own `setLanguage` changes before its
|
|
33
|
+
* echo arrives). The player sends `appearance.changed` once per change
|
|
34
|
+
* of the theme or of the language, each with both values: an event whose
|
|
35
|
+
* language differs from this one is a language change, and its theme is
|
|
36
|
+
* compared with `theme` as before. Only a newest event that repeats the
|
|
37
|
+
* start-up language is taken for a theme change; a plugin woken for a
|
|
38
|
+
* language change gets that too, so its listeners may hear the unchanged
|
|
39
|
+
* theme once (the event does not say what changed).
|
|
40
|
+
*/
|
|
41
|
+
#eventLanguage;
|
|
21
42
|
#started = false;
|
|
22
43
|
constructor(runtime) {
|
|
23
44
|
this.#runtime = runtime;
|
|
24
45
|
this.theme =
|
|
25
46
|
windowOf(runtime).appearance ?? "vestra";
|
|
26
47
|
this.language = preference(windowOf(runtime).language) ?? "system";
|
|
48
|
+
this.#eventLanguage = this.language;
|
|
27
49
|
}
|
|
28
50
|
start() {
|
|
29
51
|
if (this.#started)
|
|
@@ -33,22 +55,47 @@ class AppearanceService {
|
|
|
33
55
|
this.#runtime.on("appearance.changed", (data) => {
|
|
34
56
|
const event = data;
|
|
35
57
|
const language = preference(event?.language);
|
|
58
|
+
// Older players send no language: their events are theme changes.
|
|
59
|
+
const languageChanged = language !== undefined && language !== this.#eventLanguage;
|
|
60
|
+
if (language)
|
|
61
|
+
this.#eventLanguage = language;
|
|
36
62
|
if (language && language !== this.language) {
|
|
37
63
|
this.language = language;
|
|
38
64
|
this.#runtime.invalidate();
|
|
39
65
|
}
|
|
40
66
|
const theme = event?.theme;
|
|
41
|
-
if (theme
|
|
67
|
+
if (!theme || !THEMES.includes(theme))
|
|
68
|
+
return;
|
|
69
|
+
if (languageChanged) {
|
|
70
|
+
// The theme came along unchanged (or this plugin set it already).
|
|
42
71
|
this.accept(theme);
|
|
72
|
+
this.#notified = theme;
|
|
73
|
+
}
|
|
74
|
+
else
|
|
75
|
+
this.changed(theme);
|
|
43
76
|
}, "events", true);
|
|
44
77
|
}
|
|
78
|
+
/** A theme change event: listeners hear it unless they already did. */
|
|
79
|
+
changed(theme) {
|
|
80
|
+
const changed = theme !== this.theme;
|
|
81
|
+
this.theme = theme;
|
|
82
|
+
if (theme !== this.#notified)
|
|
83
|
+
this.#notify(theme);
|
|
84
|
+
if (changed)
|
|
85
|
+
this.#runtime.invalidate();
|
|
86
|
+
}
|
|
87
|
+
/** After this plugin's own `set`: only a change from the current theme. */
|
|
45
88
|
accept(theme) {
|
|
46
89
|
if (theme === this.theme)
|
|
47
90
|
return;
|
|
48
91
|
this.theme = theme;
|
|
92
|
+
this.#notify(theme);
|
|
93
|
+
this.#runtime.invalidate();
|
|
94
|
+
}
|
|
95
|
+
#notify(theme) {
|
|
96
|
+
this.#notified = theme;
|
|
49
97
|
for (const listener of [...this.#listeners])
|
|
50
98
|
listener(theme);
|
|
51
|
-
this.#runtime.invalidate();
|
|
52
99
|
}
|
|
53
100
|
watch(listener) {
|
|
54
101
|
this.#listeners.add(listener);
|
package/dist/plugin/packages.js
CHANGED
|
@@ -83,9 +83,11 @@ function installOptions(options) {
|
|
|
83
83
|
return {};
|
|
84
84
|
if (typeof options !== "object" || options === null)
|
|
85
85
|
throw failure("invalid", "install options must be an object");
|
|
86
|
-
const { source, replace } = options;
|
|
86
|
+
const { source, replace, enabled } = options;
|
|
87
87
|
if (replace !== undefined && typeof replace !== "boolean")
|
|
88
88
|
throw failure("invalid", "replace must be a boolean");
|
|
89
|
+
if (enabled !== undefined && typeof enabled !== "boolean")
|
|
90
|
+
throw failure("invalid", "enabled must be a boolean");
|
|
89
91
|
if (source !== undefined) {
|
|
90
92
|
const kinds = ["repository", "link", "file", "workshop"];
|
|
91
93
|
if (typeof source !== "object" ||
|
|
@@ -99,6 +101,7 @@ function installOptions(options) {
|
|
|
99
101
|
return {
|
|
100
102
|
...(source === undefined ? {} : { source: { ...source } }),
|
|
101
103
|
...(replace ? { replace: true } : {}),
|
|
104
|
+
...(enabled === undefined ? {} : { enabled }),
|
|
102
105
|
};
|
|
103
106
|
}
|
|
104
107
|
function candidate(scope, directory, checked) {
|
package/dist/plugin/resolvers.js
CHANGED
|
@@ -17,6 +17,14 @@ class FileTypesService {
|
|
|
17
17
|
#runtime;
|
|
18
18
|
types;
|
|
19
19
|
#listeners = new Set();
|
|
20
|
+
/**
|
|
21
|
+
* The list listeners last heard, not the one read at start: a plugin
|
|
22
|
+
* woken from standby reads the new list from `window.state`, then gets
|
|
23
|
+
* the `resolvers.fileTypes` the player held for it, and its listeners
|
|
24
|
+
* still hear that change. The player sends the event only when the list
|
|
25
|
+
* changed, so a first event is always news to the listeners.
|
|
26
|
+
*/
|
|
27
|
+
#notified;
|
|
20
28
|
#started = false;
|
|
21
29
|
constructor(runtime) {
|
|
22
30
|
this.#runtime = runtime;
|
|
@@ -30,7 +38,7 @@ class FileTypesService {
|
|
|
30
38
|
this.#runtime.on("resolvers.fileTypes", (data) => {
|
|
31
39
|
const types = data?.fileTypes;
|
|
32
40
|
if (Array.isArray(types) && types.every((t) => typeof t === "string"))
|
|
33
|
-
this.
|
|
41
|
+
this.changed(types);
|
|
34
42
|
}, "events", true);
|
|
35
43
|
// What changed between the start-up read and the subscription.
|
|
36
44
|
const window = windowOf(this.#runtime);
|
|
@@ -39,16 +47,33 @@ class FileTypesService {
|
|
|
39
47
|
this.accept(window.fileTypes, this.#runtime.can("events"));
|
|
40
48
|
});
|
|
41
49
|
}
|
|
42
|
-
/** `
|
|
50
|
+
/** From `resolvers.fileTypes`: listeners hear it unless they already did. */
|
|
51
|
+
changed(types) {
|
|
52
|
+
const changed = !sameTypes(types, this.types);
|
|
53
|
+
if (changed)
|
|
54
|
+
this.types = Object.freeze([...types]);
|
|
55
|
+
if (!this.#notified || !sameTypes(types, this.#notified))
|
|
56
|
+
this.#notify(types);
|
|
57
|
+
if (changed)
|
|
58
|
+
this.#runtime.invalidate();
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* From a `window.state` read: only a change from the current list.
|
|
62
|
+
* `notify`: false without `events`, whose listeners never fire.
|
|
63
|
+
*/
|
|
43
64
|
accept(types, notify = true) {
|
|
44
65
|
if (sameTypes(types, this.types))
|
|
45
66
|
return;
|
|
46
67
|
this.types = Object.freeze([...types]);
|
|
47
68
|
if (notify)
|
|
48
|
-
|
|
49
|
-
listener([...types]);
|
|
69
|
+
this.#notify(types);
|
|
50
70
|
this.#runtime.invalidate();
|
|
51
71
|
}
|
|
72
|
+
#notify(types) {
|
|
73
|
+
this.#notified = Object.freeze([...types]);
|
|
74
|
+
for (const listener of [...this.#listeners])
|
|
75
|
+
listener([...types]);
|
|
76
|
+
}
|
|
52
77
|
watch(listener) {
|
|
53
78
|
this.#listeners.add(listener);
|
|
54
79
|
const held = this.#runtime.hold("resolvers.fileTypes");
|
package/dist/plugin/testing.d.ts
CHANGED
|
@@ -2436,7 +2436,7 @@ export declare class TestHost implements Transport {
|
|
|
2436
2436
|
method: "setEnabled" | "reload" | "uninstall" | "install";
|
|
2437
2437
|
/** The target plugin id. */
|
|
2438
2438
|
id: string;
|
|
2439
|
-
/** `setEnabled`'s value. */
|
|
2439
|
+
/** `setEnabled`'s value, or `install`'s `enabled` option when given. */
|
|
2440
2440
|
enabled?: boolean;
|
|
2441
2441
|
/** `uninstall`'s `removeData` option. */
|
|
2442
2442
|
removeData?: boolean;
|
|
@@ -2450,7 +2450,13 @@ export declare class TestHost implements Transport {
|
|
|
2450
2450
|
* `.vxp` file) → its manifest. Other paths fail with `missing`. `install`
|
|
2451
2451
|
* adds or upgrades the entry in {@link TestHost.packages} following the
|
|
2452
2452
|
* player's source rules: the same source upgrades, another source needs
|
|
2453
|
-
* `replace` (`conflict` otherwise), a built-in id is `unsupported`.
|
|
2453
|
+
* `replace` (`conflict` otherwise), a built-in id is `unsupported`. As
|
|
2454
|
+
* player 0.6.4 (planned), the installed entry's `state` follows
|
|
2455
|
+
* {@link "@vestra-player/sdk"!InstallOptions.enabled}: a first install
|
|
2456
|
+
* runs, an update of a `disabled` entry stays `disabled` unless `enabled`
|
|
2457
|
+
* is `true`, and `enabled: false` installs it `disabled`. Set
|
|
2458
|
+
* {@link TestHost.installKeepsDisabled} to `false` for the older players'
|
|
2459
|
+
* rule (every install runs).
|
|
2454
2460
|
*
|
|
2455
2461
|
* @example Offer a package file the plugin can install
|
|
2456
2462
|
* The plugin can validate and install `/downloads/someone.demo-1.0.0.vxp`
|
|
@@ -2467,6 +2473,32 @@ export declare class TestHost implements Transport {
|
|
|
2467
2473
|
* ```
|
|
2468
2474
|
*/
|
|
2469
2475
|
readonly candidates: Map<string, PackageManifest>;
|
|
2476
|
+
/**
|
|
2477
|
+
* Which player's install rule `packages.install` plays. `true` (default,
|
|
2478
|
+
* SDK 1.5.55): player 0.6.4 (planned), where an update or reinstall
|
|
2479
|
+
* keeps a `disabled` plugin disabled unless the plugin passes
|
|
2480
|
+
* {@link "@vestra-player/sdk"!InstallOptions.enabled}. `false`: players
|
|
2481
|
+
* up to 0.6.3, where every install runs the plugin and `enabled` is
|
|
2482
|
+
* ignored (it is still checked and recorded in
|
|
2483
|
+
* {@link TestHost.packageCalls}). A test written for the older rule that
|
|
2484
|
+
* updates a disabled plugin sets it to `false`; one that keeps testing
|
|
2485
|
+
* both runs once with each.
|
|
2486
|
+
*
|
|
2487
|
+
* @example Keep a test written for players up to 0.6.3
|
|
2488
|
+
* The plugin under test installs an update over a plugin the user
|
|
2489
|
+
* disabled and expects it to run afterwards, as players up to 0.6.3 do.
|
|
2490
|
+
* With `false` the host plays that rule; the default plays player 0.6.4,
|
|
2491
|
+
* where the update stays disabled.
|
|
2492
|
+
* ```ts
|
|
2493
|
+
* declare const host: TestHost;
|
|
2494
|
+
* // ---cut---
|
|
2495
|
+
* host.installKeepsDisabled = false; // every install runs, as before 0.6.4
|
|
2496
|
+
* ```
|
|
2497
|
+
*
|
|
2498
|
+
* @capability None.
|
|
2499
|
+
* @since 1.5.55
|
|
2500
|
+
*/
|
|
2501
|
+
installKeepsDisabled: boolean;
|
|
2470
2502
|
/**
|
|
2471
2503
|
* Remote images (`plugin.ui.markdown`, `plugin.ui.remoteImage`): address
|
|
2472
2504
|
* → a local PNG or JPEG file served for it, or `null` for a failing
|
package/dist/plugin/testing.js
CHANGED
|
@@ -3099,7 +3099,13 @@ export class TestHost {
|
|
|
3099
3099
|
* `.vxp` file) → its manifest. Other paths fail with `missing`. `install`
|
|
3100
3100
|
* adds or upgrades the entry in {@link TestHost.packages} following the
|
|
3101
3101
|
* player's source rules: the same source upgrades, another source needs
|
|
3102
|
-
* `replace` (`conflict` otherwise), a built-in id is `unsupported`.
|
|
3102
|
+
* `replace` (`conflict` otherwise), a built-in id is `unsupported`. As
|
|
3103
|
+
* player 0.6.4 (planned), the installed entry's `state` follows
|
|
3104
|
+
* {@link "@vestra-player/sdk"!InstallOptions.enabled}: a first install
|
|
3105
|
+
* runs, an update of a `disabled` entry stays `disabled` unless `enabled`
|
|
3106
|
+
* is `true`, and `enabled: false` installs it `disabled`. Set
|
|
3107
|
+
* {@link TestHost.installKeepsDisabled} to `false` for the older players'
|
|
3108
|
+
* rule (every install runs).
|
|
3103
3109
|
*
|
|
3104
3110
|
* @example Offer a package file the plugin can install
|
|
3105
3111
|
* The plugin can validate and install `/downloads/someone.demo-1.0.0.vxp`
|
|
@@ -3116,6 +3122,32 @@ export class TestHost {
|
|
|
3116
3122
|
* ```
|
|
3117
3123
|
*/
|
|
3118
3124
|
candidates = new Map();
|
|
3125
|
+
/**
|
|
3126
|
+
* Which player's install rule `packages.install` plays. `true` (default,
|
|
3127
|
+
* SDK 1.5.55): player 0.6.4 (planned), where an update or reinstall
|
|
3128
|
+
* keeps a `disabled` plugin disabled unless the plugin passes
|
|
3129
|
+
* {@link "@vestra-player/sdk"!InstallOptions.enabled}. `false`: players
|
|
3130
|
+
* up to 0.6.3, where every install runs the plugin and `enabled` is
|
|
3131
|
+
* ignored (it is still checked and recorded in
|
|
3132
|
+
* {@link TestHost.packageCalls}). A test written for the older rule that
|
|
3133
|
+
* updates a disabled plugin sets it to `false`; one that keeps testing
|
|
3134
|
+
* both runs once with each.
|
|
3135
|
+
*
|
|
3136
|
+
* @example Keep a test written for players up to 0.6.3
|
|
3137
|
+
* The plugin under test installs an update over a plugin the user
|
|
3138
|
+
* disabled and expects it to run afterwards, as players up to 0.6.3 do.
|
|
3139
|
+
* With `false` the host plays that rule; the default plays player 0.6.4,
|
|
3140
|
+
* where the update stays disabled.
|
|
3141
|
+
* ```ts
|
|
3142
|
+
* declare const host: TestHost;
|
|
3143
|
+
* // ---cut---
|
|
3144
|
+
* host.installKeepsDisabled = false; // every install runs, as before 0.6.4
|
|
3145
|
+
* ```
|
|
3146
|
+
*
|
|
3147
|
+
* @capability None.
|
|
3148
|
+
* @since 1.5.55
|
|
3149
|
+
*/
|
|
3150
|
+
installKeepsDisabled = true;
|
|
3119
3151
|
/**
|
|
3120
3152
|
* Remote images (`plugin.ui.markdown`, `plugin.ui.remoteImage`): address
|
|
3121
3153
|
* → a local PNG or JPEG file served for it, or `null` for a failing
|
|
@@ -5105,6 +5137,10 @@ export class TestHost {
|
|
|
5105
5137
|
if (source.kind === "bundled")
|
|
5106
5138
|
throw new HostError("invalid", "source bundled is reserved");
|
|
5107
5139
|
const replace = p.replace === true;
|
|
5140
|
+
// As player 0.6.4: `enabled` is a boolean, or absent (`null` too).
|
|
5141
|
+
const requested = p.enabled ?? undefined;
|
|
5142
|
+
if (requested !== undefined && typeof requested !== "boolean")
|
|
5143
|
+
throw new HostError("invalid", "enabled must be a boolean");
|
|
5108
5144
|
const entry = this.packages.find((each) => each.id === manifest.id);
|
|
5109
5145
|
if (entry?.builtin)
|
|
5110
5146
|
throw new HostError("unsupported", "built-in plugins are updated with the player");
|
|
@@ -5120,12 +5156,19 @@ export class TestHost {
|
|
|
5120
5156
|
id: manifest.id,
|
|
5121
5157
|
source,
|
|
5122
5158
|
replace,
|
|
5159
|
+
...(requested === undefined ? {} : { enabled: requested }),
|
|
5123
5160
|
});
|
|
5161
|
+
// As player 0.6.4: a first install runs; an update or reinstall (any
|
|
5162
|
+
// source, `replace` included) keeps a disabled plugin disabled unless
|
|
5163
|
+
// `enabled` is given. Players up to 0.6.3 run every install.
|
|
5164
|
+
const enabled = !this.installKeepsDisabled
|
|
5165
|
+
? true
|
|
5166
|
+
: (requested ?? entry?.state !== "disabled");
|
|
5124
5167
|
const next = {
|
|
5125
5168
|
id: manifest.id,
|
|
5126
5169
|
version: manifest.version,
|
|
5127
5170
|
origin: "installed",
|
|
5128
|
-
state: "running",
|
|
5171
|
+
state: enabled ? "running" : "disabled",
|
|
5129
5172
|
error: null,
|
|
5130
5173
|
capabilities: [...manifest.capabilities],
|
|
5131
5174
|
directory: `/test-host/packages/${manifest.id}`,
|
package/dist/plugin/types.d.ts
CHANGED
|
@@ -592,15 +592,33 @@ export interface Plugin extends Scope {
|
|
|
592
592
|
* - `uninstall`: the user uninstalls it.
|
|
593
593
|
* - `reload`: it is reloaded (from the plugin manager, or a development
|
|
594
594
|
* load with `--plugin`).
|
|
595
|
-
* - `upgrade`:
|
|
596
|
-
*
|
|
597
|
-
*
|
|
598
|
-
* - `
|
|
599
|
-
*
|
|
600
|
-
*
|
|
595
|
+
* - `upgrade`: an install or update replaces the running version; also a
|
|
596
|
+
* new version stopped because its install was cancelled (the previous
|
|
597
|
+
* version comes back).
|
|
598
|
+
* - `idle`: lazy activation parks it; its next use starts it again
|
|
599
|
+
* ([Idle plugins](../../docs/explanation/plugins.md#idle-plugins-are-stopped)).
|
|
600
|
+
* Lazy activation is off by default (`VESTRA_LAZY=1` turns it on); when
|
|
601
|
+
* on, it parks a plugin after a while without use, and parks one whose
|
|
602
|
+
* first run it judges lazy right after it is ready.
|
|
603
|
+
* - `failed`: the player stops it for a fault it can still tell the plugin
|
|
604
|
+
* about (planned to cover a malformed request, an unreadable or
|
|
605
|
+
* oversized line, an activation timeout, an uncaught exception the
|
|
606
|
+
* shared process attributes to this plugin).
|
|
601
607
|
* - `host-lost`: the plugin's runner saw its pipe to the player close
|
|
602
608
|
* without a stop: the player crashed or was killed. Requests are refused
|
|
603
|
-
* with `disabled`; clean up locally only.
|
|
609
|
+
* with `disabled`; clean up locally only. Player 0.6.4 sends `quit` on a
|
|
610
|
+
* normal exit; read `host-lost` as "the player is gone", not as proof of
|
|
611
|
+
* a crash.
|
|
612
|
+
*
|
|
613
|
+
* Some ends are no stop at all: the plugin gets no reason and its
|
|
614
|
+
* {@link Scope.onDispose} cleanups do not run. That is the case when its
|
|
615
|
+
* own process is killed because its input queue is full or a write to it
|
|
616
|
+
* failed, when its own process fails to activate (the runner exits without
|
|
617
|
+
* cleaning up), when the process has already exited, and when the shared
|
|
618
|
+
* runner is restarted as a whole (a hang, a fault it cannot attribute, an
|
|
619
|
+
* unreadable line of its own, a member's full input queue, the runner
|
|
620
|
+
* exiting): every member is killed with it. Keep state that must survive
|
|
621
|
+
* such an end in storage as it changes, not only in a cleanup.
|
|
604
622
|
*
|
|
605
623
|
* Planned for player 0.6.4: the player sends the reason with
|
|
606
624
|
* `runtime.stop` and its runner reports `host-lost`; the exact list may
|
|
@@ -3122,7 +3140,13 @@ export interface Appearance {
|
|
|
3122
3140
|
* Needs `events`: without it the listener never runs for other plugins'
|
|
3123
3141
|
* changes. Views need no help to follow the palette; use this for things
|
|
3124
3142
|
* the plugin draws itself (a rendered image, colours it computes). Errors
|
|
3125
|
-
* go to {@link Scope.onError}.
|
|
3143
|
+
* go to {@link Scope.onError}. When the player starts a stopped plugin
|
|
3144
|
+
* for a palette change (player 0.5.21), {@link Appearance.theme} already
|
|
3145
|
+
* has the new palette at start and the listener still hears the change
|
|
3146
|
+
* (SDK 1.5.56; before, it did not run). A language change does not call
|
|
3147
|
+
* it, except in one case: a plugin started for a language change alone
|
|
3148
|
+
* may hear the unchanged palette once, since the player's event does not
|
|
3149
|
+
* say which of the two changed.
|
|
3126
3150
|
*
|
|
3127
3151
|
* @capability events
|
|
3128
3152
|
*
|
|
@@ -8406,6 +8430,10 @@ export interface Resolvers {
|
|
|
8406
8430
|
/**
|
|
8407
8431
|
* {@link Resolvers.fileTypes} changed: a file resolver was added, or its
|
|
8408
8432
|
* plugin stopped. Needs `events`; without it the listener never fires.
|
|
8433
|
+
* When the player starts a stopped plugin for this change (player
|
|
8434
|
+
* 0.5.21), {@link Resolvers.fileTypes} already has the new list at start
|
|
8435
|
+
* and the listener still hears the change (SDK 1.5.56; before, it did not
|
|
8436
|
+
* run).
|
|
8409
8437
|
*
|
|
8410
8438
|
* @param listener - Receives the new list.
|
|
8411
8439
|
* @returns A handle that removes the listener.
|
|
@@ -8516,6 +8544,27 @@ export interface InstallOptions {
|
|
|
8516
8544
|
* reconnects to it.
|
|
8517
8545
|
*/
|
|
8518
8546
|
replace?: boolean;
|
|
8547
|
+
/**
|
|
8548
|
+
* Whether the plugin runs once installed (SDK 1.5.55). Left out, a first
|
|
8549
|
+
* install runs and an update or reinstall (from any source, `replace`
|
|
8550
|
+
* included) keeps the installed plugin's state: one the user or a store
|
|
8551
|
+
* disabled stays disabled, and only its files are installed (nothing
|
|
8552
|
+
* starts it, so the change commits without an `activating` step). `true`
|
|
8553
|
+
* enables and starts it, for example a version the store disabled that
|
|
8554
|
+
* an update fixes; if it fails to start, the old version is restored,
|
|
8555
|
+
* still disabled. `false` installs it disabled: a running version is
|
|
8556
|
+
* stopped and its session values go, as with {@link Packages.setEnabled}.
|
|
8557
|
+
* Built-in plugins always run (and cannot be installed over). A value
|
|
8558
|
+
* that is not a boolean is `invalid`.
|
|
8559
|
+
*
|
|
8560
|
+
* Planned for player 0.6.4: older players ignore it and an install there
|
|
8561
|
+
* always runs the plugin, so after a committed install call
|
|
8562
|
+
* {@link Packages.setEnabled} when the state matters.
|
|
8563
|
+
*
|
|
8564
|
+
* @player 0.6.4 (planned); older players ignore it (an install always
|
|
8565
|
+
* enables the plugin).
|
|
8566
|
+
*/
|
|
8567
|
+
enabled?: boolean;
|
|
8519
8568
|
}
|
|
8520
8569
|
/**
|
|
8521
8570
|
* A validated package directory, from {@link Packages.validate}.
|
|
@@ -8560,9 +8609,14 @@ export interface PackageCandidate {
|
|
|
8560
8609
|
* a built-in id fails with `unsupported` (built-ins update with the
|
|
8561
8610
|
* player). A reserved id (`vestra` or `vestra.*`, which every built-in
|
|
8562
8611
|
* uses) fails with `reserved` (player 0.4.1; already when validated), as
|
|
8563
|
-
* only the player installs built-in plugins.
|
|
8612
|
+
* only the player installs built-in plugins. An update keeps a disabled
|
|
8613
|
+
* plugin disabled unless `enabled` says otherwise (planned for player
|
|
8614
|
+
* 0.6.4; see {@link InstallOptions.enabled}).
|
|
8564
8615
|
*
|
|
8565
8616
|
* @capability plugins.manage
|
|
8617
|
+
* @throws `invalid` (rejects) An option is malformed: a `source` the
|
|
8618
|
+
* player does not accept, or a `replace` or `enabled` that is not a
|
|
8619
|
+
* boolean.
|
|
8566
8620
|
* @throws `conflict` (rejects) The plugin is installed from another source
|
|
8567
8621
|
* and `replace` is not set.
|
|
8568
8622
|
* @throws `unsupported` (rejects) A built-in plugin's id.
|
|
@@ -8585,7 +8639,19 @@ export interface PackageCandidate {
|
|
|
8585
8639
|
* declare const candidate: PackageCandidate;
|
|
8586
8640
|
* // ---cut---
|
|
8587
8641
|
* const change = await candidate.install({ replace: true });
|
|
8588
|
-
* await change.done; // "committed" once
|
|
8642
|
+
* await change.done; // "committed" once installed (running unless it stays disabled)
|
|
8643
|
+
* ```
|
|
8644
|
+
*
|
|
8645
|
+
* @example Enable a fixed version of a plugin the store disabled
|
|
8646
|
+
* A store disabled a version it revoked; the update that fixes it
|
|
8647
|
+
* should run again. Without `enabled` the update keeps the plugin
|
|
8648
|
+
* disabled (player 0.6.4); `true` enables it once installed. Older
|
|
8649
|
+
* players ignore the option and enable every install.
|
|
8650
|
+
* ```ts
|
|
8651
|
+
* declare const candidate: PackageCandidate;
|
|
8652
|
+
* // ---cut---
|
|
8653
|
+
* const change = await candidate.install({ enabled: true });
|
|
8654
|
+
* await change.done; // "committed" once the fixed version runs
|
|
8589
8655
|
* ```
|
|
8590
8656
|
*
|
|
8591
8657
|
* @since 1.0.0
|
|
@@ -9665,7 +9731,10 @@ export interface Storage {
|
|
|
9665
9731
|
* | --- | --- |
|
|
9666
9732
|
* | The plugin's process restarts: reload, upgrade, crash | kept |
|
|
9667
9733
|
* | The plugin failed to start or failed, and starts again | kept |
|
|
9668
|
-
* | The user
|
|
9734
|
+
* | The user, or a plugin with `plugins.manage` (`packages.setEnabled(id, false)`), disables the plugin | cleared |
|
|
9735
|
+
* | An update or reinstall with `enabled: false` switches an enabled plugin off (player 0.6.4) | cleared |
|
|
9736
|
+
* | The plugin is uninstalled | cleared |
|
|
9737
|
+
* | An earlier installation kept for a later reinstall (after a `replace`) is dropped because the player keeps only so many | cleared |
|
|
9669
9738
|
* | The player quits | cleared |
|
|
9670
9739
|
*
|
|
9671
9740
|
* - **Per plugin.** A plugin sees only its own keys. Like
|
package/dist/wire.d.ts
CHANGED
|
@@ -731,10 +731,15 @@ export declare function createWire(transport: Transport): {
|
|
|
731
731
|
/** Player 0.2.0: a `.vxp` file. */
|
|
732
732
|
validateArchive: (file: string) => Promise<PackageValidation>;
|
|
733
733
|
installed: (id: string) => Promise<InstalledPackage>;
|
|
734
|
-
/**
|
|
734
|
+
/**
|
|
735
|
+
* `source` and `replace` are sent only when given (player 0.2.0);
|
|
736
|
+
* `enabled` only when given (planned for player 0.6.4, older players
|
|
737
|
+
* ignore it).
|
|
738
|
+
*/
|
|
735
739
|
install: (token: string, expectedRevision: number, requestId: string, options?: {
|
|
736
740
|
source?: WireSource;
|
|
737
741
|
replace?: boolean;
|
|
742
|
+
enabled?: boolean;
|
|
738
743
|
}) => Promise<PackageOperation>;
|
|
739
744
|
operation: (operationId: string) => Promise<PackageOperation>;
|
|
740
745
|
cancel: (operationId: string) => Promise<PackageOperation>;
|
package/dist/wire.js
CHANGED
|
@@ -341,13 +341,20 @@ export function createWire(transport) {
|
|
|
341
341
|
file,
|
|
342
342
|
}),
|
|
343
343
|
installed: (id) => transport.request("packages.installed", { id }),
|
|
344
|
-
/**
|
|
344
|
+
/**
|
|
345
|
+
* `source` and `replace` are sent only when given (player 0.2.0);
|
|
346
|
+
* `enabled` only when given (planned for player 0.6.4, older players
|
|
347
|
+
* ignore it).
|
|
348
|
+
*/
|
|
345
349
|
install: (token, expectedRevision, requestId, options = {}) => transport.request("packages.install", {
|
|
346
350
|
token,
|
|
347
351
|
expectedRevision,
|
|
348
352
|
requestId,
|
|
349
353
|
...(options.source === undefined ? {} : { source: options.source }),
|
|
350
354
|
...(options.replace ? { replace: true } : {}),
|
|
355
|
+
...(options.enabled === undefined
|
|
356
|
+
? {}
|
|
357
|
+
: { enabled: options.enabled }),
|
|
351
358
|
}),
|
|
352
359
|
operation: (operationId) => transport.request("packages.operation", {
|
|
353
360
|
operationId,
|
package/llms.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Vestra Player SDK
|
|
2
2
|
|
|
3
|
-
> @vestra-player/sdk 1.5.
|
|
3
|
+
> @vestra-player/sdk 1.5.57: write plugins for Vestra Player in TypeScript (plugin API `vestra/1`). A plugin is a folder with `manifest.json` and an entry module whose default export `activate(plugin)` the player calls. The documentation is a tree of short pages read by path, like `go doc`: the same pages and API as https://vestrasys.github.io/vestra-player-sdk/.
|
|
4
4
|
|
|
5
5
|
## Read the documentation
|
|
6
6
|
|