@vestra-player/sdk 1.5.24 → 1.5.26
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 +19 -0
- package/bin/vestra-plugin.mjs +85 -10
- package/dist/api-requests.d.ts +36 -0
- package/dist/api-requests.js +143 -0
- package/dist/capabilities.d.ts +6 -3
- package/dist/capabilities.js +16 -230
- package/dist/capability-check.d.ts +85 -0
- package/dist/capability-check.js +203 -0
- package/dist/capability-rules.d.ts +102 -0
- package/dist/capability-rules.js +132 -0
- package/dist/host-capabilities.d.ts +10 -0
- package/dist/host-capabilities.js +360 -0
- package/dist/player-runtime.d.ts +2 -1
- package/dist/player-runtime.js +2 -1
- package/dist/plugin/services.d.ts +35 -0
- package/dist/plugin/services.js +87 -2
- package/dist/plugin/testing.d.ts +24 -1
- package/dist/plugin/testing.js +71 -1
- package/dist/plugin/types.d.ts +79 -1
- package/dist/ts-plugin.d.ts +23 -0
- package/dist/ts-plugin.js +135 -0
- package/dist/wire.d.ts +12 -0
- package/dist/wire.js +10 -0
- package/llms-full.txt +201 -8
- package/llms.txt +23 -23
- package/package.json +9 -3
- package/ts-plugin/index.d.ts +8 -0
- package/ts-plugin/index.js +6 -0
- package/ts-plugin/package.json +3 -0
package/dist/plugin/testing.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { BuiltinCommand, BuiltinSubmenu } from "../commands.ts";
|
|
|
7
7
|
import { CAPABILITY_INFO, MESSAGE_LIMIT } from "../capabilities.ts";
|
|
8
8
|
import type { CapabilityInfo } from "../capabilities.ts";
|
|
9
9
|
import { OSD_LIMITS } from "./osd.ts";
|
|
10
|
-
import { SECRET_LIMITS } from "./services.ts";
|
|
10
|
+
import { SECRET_LIMITS, SESSION_STORAGE_LIMITS } from "./services.ts";
|
|
11
11
|
import { BINDING_INPUTS } from "./bindings.ts";
|
|
12
12
|
import { THEME_PALETTES } from "../palettes.ts";
|
|
13
13
|
import { IMAGE_LIMITS, UI_VALUE_LIMITS } from "./values.ts";
|
|
@@ -34,6 +34,8 @@ export { MESSAGE_LIMIT };
|
|
|
34
34
|
export { OSD_LIMITS };
|
|
35
35
|
/** The player's limits on stored secrets. */
|
|
36
36
|
export { SECRET_LIMITS };
|
|
37
|
+
/** The player's limits on `plugin.storage.session`. */
|
|
38
|
+
export { SESSION_STORAGE_LIMITS };
|
|
37
39
|
/** The names bindings are made of. */
|
|
38
40
|
export { BINDING_INPUTS };
|
|
39
41
|
/** The colours of the player's palettes, by `Theme` token. */
|
|
@@ -1072,6 +1074,18 @@ export declare class TestHost implements Transport {
|
|
|
1072
1074
|
* new name fails `limit` once 256 names are stored.
|
|
1073
1075
|
*/
|
|
1074
1076
|
readonly secrets: Map<string, string>;
|
|
1077
|
+
/**
|
|
1078
|
+
* `plugin.storage.session`: key → value, as the player keeps them. Preset
|
|
1079
|
+
* entries to simulate values an earlier run of the plugin stored in the
|
|
1080
|
+
* same player session, or pass `startPlugin(..., { sessionStorage })`.
|
|
1081
|
+
* Like the player, a key is 1–1024 bytes, a `set` that would make the
|
|
1082
|
+
* entries larger than 1 MiB (keys, values as compact JSON and
|
|
1083
|
+
* `SESSION_STORAGE_LIMITS.entryBytes` per key) fails `limit`, and an
|
|
1084
|
+
* object's properties are stored sorted, as the player hands them back.
|
|
1085
|
+
*
|
|
1086
|
+
* @since 1.5.25
|
|
1087
|
+
*/
|
|
1088
|
+
readonly sessionStorage: Map<string, Json>;
|
|
1075
1089
|
/** What `plugin.app.system()` reports. */
|
|
1076
1090
|
systemInfo: WireSystemInfo;
|
|
1077
1091
|
/**
|
|
@@ -1594,6 +1608,15 @@ export declare function startPlugin(activate: (plugin: Plugin) => unknown, optio
|
|
|
1594
1608
|
host?: TestHost;
|
|
1595
1609
|
/** Use an existing directory as `plugin.dataDirectory` (to test a restart). */
|
|
1596
1610
|
dataDirectory?: string;
|
|
1611
|
+
/**
|
|
1612
|
+
* Start with these `plugin.storage.session` values (to test a restart
|
|
1613
|
+
* within one player session): pass the previous run's
|
|
1614
|
+
* `host.sessionStorage`. They are copied into the new host's
|
|
1615
|
+
* {@link TestHost.sessionStorage}.
|
|
1616
|
+
*
|
|
1617
|
+
* @since 1.5.25
|
|
1618
|
+
*/
|
|
1619
|
+
sessionStorage?: ReadonlyMap<string, Json>;
|
|
1597
1620
|
/**
|
|
1598
1621
|
* The interface language to play ({@link TestHost.locale}): the plugin's
|
|
1599
1622
|
* `locales/<locale>.po` (or `.json`) in `directory` is loaded and its
|
package/dist/plugin/testing.js
CHANGED
|
@@ -69,7 +69,7 @@ import { localSocketPath } from "./local.js";
|
|
|
69
69
|
import { BUILTIN_COMMANDS, BUILTIN_SUBMENUS, COMMAND_LIMITS, MENU_IDS, } from "../commands.js";
|
|
70
70
|
import { CAPABILITY_INFO, MESSAGE_LIMIT } from "../capabilities.js";
|
|
71
71
|
import { OSD_LIMITS } from "./osd.js";
|
|
72
|
-
import { SECRET_LIMITS } from "./services.js";
|
|
72
|
+
import { SECRET_LIMITS, SESSION_STORAGE_LIMITS } from "./services.js";
|
|
73
73
|
import { BINDING_INPUTS } from "./bindings.js";
|
|
74
74
|
import { THEME_PALETTES } from "../palettes.js";
|
|
75
75
|
import { IMAGE_LIMITS, UI_VALUE_LIMITS, valueLimitProblem } from "./values.js";
|
|
@@ -93,6 +93,8 @@ export { MESSAGE_LIMIT };
|
|
|
93
93
|
export { OSD_LIMITS };
|
|
94
94
|
/** The player's limits on stored secrets. */
|
|
95
95
|
export { SECRET_LIMITS };
|
|
96
|
+
/** The player's limits on `plugin.storage.session`. */
|
|
97
|
+
export { SESSION_STORAGE_LIMITS };
|
|
96
98
|
/** The names bindings are made of. */
|
|
97
99
|
export { BINDING_INPUTS };
|
|
98
100
|
/** The colours of the player's palettes, by `Theme` token. */
|
|
@@ -131,6 +133,34 @@ export { LIST_SEPARATORS };
|
|
|
131
133
|
* ```
|
|
132
134
|
*/
|
|
133
135
|
export { LOCALES };
|
|
136
|
+
/** A `plugin.storage.session` key, checked as the player does. */
|
|
137
|
+
function sessionKey(key) {
|
|
138
|
+
if (typeof key !== "string" ||
|
|
139
|
+
!key ||
|
|
140
|
+
key.includes("\0") ||
|
|
141
|
+
Buffer.byteLength(key) > SESSION_STORAGE_LIMITS.keyBytes)
|
|
142
|
+
throw new HostError("invalid", "session storage keys are 1–1024 bytes of text without NUL");
|
|
143
|
+
return key;
|
|
144
|
+
}
|
|
145
|
+
/** Code point (UTF-8 byte) order, as the player sorts session keys. */
|
|
146
|
+
const byCodePoint = (a, b) => Buffer.compare(Buffer.from(a), Buffer.from(b));
|
|
147
|
+
/**
|
|
148
|
+
* A JSON value as the player hands it back: the player does not keep the
|
|
149
|
+
* order of object properties, it sorts them.
|
|
150
|
+
*/
|
|
151
|
+
function sortedJson(value) {
|
|
152
|
+
if (Array.isArray(value))
|
|
153
|
+
return value.map(sortedJson);
|
|
154
|
+
if (value && typeof value === "object")
|
|
155
|
+
return Object.fromEntries(Object.keys(value)
|
|
156
|
+
.sort(byCodePoint)
|
|
157
|
+
.map((key) => [key, sortedJson(value[key])]));
|
|
158
|
+
return value;
|
|
159
|
+
}
|
|
160
|
+
/** What one session entry counts towards the player's quota. */
|
|
161
|
+
const sessionCost = (key, value) => Buffer.byteLength(key) +
|
|
162
|
+
Buffer.byteLength(JSON.stringify(value)) +
|
|
163
|
+
SESSION_STORAGE_LIMITS.entryBytes;
|
|
134
164
|
const kebab = (name) => name.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
|
|
135
165
|
const tick = () => new Promise((resolve) => setImmediate(resolve));
|
|
136
166
|
function initialSource() {
|
|
@@ -1156,6 +1186,18 @@ export class TestHost {
|
|
|
1156
1186
|
* new name fails `limit` once 256 names are stored.
|
|
1157
1187
|
*/
|
|
1158
1188
|
secrets = new Map();
|
|
1189
|
+
/**
|
|
1190
|
+
* `plugin.storage.session`: key → value, as the player keeps them. Preset
|
|
1191
|
+
* entries to simulate values an earlier run of the plugin stored in the
|
|
1192
|
+
* same player session, or pass `startPlugin(..., { sessionStorage })`.
|
|
1193
|
+
* Like the player, a key is 1–1024 bytes, a `set` that would make the
|
|
1194
|
+
* entries larger than 1 MiB (keys, values as compact JSON and
|
|
1195
|
+
* `SESSION_STORAGE_LIMITS.entryBytes` per key) fails `limit`, and an
|
|
1196
|
+
* object's properties are stored sorted, as the player hands them back.
|
|
1197
|
+
*
|
|
1198
|
+
* @since 1.5.25
|
|
1199
|
+
*/
|
|
1200
|
+
sessionStorage = new Map();
|
|
1159
1201
|
/** What `plugin.app.system()` reports. */
|
|
1160
1202
|
systemInfo = {
|
|
1161
1203
|
os: "macos",
|
|
@@ -1622,6 +1664,11 @@ export class TestHost {
|
|
|
1622
1664
|
*/
|
|
1623
1665
|
async request(method, params = {}) {
|
|
1624
1666
|
const record = (params ?? {});
|
|
1667
|
+
// The plugin runner refuses a request over the message limit before
|
|
1668
|
+
// sending it (runtime/runner.ts): the player never sees it.
|
|
1669
|
+
const line = JSON.stringify({ id: this.calls.length + 1, method, params });
|
|
1670
|
+
if (Buffer.byteLength(line, "utf8") + 1 > MESSAGE_LIMIT)
|
|
1671
|
+
throw new HostError("limit", `${method}: request exceeds the player's 1 MiB message limit`);
|
|
1625
1672
|
this.calls.push({ method, params: record });
|
|
1626
1673
|
this.#inFlight++;
|
|
1627
1674
|
try {
|
|
@@ -2709,6 +2756,27 @@ export class TestHost {
|
|
|
2709
2756
|
this.secrets.set(name, String(p.value));
|
|
2710
2757
|
},
|
|
2711
2758
|
"secrets.delete": (p) => void this.secrets.delete(String(p.name)),
|
|
2759
|
+
// As the player (host/src/session_storage.rs).
|
|
2760
|
+
"storage.session.get": (p) => {
|
|
2761
|
+
const key = sessionKey(p.key);
|
|
2762
|
+
return this.sessionStorage.has(key)
|
|
2763
|
+
? { value: structuredClone(this.sessionStorage.get(key)) }
|
|
2764
|
+
: {};
|
|
2765
|
+
},
|
|
2766
|
+
"storage.session.set": (p) => {
|
|
2767
|
+
const key = sessionKey(p.key);
|
|
2768
|
+
if (!("value" in p))
|
|
2769
|
+
throw new HostError("invalid", "set needs a JSON value");
|
|
2770
|
+
let total = sessionCost(key, p.value);
|
|
2771
|
+
for (const [other, value] of this.sessionStorage)
|
|
2772
|
+
if (other !== key)
|
|
2773
|
+
total += sessionCost(other, value);
|
|
2774
|
+
if (total > SESSION_STORAGE_LIMITS.bytes)
|
|
2775
|
+
throw new HostError("limit", `session storage holds at most ${SESSION_STORAGE_LIMITS.bytes} bytes per plugin (keys, JSON values and ${SESSION_STORAGE_LIMITS.entryBytes} per key); this value would make ${total}`);
|
|
2776
|
+
this.sessionStorage.set(key, sortedJson(p.value));
|
|
2777
|
+
},
|
|
2778
|
+
"storage.session.delete": (p) => void this.sessionStorage.delete(sessionKey(p.key)),
|
|
2779
|
+
"storage.session.keys": () => [...this.sessionStorage.keys()].sort(byCodePoint),
|
|
2712
2780
|
"app.system": () => structuredClone(this.systemInfo),
|
|
2713
2781
|
"clipboard.readText": () => this.clipboard.text,
|
|
2714
2782
|
"clipboard.writeText": (p) => {
|
|
@@ -3741,6 +3809,8 @@ export async function startPlugin(activate, options = {}) {
|
|
|
3741
3809
|
host.manifestId = manifest.id;
|
|
3742
3810
|
host.loadCatalog(directory);
|
|
3743
3811
|
const data = options.dataDirectory ?? host.dataDirectory;
|
|
3812
|
+
for (const [key, value] of options.sessionStorage ?? [])
|
|
3813
|
+
host.sessionStorage.set(key, sortedJson(value));
|
|
3744
3814
|
mkdirSync(data, { recursive: true });
|
|
3745
3815
|
const instance = await createPlugin(host, manifest, directory, data, host.app);
|
|
3746
3816
|
await activate(instance.plugin);
|
package/dist/plugin/types.d.ts
CHANGED
|
@@ -4390,7 +4390,8 @@ export interface SubtitleConditional {
|
|
|
4390
4390
|
}
|
|
4391
4391
|
/**
|
|
4392
4392
|
* `plugin.subtitles`: tracks, uploads and style. Reading needs
|
|
4393
|
-
* `subtitles.read
|
|
4393
|
+
* `subtitles.read`; changes need `subtitles.control` and also
|
|
4394
|
+
* `subtitles.read`, since they read the current selection first.
|
|
4394
4395
|
*
|
|
4395
4396
|
* @example Load an SRT file as the secondary subtitle
|
|
4396
4397
|
* ```ts
|
|
@@ -5232,6 +5233,83 @@ export interface Storage {
|
|
|
5232
5233
|
validate?: (value: unknown) => value is T;
|
|
5233
5234
|
debounceMs?: number;
|
|
5234
5235
|
}): JsonStore<T>;
|
|
5236
|
+
/**
|
|
5237
|
+
* Values the player keeps in its memory for this plugin until the player
|
|
5238
|
+
* quits: they survive a restart of the plugin's process (reload, upgrade,
|
|
5239
|
+
* crash, failed start) but not the player. See {@link SessionStorage}.
|
|
5240
|
+
*
|
|
5241
|
+
* @since 1.5.25
|
|
5242
|
+
*/
|
|
5243
|
+
readonly session: SessionStorage;
|
|
5244
|
+
}
|
|
5245
|
+
/**
|
|
5246
|
+
* `plugin.storage.session`: JSON values that live as long as the **player**,
|
|
5247
|
+
* not as long as the plugin's process (like `chrome.storage.session`). No
|
|
5248
|
+
* capability; needs player 0.5.5 (older players reject the calls with
|
|
5249
|
+
* `unsupported`).
|
|
5250
|
+
*
|
|
5251
|
+
* `activate` can run more than once in one player session: the player stops
|
|
5252
|
+
* and starts a plugin's process again when the plugin is reloaded (also a
|
|
5253
|
+
* development load with `--plugin`) or upgraded, and after a crash or a
|
|
5254
|
+
* failed start. A later player may also restart plugins that share a
|
|
5255
|
+
* process, or stop an idle plugin and start it on its next use. Variables do
|
|
5256
|
+
* not survive that; keep state that must survive a restart here (or in
|
|
5257
|
+
* {@link Storage.json | storage.json} when it must also survive the player
|
|
5258
|
+
* quitting).
|
|
5259
|
+
*
|
|
5260
|
+
* | | Values |
|
|
5261
|
+
* | --- | --- |
|
|
5262
|
+
* | The plugin's process restarts: reload, upgrade, crash | kept |
|
|
5263
|
+
* | The plugin failed to start or failed, and starts again | kept |
|
|
5264
|
+
* | The user disables the plugin, or uninstalls it | cleared |
|
|
5265
|
+
* | The player quits | cleared |
|
|
5266
|
+
*
|
|
5267
|
+
* - **Per plugin.** A plugin sees only its own keys. Like
|
|
5268
|
+
* {@link Plugin.dataDirectory | dataDirectory}, the values belong to the
|
|
5269
|
+
* installation: a development load (`--plugin`) has its own.
|
|
5270
|
+
* - **JSON.** `set` stores what `JSON.stringify(value)` writes, and `get`
|
|
5271
|
+
* returns that, parsed: `undefined` object properties are dropped, a `Date`
|
|
5272
|
+
* comes back as a string, `NaN` as `null`. **The order of an object's
|
|
5273
|
+
* properties is not kept**: they may come back in another order. A value
|
|
5274
|
+
* `JSON.stringify` cannot write (`undefined`, a function, a cycle, a
|
|
5275
|
+
* `BigInt`) or a string with a lone surrogate (`"\uD800"`) fails with
|
|
5276
|
+
* `invalid`. The value is not checked when it comes back: after an upgrade
|
|
5277
|
+
* an older version's value may be read, so check its shape when it matters.
|
|
5278
|
+
* - **Bounded.** Keys are 1–1024 bytes of text. A plugin holds at most 1 MiB,
|
|
5279
|
+
* counting every key, its value as compact JSON and 128 bytes per key
|
|
5280
|
+
* ({@link "@vestra-player/sdk/testing"!SESSION_STORAGE_LIMITS | SESSION_STORAGE_LIMITS}); a
|
|
5281
|
+
* `set` beyond it fails with `limit` and changes nothing. One `set` is
|
|
5282
|
+
* also one message to the player, at most 1 MiB with its key, so a single
|
|
5283
|
+
* value is a little smaller than that; a larger one fails with `limit`.
|
|
5284
|
+
* Split large state over several keys.
|
|
5285
|
+
* - **Each call is one request.** A `set` is kept once its promise
|
|
5286
|
+
* resolves, so a crash right after loses nothing; there is no debounce.
|
|
5287
|
+
* Calls answer in order.
|
|
5288
|
+
*
|
|
5289
|
+
* @example
|
|
5290
|
+
* ```ts
|
|
5291
|
+
* // A queue the plugin is working through: after a reload or a crash it
|
|
5292
|
+
* // goes on where it stopped, after the player quits it starts afresh.
|
|
5293
|
+
* const pending = (await plugin.storage.session.get<string[]>("queue")) ?? [];
|
|
5294
|
+
* pending.push("https://example.com/a.mkv");
|
|
5295
|
+
* await plugin.storage.session.set("queue", pending);
|
|
5296
|
+
* ```
|
|
5297
|
+
*
|
|
5298
|
+
* @since 1.5.25
|
|
5299
|
+
*/
|
|
5300
|
+
export interface SessionStorage {
|
|
5301
|
+
/** The value stored under `key`, or `undefined` when there is none. */
|
|
5302
|
+
get<T = unknown>(key: string): Promise<T | undefined>;
|
|
5303
|
+
/**
|
|
5304
|
+
* Store or replace `value` under `key`. `invalid` for a bad key or a value
|
|
5305
|
+
* that is not JSON, `limit` when the plugin's values would be over 1 MiB
|
|
5306
|
+
* or the request over the 1 MiB message limit.
|
|
5307
|
+
*/
|
|
5308
|
+
set(key: string, value: unknown): Promise<void>;
|
|
5309
|
+
/** Remove a key; removing a missing one is fine. */
|
|
5310
|
+
delete(key: string): Promise<void>;
|
|
5311
|
+
/** The keys stored, in code point order. */
|
|
5312
|
+
keys(): Promise<string[]>;
|
|
5235
5313
|
}
|
|
5236
5314
|
/**
|
|
5237
5315
|
* `plugin.system`: files and links, executed by the player. No capability.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The TypeScript language-service plugin `@vestra-player/sdk/ts-plugin`: the
|
|
3
|
+
* capability checker of `vestra-plugin check` in the editor. A use of an API
|
|
4
|
+
* member whose capability `manifest.json` does not declare is an error at
|
|
5
|
+
* the member, with the quick fix "Add '<capability>' to manifest.json".
|
|
6
|
+
*
|
|
7
|
+
* A project enables it in its tsconfig.json:
|
|
8
|
+
*
|
|
9
|
+
* { "compilerOptions": { "plugins": [{ "name": "@vestra-player/sdk/ts-plugin" }] } }
|
|
10
|
+
*
|
|
11
|
+
* The editor must run the project's own TypeScript (VS Code: "TypeScript:
|
|
12
|
+
* Select TypeScript Version" → "Use Workspace Version"); `tsc` ignores
|
|
13
|
+
* language-service plugins, which is why `vestra-plugin check` runs the same
|
|
14
|
+
* checker. The manifest is the nearest `manifest.json` above the file; files
|
|
15
|
+
* in its `tests/`, `node_modules/`, `dist/` and `vendor/` are not checked.
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
import type TS from "typescript";
|
|
20
|
+
/** @internal The plugin factory tsserver calls (`ts-plugin/index.js`). */
|
|
21
|
+
export default function init(modules: {
|
|
22
|
+
typescript: typeof TS;
|
|
23
|
+
}): TS.server.PluginModule;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { addCapabilityEdit, CAPABILITY_DIAGNOSTIC, findUndeclaredCapabilities, isPluginSource, } from "./capability-check.js";
|
|
2
|
+
const FIX_NAME = "vestraAddCapability";
|
|
3
|
+
/** The nearest manifest.json at or above a file, with its capabilities. */
|
|
4
|
+
function manifestFor(ts, file) {
|
|
5
|
+
let directory = file.replaceAll("\\", "/");
|
|
6
|
+
for (;;) {
|
|
7
|
+
const parent = directory.slice(0, directory.lastIndexOf("/"));
|
|
8
|
+
if (!parent || parent === directory)
|
|
9
|
+
return undefined;
|
|
10
|
+
directory = parent;
|
|
11
|
+
const path = `${directory}/manifest.json`;
|
|
12
|
+
if (!ts.sys.fileExists(path))
|
|
13
|
+
continue;
|
|
14
|
+
try {
|
|
15
|
+
const manifest = JSON.parse(ts.sys.readFile(path) ?? "");
|
|
16
|
+
if (!Array.isArray(manifest.capabilities))
|
|
17
|
+
return undefined;
|
|
18
|
+
return {
|
|
19
|
+
path,
|
|
20
|
+
directory,
|
|
21
|
+
capabilities: manifest.capabilities.filter((each) => typeof each === "string"),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The manifest's text for the quick fix. The TypeScript server applies a
|
|
31
|
+
* code fix only to files it knows, and editors do not hand it JSON files
|
|
32
|
+
* (VS Code does not), so the manifest is made known first; its text is then
|
|
33
|
+
* the server's: an open buffer when the editor shares one, else the file on
|
|
34
|
+
* disk (VS Code: the saved manifest, not unsaved edits).
|
|
35
|
+
*/
|
|
36
|
+
function manifestText(ts, info, path) {
|
|
37
|
+
const service = info.project?.projectService;
|
|
38
|
+
const script = service && ts.server?.toNormalizedPath
|
|
39
|
+
? service.getOrCreateScriptInfoForNormalizedPath(ts.server.toNormalizedPath(path), false)
|
|
40
|
+
: undefined;
|
|
41
|
+
const snapshot = script?.getSnapshot();
|
|
42
|
+
return snapshot
|
|
43
|
+
? snapshot.getText(0, snapshot.getLength())
|
|
44
|
+
: ts.sys.readFile(path);
|
|
45
|
+
}
|
|
46
|
+
/** @internal The plugin factory tsserver calls (`ts-plugin/index.js`). */
|
|
47
|
+
export default function init(modules) {
|
|
48
|
+
const ts = modules.typescript;
|
|
49
|
+
return {
|
|
50
|
+
create(info) {
|
|
51
|
+
const service = info.languageService;
|
|
52
|
+
const log = (message) => info.project?.projectService?.logger?.info(`[@vestra-player/sdk/ts-plugin] ${message}`);
|
|
53
|
+
const findings = (file) => {
|
|
54
|
+
const manifest = manifestFor(ts, file);
|
|
55
|
+
const program = service.getProgram();
|
|
56
|
+
if (!manifest || !program || !isPluginSource(manifest.directory, file))
|
|
57
|
+
return [];
|
|
58
|
+
try {
|
|
59
|
+
return findUndeclaredCapabilities(ts, program, {
|
|
60
|
+
files: [file],
|
|
61
|
+
capabilities: manifest.capabilities,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
log(`check failed: ${error.stack ?? String(error)}`);
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const proxy = Object.create(null);
|
|
70
|
+
for (const key of Object.keys(service)) {
|
|
71
|
+
const member = service[key];
|
|
72
|
+
proxy[key] =
|
|
73
|
+
typeof member === "function" ? member.bind(service) : member;
|
|
74
|
+
}
|
|
75
|
+
proxy.getSemanticDiagnostics = (file) => {
|
|
76
|
+
const prior = service.getSemanticDiagnostics(file);
|
|
77
|
+
const source = service.getProgram()?.getSourceFile(file);
|
|
78
|
+
return [
|
|
79
|
+
...prior,
|
|
80
|
+
...findings(file).map((finding) => ({
|
|
81
|
+
file: source,
|
|
82
|
+
start: finding.start,
|
|
83
|
+
length: finding.length,
|
|
84
|
+
messageText: finding.message,
|
|
85
|
+
category: ts.DiagnosticCategory.Error,
|
|
86
|
+
code: CAPABILITY_DIAGNOSTIC,
|
|
87
|
+
source: "vestra",
|
|
88
|
+
})),
|
|
89
|
+
];
|
|
90
|
+
};
|
|
91
|
+
proxy.getSupportedCodeFixes = (file) => [
|
|
92
|
+
...service.getSupportedCodeFixes(file),
|
|
93
|
+
String(CAPABILITY_DIAGNOSTIC),
|
|
94
|
+
];
|
|
95
|
+
proxy.getCodeFixesAtPosition = (file, start, end, errorCodes, format, preferences) => {
|
|
96
|
+
const prior = service.getCodeFixesAtPosition(file, start, end, errorCodes, format, preferences);
|
|
97
|
+
if (!errorCodes.includes(CAPABILITY_DIAGNOSTIC))
|
|
98
|
+
return prior;
|
|
99
|
+
const manifest = manifestFor(ts, file);
|
|
100
|
+
if (!manifest)
|
|
101
|
+
return prior;
|
|
102
|
+
const text = manifestText(ts, info, manifest.path);
|
|
103
|
+
if (text === undefined)
|
|
104
|
+
return prior;
|
|
105
|
+
const wanted = new Set(findings(file)
|
|
106
|
+
.filter((finding) => finding.start <= end && finding.start + finding.length >= start)
|
|
107
|
+
.map((finding) => finding.capability));
|
|
108
|
+
const fixes = [];
|
|
109
|
+
for (const capability of wanted) {
|
|
110
|
+
const edit = addCapabilityEdit(ts, text, capability);
|
|
111
|
+
if (!edit)
|
|
112
|
+
continue;
|
|
113
|
+
fixes.push({
|
|
114
|
+
fixName: FIX_NAME,
|
|
115
|
+
description: `Add '${capability}' to manifest.json`,
|
|
116
|
+
changes: [
|
|
117
|
+
{
|
|
118
|
+
fileName: manifest.path,
|
|
119
|
+
textChanges: [
|
|
120
|
+
{
|
|
121
|
+
span: { start: edit.start, length: edit.length },
|
|
122
|
+
newText: edit.newText,
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return [...prior, ...fixes];
|
|
130
|
+
};
|
|
131
|
+
log("enabled");
|
|
132
|
+
return proxy;
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
package/dist/wire.d.ts
CHANGED
|
@@ -488,6 +488,18 @@ export declare function createWire(transport: Transport): {
|
|
|
488
488
|
set: (name: string, value: string) => Promise<void>;
|
|
489
489
|
delete: (name: string) => Promise<void>;
|
|
490
490
|
};
|
|
491
|
+
/**
|
|
492
|
+
* JSON values the player keeps per plugin until it quits (SDK 1.5.25,
|
|
493
|
+
* player 0.5.5). `get` answers `{}` for a missing key.
|
|
494
|
+
*/
|
|
495
|
+
storageSession: {
|
|
496
|
+
get: (key: string) => Promise<{
|
|
497
|
+
value?: Json;
|
|
498
|
+
}>;
|
|
499
|
+
set: (key: string, value: Json) => Promise<void>;
|
|
500
|
+
delete: (key: string) => Promise<void>;
|
|
501
|
+
keys: () => Promise<string[]>;
|
|
502
|
+
};
|
|
491
503
|
app: {
|
|
492
504
|
system: () => Promise<WireSystemInfo>;
|
|
493
505
|
};
|
package/dist/wire.js
CHANGED
|
@@ -182,6 +182,16 @@ export function createWire(transport) {
|
|
|
182
182
|
set: (name, value) => transport.request("secrets.set", { name, value }),
|
|
183
183
|
delete: (name) => transport.request("secrets.delete", { name }),
|
|
184
184
|
},
|
|
185
|
+
/**
|
|
186
|
+
* JSON values the player keeps per plugin until it quits (SDK 1.5.25,
|
|
187
|
+
* player 0.5.5). `get` answers `{}` for a missing key.
|
|
188
|
+
*/
|
|
189
|
+
storageSession: {
|
|
190
|
+
get: (key) => transport.request("storage.session.get", { key }),
|
|
191
|
+
set: (key, value) => transport.request("storage.session.set", { key, value }),
|
|
192
|
+
delete: (key) => transport.request("storage.session.delete", { key }),
|
|
193
|
+
keys: () => transport.request("storage.session.keys"),
|
|
194
|
+
},
|
|
185
195
|
app: {
|
|
186
196
|
system: () => transport.request("app.system"),
|
|
187
197
|
},
|