@blastlauncher/engine 0.0.0-stage → 3.0.0-alpha.1

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.
@@ -0,0 +1,39 @@
1
+ import type { CapabilityHandler } from "./broker.js";
2
+ import { type OpenExtensionOptions } from "./extension.js";
3
+ import type { DependencyInstaller } from "./install-dependencies.js";
4
+ /** Shared defaults and capability registrations for a group of extensions. */
5
+ export declare function createEngine(options?: {
6
+ timeoutMs?: number;
7
+ dataDir?: string;
8
+ installer?: DependencyInstaller;
9
+ watcher?: OpenExtensionOptions["watcher"];
10
+ debounce?: OpenExtensionOptions["debounce"];
11
+ dependencies?: "manual" | "auto";
12
+ handlers?: Record<string, CapabilityHandler>;
13
+ }): {
14
+ /** Later registrations win; changes apply to future launches. */
15
+ register(handlers: Record<string, CapabilityHandler>): () => void;
16
+ openExtension(extensionOptions: OpenExtensionOptions): {
17
+ manifest: () => Promise<import("./extension-manifest.js").ExtensionManifest>;
18
+ watch(listener: (update: import("./extension-manifest.js").ExtensionManifest | Error) => void): () => void;
19
+ dependencies: {
20
+ check: () => Promise<import("./install-dependencies.js").DependencyInstallStatus>;
21
+ install: (installOptions?: {
22
+ onLog?: (message: string) => void;
23
+ }) => Promise<import("./install-dependencies.js").DependencyInstallStatus>;
24
+ };
25
+ preferences: (name: string, scope?: "extension" | "command") => Promise<{
26
+ extension: import("./bundle.js").Preference[];
27
+ command: import("./bundle.js").Preference[];
28
+ values: import("./storage.js").StorageItems;
29
+ missing: import("./bundle.js").Preference[];
30
+ }>;
31
+ savePreferences(name: string, values: import("./storage.js").StorageItems, scope?: "extension" | "command"): Promise<void>;
32
+ launch(name: string, launchOptions?: import("./extension.js").ExtensionLaunchOptions): Promise<{
33
+ session: import("./session.js").Session;
34
+ close: () => Promise<void>;
35
+ }>;
36
+ close(): Promise<void>;
37
+ };
38
+ close(): Promise<void>;
39
+ };
package/dist/engine.js ADDED
@@ -0,0 +1,53 @@
1
+ import { createExtensionHandle, } from "./extension.js";
2
+ /** Shared defaults and capability registrations for a group of extensions. */
3
+ export function createEngine(options = {}) {
4
+ const registrations = new Set();
5
+ const extensions = new Set();
6
+ let closing;
7
+ const ensureOpen = () => {
8
+ if (closing)
9
+ throw new Error("Engine is closed");
10
+ };
11
+ const registeredHandlers = () => Object.assign({}, ...registrations);
12
+ if (options.handlers)
13
+ registrations.add({ ...options.handlers });
14
+ return {
15
+ /** Later registrations win; changes apply to future launches. */
16
+ register(handlers) {
17
+ ensureOpen();
18
+ const registration = { ...handlers };
19
+ registrations.add(registration);
20
+ return () => {
21
+ registrations.delete(registration);
22
+ };
23
+ },
24
+ openExtension(extensionOptions) {
25
+ ensureOpen();
26
+ const extension = createExtensionHandle({
27
+ ...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
28
+ ...(options.dataDir === undefined ? {} : { dataDir: options.dataDir }),
29
+ ...(options.installer === undefined ? {} : { installer: options.installer }),
30
+ ...(options.watcher === undefined ? {} : { watcher: options.watcher }),
31
+ ...(options.debounce === undefined ? {} : { debounce: options.debounce }),
32
+ ...extensionOptions,
33
+ }, registeredHandlers, options.dependencies === "auto");
34
+ const close = extension.close;
35
+ extension.close = async () => {
36
+ try {
37
+ await close();
38
+ }
39
+ finally {
40
+ extensions.delete(extension);
41
+ }
42
+ };
43
+ extensions.add(extension);
44
+ return extension;
45
+ },
46
+ close() {
47
+ return (closing ??= Promise.resolve().then(async () => {
48
+ registrations.clear();
49
+ await Promise.all([...extensions].map((extension) => extension.close()));
50
+ }));
51
+ },
52
+ };
53
+ }
@@ -0,0 +1,34 @@
1
+ import { type Preference } from "./bundle.js";
2
+ export type ExtensionMode = "view" | "no-view" | "menu-bar";
3
+ export type ExtensionOption = {
4
+ title: string;
5
+ value: string;
6
+ };
7
+ export type ExtensionArgument = {
8
+ name: string;
9
+ type: "text" | "password" | "dropdown";
10
+ /** A required argument must be filled before Enter launches the command. */
11
+ required: boolean;
12
+ placeholder: string;
13
+ /** A dropdown argument's options, in declaration order. */
14
+ data: ExtensionOption[];
15
+ };
16
+ export type ExtensionCommand = {
17
+ name: string;
18
+ title: string;
19
+ subtitle: string;
20
+ description: string;
21
+ mode: ExtensionMode;
22
+ arguments: ExtensionArgument[];
23
+ preferences: Preference[];
24
+ };
25
+ export type ExtensionManifest = {
26
+ name: string;
27
+ title: string;
28
+ preferences: Preference[];
29
+ commands: ExtensionCommand[];
30
+ };
31
+ /** Validates manifest metadata shared by embedders and reference clients. */
32
+ export declare function parseManifest(value: unknown): ExtensionManifest;
33
+ /** Required argument names that the form has not filled yet. */
34
+ export declare function missingRequired(args: ExtensionArgument[], values: Record<string, string>): string[];
@@ -0,0 +1,79 @@
1
+ import { parsePreferences } from "./bundle.js";
2
+ const MODES = ["view", "no-view", "menu-bar"];
3
+ const ARGUMENT_TYPES = ["text", "password", "dropdown"];
4
+ function string(value) {
5
+ return typeof value === "string" ? value : "";
6
+ }
7
+ /** Validates manifest metadata shared by embedders and reference clients. */
8
+ export function parseManifest(value) {
9
+ if (!value || typeof value !== "object" || Array.isArray(value))
10
+ throw new Error("manifest must be an object");
11
+ const manifest = value;
12
+ const name = string(manifest.name);
13
+ if (!name)
14
+ throw new Error('manifest needs a string "name"');
15
+ if (!Array.isArray(manifest.commands))
16
+ throw new Error('manifest needs a "commands" array');
17
+ return {
18
+ name,
19
+ title: string(manifest.title) || name,
20
+ preferences: parsePreferences(manifest.preferences),
21
+ commands: manifest.commands.map(parseCommand),
22
+ };
23
+ }
24
+ function parseCommand(value, index) {
25
+ if (!value || typeof value !== "object" || Array.isArray(value))
26
+ throw new Error(`command ${index + 1} must be an object`);
27
+ const command = value;
28
+ const name = string(command.name);
29
+ if (!name)
30
+ throw new Error(`command ${index + 1} needs a name`);
31
+ const mode = command.mode === undefined ? "view" : command.mode;
32
+ if (typeof mode !== "string" || !MODES.includes(mode))
33
+ throw new Error(`command ${JSON.stringify(name)} has an unknown mode`);
34
+ return {
35
+ name,
36
+ title: string(command.title) || name,
37
+ subtitle: string(command.subtitle),
38
+ description: string(command.description),
39
+ mode: mode,
40
+ preferences: parsePreferences(command.preferences),
41
+ arguments: Array.isArray(command.arguments) ? command.arguments.map(parseArgument) : [],
42
+ };
43
+ }
44
+ function parseArgument(value, index) {
45
+ if (!value || typeof value !== "object" || Array.isArray(value))
46
+ throw new Error(`argument ${index + 1} must be an object`);
47
+ const argument = value;
48
+ const name = string(argument.name);
49
+ if (!name)
50
+ throw new Error(`argument ${index + 1} needs a name`);
51
+ const type = string(argument.type);
52
+ if (!ARGUMENT_TYPES.includes(type))
53
+ throw new Error(`argument ${JSON.stringify(name)} has an unknown type ${JSON.stringify(type)}`);
54
+ const data = Array.isArray(argument.data)
55
+ ? argument.data.map(parseOption).filter((option) => option !== undefined)
56
+ : [];
57
+ return {
58
+ name,
59
+ type: type,
60
+ required: argument.required === true,
61
+ placeholder: string(argument.placeholder),
62
+ data,
63
+ };
64
+ }
65
+ function parseOption(value) {
66
+ if (!value || typeof value !== "object" || Array.isArray(value))
67
+ return undefined;
68
+ const option = value;
69
+ const optionValue = string(option.value);
70
+ if (!optionValue)
71
+ return undefined;
72
+ return { title: string(option.title) || optionValue, value: optionValue };
73
+ }
74
+ /** Required argument names that the form has not filled yet. */
75
+ export function missingRequired(args, values) {
76
+ return args
77
+ .filter((argument) => argument.required && (values[argument.name] ?? "") === "")
78
+ .map((argument) => argument.name);
79
+ }
@@ -0,0 +1,93 @@
1
+ import { type Preference } from "./bundle.js";
2
+ import { type ExtensionManifest } from "./extension-manifest.js";
3
+ import { type DependencyInstallStatus, type DependencyInstaller } from "./install-dependencies.js";
4
+ import { type StartOptions } from "./session.js";
5
+ import { type StorageItems } from "./storage.js";
6
+ import type { CapabilityHandler } from "./broker.js";
7
+ export type ExtensionLaunchOptions = Pick<StartOptions, "argumentValues" | "launchContext" | "fallbackText"> & {
8
+ /** Install unresolved manifest dependencies before bundling this launch. */
9
+ installDependencies?: boolean;
10
+ /** Receives installer output when this launch performs an install. */
11
+ onDependencyLog?: (message: string) => void;
12
+ timeoutMs?: number;
13
+ signal?: AbortSignal;
14
+ handlers?: Record<string, CapabilityHandler>;
15
+ };
16
+ export declare class MissingLaunchValuesError extends Error {
17
+ readonly code = "MISSING_LAUNCH_VALUES";
18
+ readonly preferences: Preference[];
19
+ readonly arguments: string[];
20
+ constructor(preferences: Preference[], args: string[]);
21
+ }
22
+ export declare class DependenciesRequiredError extends Error {
23
+ readonly code = "DEPENDENCIES_REQUIRED";
24
+ readonly unresolved: string[];
25
+ readonly installable: boolean;
26
+ readonly pinned: boolean;
27
+ constructor(status: Pick<DependencyInstallStatus, "unresolved" | "installable" | "pinned">);
28
+ }
29
+ export type OpenExtensionOptions = {
30
+ timeoutMs?: number;
31
+ extensionDir: string;
32
+ dataDir?: string;
33
+ installer?: DependencyInstaller;
34
+ handlers?: Record<string, CapabilityHandler>;
35
+ watcher?: ExtensionWatcherFactory;
36
+ debounce?: DebounceScheduler;
37
+ };
38
+ export type ExtensionWatcher = {
39
+ on(event: "error", listener: (error: Error) => void): ExtensionWatcher;
40
+ close(): void;
41
+ };
42
+ export type ExtensionWatcherFactory = (path: string, listener: (event: string, filename: string | Buffer | null) => void) => ExtensionWatcher;
43
+ export type DebounceScheduler = {
44
+ schedule(callback: () => void, delayMs: number): unknown;
45
+ cancel(handle: unknown): void;
46
+ };
47
+ /** Opens an extension without starting a command or installing dependencies. */
48
+ export declare function openExtension(options: OpenExtensionOptions): {
49
+ manifest: () => Promise<ExtensionManifest>;
50
+ watch(listener: (update: ExtensionManifest | Error) => void): () => void;
51
+ dependencies: {
52
+ check: () => Promise<DependencyInstallStatus>;
53
+ install: (installOptions?: {
54
+ onLog?: (message: string) => void;
55
+ }) => Promise<DependencyInstallStatus>;
56
+ };
57
+ preferences: (name: string, scope?: "extension" | "command") => Promise<{
58
+ extension: Preference[];
59
+ command: Preference[];
60
+ values: StorageItems;
61
+ missing: Preference[];
62
+ }>;
63
+ savePreferences(name: string, values: StorageItems, scope?: "extension" | "command"): Promise<void>;
64
+ launch(name: string, launchOptions?: ExtensionLaunchOptions): Promise<{
65
+ session: import("./session.js").Session;
66
+ close: () => Promise<void>;
67
+ }>;
68
+ close(): Promise<void>;
69
+ };
70
+ /** Internal constructor shared with the engine's live handler registry. */
71
+ export declare function createExtensionHandle(options: OpenExtensionOptions, engineHandlers?: () => Record<string, CapabilityHandler>, defaultInstallDependencies?: boolean): {
72
+ manifest: () => Promise<ExtensionManifest>;
73
+ watch(listener: (update: ExtensionManifest | Error) => void): () => void;
74
+ dependencies: {
75
+ check: () => Promise<DependencyInstallStatus>;
76
+ install: (installOptions?: {
77
+ onLog?: (message: string) => void;
78
+ }) => Promise<DependencyInstallStatus>;
79
+ };
80
+ preferences: (name: string, scope?: "extension" | "command") => Promise<{
81
+ extension: Preference[];
82
+ command: Preference[];
83
+ values: StorageItems;
84
+ missing: Preference[];
85
+ }>;
86
+ savePreferences(name: string, values: StorageItems, scope?: "extension" | "command"): Promise<void>;
87
+ launch(name: string, launchOptions?: ExtensionLaunchOptions): Promise<{
88
+ session: import("./session.js").Session;
89
+ close: () => Promise<void>;
90
+ }>;
91
+ close(): Promise<void>;
92
+ };
93
+ export type ExtensionHandle = ReturnType<typeof createExtensionHandle>;
@@ -0,0 +1,295 @@
1
+ import { watch as watchDirectory } from "node:fs";
2
+ import { readFile, rm } from "node:fs/promises";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { bundleCommand, MissingDependencyError } from "./bundle.js";
5
+ import { createSessionBroker } from "./client.js";
6
+ import { parseManifest, missingRequired } from "./extension-manifest.js";
7
+ import { checkExtensionDependencies, installExtensionDependencies, } from "./install-dependencies.js";
8
+ import { createPreferenceStore } from "./preferences.js";
9
+ import { startCommand } from "./session.js";
10
+ import { defaultDataDir } from "./storage.js";
11
+ export class MissingLaunchValuesError extends Error {
12
+ code = "MISSING_LAUNCH_VALUES";
13
+ preferences;
14
+ arguments;
15
+ constructor(preferences, args) {
16
+ super(`Missing required values: ${[...preferences.map((p) => p.name), ...args].join(", ")}`);
17
+ this.name = "MissingLaunchValuesError";
18
+ this.preferences = preferences;
19
+ this.arguments = args;
20
+ }
21
+ }
22
+ export class DependenciesRequiredError extends Error {
23
+ code = "DEPENDENCIES_REQUIRED";
24
+ unresolved;
25
+ installable;
26
+ pinned;
27
+ constructor(status) {
28
+ const names = [...new Set(status.unresolved)].toSorted();
29
+ super(`Missing dependencies: ${names.join(", ")}. Run blast deps <extensionDir> to install them before launching.`);
30
+ this.name = "DependenciesRequiredError";
31
+ this.unresolved = names;
32
+ this.installable = status.installable;
33
+ this.pinned = status.pinned;
34
+ }
35
+ }
36
+ /** Opens an extension without starting a command or installing dependencies. */
37
+ export function openExtension(options) {
38
+ return createExtensionHandle(options);
39
+ }
40
+ /** Internal constructor shared with the engine's live handler registry. */
41
+ export function createExtensionHandle(options, engineHandlers = () => ({}), defaultInstallDependencies = false) {
42
+ const extensionDir = resolve(options.extensionDir);
43
+ const dataDir = options.dataDir ?? defaultDataDir();
44
+ const dependencyOptions = { extensionDir, dataDir };
45
+ const watcherFactory = options.watcher ?? ((path, listener) => watchDirectory(path, listener));
46
+ const debounce = options.debounce ??
47
+ {
48
+ schedule: (callback, delayMs) => setTimeout(callback, delayMs),
49
+ cancel: (handle) => clearTimeout(handle),
50
+ };
51
+ let closed = false;
52
+ let closing;
53
+ let queue = Promise.resolve();
54
+ const subscriptions = new Set();
55
+ const sessions = new Set();
56
+ const launches = new Set();
57
+ const ensureOpen = () => {
58
+ if (closed)
59
+ throw new Error("Extension handle is closed");
60
+ };
61
+ const manifest = async () => {
62
+ ensureOpen();
63
+ try {
64
+ return parseManifest(JSON.parse(await readFile(join(extensionDir, "package.json"), "utf8")));
65
+ }
66
+ catch (error) {
67
+ throw new Error(`Invalid extension manifest: ${error instanceof Error ? error.message : error}`, {
68
+ cause: error,
69
+ });
70
+ }
71
+ };
72
+ const commandManifest = async (name) => {
73
+ const extension = await manifest();
74
+ const command = extension.commands.find((c) => c.name === name);
75
+ if (!command)
76
+ throw new Error(`Unknown extension command: ${name}`);
77
+ return { extension, command };
78
+ };
79
+ const preferences = async (name, scope) => {
80
+ const { extension, command } = await commandManifest(name);
81
+ const store = createPreferenceStore({ extensionName: extension.name, dataDir });
82
+ const stored = await store.read();
83
+ const values = scope === "extension" ? stored.extension : { ...stored.extension, ...stored.commands[name] };
84
+ const fields = [
85
+ ...new Map([...extension.preferences, ...command.preferences].map((p) => [p.name, p])).values(),
86
+ ];
87
+ return {
88
+ extension: extension.preferences,
89
+ command: command.preferences,
90
+ values,
91
+ missing: fields.filter((p) => p.required &&
92
+ p.default === undefined &&
93
+ (values[p.name] === undefined || values[p.name] === "")),
94
+ };
95
+ };
96
+ // A failed install remains observable to its caller but does not poison the
97
+ // queue: commands may still bundle against the shared dependency fallback.
98
+ const serialize = (operation) => {
99
+ ensureOpen();
100
+ const next = queue.then(() => {
101
+ ensureOpen();
102
+ return operation();
103
+ });
104
+ queue = next.catch(() => { });
105
+ return next;
106
+ };
107
+ const launch = async (name, launchOptions = {}) => {
108
+ ensureOpen();
109
+ await queue;
110
+ const { command } = await commandManifest(name);
111
+ const prefs = await preferences(name);
112
+ const args = missingRequired(command.arguments, launchOptions.argumentValues ?? {});
113
+ if (prefs.missing.length || args.length)
114
+ throw new MissingLaunchValuesError(prefs.missing, args);
115
+ let dependencyStatus = await checkExtensionDependencies({
116
+ ...dependencyOptions,
117
+ });
118
+ const installDependencies = launchOptions.installDependencies ?? defaultInstallDependencies;
119
+ if (dependencyStatus.unresolved.length && installDependencies) {
120
+ await installExtensionDependencies({
121
+ ...dependencyOptions,
122
+ ...(options.installer ? { installer: options.installer } : {}),
123
+ ...(launchOptions.onDependencyLog ? { onLog: launchOptions.onDependencyLog } : {}),
124
+ });
125
+ dependencyStatus = await checkExtensionDependencies({
126
+ ...dependencyOptions,
127
+ });
128
+ }
129
+ // A successful installer publishes its key before the bundle runs. Some
130
+ // installers provision packages lazily, so an installable manifest is no
131
+ // longer launch-blocking once that key is current; undeclared imports are
132
+ // never installable and remain an immediate typed failure.
133
+ if (dependencyStatus.unresolved.length &&
134
+ (dependencyStatus.needed || !dependencyStatus.installable))
135
+ throw new DependenciesRequiredError(dependencyStatus);
136
+ ensureOpen();
137
+ const timeoutMs = launchOptions.timeoutMs ?? options.timeoutMs;
138
+ const bundle = () => bundleCommand({
139
+ extensionDir,
140
+ command: name,
141
+ dataDir,
142
+ ...(timeoutMs === undefined ? {} : { timeoutMs }),
143
+ ...(launchOptions.signal === undefined ? {} : { signal: launchOptions.signal }),
144
+ });
145
+ let bundled;
146
+ try {
147
+ bundled = await bundle();
148
+ }
149
+ catch (error) {
150
+ if (!(error instanceof MissingDependencyError))
151
+ throw error;
152
+ const declared = new Set(Object.keys(JSON.parse(await readFile(join(extensionDir, "package.json"), "utf8")).dependencies ?? {}));
153
+ const status = {
154
+ unresolved: error.packages,
155
+ installable: error.packages.every((packageName) => declared.has(packageName)),
156
+ pinned: (await checkExtensionDependencies(dependencyOptions)).pinned,
157
+ };
158
+ if (!installDependencies || !status.installable)
159
+ throw new DependenciesRequiredError(status);
160
+ await installExtensionDependencies({
161
+ ...dependencyOptions,
162
+ ...(options.installer ? { installer: options.installer } : {}),
163
+ ...(launchOptions.onDependencyLog ? { onLog: launchOptions.onDependencyLog } : {}),
164
+ });
165
+ try {
166
+ bundled = await bundle();
167
+ }
168
+ catch (retryError) {
169
+ if (retryError instanceof MissingDependencyError) {
170
+ throw new DependenciesRequiredError({
171
+ unresolved: retryError.packages,
172
+ installable: retryError.packages.every((packageName) => declared.has(packageName)),
173
+ pinned: status.pinned,
174
+ });
175
+ }
176
+ throw retryError;
177
+ }
178
+ }
179
+ const cleanup = () => rm(dirname(bundled.entry), { recursive: true, force: true });
180
+ try {
181
+ ensureOpen();
182
+ const session = await startCommand({
183
+ ...bundled,
184
+ preferences: prefs.values,
185
+ dataDir,
186
+ ...(launchOptions.argumentValues === undefined
187
+ ? {}
188
+ : { argumentValues: launchOptions.argumentValues }),
189
+ ...(launchOptions.fallbackText === undefined
190
+ ? {}
191
+ : { fallbackText: launchOptions.fallbackText }),
192
+ ...(launchOptions.launchContext === undefined
193
+ ? {}
194
+ : { launchContext: launchOptions.launchContext }),
195
+ broker: createSessionBroker({ extensionName: bundled.extensionName, dataDir }, { handlers: { ...engineHandlers(), ...options.handlers, ...launchOptions.handlers } }),
196
+ });
197
+ let stopping;
198
+ const opened = {
199
+ session,
200
+ close: () => (stopping ??= (async () => {
201
+ try {
202
+ await session.shutdown({ timeoutMs: 5000 });
203
+ }
204
+ finally {
205
+ await cleanup();
206
+ sessions.delete(opened);
207
+ }
208
+ })()),
209
+ };
210
+ sessions.add(opened);
211
+ if (closed) {
212
+ await opened.close();
213
+ ensureOpen();
214
+ }
215
+ return opened;
216
+ }
217
+ catch (error) {
218
+ await cleanup();
219
+ throw error;
220
+ }
221
+ };
222
+ return {
223
+ manifest,
224
+ watch(listener) {
225
+ ensureOpen();
226
+ let active = true;
227
+ let timer;
228
+ const reload = async () => {
229
+ let update;
230
+ try {
231
+ update = await manifest();
232
+ }
233
+ catch (error) {
234
+ update = error instanceof Error ? error : new Error(String(error));
235
+ }
236
+ if (active)
237
+ listener(update);
238
+ };
239
+ // Directory watching also observes manifests replaced via atomic rename.
240
+ const watcher = watcherFactory(extensionDir, (_event, file) => {
241
+ if (file && file !== "package.json" && file !== "package-lock.json")
242
+ return;
243
+ if (timer !== undefined)
244
+ debounce.cancel(timer);
245
+ timer = debounce.schedule(() => {
246
+ void reload();
247
+ }, 100);
248
+ });
249
+ watcher.on("error", (error) => {
250
+ if (active)
251
+ listener(error);
252
+ });
253
+ const unsubscribe = () => {
254
+ active = false;
255
+ if (timer !== undefined)
256
+ debounce.cancel(timer);
257
+ watcher.close();
258
+ subscriptions.delete(unsubscribe);
259
+ };
260
+ subscriptions.add(unsubscribe);
261
+ return unsubscribe;
262
+ },
263
+ dependencies: {
264
+ check: () => serialize(() => checkExtensionDependencies(dependencyOptions)),
265
+ install: (installOptions = {}) => serialize(() => installExtensionDependencies({
266
+ ...dependencyOptions,
267
+ ...installOptions,
268
+ ...(options.installer ? { installer: options.installer } : {}),
269
+ })),
270
+ },
271
+ preferences,
272
+ async savePreferences(name, values, scope) {
273
+ const { extension, command } = await commandManifest(name);
274
+ const isExtension = (key) => scope === "extension" ||
275
+ (extension.preferences.some((p) => p.name === key) &&
276
+ !command.preferences.some((p) => p.name === key));
277
+ await createPreferenceStore({ extensionName: extension.name, dataDir }).save(Object.fromEntries(Object.entries(values).filter(([key]) => isExtension(key))), name, Object.fromEntries(Object.entries(values).filter(([key]) => !isExtension(key))));
278
+ },
279
+ launch(name, launchOptions) {
280
+ const pending = launch(name, launchOptions);
281
+ launches.add(pending);
282
+ void pending.finally(() => launches.delete(pending)).catch(() => { });
283
+ return pending;
284
+ },
285
+ close() {
286
+ return (closing ??= (async () => {
287
+ closed = true;
288
+ for (const unsubscribe of subscriptions)
289
+ unsubscribe();
290
+ await Promise.allSettled([queue, ...launches]);
291
+ await Promise.all([...sessions].map((session) => session.close()));
292
+ })());
293
+ },
294
+ };
295
+ }
@@ -0,0 +1,13 @@
1
+ export * from "./broker.js";
2
+ export * from "./bundle.js";
3
+ export * from "./session.js";
4
+ export * from "./storage.js";
5
+ export * from "./oauth.js";
6
+ export * from "./provider.js";
7
+ export * from "./dependencies.js";
8
+ export * from "./client.js";
9
+ export * from "./install-dependencies.js";
10
+ export * from "./preferences.js";
11
+ export { openExtension, MissingLaunchValuesError, DependenciesRequiredError, type OpenExtensionOptions, type ExtensionHandle, type ExtensionLaunchOptions, type DebounceScheduler, type ExtensionWatcher, type ExtensionWatcherFactory, } from "./extension.js";
12
+ export * from "./extension-manifest.js";
13
+ export * from "./engine.js";
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ export * from "./broker.js";
2
+ export * from "./bundle.js";
3
+ export * from "./session.js";
4
+ export * from "./storage.js";
5
+ export * from "./oauth.js";
6
+ export * from "./provider.js";
7
+ export * from "./dependencies.js";
8
+ export * from "./client.js";
9
+ export * from "./install-dependencies.js";
10
+ export * from "./preferences.js";
11
+ export { openExtension, MissingLaunchValuesError, DependenciesRequiredError, } from "./extension.js";
12
+ export * from "./extension-manifest.js";
13
+ export * from "./engine.js";
@@ -0,0 +1,30 @@
1
+ import { type SharedDependencyLoader } from "./dependencies.js";
2
+ export type DependencyInstallOptions = {
3
+ extensionDir: string;
4
+ dataDir?: string;
5
+ sharedDependencies?: SharedDependencyLoader;
6
+ };
7
+ export type DependencyInstallStatus = {
8
+ directory: string;
9
+ key: string;
10
+ needed: boolean;
11
+ pinned: boolean;
12
+ /** Declared packages unavailable from a matching install or shared fallback. */
13
+ unresolved: string[];
14
+ /** Whether the manifest declares every unresolved package and can be installed. */
15
+ installable: boolean;
16
+ };
17
+ export type DependencyInstaller = (request: {
18
+ command: string;
19
+ args: string[];
20
+ cwd: string;
21
+ onLog?: (message: string) => void;
22
+ }) => Promise<void>;
23
+ export declare const npmDependencyInstaller: DependencyInstaller;
24
+ /** Cheap disk-only check; a key is published only after a successful install. */
25
+ export declare function checkExtensionDependencies(options: DependencyInstallOptions): Promise<DependencyInstallStatus>;
26
+ /** Installs only production dependencies; never writes to the extension. */
27
+ export declare function installExtensionDependencies(options: DependencyInstallOptions & {
28
+ installer?: DependencyInstaller;
29
+ onLog?: (message: string) => void;
30
+ }): Promise<DependencyInstallStatus>;