@uniqbit/mate-core 0.18.3-canary.0 → 0.18.3-canary.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniqbit/mate-core",
3
- "version": "0.18.3-canary.0",
3
+ "version": "0.18.3-canary.1",
4
4
  "description": "Core framework and plugin APIs for Mate.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,7 +1,10 @@
1
1
  import path from "node:path";
2
2
 
3
3
  import { FRAMEWORK_NAME } from "../../framework";
4
- import { getDefaultSetupSelections } from "./setup-compatibilities";
4
+ import {
5
+ getDefaultSetupSelections,
6
+ listSetupCapabilityCompatibilities,
7
+ } from "./setup-compatibilities";
5
8
  import { YamlFileStore } from "./yaml-file-store";
6
9
  import { ConfigError, type FrameworkConfig } from "./types";
7
10
 
@@ -143,7 +146,7 @@ function validatePluginDeclarations(config: FrameworkConfig): void {
143
146
 
144
147
  export const AUDIENCE_NAME_PATTERN = /^[a-z][a-z0-9-]{0,63}$/;
145
148
 
146
- const AUDIENCE_KEYS = ["plugins", "studio"] as const;
149
+ const AUDIENCE_KEYS = ["capabilities", "plugins", "studio"] as const;
147
150
 
148
151
  function isRecord(value: unknown): value is Record<string, unknown> {
149
152
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -182,6 +185,43 @@ function validateAudiencePlugins(audience: string, plugins: unknown): Map<string
182
185
  return versions;
183
186
  }
184
187
 
188
+ function validateAudienceCapabilities(
189
+ audience: string,
190
+ capabilities: unknown,
191
+ baseNames: ReadonlySet<string>,
192
+ ): void {
193
+ if (!Array.isArray(capabilities)) {
194
+ throw new ConfigError(`audience "${audience}": capabilities must be a list.`);
195
+ }
196
+ const builtin = new Set(
197
+ listSetupCapabilityCompatibilities().map((entry) => entry.capability.name),
198
+ );
199
+ const seen = new Set<string>();
200
+ for (const entry of capabilities) {
201
+ if (!isRecord(entry) || typeof entry.name !== "string" || entry.name === "") {
202
+ throw new ConfigError(
203
+ `audience "${audience}": capabilities entry must have a string name: ${JSON.stringify(entry)}`,
204
+ );
205
+ }
206
+ if (builtin.has(entry.name)) {
207
+ throw new ConfigError(
208
+ `audience "${audience}": capability "${entry.name}" is built in; declare it in the base capabilities.`,
209
+ );
210
+ }
211
+ if (baseNames.has(entry.name)) {
212
+ throw new ConfigError(
213
+ `audience "${audience}": capability "${entry.name}" is also declared in base capabilities.`,
214
+ );
215
+ }
216
+ if (seen.has(entry.name)) {
217
+ throw new ConfigError(
218
+ `audience "${audience}": capability "${entry.name}" is declared more than once.`,
219
+ );
220
+ }
221
+ seen.add(entry.name);
222
+ }
223
+ }
224
+
185
225
  /**
186
226
  * Validates the optional `audiences` mapping. Only companions may declare
187
227
  * audiences; names are safe identifiers and keys form a closed set so typos
@@ -198,6 +238,7 @@ function validateAudiences(config: FrameworkConfig): void {
198
238
  }
199
239
 
200
240
  const basePackages = new Set((config.plugins ?? []).map((plugin) => plugin.package));
241
+ const baseCapabilities = new Set((config.capabilities ?? []).map((entry) => entry.name));
201
242
  const seen = new Map<string, { audience: string; version: string }>();
202
243
 
203
244
  for (const [name, audience] of Object.entries(audiences)) {
@@ -217,6 +258,10 @@ function validateAudiences(config: FrameworkConfig): void {
217
258
  }
218
259
  }
219
260
 
261
+ if (audience.capabilities !== undefined) {
262
+ validateAudienceCapabilities(name, audience.capabilities, baseCapabilities);
263
+ }
264
+
220
265
  if (audience.plugins !== undefined) {
221
266
  for (const [pkg, version] of validateAudiencePlugins(name, audience.plugins)) {
222
267
  if (basePackages.has(pkg)) {
@@ -127,9 +127,12 @@ export interface StudioTerminalConfig {
127
127
 
128
128
  /**
129
129
  * A named slice of a companion's configuration, activated by `MATE_AUDIENCE`.
130
- * Plugins add to the base list; `studio.terminal.agent` overrides the base.
130
+ * Plugins and capabilities add to the base lists; `studio.terminal.agent`
131
+ * overrides the base. Capabilities here name what a declared plugin provides,
132
+ * never a built-in one, so what `install` writes does not depend on the audience.
131
133
  */
132
134
  export interface AudienceConfig {
135
+ capabilities?: CapabilityConfig[];
133
136
  plugins?: PluginDeclaration[];
134
137
  studio?: { terminal?: StudioTerminalConfig };
135
138
  }