@openchamber/sdk 2.5.0 → 2.5.2
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/API.md +33 -4
- package/DOCUMENTATION.md +41 -7
- package/GUEST_SERVICES.md +129 -3
- package/dist/index.d.ts +7 -5
- package/dist/index.js +3 -2
- package/dist/manifest.d.ts +188 -5
- package/dist/manifest.js +39 -5
- package/dist/parse.d.ts +230 -2
- package/dist/parse.js +198 -7
- package/dist/protocol.d.ts +30 -0
- package/dist/schemas.d.ts +2 -1
- package/dist/schemas.js +2 -1
- package/dist/service-provider-schemas.d.ts +510 -0
- package/dist/service-provider-schemas.js +221 -1
- package/dist/service-providers.d.ts +377 -2
- package/dist/service-providers.js +216 -0
- package/dist/status-view-schemas.d.ts +166 -0
- package/dist/status-view-schemas.js +113 -0
- package/dist/status-view.d.ts +129 -0
- package/dist/status-view.js +15 -0
- package/dist/ui/style.js +16 -9
- package/dist/workspace-schemas.d.ts +9 -0
- package/dist/workspace-schemas.js +1 -0
- package/dist/workspace.d.ts +13 -0
- package/package.json +2 -2
package/dist/parse.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { OPENCHAMBER_SDK_MANIFEST_API_VERSIONS } from './api-version.js';
|
|
3
3
|
import { OPENCHAMBER_ENGINE_PATTERN } from './host-version.js';
|
|
4
|
-
import { DECLARED_GUEST_CAPABILITIES, GUEST_ACTIONS_MAX, GUEST_ACTION_LABEL_MAX, GUEST_COMMANDS_MAX, GUEST_COMMAND_DESCRIPTION_MAX, GUEST_COMMAND_NAME, GUEST_FILESYSTEM_PATTERNS_MAX, GUEST_FILESYSTEM_PATTERN_MAX, GUEST_ORIGINS_MAX, GUEST_SERVICE_PROVIDES, GUEST_TOOLS_MAX, GUEST_TOOL_COLUMNS_MAX, GUEST_TOOL_COLUMN_MAX, GUEST_TOOL_LANGUAGE_MAX, GUEST_TOOL_MATCH, GUEST_TOOL_MATCH_MAX, GUEST_TOOL_NAME_MAX, GUEST_TOOL_OUTPUTS, GUEST_TOOL_TEMPLATE_MAX, PANEL_ID, hasGuestPage, isGuestFilesystemPattern, isGuestPackageSvgIcon, isSafeAssetPath, resolveIntegrationAuth, GUEST_SURFACE_DOCKS, GUEST_SURFACE_DOCK_SIZE_MAX, GUEST_SURFACE_DOCK_SIZE_MIN, GUEST_STATUS_SECTION_HEIGHT_MAX, GUEST_STATUS_SECTION_HEIGHT_MIN, GUEST_STATUS_SECTION_TITLE_MAX, } from './manifest.js';
|
|
4
|
+
import { DECLARED_GUEST_CAPABILITIES, GUEST_ACTIONS_MAX, GUEST_ACTION_LABEL_MAX, GUEST_COMMANDS_MAX, GUEST_COMMAND_DESCRIPTION_MAX, GUEST_COMMAND_NAME, GUEST_FILESYSTEM_PATTERNS_MAX, GUEST_FILESYSTEM_PATTERN_MAX, GUEST_ORIGINS_MAX, GUEST_SERVICE_PROVIDES, GUEST_TOOLS_MAX, GUEST_TOOL_COLUMNS_MAX, GUEST_TOOL_COLUMN_MAX, GUEST_TOOL_LANGUAGE_MAX, GUEST_TOOL_MATCH, GUEST_TOOL_MATCH_MAX, GUEST_TOOL_NAME_MAX, GUEST_TOOL_OUTPUTS, GUEST_TOOL_TEMPLATE_MAX, PANEL_ID, hasGuestPage, isGuestFilesystemPattern, isGuestPackageSvgIcon, isSafeAssetPath, resolveIntegrationAuth, GUEST_SURFACE_DOCKS, GUEST_SURFACE_DOCK_SIZE_MAX, GUEST_SURFACE_DOCK_SIZE_MIN, GUEST_STATUS_SECTION_HEIGHT_MAX, GUEST_STATUS_SECTION_HEIGHT_MIN, GUEST_STATUS_SECTION_TITLE_MAX, SOURCE_CONTROL_DEFAULT_INSTANCES_MAX, SOURCE_CONTROL_GUEST_ID_MAX, SOURCE_CONTROL_URL_TEMPLATES_MAX, SOURCE_CONTROL_URL_TEMPLATE_MAX, SOURCE_CONTROL_URL_TOKENS, } from './manifest.js';
|
|
5
|
+
import { isGuestRequestPath } from './contract.js';
|
|
6
|
+
import { GUEST_STATUS_SOURCE_INTERVAL_MAX, GUEST_STATUS_SOURCE_INTERVAL_MIN } from './status-view.js';
|
|
5
7
|
import { GUEST_FILE_EDITORS_MAX, GUEST_FILE_EDITOR_PATTERNS_MAX, GUEST_FILE_EDITOR_TITLE_MAX, isFileEditorPattern, } from './file-editor.js';
|
|
6
8
|
const isPanelIcon = (value) => (PANEL_ID.test(value) || isGuestPackageSvgIcon(value));
|
|
7
9
|
const isHttpsUrl = (value) => {
|
|
@@ -156,6 +158,148 @@ const serviceSchema = z.object({
|
|
|
156
158
|
.optional(),
|
|
157
159
|
surface: z.literal(true).optional(),
|
|
158
160
|
});
|
|
161
|
+
// SVG path data only, the same rule the UI applies to a provider's icon path:
|
|
162
|
+
// commands, numbers and separators, nothing that could close the attribute.
|
|
163
|
+
const SVG_PATH_DATA = /^[MmLlHhVvCcSsQqTtAaZz0-9eE.,\s+-]+$/;
|
|
164
|
+
// A literal URL template segment: no placeholder braces, no regex syntax.
|
|
165
|
+
const URL_TEMPLATE_LITERAL = /^[A-Za-z0-9._~-]+$/;
|
|
166
|
+
const URL_TEMPLATE_TOKENS = new Set(SOURCE_CONTROL_URL_TOKENS);
|
|
167
|
+
/**
|
|
168
|
+
* A web path template of a change request or issue: `/`, then segments that
|
|
169
|
+
* are each a literal or one of the known placeholders. `{repo}` and
|
|
170
|
+
* `{number}` appear exactly once, and the repository's owner exactly once as
|
|
171
|
+
* `{owner}` (`owner/name` paths) or `{namespace+}` (`namespace/name` paths).
|
|
172
|
+
*/
|
|
173
|
+
const isUrlTemplate = (value, repositoryPath) => {
|
|
174
|
+
if (!value.startsWith('/') || value.length > SOURCE_CONTROL_URL_TEMPLATE_MAX)
|
|
175
|
+
return false;
|
|
176
|
+
const segments = value.slice(1).split('/');
|
|
177
|
+
// A normalized URL never has a `.` or `..` segment, so a template with one matches nothing.
|
|
178
|
+
if (segments.some((segment) => segment === '.' || segment === '..'))
|
|
179
|
+
return false;
|
|
180
|
+
if (segments.some((segment) => !URL_TEMPLATE_TOKENS.has(segment) && !URL_TEMPLATE_LITERAL.test(segment)))
|
|
181
|
+
return false;
|
|
182
|
+
const count = (token) => segments.filter((segment) => segment === token).length;
|
|
183
|
+
const owner = repositoryPath === 'owner/name' ? '{owner}' : '{namespace+}';
|
|
184
|
+
const other = repositoryPath === 'owner/name' ? '{namespace+}' : '{owner}';
|
|
185
|
+
return count('{repo}') === 1 && count('{number}') === 1 && count(owner) === 1 && count(other) === 0;
|
|
186
|
+
};
|
|
187
|
+
// A file inside the repository, as a change request template names it:
|
|
188
|
+
// relative, no `.` or `..` segment, dot-folders like `.gitea/` allowed.
|
|
189
|
+
const REPOSITORY_FILE_SEGMENT = /^[A-Za-z0-9._-]+$/;
|
|
190
|
+
const isRepositoryFilePath = (value) => value.length > 0 && value.split('/')
|
|
191
|
+
.every((segment) => REPOSITORY_FILE_SEGMENT.test(segment) && segment !== '.' && segment !== '..');
|
|
192
|
+
const prefixSchema = z.string().min(1).max(4).regex(/^\S+$/);
|
|
193
|
+
const literalSchema = z.string().trim().min(1).max(64);
|
|
194
|
+
const sourceControlCapabilitiesSchema = z.object({
|
|
195
|
+
projects: z.boolean().default(false),
|
|
196
|
+
issues: z.boolean().default(false),
|
|
197
|
+
changeRequests: z.boolean().default(false),
|
|
198
|
+
draftChangeRequests: z.boolean().default(false),
|
|
199
|
+
merge: z.union([
|
|
200
|
+
z.literal(false),
|
|
201
|
+
z.object({
|
|
202
|
+
methods: z.array(z.enum(['merge', 'squash', 'rebase', 'submit'])).min(1).max(3)
|
|
203
|
+
.refine((methods) => new Set(methods).size === methods.length, { message: 'merge methods must be unique' })
|
|
204
|
+
// A host that submits a change merges it the one way its project is set up.
|
|
205
|
+
.refine((methods) => !methods.includes('submit') || methods.length === 1, { message: '"submit" is the only merge method when present' }),
|
|
206
|
+
}).strict(),
|
|
207
|
+
]).default(false),
|
|
208
|
+
ci: z.boolean().default(false),
|
|
209
|
+
comments: z.boolean().default(false),
|
|
210
|
+
reviews: z.boolean().default(false),
|
|
211
|
+
state: z.boolean().default(false),
|
|
212
|
+
labels: z.boolean().default(false),
|
|
213
|
+
reviewers: z.boolean().default(false),
|
|
214
|
+
changeRequestPatch: z.boolean().default(false),
|
|
215
|
+
liveSummaries: z.boolean().default(false),
|
|
216
|
+
// Left out rather than defaulted, so a manifest that does not use them parses as before.
|
|
217
|
+
reviewModel: z.enum(['verdict', 'votes']).optional(),
|
|
218
|
+
revisions: z.boolean().optional(),
|
|
219
|
+
}).strict();
|
|
220
|
+
// A Git username OpenChamber puts before the token in an HTTPS credential.
|
|
221
|
+
const FIXED_USERNAME = /^[A-Za-z0-9._-]{1,64}$/;
|
|
222
|
+
const urlTemplatesSchema = z.array(z.string().trim().min(1)).max(SOURCE_CONTROL_URL_TEMPLATES_MAX);
|
|
223
|
+
const sourceControlSchema = z.object({
|
|
224
|
+
wire: z.literal(1),
|
|
225
|
+
label: literalSchema,
|
|
226
|
+
terminology: z.object({
|
|
227
|
+
changeRequest: z.enum(['pullRequest', 'mergeRequest', 'change']),
|
|
228
|
+
ci: z.enum(['checks', 'pipeline']),
|
|
229
|
+
}).strict(),
|
|
230
|
+
referencePrefix: z.object({ changeRequest: prefixSchema, issue: prefixSchema }).strict(),
|
|
231
|
+
repositoryPath: z.enum(['owner/name', 'namespace/name']),
|
|
232
|
+
instances: z.object({
|
|
233
|
+
defaultInstances: z.array(z.string().trim().refine(isHttpsOrigin, { message: 'must be an https origin with no path, like https://git.example.com' }))
|
|
234
|
+
.max(SOURCE_CONTROL_DEFAULT_INSTANCES_MAX)
|
|
235
|
+
.refine((origins) => new Set(origins).size === origins.length, { message: 'default instances must be unique' }),
|
|
236
|
+
custom: z.boolean(),
|
|
237
|
+
allowLoopbackHttp: z.boolean(),
|
|
238
|
+
allowPort: z.boolean(),
|
|
239
|
+
placeholder: z.string().trim().max(256),
|
|
240
|
+
}).strict().refine((instances) => instances.custom || instances.defaultInstances.length > 0, { message: 'without custom instances, defaultInstances names at least one' }),
|
|
241
|
+
capabilities: sourceControlCapabilitiesSchema,
|
|
242
|
+
token: z.object({
|
|
243
|
+
header: z.enum(['authorization', 'private-token']),
|
|
244
|
+
scheme: z.enum(['bearer', 'token', 'basic', 'none']),
|
|
245
|
+
username: z.boolean().optional(),
|
|
246
|
+
scopes: z.array(z.string().trim().min(1).max(64)).max(32),
|
|
247
|
+
createPath: z.string().trim().max(256).refine(isSafeApiPath, { message: 'must be a path on the instance starting with "/", with no "." or ".." segment' }).optional(),
|
|
248
|
+
}).strict().refine((token) => (token.header === 'private-token') === (token.scheme === 'none'), { message: 'private-token takes the bare token; authorization takes a bearer, token or basic scheme' }).refine(
|
|
249
|
+
// Basic sends `login:token`, so Settings must ask for the login; no other scheme uses one.
|
|
250
|
+
(token) => (token.scheme === 'basic') === (token.username === true), { message: 'the basic scheme needs username: true, and username is only for the basic scheme' }),
|
|
251
|
+
transport: z.object({
|
|
252
|
+
httpsUsername: z.union([
|
|
253
|
+
z.object({ kind: z.literal('login') }).strict(),
|
|
254
|
+
z.object({ kind: z.literal('fixed'), value: z.string().regex(FIXED_USERNAME) }).strict(),
|
|
255
|
+
]),
|
|
256
|
+
appendDotGitToHttpsClone: z.boolean(),
|
|
257
|
+
}).strict(),
|
|
258
|
+
limits: z.object({
|
|
259
|
+
commentMaxLength: z.number().int().min(1).max(1_000_000),
|
|
260
|
+
reviewerId: z.enum(['numeric', 'login']),
|
|
261
|
+
labelsMax: z.number().int().min(0).max(100),
|
|
262
|
+
reviewersMax: z.number().int().min(0).max(50),
|
|
263
|
+
}).strict(),
|
|
264
|
+
trackedItems: z.object({ batchLimit: z.number().int().min(1).max(50) }).strict(),
|
|
265
|
+
presentation: z.object({
|
|
266
|
+
abbreviation: z.string().trim().min(1).max(8),
|
|
267
|
+
promptNoun: literalSchema,
|
|
268
|
+
sharedNumberSpace: z.boolean(),
|
|
269
|
+
iconPath: z.string().max(8192).regex(SVG_PATH_DATA).optional(),
|
|
270
|
+
urls: z.object({ changeRequest: urlTemplatesSchema, issue: urlTemplatesSchema }).strict(),
|
|
271
|
+
features: z.object({
|
|
272
|
+
authorCanReview: z.boolean().default(false),
|
|
273
|
+
issueCloseReason: z.boolean().default(false),
|
|
274
|
+
submitRequirements: z.boolean().optional(),
|
|
275
|
+
}).strict(),
|
|
276
|
+
changeRequestTemplates: z.array(z.string().trim().max(256).refine(isRepositoryFilePath, {
|
|
277
|
+
message: 'must be a file path inside the repository, like .gitea/pull_request_template.md, with no "." or ".." segment',
|
|
278
|
+
})).max(8),
|
|
279
|
+
}).strict(),
|
|
280
|
+
}).strict().superRefine((value, ctx) => {
|
|
281
|
+
for (const kind of ['changeRequest', 'issue']) {
|
|
282
|
+
value.presentation.urls[kind].forEach((template, index) => {
|
|
283
|
+
if (isUrlTemplate(template, value.repositoryPath))
|
|
284
|
+
return;
|
|
285
|
+
ctx.addIssue({
|
|
286
|
+
code: 'custom', path: ['presentation', 'urls', kind, index],
|
|
287
|
+
message: `templates start with "/" and use literal segments and ${SOURCE_CONTROL_URL_TOKENS.join(', ')}: {repo} and {number} once, and ${value.repositoryPath === 'owner/name' ? '{owner}' : '{namespace+}'} once for ${value.repositoryPath} paths.`,
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
// Change request patterns are matched first, so an issue template that is
|
|
292
|
+
// also a change request template would read every issue link as one.
|
|
293
|
+
const changeRequestTemplates = new Set(value.presentation.urls.changeRequest);
|
|
294
|
+
value.presentation.urls.issue.forEach((template, index) => {
|
|
295
|
+
if (!changeRequestTemplates.has(template))
|
|
296
|
+
return;
|
|
297
|
+
ctx.addIssue({
|
|
298
|
+
code: 'custom', path: ['presentation', 'urls', 'issue', index],
|
|
299
|
+
message: 'is also a changeRequest template; a link names either a change request or an issue.',
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
});
|
|
159
303
|
const uniqueBy = (items, key) => (new Set(items.map(key)).size === items.length);
|
|
160
304
|
// `roles` only means something on a message action and `payload` only on a
|
|
161
305
|
// session action; a dead field is a misconfiguration and fails closed.
|
|
@@ -212,12 +356,16 @@ const contributesSchema = z.object({
|
|
|
212
356
|
title: z.string().trim().min(1).max(200).optional(),
|
|
213
357
|
})]).optional(),
|
|
214
358
|
statusSection: z.union([z.literal(true), z.object({
|
|
215
|
-
entry: z.string().trim().refine((value) => isSafeAssetPath(value) && value.toLowerCase().endsWith('.html')),
|
|
359
|
+
entry: z.string().trim().refine((value) => isSafeAssetPath(value) && value.toLowerCase().endsWith('.html')).optional(),
|
|
360
|
+
source: z.object({
|
|
361
|
+
path: z.string().trim().refine(isGuestRequestPath),
|
|
362
|
+
interval: z.number().int().min(GUEST_STATUS_SOURCE_INTERVAL_MIN).max(GUEST_STATUS_SOURCE_INTERVAL_MAX).optional(),
|
|
363
|
+
}).strict().optional(),
|
|
216
364
|
title: z.string().trim().min(1).max(GUEST_STATUS_SECTION_TITLE_MAX).optional(),
|
|
217
365
|
height: z.number().int().min(GUEST_STATUS_SECTION_HEIGHT_MIN).max(GUEST_STATUS_SECTION_HEIGHT_MAX).optional(),
|
|
218
366
|
defaultExpanded: z.boolean().optional(),
|
|
219
367
|
requiresProject: z.boolean().optional(),
|
|
220
|
-
})]).optional(),
|
|
368
|
+
}).refine((section) => section.entry !== undefined || section.source !== undefined)]).optional(),
|
|
221
369
|
capabilities: z.array(z.enum(DECLARED_GUEST_CAPABILITIES)).max(8).optional(),
|
|
222
370
|
integration: integrationSchema.optional(),
|
|
223
371
|
service: serviceSchema.optional(),
|
|
@@ -229,10 +377,15 @@ const contributesSchema = z.object({
|
|
|
229
377
|
commands: commandsSchema.optional(),
|
|
230
378
|
tools: toolsSchema.optional(),
|
|
231
379
|
fileEditors: fileEditorsSchema.optional(),
|
|
380
|
+
sourceControl: sourceControlSchema.optional(),
|
|
232
381
|
});
|
|
233
382
|
/**
|
|
234
383
|
* Contributions that need either the panel or background execution frame.
|
|
235
384
|
*/
|
|
385
|
+
/** A section the host draws from the service's answers: the host is the service's caller. */
|
|
386
|
+
const hasStatusSource = (contributes) => (Boolean(contributes.statusSection && contributes.statusSection !== true && contributes.statusSection.source));
|
|
387
|
+
/** A section the host loads as a frame: `true`, or an object with an `entry`. */
|
|
388
|
+
const hasStatusFrame = (contributes) => (contributes.statusSection === true || Boolean(contributes.statusSection?.entry));
|
|
236
389
|
const runtimeContributions = (contributes) => {
|
|
237
390
|
const declared = [];
|
|
238
391
|
if (contributes.page !== undefined)
|
|
@@ -246,7 +399,8 @@ const runtimeContributions = (contributes) => {
|
|
|
246
399
|
// A service the host starts itself (it provides a role or a surface) needs
|
|
247
400
|
// no frame; one that only answers a panel's `serviceRequest` has no caller
|
|
248
401
|
// without one.
|
|
249
|
-
if (contributes.service !== undefined && !contributes.service.provides?.length && !contributes.service.surface
|
|
402
|
+
if (contributes.service !== undefined && !contributes.service.provides?.length && !contributes.service.surface
|
|
403
|
+
&& !hasStatusSource(contributes))
|
|
250
404
|
declared.push('service');
|
|
251
405
|
if (contributes.filesystem !== undefined)
|
|
252
406
|
declared.push('filesystem');
|
|
@@ -264,6 +418,23 @@ export const openChamberManifestSchema = z.object({
|
|
|
264
418
|
openchamber: z.string().trim().regex(OPENCHAMBER_ENGINE_PATTERN),
|
|
265
419
|
}).strict().optional(),
|
|
266
420
|
contributes: contributesSchema.superRefine((contributes, ctx) => {
|
|
421
|
+
// The Git host and the role that serves it come together, and the host
|
|
422
|
+
// names it `ext-<panel.id>` within the 32-character provider id.
|
|
423
|
+
const providesSourceControl = contributes.service?.provides?.includes('source-control') === true;
|
|
424
|
+
if (providesSourceControl !== (contributes.sourceControl !== undefined)) {
|
|
425
|
+
ctx.addIssue({
|
|
426
|
+
code: 'custom', path: ['service', 'provides'],
|
|
427
|
+
message: 'service.provides "source-control" and contributes.sourceControl come together: declare both or neither.',
|
|
428
|
+
});
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (providesSourceControl && contributes.panel.id.length > SOURCE_CONTROL_GUEST_ID_MAX) {
|
|
432
|
+
ctx.addIssue({
|
|
433
|
+
code: 'custom', path: ['service', 'provides'],
|
|
434
|
+
message: `A source-control extension's panel.id is at most ${SOURCE_CONTROL_GUEST_ID_MAX} characters: OpenChamber names its Git host "ext-<panel.id>".`,
|
|
435
|
+
});
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
267
438
|
// Docking only means something for a page beside a shared surface.
|
|
268
439
|
const docked = contributes.panel.dock !== undefined || contributes.panel.size !== undefined;
|
|
269
440
|
if (docked && !(contributes.service?.surface && hasGuestPage(contributes))) {
|
|
@@ -281,6 +452,14 @@ export const openChamberManifestSchema = z.object({
|
|
|
281
452
|
});
|
|
282
453
|
return;
|
|
283
454
|
}
|
|
455
|
+
// The host reads a source from the extension's service; without one it has nothing to ask.
|
|
456
|
+
if (hasStatusSource(contributes) && !contributes.service) {
|
|
457
|
+
ctx.addIssue({
|
|
458
|
+
code: 'custom', path: ['statusSection', 'source'],
|
|
459
|
+
message: 'contributes.statusSection.source is read from the extension\'s service; declare contributes.service.',
|
|
460
|
+
});
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
284
463
|
if (hasGuestPage(contributes))
|
|
285
464
|
return;
|
|
286
465
|
if (contributes.background) {
|
|
@@ -298,7 +477,7 @@ export const openChamberManifestSchema = z.object({
|
|
|
298
477
|
});
|
|
299
478
|
return;
|
|
300
479
|
}
|
|
301
|
-
if (contributes
|
|
480
|
+
if (hasStatusFrame(contributes) || contributes.fileEditors) {
|
|
302
481
|
// A status section or file editor frame runs code and may use what the
|
|
303
482
|
// package is granted, but it only hosts itself: things the host opens or
|
|
304
483
|
// invokes elsewhere need a panel or background entry.
|
|
@@ -343,6 +522,9 @@ const fail = (code, message) => ({
|
|
|
343
522
|
code,
|
|
344
523
|
message,
|
|
345
524
|
});
|
|
525
|
+
/** `contributes.sourceControl.instances.defaultInstances[0]`, relative to the `openchamber` block. */
|
|
526
|
+
const fieldPath = (issuePath) => (issuePath[0] === 'openchamber' ? issuePath.slice(1) : issuePath)
|
|
527
|
+
.reduce((field, segment) => (Number.isInteger(segment) ? `${field}[${String(segment)}]` : `${field ? `${field}.` : ''}${String(segment)}`), '');
|
|
346
528
|
const failureFromIssue = (issue) => {
|
|
347
529
|
// Paths are reported relative to the `openchamber` block whether the
|
|
348
530
|
// document was a bare manifest or a package.json envelope.
|
|
@@ -395,7 +577,7 @@ const failureFromIssue = (issue) => {
|
|
|
395
577
|
if (path === 'contributes.statusSection' || path.startsWith('contributes.statusSection.')) {
|
|
396
578
|
return fail('invalid-status-section', issue.code === 'custom'
|
|
397
579
|
? issue.message
|
|
398
|
-
: `contributes.statusSection must be true or { entry
|
|
580
|
+
: `contributes.statusSection must be true or { entry?: "<package .html>", source?: { path: "/status", interval?: ${GUEST_STATUS_SOURCE_INTERVAL_MIN} to ${GUEST_STATUS_SOURCE_INTERVAL_MAX} }, title?: "1 to ${GUEST_STATUS_SECTION_TITLE_MAX} characters", height?: ${GUEST_STATUS_SECTION_HEIGHT_MIN} to ${GUEST_STATUS_SECTION_HEIGHT_MAX} } with entry, source, or both.`);
|
|
399
581
|
}
|
|
400
582
|
if (path === 'contributes.background' || path.startsWith('contributes.background.')) {
|
|
401
583
|
return fail('invalid-background', 'contributes.background needs an entry ending in .html inside the package.');
|
|
@@ -427,8 +609,17 @@ const failureFromIssue = (issue) => {
|
|
|
427
609
|
if (path.startsWith('contributes.integration')) {
|
|
428
610
|
return fail('invalid-integration', 'contributes.integration needs a name, description, and oauth, token, or host.');
|
|
429
611
|
}
|
|
612
|
+
if (path.startsWith('contributes.sourceControl')) {
|
|
613
|
+
// The field is named either way: a refine's message alone does not say which entry failed.
|
|
614
|
+
const field = fieldPath(issue.path);
|
|
615
|
+
return fail('invalid-service', issue.code === 'custom'
|
|
616
|
+
? `${field}: ${issue.message}`
|
|
617
|
+
: `${field} is invalid: see "contributes.sourceControl" in GUEST_SERVICES.md for every field.`);
|
|
618
|
+
}
|
|
430
619
|
if (path.startsWith('contributes.service')) {
|
|
431
|
-
return fail('invalid-service',
|
|
620
|
+
return fail('invalid-service', issue.code === 'custom'
|
|
621
|
+
? issue.message
|
|
622
|
+
: 'contributes.service needs entry, runtime "host", optional permissions, optional provides ("browser", "source-control"), and optional surface (true).');
|
|
432
623
|
}
|
|
433
624
|
return fail('missing-panel', 'contributes.panel is required.');
|
|
434
625
|
};
|
package/dist/protocol.d.ts
CHANGED
|
@@ -89,6 +89,15 @@ export declare const hostMessageSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
89
89
|
id: z.ZodString;
|
|
90
90
|
data: z.ZodOptional<z.ZodJSONSchema>;
|
|
91
91
|
}, z.core.$strip>>;
|
|
92
|
+
links: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
93
|
+
title: z.ZodString;
|
|
94
|
+
url: z.ZodString;
|
|
95
|
+
kind: z.ZodEnum<{
|
|
96
|
+
change: "change";
|
|
97
|
+
issue: "issue";
|
|
98
|
+
}>;
|
|
99
|
+
linkedAt: z.ZodNumber;
|
|
100
|
+
}, z.core.$strip>>>;
|
|
92
101
|
}, z.core.$strip>>;
|
|
93
102
|
}, z.core.$strip>], "kind">;
|
|
94
103
|
}, z.core.$strip>;
|
|
@@ -503,6 +512,15 @@ export declare const hostMessageSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
503
512
|
id: z.ZodString;
|
|
504
513
|
data: z.ZodOptional<z.ZodJSONSchema>;
|
|
505
514
|
}, z.core.$strip>>;
|
|
515
|
+
links: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
516
|
+
title: z.ZodString;
|
|
517
|
+
url: z.ZodString;
|
|
518
|
+
kind: z.ZodEnum<{
|
|
519
|
+
change: "change";
|
|
520
|
+
issue: "issue";
|
|
521
|
+
}>;
|
|
522
|
+
linkedAt: z.ZodNumber;
|
|
523
|
+
}, z.core.$strip>>>;
|
|
506
524
|
}, z.core.$strip>>;
|
|
507
525
|
}, z.core.$strip>], "kind">, z.ZodObject<{
|
|
508
526
|
output: z.ZodString;
|
|
@@ -645,6 +663,12 @@ export declare const hostMessageSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
645
663
|
id: string;
|
|
646
664
|
data?: z.util.JSONType | undefined;
|
|
647
665
|
}[];
|
|
666
|
+
links?: {
|
|
667
|
+
title: string;
|
|
668
|
+
url: string;
|
|
669
|
+
kind: "change" | "issue";
|
|
670
|
+
linkedAt: number;
|
|
671
|
+
}[] | undefined;
|
|
648
672
|
}[];
|
|
649
673
|
} | {
|
|
650
674
|
status: number;
|
|
@@ -788,6 +812,12 @@ export declare const hostMessageSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
788
812
|
id: string;
|
|
789
813
|
data?: z.util.JSONType | undefined;
|
|
790
814
|
}[];
|
|
815
|
+
links?: {
|
|
816
|
+
title: string;
|
|
817
|
+
url: string;
|
|
818
|
+
kind: "change" | "issue";
|
|
819
|
+
linkedAt: number;
|
|
820
|
+
}[] | undefined;
|
|
791
821
|
}[];
|
|
792
822
|
} | {
|
|
793
823
|
status: number;
|
package/dist/schemas.d.ts
CHANGED
|
@@ -2,8 +2,9 @@ export { openChamberManifestSchema, PACKAGE_VERSION_PATTERN, packageManifestSche
|
|
|
2
2
|
export type { ManifestDocument } from './parse.ts';
|
|
3
3
|
export { guestStorageRequestSchema, guestStorageResultSchema, guestWorkspaceSnapshotSchema } from './workspace-schemas.ts';
|
|
4
4
|
export { guestStatusControlsSchema, guestStatusControlEventSchema } from './status-control-schemas.ts';
|
|
5
|
+
export { guestStatusViewSchema } from './status-view-schemas.ts';
|
|
5
6
|
export { guestPopoverClosedEventSchema, guestPopoverRequestSchema } from './popover-schemas.ts';
|
|
6
7
|
export { guestMessageSchema, hostMessageSchema, parseGuestMessage, parseHostMessage, } from './protocol.ts';
|
|
7
|
-
export { browserControlActionSchema, browserProviderResultSchema } from './service-provider-schemas.ts';
|
|
8
|
+
export { browserControlActionSchema, browserProviderResultSchema, sourceControlProviderMethodSchema, sourceControlProviderSchemasV1, } from './service-provider-schemas.ts';
|
|
8
9
|
export { surfaceClipboardAnswerSchema, surfaceControllerSchema, surfaceHostMessageSchema, surfaceInputEventSchema, surfaceResizeAnswerSchema, surfaceViewerMessageSchema, } from './service-surface-schemas.ts';
|
|
9
10
|
export type { SurfaceHostMessage, SurfaceViewerMessage } from './service-surface-schemas.ts';
|
package/dist/schemas.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
export { openChamberManifestSchema, PACKAGE_VERSION_PATTERN, packageManifestSchema, parseManifest, parseManifestJson, } from './parse.js';
|
|
5
5
|
export { guestStorageRequestSchema, guestStorageResultSchema, guestWorkspaceSnapshotSchema } from './workspace-schemas.js';
|
|
6
6
|
export { guestStatusControlsSchema, guestStatusControlEventSchema } from './status-control-schemas.js';
|
|
7
|
+
export { guestStatusViewSchema } from './status-view-schemas.js';
|
|
7
8
|
export { guestPopoverClosedEventSchema, guestPopoverRequestSchema } from './popover-schemas.js';
|
|
8
9
|
export { guestMessageSchema, hostMessageSchema, parseGuestMessage, parseHostMessage, } from './protocol.js';
|
|
9
|
-
export { browserControlActionSchema, browserProviderResultSchema } from './service-provider-schemas.js';
|
|
10
|
+
export { browserControlActionSchema, browserProviderResultSchema, sourceControlProviderMethodSchema, sourceControlProviderSchemasV1, } from './service-provider-schemas.js';
|
|
10
11
|
export { surfaceClipboardAnswerSchema, surfaceControllerSchema, surfaceHostMessageSchema, surfaceInputEventSchema, surfaceResizeAnswerSchema, surfaceViewerMessageSchema, } from './service-surface-schemas.js';
|