@openchamber/sdk 2.5.0 → 2.5.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/API.md +7 -3
- package/DOCUMENTATION.md +38 -5
- package/GUEST_SERVICES.md +129 -3
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/manifest.d.ts +178 -4
- package/dist/manifest.js +30 -4
- package/dist/parse.d.ts +220 -0
- package/dist/parse.js +174 -2
- package/dist/schemas.d.ts +1 -1
- package/dist/schemas.js +1 -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/ui/style.js +16 -9
- package/package.json +2 -2
package/dist/manifest.js
CHANGED
|
@@ -87,11 +87,37 @@ export const resolveIntegrationApi = (integration) => {
|
|
|
87
87
|
};
|
|
88
88
|
/**
|
|
89
89
|
* Host roles a service can stand in for. `browser` answers the agent's
|
|
90
|
-
* `browser.*` actions in place of the in-app browser view
|
|
91
|
-
*
|
|
92
|
-
*
|
|
90
|
+
* `browser.*` actions in place of the in-app browser view. `source-control`
|
|
91
|
+
* makes the extension a Git host next to GitHub, GitLab and Gitea, described
|
|
92
|
+
* by `contributes.sourceControl`. Both contracts are in `service-providers.ts`.
|
|
93
|
+
* A service that provides a role needs no panel or background entry: the host
|
|
94
|
+
* starts it on the first call.
|
|
93
95
|
*/
|
|
94
|
-
export const GUEST_SERVICE_PROVIDES = ['browser'];
|
|
96
|
+
export const GUEST_SERVICE_PROVIDES = ['browser', 'source-control'];
|
|
97
|
+
/**
|
|
98
|
+
* The host names an extension's Git host `ext-<panel.id>`, and provider ids
|
|
99
|
+
* are at most 32 characters, so a guest taking the role has an id of at most
|
|
100
|
+
* this many.
|
|
101
|
+
*/
|
|
102
|
+
export const SOURCE_CONTROL_GUEST_ID_MAX = 28;
|
|
103
|
+
/** The provider id OpenChamber gives the Git host an extension provides. */
|
|
104
|
+
export const sourceControlProviderIdOf = (guestId) => `ext-${guestId}`;
|
|
105
|
+
/** Default instances a Git host extension may name. */
|
|
106
|
+
export const SOURCE_CONTROL_DEFAULT_INSTANCES_MAX = 4;
|
|
107
|
+
/** URL templates per kind (`presentation.urls.changeRequest` / `.issue`). */
|
|
108
|
+
export const SOURCE_CONTROL_URL_TEMPLATES_MAX = 8;
|
|
109
|
+
/** Characters in one URL template. */
|
|
110
|
+
export const SOURCE_CONTROL_URL_TEMPLATE_MAX = 256;
|
|
111
|
+
/**
|
|
112
|
+
* The placeholders a URL template may use, each a whole path segment.
|
|
113
|
+
* `{namespace+}` is one or more segments (GitLab groups); `{owner}` exactly one.
|
|
114
|
+
*/
|
|
115
|
+
export const SOURCE_CONTROL_URL_TOKENS = ['{owner}', '{namespace+}', '{repo}', '{number}'];
|
|
116
|
+
export const toPublicSourceControl = (contribution) => ({
|
|
117
|
+
label: contribution.label,
|
|
118
|
+
defaultInstances: [...contribution.instances.defaultInstances],
|
|
119
|
+
custom: contribution.instances.custom,
|
|
120
|
+
});
|
|
95
121
|
export const serviceProvides = (service, role) => Boolean(service?.provides?.includes(role));
|
|
96
122
|
/**
|
|
97
123
|
* What a guest may do beyond drawing its own panel. The user approves the
|
package/dist/parse.d.ts
CHANGED
|
@@ -114,6 +114,7 @@ export declare const openChamberManifestSchema: z.ZodObject<{
|
|
|
114
114
|
}>>>;
|
|
115
115
|
provides: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
116
116
|
browser: "browser";
|
|
117
|
+
"source-control": "source-control";
|
|
117
118
|
}>>>;
|
|
118
119
|
surface: z.ZodOptional<z.ZodLiteral<true>>;
|
|
119
120
|
}, z.core.$strip>>;
|
|
@@ -170,6 +171,115 @@ export declare const openChamberManifestSchema: z.ZodObject<{
|
|
|
170
171
|
text: "text";
|
|
171
172
|
}>>;
|
|
172
173
|
}, z.core.$strip>>>;
|
|
174
|
+
sourceControl: z.ZodOptional<z.ZodObject<{
|
|
175
|
+
wire: z.ZodLiteral<1>;
|
|
176
|
+
label: z.ZodString;
|
|
177
|
+
terminology: z.ZodObject<{
|
|
178
|
+
changeRequest: z.ZodEnum<{
|
|
179
|
+
change: "change";
|
|
180
|
+
mergeRequest: "mergeRequest";
|
|
181
|
+
pullRequest: "pullRequest";
|
|
182
|
+
}>;
|
|
183
|
+
ci: z.ZodEnum<{
|
|
184
|
+
checks: "checks";
|
|
185
|
+
pipeline: "pipeline";
|
|
186
|
+
}>;
|
|
187
|
+
}, z.core.$strict>;
|
|
188
|
+
referencePrefix: z.ZodObject<{
|
|
189
|
+
changeRequest: z.ZodString;
|
|
190
|
+
issue: z.ZodString;
|
|
191
|
+
}, z.core.$strict>;
|
|
192
|
+
repositoryPath: z.ZodEnum<{
|
|
193
|
+
"namespace/name": "namespace/name";
|
|
194
|
+
"owner/name": "owner/name";
|
|
195
|
+
}>;
|
|
196
|
+
instances: z.ZodObject<{
|
|
197
|
+
defaultInstances: z.ZodArray<z.ZodString>;
|
|
198
|
+
custom: z.ZodBoolean;
|
|
199
|
+
allowLoopbackHttp: z.ZodBoolean;
|
|
200
|
+
allowPort: z.ZodBoolean;
|
|
201
|
+
placeholder: z.ZodString;
|
|
202
|
+
}, z.core.$strict>;
|
|
203
|
+
capabilities: z.ZodObject<{
|
|
204
|
+
projects: z.ZodDefault<z.ZodBoolean>;
|
|
205
|
+
issues: z.ZodDefault<z.ZodBoolean>;
|
|
206
|
+
changeRequests: z.ZodDefault<z.ZodBoolean>;
|
|
207
|
+
draftChangeRequests: z.ZodDefault<z.ZodBoolean>;
|
|
208
|
+
merge: z.ZodDefault<z.ZodUnion<readonly [z.ZodLiteral<false>, z.ZodObject<{
|
|
209
|
+
methods: z.ZodArray<z.ZodEnum<{
|
|
210
|
+
merge: "merge";
|
|
211
|
+
rebase: "rebase";
|
|
212
|
+
squash: "squash";
|
|
213
|
+
submit: "submit";
|
|
214
|
+
}>>;
|
|
215
|
+
}, z.core.$strict>]>>;
|
|
216
|
+
ci: z.ZodDefault<z.ZodBoolean>;
|
|
217
|
+
comments: z.ZodDefault<z.ZodBoolean>;
|
|
218
|
+
reviews: z.ZodDefault<z.ZodBoolean>;
|
|
219
|
+
state: z.ZodDefault<z.ZodBoolean>;
|
|
220
|
+
labels: z.ZodDefault<z.ZodBoolean>;
|
|
221
|
+
reviewers: z.ZodDefault<z.ZodBoolean>;
|
|
222
|
+
changeRequestPatch: z.ZodDefault<z.ZodBoolean>;
|
|
223
|
+
liveSummaries: z.ZodDefault<z.ZodBoolean>;
|
|
224
|
+
reviewModel: z.ZodOptional<z.ZodEnum<{
|
|
225
|
+
verdict: "verdict";
|
|
226
|
+
votes: "votes";
|
|
227
|
+
}>>;
|
|
228
|
+
revisions: z.ZodOptional<z.ZodBoolean>;
|
|
229
|
+
}, z.core.$strict>;
|
|
230
|
+
token: z.ZodObject<{
|
|
231
|
+
header: z.ZodEnum<{
|
|
232
|
+
authorization: "authorization";
|
|
233
|
+
"private-token": "private-token";
|
|
234
|
+
}>;
|
|
235
|
+
scheme: z.ZodEnum<{
|
|
236
|
+
basic: "basic";
|
|
237
|
+
bearer: "bearer";
|
|
238
|
+
none: "none";
|
|
239
|
+
token: "token";
|
|
240
|
+
}>;
|
|
241
|
+
username: z.ZodOptional<z.ZodBoolean>;
|
|
242
|
+
scopes: z.ZodArray<z.ZodString>;
|
|
243
|
+
createPath: z.ZodOptional<z.ZodString>;
|
|
244
|
+
}, z.core.$strict>;
|
|
245
|
+
transport: z.ZodObject<{
|
|
246
|
+
httpsUsername: z.ZodUnion<readonly [z.ZodObject<{
|
|
247
|
+
kind: z.ZodLiteral<"login">;
|
|
248
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
249
|
+
kind: z.ZodLiteral<"fixed">;
|
|
250
|
+
value: z.ZodString;
|
|
251
|
+
}, z.core.$strict>]>;
|
|
252
|
+
appendDotGitToHttpsClone: z.ZodBoolean;
|
|
253
|
+
}, z.core.$strict>;
|
|
254
|
+
limits: z.ZodObject<{
|
|
255
|
+
commentMaxLength: z.ZodNumber;
|
|
256
|
+
reviewerId: z.ZodEnum<{
|
|
257
|
+
login: "login";
|
|
258
|
+
numeric: "numeric";
|
|
259
|
+
}>;
|
|
260
|
+
labelsMax: z.ZodNumber;
|
|
261
|
+
reviewersMax: z.ZodNumber;
|
|
262
|
+
}, z.core.$strict>;
|
|
263
|
+
trackedItems: z.ZodObject<{
|
|
264
|
+
batchLimit: z.ZodNumber;
|
|
265
|
+
}, z.core.$strict>;
|
|
266
|
+
presentation: z.ZodObject<{
|
|
267
|
+
abbreviation: z.ZodString;
|
|
268
|
+
promptNoun: z.ZodString;
|
|
269
|
+
sharedNumberSpace: z.ZodBoolean;
|
|
270
|
+
iconPath: z.ZodOptional<z.ZodString>;
|
|
271
|
+
urls: z.ZodObject<{
|
|
272
|
+
changeRequest: z.ZodArray<z.ZodString>;
|
|
273
|
+
issue: z.ZodArray<z.ZodString>;
|
|
274
|
+
}, z.core.$strict>;
|
|
275
|
+
features: z.ZodObject<{
|
|
276
|
+
authorCanReview: z.ZodDefault<z.ZodBoolean>;
|
|
277
|
+
issueCloseReason: z.ZodDefault<z.ZodBoolean>;
|
|
278
|
+
submitRequirements: z.ZodOptional<z.ZodBoolean>;
|
|
279
|
+
}, z.core.$strict>;
|
|
280
|
+
changeRequestTemplates: z.ZodArray<z.ZodString>;
|
|
281
|
+
}, z.core.$strict>;
|
|
282
|
+
}, z.core.$strict>>;
|
|
173
283
|
}, z.core.$strip>;
|
|
174
284
|
}, z.core.$strip>;
|
|
175
285
|
export declare const PACKAGE_VERSION_PATTERN: RegExp;
|
|
@@ -289,6 +399,7 @@ export declare const packageManifestSchema: z.ZodObject<{
|
|
|
289
399
|
}>>>;
|
|
290
400
|
provides: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
291
401
|
browser: "browser";
|
|
402
|
+
"source-control": "source-control";
|
|
292
403
|
}>>>;
|
|
293
404
|
surface: z.ZodOptional<z.ZodLiteral<true>>;
|
|
294
405
|
}, z.core.$strip>>;
|
|
@@ -345,6 +456,115 @@ export declare const packageManifestSchema: z.ZodObject<{
|
|
|
345
456
|
text: "text";
|
|
346
457
|
}>>;
|
|
347
458
|
}, z.core.$strip>>>;
|
|
459
|
+
sourceControl: z.ZodOptional<z.ZodObject<{
|
|
460
|
+
wire: z.ZodLiteral<1>;
|
|
461
|
+
label: z.ZodString;
|
|
462
|
+
terminology: z.ZodObject<{
|
|
463
|
+
changeRequest: z.ZodEnum<{
|
|
464
|
+
change: "change";
|
|
465
|
+
mergeRequest: "mergeRequest";
|
|
466
|
+
pullRequest: "pullRequest";
|
|
467
|
+
}>;
|
|
468
|
+
ci: z.ZodEnum<{
|
|
469
|
+
checks: "checks";
|
|
470
|
+
pipeline: "pipeline";
|
|
471
|
+
}>;
|
|
472
|
+
}, z.core.$strict>;
|
|
473
|
+
referencePrefix: z.ZodObject<{
|
|
474
|
+
changeRequest: z.ZodString;
|
|
475
|
+
issue: z.ZodString;
|
|
476
|
+
}, z.core.$strict>;
|
|
477
|
+
repositoryPath: z.ZodEnum<{
|
|
478
|
+
"namespace/name": "namespace/name";
|
|
479
|
+
"owner/name": "owner/name";
|
|
480
|
+
}>;
|
|
481
|
+
instances: z.ZodObject<{
|
|
482
|
+
defaultInstances: z.ZodArray<z.ZodString>;
|
|
483
|
+
custom: z.ZodBoolean;
|
|
484
|
+
allowLoopbackHttp: z.ZodBoolean;
|
|
485
|
+
allowPort: z.ZodBoolean;
|
|
486
|
+
placeholder: z.ZodString;
|
|
487
|
+
}, z.core.$strict>;
|
|
488
|
+
capabilities: z.ZodObject<{
|
|
489
|
+
projects: z.ZodDefault<z.ZodBoolean>;
|
|
490
|
+
issues: z.ZodDefault<z.ZodBoolean>;
|
|
491
|
+
changeRequests: z.ZodDefault<z.ZodBoolean>;
|
|
492
|
+
draftChangeRequests: z.ZodDefault<z.ZodBoolean>;
|
|
493
|
+
merge: z.ZodDefault<z.ZodUnion<readonly [z.ZodLiteral<false>, z.ZodObject<{
|
|
494
|
+
methods: z.ZodArray<z.ZodEnum<{
|
|
495
|
+
merge: "merge";
|
|
496
|
+
rebase: "rebase";
|
|
497
|
+
squash: "squash";
|
|
498
|
+
submit: "submit";
|
|
499
|
+
}>>;
|
|
500
|
+
}, z.core.$strict>]>>;
|
|
501
|
+
ci: z.ZodDefault<z.ZodBoolean>;
|
|
502
|
+
comments: z.ZodDefault<z.ZodBoolean>;
|
|
503
|
+
reviews: z.ZodDefault<z.ZodBoolean>;
|
|
504
|
+
state: z.ZodDefault<z.ZodBoolean>;
|
|
505
|
+
labels: z.ZodDefault<z.ZodBoolean>;
|
|
506
|
+
reviewers: z.ZodDefault<z.ZodBoolean>;
|
|
507
|
+
changeRequestPatch: z.ZodDefault<z.ZodBoolean>;
|
|
508
|
+
liveSummaries: z.ZodDefault<z.ZodBoolean>;
|
|
509
|
+
reviewModel: z.ZodOptional<z.ZodEnum<{
|
|
510
|
+
verdict: "verdict";
|
|
511
|
+
votes: "votes";
|
|
512
|
+
}>>;
|
|
513
|
+
revisions: z.ZodOptional<z.ZodBoolean>;
|
|
514
|
+
}, z.core.$strict>;
|
|
515
|
+
token: z.ZodObject<{
|
|
516
|
+
header: z.ZodEnum<{
|
|
517
|
+
authorization: "authorization";
|
|
518
|
+
"private-token": "private-token";
|
|
519
|
+
}>;
|
|
520
|
+
scheme: z.ZodEnum<{
|
|
521
|
+
basic: "basic";
|
|
522
|
+
bearer: "bearer";
|
|
523
|
+
none: "none";
|
|
524
|
+
token: "token";
|
|
525
|
+
}>;
|
|
526
|
+
username: z.ZodOptional<z.ZodBoolean>;
|
|
527
|
+
scopes: z.ZodArray<z.ZodString>;
|
|
528
|
+
createPath: z.ZodOptional<z.ZodString>;
|
|
529
|
+
}, z.core.$strict>;
|
|
530
|
+
transport: z.ZodObject<{
|
|
531
|
+
httpsUsername: z.ZodUnion<readonly [z.ZodObject<{
|
|
532
|
+
kind: z.ZodLiteral<"login">;
|
|
533
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
534
|
+
kind: z.ZodLiteral<"fixed">;
|
|
535
|
+
value: z.ZodString;
|
|
536
|
+
}, z.core.$strict>]>;
|
|
537
|
+
appendDotGitToHttpsClone: z.ZodBoolean;
|
|
538
|
+
}, z.core.$strict>;
|
|
539
|
+
limits: z.ZodObject<{
|
|
540
|
+
commentMaxLength: z.ZodNumber;
|
|
541
|
+
reviewerId: z.ZodEnum<{
|
|
542
|
+
login: "login";
|
|
543
|
+
numeric: "numeric";
|
|
544
|
+
}>;
|
|
545
|
+
labelsMax: z.ZodNumber;
|
|
546
|
+
reviewersMax: z.ZodNumber;
|
|
547
|
+
}, z.core.$strict>;
|
|
548
|
+
trackedItems: z.ZodObject<{
|
|
549
|
+
batchLimit: z.ZodNumber;
|
|
550
|
+
}, z.core.$strict>;
|
|
551
|
+
presentation: z.ZodObject<{
|
|
552
|
+
abbreviation: z.ZodString;
|
|
553
|
+
promptNoun: z.ZodString;
|
|
554
|
+
sharedNumberSpace: z.ZodBoolean;
|
|
555
|
+
iconPath: z.ZodOptional<z.ZodString>;
|
|
556
|
+
urls: z.ZodObject<{
|
|
557
|
+
changeRequest: z.ZodArray<z.ZodString>;
|
|
558
|
+
issue: z.ZodArray<z.ZodString>;
|
|
559
|
+
}, z.core.$strict>;
|
|
560
|
+
features: z.ZodObject<{
|
|
561
|
+
authorCanReview: z.ZodDefault<z.ZodBoolean>;
|
|
562
|
+
issueCloseReason: z.ZodDefault<z.ZodBoolean>;
|
|
563
|
+
submitRequirements: z.ZodOptional<z.ZodBoolean>;
|
|
564
|
+
}, z.core.$strict>;
|
|
565
|
+
changeRequestTemplates: z.ZodArray<z.ZodString>;
|
|
566
|
+
}, z.core.$strict>;
|
|
567
|
+
}, z.core.$strict>>;
|
|
348
568
|
}, z.core.$strip>;
|
|
349
569
|
}, z.core.$strip>;
|
|
350
570
|
}, z.core.$strip>;
|
package/dist/parse.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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
5
|
import { GUEST_FILE_EDITORS_MAX, GUEST_FILE_EDITOR_PATTERNS_MAX, GUEST_FILE_EDITOR_TITLE_MAX, isFileEditorPattern, } from './file-editor.js';
|
|
6
6
|
const isPanelIcon = (value) => (PANEL_ID.test(value) || isGuestPackageSvgIcon(value));
|
|
7
7
|
const isHttpsUrl = (value) => {
|
|
@@ -156,6 +156,148 @@ const serviceSchema = z.object({
|
|
|
156
156
|
.optional(),
|
|
157
157
|
surface: z.literal(true).optional(),
|
|
158
158
|
});
|
|
159
|
+
// SVG path data only, the same rule the UI applies to a provider's icon path:
|
|
160
|
+
// commands, numbers and separators, nothing that could close the attribute.
|
|
161
|
+
const SVG_PATH_DATA = /^[MmLlHhVvCcSsQqTtAaZz0-9eE.,\s+-]+$/;
|
|
162
|
+
// A literal URL template segment: no placeholder braces, no regex syntax.
|
|
163
|
+
const URL_TEMPLATE_LITERAL = /^[A-Za-z0-9._~-]+$/;
|
|
164
|
+
const URL_TEMPLATE_TOKENS = new Set(SOURCE_CONTROL_URL_TOKENS);
|
|
165
|
+
/**
|
|
166
|
+
* A web path template of a change request or issue: `/`, then segments that
|
|
167
|
+
* are each a literal or one of the known placeholders. `{repo}` and
|
|
168
|
+
* `{number}` appear exactly once, and the repository's owner exactly once as
|
|
169
|
+
* `{owner}` (`owner/name` paths) or `{namespace+}` (`namespace/name` paths).
|
|
170
|
+
*/
|
|
171
|
+
const isUrlTemplate = (value, repositoryPath) => {
|
|
172
|
+
if (!value.startsWith('/') || value.length > SOURCE_CONTROL_URL_TEMPLATE_MAX)
|
|
173
|
+
return false;
|
|
174
|
+
const segments = value.slice(1).split('/');
|
|
175
|
+
// A normalized URL never has a `.` or `..` segment, so a template with one matches nothing.
|
|
176
|
+
if (segments.some((segment) => segment === '.' || segment === '..'))
|
|
177
|
+
return false;
|
|
178
|
+
if (segments.some((segment) => !URL_TEMPLATE_TOKENS.has(segment) && !URL_TEMPLATE_LITERAL.test(segment)))
|
|
179
|
+
return false;
|
|
180
|
+
const count = (token) => segments.filter((segment) => segment === token).length;
|
|
181
|
+
const owner = repositoryPath === 'owner/name' ? '{owner}' : '{namespace+}';
|
|
182
|
+
const other = repositoryPath === 'owner/name' ? '{namespace+}' : '{owner}';
|
|
183
|
+
return count('{repo}') === 1 && count('{number}') === 1 && count(owner) === 1 && count(other) === 0;
|
|
184
|
+
};
|
|
185
|
+
// A file inside the repository, as a change request template names it:
|
|
186
|
+
// relative, no `.` or `..` segment, dot-folders like `.gitea/` allowed.
|
|
187
|
+
const REPOSITORY_FILE_SEGMENT = /^[A-Za-z0-9._-]+$/;
|
|
188
|
+
const isRepositoryFilePath = (value) => value.length > 0 && value.split('/')
|
|
189
|
+
.every((segment) => REPOSITORY_FILE_SEGMENT.test(segment) && segment !== '.' && segment !== '..');
|
|
190
|
+
const prefixSchema = z.string().min(1).max(4).regex(/^\S+$/);
|
|
191
|
+
const literalSchema = z.string().trim().min(1).max(64);
|
|
192
|
+
const sourceControlCapabilitiesSchema = z.object({
|
|
193
|
+
projects: z.boolean().default(false),
|
|
194
|
+
issues: z.boolean().default(false),
|
|
195
|
+
changeRequests: z.boolean().default(false),
|
|
196
|
+
draftChangeRequests: z.boolean().default(false),
|
|
197
|
+
merge: z.union([
|
|
198
|
+
z.literal(false),
|
|
199
|
+
z.object({
|
|
200
|
+
methods: z.array(z.enum(['merge', 'squash', 'rebase', 'submit'])).min(1).max(3)
|
|
201
|
+
.refine((methods) => new Set(methods).size === methods.length, { message: 'merge methods must be unique' })
|
|
202
|
+
// A host that submits a change merges it the one way its project is set up.
|
|
203
|
+
.refine((methods) => !methods.includes('submit') || methods.length === 1, { message: '"submit" is the only merge method when present' }),
|
|
204
|
+
}).strict(),
|
|
205
|
+
]).default(false),
|
|
206
|
+
ci: z.boolean().default(false),
|
|
207
|
+
comments: z.boolean().default(false),
|
|
208
|
+
reviews: z.boolean().default(false),
|
|
209
|
+
state: z.boolean().default(false),
|
|
210
|
+
labels: z.boolean().default(false),
|
|
211
|
+
reviewers: z.boolean().default(false),
|
|
212
|
+
changeRequestPatch: z.boolean().default(false),
|
|
213
|
+
liveSummaries: z.boolean().default(false),
|
|
214
|
+
// Left out rather than defaulted, so a manifest that does not use them parses as before.
|
|
215
|
+
reviewModel: z.enum(['verdict', 'votes']).optional(),
|
|
216
|
+
revisions: z.boolean().optional(),
|
|
217
|
+
}).strict();
|
|
218
|
+
// A Git username OpenChamber puts before the token in an HTTPS credential.
|
|
219
|
+
const FIXED_USERNAME = /^[A-Za-z0-9._-]{1,64}$/;
|
|
220
|
+
const urlTemplatesSchema = z.array(z.string().trim().min(1)).max(SOURCE_CONTROL_URL_TEMPLATES_MAX);
|
|
221
|
+
const sourceControlSchema = z.object({
|
|
222
|
+
wire: z.literal(1),
|
|
223
|
+
label: literalSchema,
|
|
224
|
+
terminology: z.object({
|
|
225
|
+
changeRequest: z.enum(['pullRequest', 'mergeRequest', 'change']),
|
|
226
|
+
ci: z.enum(['checks', 'pipeline']),
|
|
227
|
+
}).strict(),
|
|
228
|
+
referencePrefix: z.object({ changeRequest: prefixSchema, issue: prefixSchema }).strict(),
|
|
229
|
+
repositoryPath: z.enum(['owner/name', 'namespace/name']),
|
|
230
|
+
instances: z.object({
|
|
231
|
+
defaultInstances: z.array(z.string().trim().refine(isHttpsOrigin, { message: 'must be an https origin with no path, like https://git.example.com' }))
|
|
232
|
+
.max(SOURCE_CONTROL_DEFAULT_INSTANCES_MAX)
|
|
233
|
+
.refine((origins) => new Set(origins).size === origins.length, { message: 'default instances must be unique' }),
|
|
234
|
+
custom: z.boolean(),
|
|
235
|
+
allowLoopbackHttp: z.boolean(),
|
|
236
|
+
allowPort: z.boolean(),
|
|
237
|
+
placeholder: z.string().trim().max(256),
|
|
238
|
+
}).strict().refine((instances) => instances.custom || instances.defaultInstances.length > 0, { message: 'without custom instances, defaultInstances names at least one' }),
|
|
239
|
+
capabilities: sourceControlCapabilitiesSchema,
|
|
240
|
+
token: z.object({
|
|
241
|
+
header: z.enum(['authorization', 'private-token']),
|
|
242
|
+
scheme: z.enum(['bearer', 'token', 'basic', 'none']),
|
|
243
|
+
username: z.boolean().optional(),
|
|
244
|
+
scopes: z.array(z.string().trim().min(1).max(64)).max(32),
|
|
245
|
+
createPath: z.string().trim().max(256).refine(isSafeApiPath, { message: 'must be a path on the instance starting with "/", with no "." or ".." segment' }).optional(),
|
|
246
|
+
}).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(
|
|
247
|
+
// Basic sends `login:token`, so Settings must ask for the login; no other scheme uses one.
|
|
248
|
+
(token) => (token.scheme === 'basic') === (token.username === true), { message: 'the basic scheme needs username: true, and username is only for the basic scheme' }),
|
|
249
|
+
transport: z.object({
|
|
250
|
+
httpsUsername: z.union([
|
|
251
|
+
z.object({ kind: z.literal('login') }).strict(),
|
|
252
|
+
z.object({ kind: z.literal('fixed'), value: z.string().regex(FIXED_USERNAME) }).strict(),
|
|
253
|
+
]),
|
|
254
|
+
appendDotGitToHttpsClone: z.boolean(),
|
|
255
|
+
}).strict(),
|
|
256
|
+
limits: z.object({
|
|
257
|
+
commentMaxLength: z.number().int().min(1).max(1_000_000),
|
|
258
|
+
reviewerId: z.enum(['numeric', 'login']),
|
|
259
|
+
labelsMax: z.number().int().min(0).max(100),
|
|
260
|
+
reviewersMax: z.number().int().min(0).max(50),
|
|
261
|
+
}).strict(),
|
|
262
|
+
trackedItems: z.object({ batchLimit: z.number().int().min(1).max(50) }).strict(),
|
|
263
|
+
presentation: z.object({
|
|
264
|
+
abbreviation: z.string().trim().min(1).max(8),
|
|
265
|
+
promptNoun: literalSchema,
|
|
266
|
+
sharedNumberSpace: z.boolean(),
|
|
267
|
+
iconPath: z.string().max(8192).regex(SVG_PATH_DATA).optional(),
|
|
268
|
+
urls: z.object({ changeRequest: urlTemplatesSchema, issue: urlTemplatesSchema }).strict(),
|
|
269
|
+
features: z.object({
|
|
270
|
+
authorCanReview: z.boolean().default(false),
|
|
271
|
+
issueCloseReason: z.boolean().default(false),
|
|
272
|
+
submitRequirements: z.boolean().optional(),
|
|
273
|
+
}).strict(),
|
|
274
|
+
changeRequestTemplates: z.array(z.string().trim().max(256).refine(isRepositoryFilePath, {
|
|
275
|
+
message: 'must be a file path inside the repository, like .gitea/pull_request_template.md, with no "." or ".." segment',
|
|
276
|
+
})).max(8),
|
|
277
|
+
}).strict(),
|
|
278
|
+
}).strict().superRefine((value, ctx) => {
|
|
279
|
+
for (const kind of ['changeRequest', 'issue']) {
|
|
280
|
+
value.presentation.urls[kind].forEach((template, index) => {
|
|
281
|
+
if (isUrlTemplate(template, value.repositoryPath))
|
|
282
|
+
return;
|
|
283
|
+
ctx.addIssue({
|
|
284
|
+
code: 'custom', path: ['presentation', 'urls', kind, index],
|
|
285
|
+
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.`,
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
// Change request patterns are matched first, so an issue template that is
|
|
290
|
+
// also a change request template would read every issue link as one.
|
|
291
|
+
const changeRequestTemplates = new Set(value.presentation.urls.changeRequest);
|
|
292
|
+
value.presentation.urls.issue.forEach((template, index) => {
|
|
293
|
+
if (!changeRequestTemplates.has(template))
|
|
294
|
+
return;
|
|
295
|
+
ctx.addIssue({
|
|
296
|
+
code: 'custom', path: ['presentation', 'urls', 'issue', index],
|
|
297
|
+
message: 'is also a changeRequest template; a link names either a change request or an issue.',
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
});
|
|
159
301
|
const uniqueBy = (items, key) => (new Set(items.map(key)).size === items.length);
|
|
160
302
|
// `roles` only means something on a message action and `payload` only on a
|
|
161
303
|
// session action; a dead field is a misconfiguration and fails closed.
|
|
@@ -229,6 +371,7 @@ const contributesSchema = z.object({
|
|
|
229
371
|
commands: commandsSchema.optional(),
|
|
230
372
|
tools: toolsSchema.optional(),
|
|
231
373
|
fileEditors: fileEditorsSchema.optional(),
|
|
374
|
+
sourceControl: sourceControlSchema.optional(),
|
|
232
375
|
});
|
|
233
376
|
/**
|
|
234
377
|
* Contributions that need either the panel or background execution frame.
|
|
@@ -264,6 +407,23 @@ export const openChamberManifestSchema = z.object({
|
|
|
264
407
|
openchamber: z.string().trim().regex(OPENCHAMBER_ENGINE_PATTERN),
|
|
265
408
|
}).strict().optional(),
|
|
266
409
|
contributes: contributesSchema.superRefine((contributes, ctx) => {
|
|
410
|
+
// The Git host and the role that serves it come together, and the host
|
|
411
|
+
// names it `ext-<panel.id>` within the 32-character provider id.
|
|
412
|
+
const providesSourceControl = contributes.service?.provides?.includes('source-control') === true;
|
|
413
|
+
if (providesSourceControl !== (contributes.sourceControl !== undefined)) {
|
|
414
|
+
ctx.addIssue({
|
|
415
|
+
code: 'custom', path: ['service', 'provides'],
|
|
416
|
+
message: 'service.provides "source-control" and contributes.sourceControl come together: declare both or neither.',
|
|
417
|
+
});
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
if (providesSourceControl && contributes.panel.id.length > SOURCE_CONTROL_GUEST_ID_MAX) {
|
|
421
|
+
ctx.addIssue({
|
|
422
|
+
code: 'custom', path: ['service', 'provides'],
|
|
423
|
+
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>".`,
|
|
424
|
+
});
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
267
427
|
// Docking only means something for a page beside a shared surface.
|
|
268
428
|
const docked = contributes.panel.dock !== undefined || contributes.panel.size !== undefined;
|
|
269
429
|
if (docked && !(contributes.service?.surface && hasGuestPage(contributes))) {
|
|
@@ -343,6 +503,9 @@ const fail = (code, message) => ({
|
|
|
343
503
|
code,
|
|
344
504
|
message,
|
|
345
505
|
});
|
|
506
|
+
/** `contributes.sourceControl.instances.defaultInstances[0]`, relative to the `openchamber` block. */
|
|
507
|
+
const fieldPath = (issuePath) => (issuePath[0] === 'openchamber' ? issuePath.slice(1) : issuePath)
|
|
508
|
+
.reduce((field, segment) => (Number.isInteger(segment) ? `${field}[${String(segment)}]` : `${field ? `${field}.` : ''}${String(segment)}`), '');
|
|
346
509
|
const failureFromIssue = (issue) => {
|
|
347
510
|
// Paths are reported relative to the `openchamber` block whether the
|
|
348
511
|
// document was a bare manifest or a package.json envelope.
|
|
@@ -427,8 +590,17 @@ const failureFromIssue = (issue) => {
|
|
|
427
590
|
if (path.startsWith('contributes.integration')) {
|
|
428
591
|
return fail('invalid-integration', 'contributes.integration needs a name, description, and oauth, token, or host.');
|
|
429
592
|
}
|
|
593
|
+
if (path.startsWith('contributes.sourceControl')) {
|
|
594
|
+
// The field is named either way: a refine's message alone does not say which entry failed.
|
|
595
|
+
const field = fieldPath(issue.path);
|
|
596
|
+
return fail('invalid-service', issue.code === 'custom'
|
|
597
|
+
? `${field}: ${issue.message}`
|
|
598
|
+
: `${field} is invalid: see "contributes.sourceControl" in GUEST_SERVICES.md for every field.`);
|
|
599
|
+
}
|
|
430
600
|
if (path.startsWith('contributes.service')) {
|
|
431
|
-
return fail('invalid-service',
|
|
601
|
+
return fail('invalid-service', issue.code === 'custom'
|
|
602
|
+
? issue.message
|
|
603
|
+
: 'contributes.service needs entry, runtime "host", optional permissions, optional provides ("browser", "source-control"), and optional surface (true).');
|
|
432
604
|
}
|
|
433
605
|
return fail('missing-panel', 'contributes.panel is required.');
|
|
434
606
|
};
|
package/dist/schemas.d.ts
CHANGED
|
@@ -4,6 +4,6 @@ export { guestStorageRequestSchema, guestStorageResultSchema, guestWorkspaceSnap
|
|
|
4
4
|
export { guestStatusControlsSchema, guestStatusControlEventSchema } from './status-control-schemas.ts';
|
|
5
5
|
export { guestPopoverClosedEventSchema, guestPopoverRequestSchema } from './popover-schemas.ts';
|
|
6
6
|
export { guestMessageSchema, hostMessageSchema, parseGuestMessage, parseHostMessage, } from './protocol.ts';
|
|
7
|
-
export { browserControlActionSchema, browserProviderResultSchema } from './service-provider-schemas.ts';
|
|
7
|
+
export { browserControlActionSchema, browserProviderResultSchema, sourceControlProviderMethodSchema, sourceControlProviderSchemasV1, } from './service-provider-schemas.ts';
|
|
8
8
|
export { surfaceClipboardAnswerSchema, surfaceControllerSchema, surfaceHostMessageSchema, surfaceInputEventSchema, surfaceResizeAnswerSchema, surfaceViewerMessageSchema, } from './service-surface-schemas.ts';
|
|
9
9
|
export type { SurfaceHostMessage, SurfaceViewerMessage } from './service-surface-schemas.ts';
|
package/dist/schemas.js
CHANGED
|
@@ -6,5 +6,5 @@ export { guestStorageRequestSchema, guestStorageResultSchema, guestWorkspaceSnap
|
|
|
6
6
|
export { guestStatusControlsSchema, guestStatusControlEventSchema } from './status-control-schemas.js';
|
|
7
7
|
export { guestPopoverClosedEventSchema, guestPopoverRequestSchema } from './popover-schemas.js';
|
|
8
8
|
export { guestMessageSchema, hostMessageSchema, parseGuestMessage, parseHostMessage, } from './protocol.js';
|
|
9
|
-
export { browserControlActionSchema, browserProviderResultSchema } from './service-provider-schemas.js';
|
|
9
|
+
export { browserControlActionSchema, browserProviderResultSchema, sourceControlProviderMethodSchema, sourceControlProviderSchemasV1, } from './service-provider-schemas.js';
|
|
10
10
|
export { surfaceClipboardAnswerSchema, surfaceControllerSchema, surfaceHostMessageSchema, surfaceInputEventSchema, surfaceResizeAnswerSchema, surfaceViewerMessageSchema, } from './service-surface-schemas.js';
|