@capybara-hq/kernel 0.1.0-rc.14 → 0.1.0-rc.16
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/dist/admin/admin.schemas.d.ts +5 -13
- package/dist/catalog/admin-catalog.controller.d.ts +27 -4
- package/dist/catalog/admin-catalog.controller.js +53 -5
- package/dist/catalog/appearance.schemas.d.ts +10 -0
- package/dist/catalog/appearance.schemas.js +20 -0
- package/dist/catalog/appearance.service.d.ts +105 -0
- package/dist/catalog/appearance.service.js +306 -0
- package/dist/catalog/catalog-import.module.js +3 -0
- package/dist/catalog/catalog-items.d.ts +11 -0
- package/dist/catalog/catalog-items.js +21 -0
- package/dist/catalog/catalog-scope.service.d.ts +7 -0
- package/dist/catalog/catalog-scope.service.js +55 -15
- package/dist/catalog/catalog.service.js +10 -2
- package/dist/catalog/cli.js +3 -0
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/catalog/org-extension.controller.d.ts +27 -7
- package/dist/catalog/org-extension.controller.js +48 -6
- package/dist/catalog/plugin-import.d.ts +28 -4
- package/dist/catalog/plugin-import.js +95 -19
- package/dist/catalog/plugin-package.d.ts +4 -2
- package/dist/catalog/plugin-package.js +29 -3
- package/dist/channels/channels.schemas.d.ts +4 -0
- package/dist/channels/channels.schemas.js +2 -0
- package/dist/channels/connections.controller.d.ts +5 -3
- package/dist/channels/connections.controller.js +6 -4
- package/dist/channels/connections.service.d.ts +26 -0
- package/dist/channels/connections.service.js +79 -6
- package/dist/client/client-access.service.d.ts +101 -0
- package/dist/client/client-access.service.js +294 -0
- package/dist/client/client-builds.service.d.ts +109 -0
- package/dist/client/client-builds.service.js +229 -0
- package/dist/client/client-delivery.controller.d.ts +25 -0
- package/dist/client/client-delivery.controller.js +66 -0
- package/dist/client/client-files.d.ts +48 -0
- package/dist/client/client-files.js +136 -0
- package/dist/client/client-reports.cron.d.ts +13 -0
- package/dist/client/client-reports.cron.js +42 -0
- package/dist/client/client-reports.service.d.ts +30 -0
- package/dist/client/client-reports.service.js +102 -0
- package/dist/client/client.controller.d.ts +45 -0
- package/dist/client/client.controller.js +121 -0
- package/dist/client/client.module.d.ts +7 -0
- package/dist/client/client.module.js +26 -0
- package/dist/client/client.schemas.d.ts +55 -0
- package/dist/client/client.schemas.js +27 -0
- package/dist/ctx/create-ctx.d.ts +4 -3
- package/dist/ctx/create-ctx.js +4 -3
- package/dist/db/kernel-schema.d.ts +47 -0
- package/dist/errors.d.ts +29 -0
- package/dist/errors.js +29 -0
- package/dist/extensions/admin-ops.d.ts +4 -1
- package/dist/extensions/admin-ops.js +22 -1
- package/dist/extensions/admin-view.d.ts +8 -0
- package/dist/extensions/admin-view.js +78 -0
- package/dist/extensions/blobs.d.ts +14 -1
- package/dist/extensions/blobs.js +48 -5
- package/dist/extensions/guide-places.d.ts +3 -1
- package/dist/extensions/guide-places.js +1 -1
- package/dist/extensions/host.d.ts +6 -1
- package/dist/extensions/host.js +20 -9
- package/dist/extensions/package.d.ts +4 -3
- package/dist/extensions/package.js +5 -1
- package/dist/extensions/permission-set.d.ts +19 -3
- package/dist/extensions/permission-set.js +19 -2
- package/dist/extensions/permission-view.d.ts +38 -7
- package/dist/extensions/permission-view.js +23 -7
- package/dist/extensions/sources.d.ts +6 -0
- package/dist/extensions/sources.js +2 -0
- package/dist/extensions/state.module.js +9 -1
- package/dist/http/api.module.js +10 -0
- package/dist/identity/settings.d.ts +41 -26
- package/dist/identity/settings.js +87 -4
- package/dist/infra/throttler/named-throttlers.d.ts +4 -0
- package/dist/infra/throttler/named-throttlers.js +14 -0
- package/dist/kernel.module.js +3 -0
- package/dist/orgs/settings.service.d.ts +2 -26
- package/dist/platform/exclusions.js +27 -1
- package/dist/platform/ops/admin.ops.js +17 -2
- package/dist/platform/ops/catalog-admin.ops.d.ts +19 -1
- package/dist/platform/ops/catalog-admin.ops.js +85 -2
- package/dist/platform/ops/org-plugins.ops.d.ts +7 -1
- package/dist/platform/ops/org-plugins.ops.js +63 -4
- package/dist/platform/ops/org-settings.ops.js +2 -2
- package/dist/platform/ops/orgs.ops.js +1 -0
- package/dist/platform/ops/ui.ops.js +1 -1
- package/dist/platform/platform-ops.module.js +2 -0
- package/dist/platform/route-schemas.js +3 -0
- package/dist/roles/worker.module.js +4 -0
- package/dist/themes/render-css.d.ts +2 -1
- package/dist/themes/render-css.js +5 -2
- package/dist/themes/theme-delivery.service.d.ts +4 -2
- package/dist/themes/theme-delivery.service.js +8 -3
- package/dist/themes/theme-state.d.ts +28 -7
- package/dist/themes/theme-state.js +24 -6
- package/dist/themes/themes-public.controller.d.ts +7 -2
- package/dist/themes/themes-public.controller.js +11 -5
- package/dist/themes/themes.service.js +2 -2
- package/dist/tools/pipeline.d.ts +3 -2
- package/dist/ui/arrangement.d.ts +41 -50
- package/dist/ui/arrangement.js +88 -15
- package/dist/ui/registry.d.ts +13 -3
- package/dist/ui/registry.js +15 -4
- package/dist/ui/ui.schemas.d.ts +4 -0
- package/dist/ui/views.d.ts +36 -9
- package/dist/ui/views.js +58 -28
- package/package.json +3 -3
- package/src/migrations/0107_extension_client_builds.sql +50 -0
|
@@ -5,6 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
7
|
import { Module } from "@nestjs/common";
|
|
8
|
+
import { ClientModule } from "../client/client.module.js";
|
|
8
9
|
import { ExtensionsModule } from "../extensions/extensions.module.js";
|
|
9
10
|
import { SuperAdminLockModule } from "../identity/super-admin.js";
|
|
10
11
|
import { QueueModule } from "../infra/queue/queue.module.js";
|
|
@@ -34,6 +35,8 @@ CatalogImportModule = __decorate([
|
|
|
34
35
|
// an org upload re-checks the owner org's admin (`PluginImport`, ruling P3-7)
|
|
35
36
|
OrgLockModule,
|
|
36
37
|
StorageModule,
|
|
38
|
+
// the client build prefix goes with an expired org upload's package (`PluginImport`)
|
|
39
|
+
ClientModule,
|
|
37
40
|
// a failed post-import platform sync enqueues the migration job (`PluginImport.syncPlatform`)
|
|
38
41
|
QueueModule.registerQueue(MAINTENANCE_QUEUE_NAME),
|
|
39
42
|
],
|
|
@@ -37,6 +37,12 @@ export interface CatalogListing {
|
|
|
37
37
|
description: string;
|
|
38
38
|
displayName: LocalizedName | null;
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* A stored manifest with `suggestedAppearance.replacements` back in plugin.json order: `order` is
|
|
42
|
+
* the version's `appearance_order` (the jsonb column does not keep key order); keys it does not
|
|
43
|
+
* name follow in stored order. Anything else is returned as is.
|
|
44
|
+
*/
|
|
45
|
+
export declare function inAuthorOrder(manifest: unknown, order: readonly string[] | null): unknown;
|
|
40
46
|
/** Input of {@link writeCatalogVersion}. */
|
|
41
47
|
export interface CatalogVersionWrite {
|
|
42
48
|
/**
|
|
@@ -60,6 +66,11 @@ export interface CatalogVersionWrite {
|
|
|
60
66
|
contentDigest: string;
|
|
61
67
|
/** The normalized plugin.json. */
|
|
62
68
|
manifest: PluginManifest;
|
|
69
|
+
/**
|
|
70
|
+
* The keys of `manifest.suggestedAppearance.replacements` in plugin.json order (the manifest may
|
|
71
|
+
* have passed through jsonb, which does not keep key order); null without replacements.
|
|
72
|
+
*/
|
|
73
|
+
appearanceOrder: string[] | null;
|
|
63
74
|
listing: CatalogListing;
|
|
64
75
|
/** The id of the item to create when there is none yet (the one the storage prefix used). */
|
|
65
76
|
newItemId: string;
|
|
@@ -69,6 +69,26 @@ export function permissionsMismatch(declared, actual) {
|
|
|
69
69
|
const extra = [...want].filter((k) => !have.has(k)).sort();
|
|
70
70
|
return missing.length > 0 || extra.length > 0 ? { missing, extra } : null;
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* A stored manifest with `suggestedAppearance.replacements` back in plugin.json order: `order` is
|
|
74
|
+
* the version's `appearance_order` (the jsonb column does not keep key order); keys it does not
|
|
75
|
+
* name follow in stored order. Anything else is returned as is.
|
|
76
|
+
*/
|
|
77
|
+
export function inAuthorOrder(manifest, order) {
|
|
78
|
+
const m = manifest;
|
|
79
|
+
const r = m?.suggestedAppearance?.replacements;
|
|
80
|
+
if (order === null || r === null || typeof r !== "object" || Array.isArray(r))
|
|
81
|
+
return manifest;
|
|
82
|
+
const map = r;
|
|
83
|
+
const keys = new Set([...order.filter((k) => Object.hasOwn(map, k)), ...Object.keys(map)]);
|
|
84
|
+
return {
|
|
85
|
+
...m,
|
|
86
|
+
suggestedAppearance: {
|
|
87
|
+
...m?.suggestedAppearance,
|
|
88
|
+
replacements: Object.fromEntries([...keys].map((k) => [k, map[k]])),
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
72
92
|
/**
|
|
73
93
|
* The live item of a name, with the source identity of its latest version
|
|
74
94
|
* ({@link sourceIdentity}; null when there is none).
|
|
@@ -274,6 +294,7 @@ export async function writeCatalogVersion(db, w) {
|
|
|
274
294
|
item_id: id,
|
|
275
295
|
version,
|
|
276
296
|
manifest: JSON.stringify(w.manifest),
|
|
297
|
+
appearance_order: w.appearanceOrder,
|
|
277
298
|
source: w.source.source,
|
|
278
299
|
source_ref: w.source.ref,
|
|
279
300
|
integrity: w.source.integrity ?? null,
|
|
@@ -166,6 +166,13 @@ export declare class CatalogScopeService {
|
|
|
166
166
|
private confirmLocked;
|
|
167
167
|
/** The item is live and its first version confirmed (no longer awaiting). */
|
|
168
168
|
private confirmed;
|
|
169
|
+
/**
|
|
170
|
+
* The error a failed confirmation step answers with: 409 `catalog.item_removed` when the item is
|
|
171
|
+
* gone or soft-deleted by now, whichever step failed (the removal's cleanup job may take the
|
|
172
|
+
* extension out under the confirmation, so a step answers `extension.not_found`), the original
|
|
173
|
+
* error logged; else (or when the item cannot be read) the original error.
|
|
174
|
+
*/
|
|
175
|
+
private removalOr;
|
|
169
176
|
/** Clears the pending state of a version an earlier confirmation applied already. */
|
|
170
177
|
private clearPending;
|
|
171
178
|
/**
|
|
@@ -335,26 +335,23 @@ let CatalogScopeService = class CatalogScopeService {
|
|
|
335
335
|
// A new version: applied (unless an earlier confirmation applied it and stopped before the
|
|
336
336
|
// approval), then its permissions approved; an approval that fails puts the pending state
|
|
337
337
|
// back, so the version shows in 待确认 again (ruling P3-37)
|
|
338
|
-
|
|
339
|
-
|
|
338
|
+
try {
|
|
339
|
+
if (item.platform_synced_version !== version) {
|
|
340
340
|
await this.updater.updateToPendingLocked(actor, itemId, version, opts);
|
|
341
341
|
}
|
|
342
|
+
else {
|
|
343
|
+
await this.clearPending(itemId, version);
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
await this.approvePackage(actor, name, sha, reviewHash, opts);
|
|
347
|
+
}
|
|
342
348
|
catch (e) {
|
|
343
|
-
|
|
344
|
-
throw itemRemovedError(name);
|
|
345
|
-
}
|
|
349
|
+
await this.restorePending(itemId, pending);
|
|
346
350
|
throw e;
|
|
347
351
|
}
|
|
348
352
|
}
|
|
349
|
-
else {
|
|
350
|
-
await this.clearPending(itemId, version);
|
|
351
|
-
}
|
|
352
|
-
try {
|
|
353
|
-
await this.approvePackage(actor, name, sha, reviewHash, opts);
|
|
354
|
-
}
|
|
355
353
|
catch (e) {
|
|
356
|
-
await this.
|
|
357
|
-
throw e;
|
|
354
|
+
throw await this.removalOr(e, itemId, name);
|
|
358
355
|
}
|
|
359
356
|
return null;
|
|
360
357
|
}
|
|
@@ -362,7 +359,10 @@ let CatalogScopeService = class CatalogScopeService {
|
|
|
362
359
|
let installedHere = false;
|
|
363
360
|
try {
|
|
364
361
|
if (install?.sha256 !== sha) {
|
|
365
|
-
await this.ops.install(name, sha, actor, opts.actorTokenId, {
|
|
362
|
+
await this.ops.install(name, sha, actor, opts.actorTokenId, {
|
|
363
|
+
catalogItemId: itemId,
|
|
364
|
+
liveItem: true,
|
|
365
|
+
});
|
|
366
366
|
installedHere = install === undefined;
|
|
367
367
|
}
|
|
368
368
|
await this.stillPending(itemId, name, version);
|
|
@@ -377,7 +377,7 @@ let CatalogScopeService = class CatalogScopeService {
|
|
|
377
377
|
if (installedHere && !(await this.confirmed(itemId))) {
|
|
378
378
|
await this.takeOut(itemId, name, actor, opts);
|
|
379
379
|
}
|
|
380
|
-
throw e;
|
|
380
|
+
throw await this.removalOr(e, itemId, name);
|
|
381
381
|
}
|
|
382
382
|
return version;
|
|
383
383
|
}
|
|
@@ -390,6 +390,46 @@ let CatalogScopeService = class CatalogScopeService {
|
|
|
390
390
|
.executeTakeFirst();
|
|
391
391
|
return row !== undefined && row.deleted_at === null && !row.awaiting_confirmation;
|
|
392
392
|
}
|
|
393
|
+
/**
|
|
394
|
+
* The error a failed confirmation step answers with: 409 `catalog.item_removed` when the item is
|
|
395
|
+
* gone or soft-deleted by now, whichever step failed (the removal's cleanup job may take the
|
|
396
|
+
* extension out under the confirmation, so a step answers `extension.not_found`), the original
|
|
397
|
+
* error logged; else (or when the item cannot be read) the original error.
|
|
398
|
+
*/
|
|
399
|
+
async removalOr(e, itemId, name) {
|
|
400
|
+
if (e instanceof ApiError && e.code === "catalog.item_removed")
|
|
401
|
+
return e;
|
|
402
|
+
let removed;
|
|
403
|
+
try {
|
|
404
|
+
const row = await this.db
|
|
405
|
+
.selectFrom("catalog_items")
|
|
406
|
+
.select("deleted_at")
|
|
407
|
+
.where("id", "=", itemId)
|
|
408
|
+
.executeTakeFirst();
|
|
409
|
+
removed = row === undefined || row.deleted_at !== null;
|
|
410
|
+
}
|
|
411
|
+
catch (err) {
|
|
412
|
+
this.log.warn("cannot read the item of a failed confirmation", { itemId, err });
|
|
413
|
+
return e;
|
|
414
|
+
}
|
|
415
|
+
if (!removed)
|
|
416
|
+
return e;
|
|
417
|
+
if (e instanceof ApiError) {
|
|
418
|
+
this.log.info("a confirmation step failed after its item was removed", {
|
|
419
|
+
itemId,
|
|
420
|
+
name,
|
|
421
|
+
code: e.code,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
this.log.warn("a confirmation step failed after its item was removed", {
|
|
426
|
+
itemId,
|
|
427
|
+
name,
|
|
428
|
+
err: e,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
return itemRemovedError(name);
|
|
432
|
+
}
|
|
393
433
|
/** Clears the pending state of a version an earlier confirmation applied already. */
|
|
394
434
|
async clearPending(itemId, version) {
|
|
395
435
|
await this.db
|
|
@@ -22,7 +22,7 @@ import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
|
22
22
|
import { Transactional } from "../infra/database/transactional.decorator.js";
|
|
23
23
|
import { sameRepository } from "../packages/provenance.js";
|
|
24
24
|
import { assertNotPlatformDefault } from "../themes/theme-usage.js";
|
|
25
|
-
import { countInstalls, itemConfirmations, itemScopeFacts, itemView, marketplaceLatest, versionUsable, } from "./catalog-items.js";
|
|
25
|
+
import { countInstalls, inAuthorOrder, itemConfirmations, itemScopeFacts, itemView, marketplaceLatest, versionUsable, } from "./catalog-items.js";
|
|
26
26
|
import { expandGitUrl, GitSourceError, gitErrorDetails, normalGitPath, readGitTreeAndOids, resolveGitCommit, } from "./git-source.js";
|
|
27
27
|
import { fetchMarketplaceArchive, marketplaceNetPolicy, marketplaceUrlProblem, } from "./marketplace-fetch.js";
|
|
28
28
|
import { assertEntryNotDeprecated, entryCompatible, entrySourceHost, HOST_VERSIONS, hostAllowed, loadMarketplaceEntry, MarketplacesService, pluginNameFrom, } from "./marketplaces.service.js";
|
|
@@ -448,7 +448,7 @@ let CatalogService = class CatalogService {
|
|
|
448
448
|
integrity: v.integrity,
|
|
449
449
|
contentDigest: v.content_digest,
|
|
450
450
|
sourceDetail: v.source_detail ?? null,
|
|
451
|
-
manifest: v.manifest,
|
|
451
|
+
manifest: inAuthorOrder(v.manifest, v.appearance_order),
|
|
452
452
|
files: v.files,
|
|
453
453
|
size: Number(v.size),
|
|
454
454
|
extensionSha256: v.extension_sha256,
|
|
@@ -487,6 +487,14 @@ let CatalogService = class CatalogService {
|
|
|
487
487
|
}
|
|
488
488
|
async writeRemove(actor, itemId, opts) {
|
|
489
489
|
await this.adminLock.lockAndAuthorize(actor);
|
|
490
|
+
// The item row before the install lock the check below takes (the global lock order: the
|
|
491
|
+
// item, then the install lock, ruling P3-50; also the item before its theme rows in removeIn)
|
|
492
|
+
await this.db
|
|
493
|
+
.selectFrom("catalog_items")
|
|
494
|
+
.select("id")
|
|
495
|
+
.where("id", "=", itemId)
|
|
496
|
+
.forUpdate()
|
|
497
|
+
.execute();
|
|
490
498
|
await this.assertExtensionDisabled(this.db, itemId, true);
|
|
491
499
|
if ((await this.removeIn(actor, itemId, opts)) === null)
|
|
492
500
|
throw notFound();
|
package/dist/catalog/cli.js
CHANGED
|
@@ -2,6 +2,7 @@ import { mkdtemp, rm } from "node:fs/promises";
|
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { OBJECT_STORE_EXTENSION_POINT, } from "@capybara-hq/plugin-sdk";
|
|
5
|
+
import { ClientBuilds } from "../client/client-builds.service.js";
|
|
5
6
|
import { ApiError } from "../errors.js";
|
|
6
7
|
import { planApprovals, snapshotCovers } from "../extensions/approvals.js";
|
|
7
8
|
import { openExtensionCli } from "../extensions/cli.js";
|
|
@@ -178,6 +179,8 @@ export async function openCatalogCli(config, log, opts = {}) {
|
|
|
178
179
|
// inspection (the registry), marketplace imports are not offered by the CLI.
|
|
179
180
|
const imports = cli.app.get(PluginImport);
|
|
180
181
|
imports.useCliStorage(storage);
|
|
182
|
+
// and so do the client files of a package with client code (stored with the package)
|
|
183
|
+
cli.app.get(ClientBuilds).useCliStorage(storage);
|
|
181
184
|
const catalog = cli.app.get(CatalogService);
|
|
182
185
|
const db = cli.app.get(KernelDb);
|
|
183
186
|
const redis = cli.app.get(REDIS);
|
|
@@ -188,7 +188,7 @@ export declare function headsToResolve(entries: readonly MarketplaceEntry[], imp
|
|
|
188
188
|
/** The host versions (an entry's `minKernel` is judged against them). */
|
|
189
189
|
export declare const HOST_VERSIONS: {
|
|
190
190
|
readonly kernel: string;
|
|
191
|
-
readonly sdk: "0.1.0-rc.
|
|
191
|
+
readonly sdk: "0.1.0-rc.12";
|
|
192
192
|
};
|
|
193
193
|
/** Whether this deployment satisfies the entry's `minKernel`. */
|
|
194
194
|
export declare const entryCompatible: (e: Pick<MarketplaceEntry, "minKernel">) => boolean;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { LocalizedName } from "@capybara-hq/plugin-sdk";
|
|
1
|
+
import type { LocalizedName, SuggestedAppearance } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { type ClientBuildSummary, ClientBuilds } from "../client/client-builds.service.js";
|
|
3
4
|
import { ExtensionDisplayNames } from "../extensions/display-name.js";
|
|
4
5
|
import { OrgExtensionConfigs, type OrgExtensionView } from "../extensions/org-config.js";
|
|
5
6
|
import { OrgOverrides, type OrgOverrideView } from "../extensions/org-overrides.js";
|
|
@@ -7,6 +8,8 @@ import { PluginIcons } from "../extensions/plugin-icons.js";
|
|
|
7
8
|
import { type Principal } from "../identity/principal.js";
|
|
8
9
|
import { type OrgContext } from "../orgs/org.guard.js";
|
|
9
10
|
import { type OrgToolSwitch, OrgToolSwitches, type OrgToolSwitchResult } from "../tools/org-tool-switches.js";
|
|
11
|
+
import { AppearanceApplyBody } from "./appearance.schemas.js";
|
|
12
|
+
import { type AppearanceResult, AppearanceService } from "./appearance.service.js";
|
|
10
13
|
import { OrgExtensionParams as ExtensionParams, OrgExtensionToolParams as ToolParams, OrgToolSwitchPut as ToolSwitchPut } from "./org-extension.schemas.js";
|
|
11
14
|
import { OrgExtensionSwitch } from "./org-extension-switch.js";
|
|
12
15
|
import type { UninstallPreview } from "./plugin-install.js";
|
|
@@ -31,20 +34,26 @@ declare const OverridesTake: z.ZodObject<{
|
|
|
31
34
|
/**
|
|
32
35
|
* One extension of the org list: the org view plus the plugin's display name, the URL of its
|
|
33
36
|
* plugin's icon on the org's catalog route (null without one or when the org may not see the
|
|
34
|
-
* plugin's item)
|
|
35
|
-
* only, also for members)
|
|
37
|
+
* plugin's item), the extension points it provides (`channel@1`, `web-search@1`, … the names
|
|
38
|
+
* only, also for members), the plugin's suggested appearance (null without one; the page offers
|
|
39
|
+
* 应用此插件的外观 to org admins when set, client extensions spec §6.5) and the client build of the
|
|
40
|
+
* running package (files with sizes, total, client API version; null without one: the permission
|
|
41
|
+
* review's 客户端代码 group, spec §5.4).
|
|
36
42
|
*/
|
|
37
43
|
export type OrgExtensionItem = OrgExtensionView & {
|
|
38
44
|
displayName: LocalizedName | null;
|
|
39
45
|
icon: string | null;
|
|
40
46
|
provides: string[];
|
|
47
|
+
suggestedAppearance: SuggestedAppearance | null;
|
|
48
|
+
client: ClientBuildSummary | null;
|
|
41
49
|
};
|
|
42
50
|
/**
|
|
43
51
|
* What a member sees of an extension: its state and the points it provides (names only, what the
|
|
44
|
-
* 扩展 page sorts by), never the org config, the secrets' state, the config schema
|
|
45
|
-
* permission list
|
|
52
|
+
* 扩展 page sorts by), never the org config, the secrets' state, the config schema, the
|
|
53
|
+
* permission list, the suggested appearance (members cannot apply it) or the client build (part of
|
|
54
|
+
* the permission review).
|
|
46
55
|
*/
|
|
47
|
-
export type OrgExtensionMemberItem = Omit<OrgExtensionItem, "config" | "secrets" | "orgConfigSchema" | "permissions">;
|
|
56
|
+
export type OrgExtensionMemberItem = Omit<OrgExtensionItem, "config" | "secrets" | "orgConfigSchema" | "permissions" | "suggestedAppearance" | "client">;
|
|
48
57
|
/**
|
|
49
58
|
* Org-level extension enablement and config (`/api/v1/orgs/:orgId/extensions`); responses echo only
|
|
50
59
|
* non-secret config and whether each secret is set.
|
|
@@ -54,7 +63,9 @@ export declare class OrgExtensionConfigsController {
|
|
|
54
63
|
private readonly displayNames;
|
|
55
64
|
private readonly extSwitch;
|
|
56
65
|
private readonly icons;
|
|
57
|
-
|
|
66
|
+
private readonly appearance;
|
|
67
|
+
private readonly clientBuilds;
|
|
68
|
+
constructor(configs: OrgExtensionConfigs, displayNames: ExtensionDisplayNames, extSwitch: OrgExtensionSwitch, icons: PluginIcons, appearance: AppearanceService, clientBuilds: ClientBuilds);
|
|
58
69
|
/**
|
|
59
70
|
* The org's extensions (also the channel plugin options: those providing `channel@1`). Members
|
|
60
71
|
* get the projection {@link OrgExtensionMemberItem}; admins the full view.
|
|
@@ -72,6 +83,15 @@ export declare class OrgExtensionConfigsController {
|
|
|
72
83
|
preview: UninstallPreview;
|
|
73
84
|
confirm: string;
|
|
74
85
|
}>;
|
|
86
|
+
/**
|
|
87
|
+
* 应用此插件的外观 (client extensions spec §6.5), for a plugin whose extension is on in the org:
|
|
88
|
+
* sets the plugin's suggested theme as the org's default theme (keeping `personal`) and each
|
|
89
|
+
* suggested replacement as the org arrangement's `use` of its slot, in one transaction.
|
|
90
|
+
* `dryRun: true` answers the same changes and writes nothing. A theme not on in the org -> 400
|
|
91
|
+
* `theme.not_available` (nothing written); a replacement not available in the org is skipped.
|
|
92
|
+
* An extension not on in the org, or a plugin without a suggestion -> 404 `extension.not_found`.
|
|
93
|
+
*/
|
|
94
|
+
applyAppearance(principal: Principal, org: OrgContext, { extension }: z.infer<typeof ExtensionParams>, body: z.infer<typeof AppearanceApplyBody>): Promise<AppearanceResult>;
|
|
75
95
|
}
|
|
76
96
|
/**
|
|
77
97
|
* The org's two-level deployment config of an extension (spec A §5.3 to §5.6): following or a copy,
|
|
@@ -9,6 +9,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
9
9
|
};
|
|
10
10
|
import { Controller, Delete, Get, HttpCode, Inject, Param, Post, Put } from "@nestjs/common";
|
|
11
11
|
import { z } from "zod";
|
|
12
|
+
import { ClientBuilds } from "../client/client-builds.service.js";
|
|
12
13
|
import { ExtensionDisplayNames } from "../extensions/display-name.js";
|
|
13
14
|
import { OrgExtensionConfigs, providesOf, } from "../extensions/org-config.js";
|
|
14
15
|
import { OrgOverrides } from "../extensions/org-overrides.js";
|
|
@@ -19,6 +20,8 @@ import { ZBody, ZParam } from "../infra/validation/zod-params.js";
|
|
|
19
20
|
import { CurrentOrg, OrgAction } from "../orgs/org.guard.js";
|
|
20
21
|
import { roleAtLeast } from "../orgs/roles.js";
|
|
21
22
|
import { OrgToolSwitches, } from "../tools/org-tool-switches.js";
|
|
23
|
+
import { AppearanceApplyBody } from "./appearance.schemas.js";
|
|
24
|
+
import { AppearanceService } from "./appearance.service.js";
|
|
22
25
|
import { OrgExtensionParams as ExtensionParams, OrgExtensionToolParams as ToolParams, OrgToolSwitchPut as ToolSwitchPut, } from "./org-extension.schemas.js";
|
|
23
26
|
import { OrgExtensionSwitch } from "./org-extension-switch.js";
|
|
24
27
|
const PutBody = z
|
|
@@ -65,6 +68,8 @@ class OrgExtensionParamsDto extends createZodDto(ExtensionParams, "OrgExtensionP
|
|
|
65
68
|
}
|
|
66
69
|
class OrgExtensionToolParamsDto extends createZodDto(ToolParams, "OrgExtensionToolParams") {
|
|
67
70
|
}
|
|
71
|
+
class OrgAppearanceApplyBody extends createZodDto(AppearanceApplyBody, "OrgAppearanceApplyBody") {
|
|
72
|
+
}
|
|
68
73
|
class OrgOverridesPutBody extends createZodDto(OverridesPut, "OrgOverridesPutBody") {
|
|
69
74
|
}
|
|
70
75
|
class OrgOverridesTakeBody extends createZodDto(OverridesTake, "OrgOverridesTakeBody") {
|
|
@@ -78,11 +83,15 @@ let OrgExtensionConfigsController = class OrgExtensionConfigsController {
|
|
|
78
83
|
displayNames;
|
|
79
84
|
extSwitch;
|
|
80
85
|
icons;
|
|
81
|
-
|
|
86
|
+
appearance;
|
|
87
|
+
clientBuilds;
|
|
88
|
+
constructor(configs, displayNames, extSwitch, icons, appearance, clientBuilds) {
|
|
82
89
|
this.configs = configs;
|
|
83
90
|
this.displayNames = displayNames;
|
|
84
91
|
this.extSwitch = extSwitch;
|
|
85
92
|
this.icons = icons;
|
|
93
|
+
this.appearance = appearance;
|
|
94
|
+
this.clientBuilds = clientBuilds;
|
|
86
95
|
}
|
|
87
96
|
/**
|
|
88
97
|
* The org's extensions (also the channel plugin options: those providing `channel@1`). Members
|
|
@@ -91,20 +100,25 @@ let OrgExtensionConfigsController = class OrgExtensionConfigsController {
|
|
|
91
100
|
async list(org) {
|
|
92
101
|
const views = await this.configs.list(org.orgId);
|
|
93
102
|
const plugins = views.map((v) => v.plugin);
|
|
94
|
-
const
|
|
103
|
+
const admin = roleAtLeast(org.role, "admin");
|
|
104
|
+
const [names, icons, suggestions, clients] = await Promise.all([
|
|
95
105
|
this.displayNames.many(plugins),
|
|
96
106
|
this.icons.many(plugins, orgIconScope(org)),
|
|
107
|
+
admin ? this.appearance.suggestions(plugins) : new Map(),
|
|
108
|
+
admin ? this.clientBuilds.installedSummaries(plugins) : new Map(),
|
|
97
109
|
]);
|
|
98
110
|
const items = views.map((v) => ({
|
|
99
111
|
...v,
|
|
100
112
|
displayName: names.get(v.plugin) ?? null,
|
|
101
113
|
icon: icons.get(v.plugin) ?? null,
|
|
102
114
|
provides: providesOf(v),
|
|
115
|
+
suggestedAppearance: suggestions.get(v.plugin) ?? null,
|
|
116
|
+
client: clients.get(v.plugin) ?? null,
|
|
103
117
|
}));
|
|
104
|
-
if (
|
|
118
|
+
if (admin)
|
|
105
119
|
return { extensions: items };
|
|
106
120
|
return {
|
|
107
|
-
extensions: items.map(({ config: _c, secrets: _s, orgConfigSchema: _o, permissions: _p, ...rest }) => rest),
|
|
121
|
+
extensions: items.map(({ config: _c, secrets: _s, orgConfigSchema: _o, permissions: _p, suggestedAppearance: _a, client: _cl, ...rest }) => rest),
|
|
108
122
|
};
|
|
109
123
|
}
|
|
110
124
|
/**
|
|
@@ -116,9 +130,11 @@ let OrgExtensionConfigsController = class OrgExtensionConfigsController {
|
|
|
116
130
|
const r = await this.extSwitch.put(org.orgId, p.account.id, extension, body, patTokenId(p));
|
|
117
131
|
if (r.kind === "preview")
|
|
118
132
|
return { preview: r.preview, confirm: r.confirm };
|
|
119
|
-
const [displayName, icons] = await Promise.all([
|
|
133
|
+
const [displayName, icons, suggestions, clients] = await Promise.all([
|
|
120
134
|
this.displayNames.get(r.view.plugin),
|
|
121
135
|
this.icons.many([r.view.plugin], orgIconScope(org)),
|
|
136
|
+
this.appearance.suggestions([r.view.plugin]),
|
|
137
|
+
this.clientBuilds.installedSummaries([r.view.plugin]),
|
|
122
138
|
]);
|
|
123
139
|
return {
|
|
124
140
|
extension: {
|
|
@@ -126,9 +142,24 @@ let OrgExtensionConfigsController = class OrgExtensionConfigsController {
|
|
|
126
142
|
displayName,
|
|
127
143
|
icon: icons.get(r.view.plugin) ?? null,
|
|
128
144
|
provides: providesOf(r.view),
|
|
145
|
+
suggestedAppearance: suggestions.get(r.view.plugin) ?? null,
|
|
146
|
+
client: clients.get(r.view.plugin) ?? null,
|
|
129
147
|
},
|
|
130
148
|
};
|
|
131
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* 应用此插件的外观 (client extensions spec §6.5), for a plugin whose extension is on in the org:
|
|
152
|
+
* sets the plugin's suggested theme as the org's default theme (keeping `personal`) and each
|
|
153
|
+
* suggested replacement as the org arrangement's `use` of its slot, in one transaction.
|
|
154
|
+
* `dryRun: true` answers the same changes and writes nothing. A theme not on in the org -> 400
|
|
155
|
+
* `theme.not_available` (nothing written); a replacement not available in the org is skipped.
|
|
156
|
+
* An extension not on in the org, or a plugin without a suggestion -> 404 `extension.not_found`.
|
|
157
|
+
*/
|
|
158
|
+
applyAppearance(principal, org, { extension }, body) {
|
|
159
|
+
const p = requireUser(principal);
|
|
160
|
+
const tokenId = patTokenId(p);
|
|
161
|
+
return this.appearance.apply({ orgId: org.orgId }, extension, { accountId: p.account.id, ...(tokenId !== undefined && { tokenId }) }, body.dryRun === true);
|
|
162
|
+
}
|
|
132
163
|
};
|
|
133
164
|
__decorate([
|
|
134
165
|
Get(),
|
|
@@ -143,12 +174,23 @@ __decorate([
|
|
|
143
174
|
__param(2, Param("extension")),
|
|
144
175
|
__param(3, ZBody(OrgExtensionPutBody))
|
|
145
176
|
], OrgExtensionConfigsController.prototype, "put", null);
|
|
177
|
+
__decorate([
|
|
178
|
+
Post(":extension/appearance"),
|
|
179
|
+
HttpCode(200),
|
|
180
|
+
OrgAction("org.update"),
|
|
181
|
+
__param(0, CurrentPrincipal()),
|
|
182
|
+
__param(1, CurrentOrg()),
|
|
183
|
+
__param(2, ZParam(OrgExtensionParamsDto)),
|
|
184
|
+
__param(3, ZBody(OrgAppearanceApplyBody))
|
|
185
|
+
], OrgExtensionConfigsController.prototype, "applyAppearance", null);
|
|
146
186
|
OrgExtensionConfigsController = __decorate([
|
|
147
187
|
Controller("api/v1/orgs/:orgId/extensions"),
|
|
148
188
|
__param(0, Inject(OrgExtensionConfigs)),
|
|
149
189
|
__param(1, Inject(ExtensionDisplayNames)),
|
|
150
190
|
__param(2, Inject(OrgExtensionSwitch)),
|
|
151
|
-
__param(3, Inject(PluginIcons))
|
|
191
|
+
__param(3, Inject(PluginIcons)),
|
|
192
|
+
__param(4, Inject(AppearanceService)),
|
|
193
|
+
__param(5, Inject(ClientBuilds))
|
|
152
194
|
], OrgExtensionConfigsController);
|
|
153
195
|
export { OrgExtensionConfigsController };
|
|
154
196
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type AgentTemplate, type AgentTemplateTier, type LocalizedName, type PluginManifest, type ThemeScheme } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import type { Queue } from "bullmq";
|
|
3
|
+
import { ClientBuilds } from "../client/client-builds.service.js";
|
|
3
4
|
import { ApiError } from "../errors.js";
|
|
4
5
|
import { ExtensionAdminOps } from "../extensions/admin-ops.js";
|
|
5
6
|
import { ExtensionAdmin } from "../extensions/admin-view.js";
|
|
@@ -103,6 +104,24 @@ export interface PluginReviewExtension {
|
|
|
103
104
|
reviewHash: string;
|
|
104
105
|
/** Sensitive permissions or migration warnings. */
|
|
105
106
|
sensitive: boolean;
|
|
107
|
+
/**
|
|
108
|
+
* The extension's client code (client extensions spec §5.4, §9.1), for the 客户端代码 group of
|
|
109
|
+
* the review: every file of `extension.client` by its manifest key with its size, checked
|
|
110
|
+
* against its integrity when the import was prepared. Absent without client code.
|
|
111
|
+
*/
|
|
112
|
+
client?: PluginReviewClient;
|
|
113
|
+
}
|
|
114
|
+
/** The client code of an extension in a review. */
|
|
115
|
+
export interface PluginReviewClient {
|
|
116
|
+
/** Entry, styles and assets in that order; `path` is the manifest key (`./dist/client/...`). */
|
|
117
|
+
files: {
|
|
118
|
+
path: string;
|
|
119
|
+
size: number;
|
|
120
|
+
}[];
|
|
121
|
+
/** Total bytes. */
|
|
122
|
+
totalBytes: number;
|
|
123
|
+
/** The client API version the bundle was built against. */
|
|
124
|
+
apiVersion: number;
|
|
106
125
|
}
|
|
107
126
|
/** One connector template in a review. */
|
|
108
127
|
export interface PluginReviewTemplate {
|
|
@@ -350,9 +369,10 @@ export interface StagedTarget {
|
|
|
350
369
|
}
|
|
351
370
|
/**
|
|
352
371
|
* The API error of a plugin package that cannot be read in a catalog path: the pre-0.1.0 format ->
|
|
353
|
-
* `catalog.legacy_format`;
|
|
354
|
-
*
|
|
355
|
-
* anything else -> `catalog.invalid_manifest` (issues as
|
|
372
|
+
* `catalog.legacy_format`; undeclared client code -> `client.undeclared`; a failing theme (themes
|
|
373
|
+
* spec §5) -> `theme.invalid` (details `[{ theme, errors }]`) or `theme.unreadable` (details
|
|
374
|
+
* `[{ theme, issues }]`, the failing pairs); anything else -> `catalog.invalid_manifest` (issues as
|
|
375
|
+
* details).
|
|
356
376
|
*/
|
|
357
377
|
export declare function catalogPackageError(e: PluginPackageError | ExtensionPackageError): ApiError;
|
|
358
378
|
export declare class PluginImport {
|
|
@@ -378,6 +398,8 @@ export declare class PluginImport {
|
|
|
378
398
|
private readonly platform;
|
|
379
399
|
/** A failed platform sync enqueues a run of the migration job here (it heals the item). */
|
|
380
400
|
private readonly maintenance;
|
|
401
|
+
/** The client build of a dropped org upload's package is deleted with it. */
|
|
402
|
+
private readonly clientBuilds;
|
|
381
403
|
/** Where plugin files go (the kernel's object storage; the CLI's own, see {@link useCliStorage}). */
|
|
382
404
|
private files;
|
|
383
405
|
/**
|
|
@@ -398,7 +420,9 @@ export declare class PluginImport {
|
|
|
398
420
|
/** The platform rows of a new item's components, written after the confirm's commit. */
|
|
399
421
|
platform: PlatformComponents,
|
|
400
422
|
/** A failed platform sync enqueues a run of the migration job here (it heals the item). */
|
|
401
|
-
maintenance: Pick<Queue, "add"
|
|
423
|
+
maintenance: Pick<Queue, "add">,
|
|
424
|
+
/** The client build of a dropped org upload's package is deleted with it. */
|
|
425
|
+
clientBuilds: ClientBuilds);
|
|
402
426
|
/** The kernel-schema handle of the current context (the transaction, else the pool). */
|
|
403
427
|
private get db();
|
|
404
428
|
/**
|