@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
|
@@ -53,19 +53,11 @@ export declare const DeploymentSettingsBody: z.ZodObject<{
|
|
|
53
53
|
uiTheme: z.ZodOptional<z.ZodObject<{
|
|
54
54
|
id: z.ZodNullable<z.ZodString>;
|
|
55
55
|
}, z.core.$strict>>;
|
|
56
|
-
uiArrangement: z.ZodOptional<z.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
"file.preview": "file.preview";
|
|
62
|
-
"org.page": "org.page";
|
|
63
|
-
"org.settings.section": "org.settings.section";
|
|
64
|
-
"platform.page": "platform.page";
|
|
65
|
-
}> & z.core.$partial, z.ZodNullable<z.ZodObject<{
|
|
66
|
-
hidden: z.ZodArray<z.ZodString>;
|
|
67
|
-
order: z.ZodArray<z.ZodString>;
|
|
68
|
-
}, z.core.$strict>>>>;
|
|
56
|
+
uiArrangement: z.ZodOptional<z.ZodType<Partial<Record<"agent.detail.section" | "conversation.composer.frame" | "conversation.empty" | "conversation.panel" | "conversation.properties.section" | "conversation.tool-result.card" | "file.preview" | "org.page" | "org.settings.section" | "platform.page" | "shell.brand" | "shell.overlay", import("../ui/arrangement.js").UiArrangementEntry | null>>, unknown, z.core.$ZodTypeInternals<Partial<Record<"agent.detail.section" | "conversation.composer.frame" | "conversation.empty" | "conversation.panel" | "conversation.properties.section" | "conversation.tool-result.card" | "file.preview" | "org.page" | "org.settings.section" | "platform.page" | "shell.brand" | "shell.overlay", import("../ui/arrangement.js").UiArrangementEntry | null>>, unknown>>>;
|
|
57
|
+
clientExtensions: z.ZodOptional<z.ZodObject<{
|
|
58
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
59
|
+
disabled: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
60
|
+
}, z.core.$strict>>;
|
|
69
61
|
}, z.core.$strict>;
|
|
70
62
|
export declare const PurposeParams: z.ZodObject<{
|
|
71
63
|
purpose: z.ZodString;
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import type { SuggestedAppearance } from "@capybara-hq/plugin-sdk";
|
|
1
2
|
import type { FastifyReply } from "fastify";
|
|
2
3
|
import { z } from "zod";
|
|
4
|
+
import { type ClientBuildSummary, ClientBuilds } from "../client/client-builds.service.js";
|
|
3
5
|
import { PluginGuides, type RenderedSetupGuide } from "../extensions/plugin-guides.js";
|
|
4
6
|
import { PluginIcons } from "../extensions/plugin-icons.js";
|
|
5
7
|
import { type Principal } from "../identity/principal.js";
|
|
6
8
|
import { AdminCatalogGuideQuery, CatalogItemsQuery, CatalogUpdateBody, CatalogUpdatePreviewBody, CatalogDisabledBody as DisabledBody, CatalogGitImportBody as GitBody, CatalogNpmImportBody as NpmBody, CatalogScopeBody as ScopeBody, CatalogScopePreviewBody as ScopePreviewBody, CatalogShownBody as ShownBody } from "./admin-catalog.schemas.js";
|
|
9
|
+
import { AppearanceApplyBody } from "./appearance.schemas.js";
|
|
10
|
+
import { type AppearanceResult, AppearanceService } from "./appearance.service.js";
|
|
7
11
|
import { type CatalogItemDetail, type CatalogItemView, CatalogService, type PermissionsMismatch } from "./catalog.service.js";
|
|
8
12
|
import { CatalogScopeService, type ScopePreview } from "./catalog-scope.service.js";
|
|
9
13
|
import { OrgCatalogService } from "./org-catalog.service.js";
|
|
@@ -39,8 +43,10 @@ export declare class AdminCatalogController {
|
|
|
39
43
|
private readonly scope;
|
|
40
44
|
private readonly updater;
|
|
41
45
|
private readonly icons;
|
|
46
|
+
private readonly appearance;
|
|
47
|
+
private readonly clientBuilds;
|
|
42
48
|
private readonly guides;
|
|
43
|
-
constructor(catalog: CatalogService, pluginImport: PluginImport, orgCatalog: OrgCatalogService, scope: CatalogScopeService, updater: PlatformPluginUpdater, icons: PluginIcons, guides: PluginGuides);
|
|
49
|
+
constructor(catalog: CatalogService, pluginImport: PluginImport, orgCatalog: OrgCatalogService, scope: CatalogScopeService, updater: PlatformPluginUpdater, icons: PluginIcons, appearance: AppearanceService, clientBuilds: ClientBuilds, guides: PluginGuides);
|
|
44
50
|
/**
|
|
45
51
|
* Prepares the import of an uploaded plugin (an npm-pack tgz as the raw body,
|
|
46
52
|
* `application/octet-stream`, at most `packages.maxBytes`): reads and checks the whole plugin
|
|
@@ -115,12 +121,29 @@ export declare class AdminCatalogController {
|
|
|
115
121
|
nextOffset: number | null;
|
|
116
122
|
}>;
|
|
117
123
|
/**
|
|
118
|
-
*
|
|
119
|
-
* `
|
|
124
|
+
* The item with its versions, plus `hasIcon` (the latest version has an icon, served by `GET
|
|
125
|
+
* items/:id/icon`) and `suggestedAppearance` (the platform version's, null without one: the page
|
|
126
|
+
* offers 应用此插件的外观 when set). Each version carries `client`: its extension package's
|
|
127
|
+
* client build (files with sizes, total, client API version; null without one), what the
|
|
128
|
+
* permission confirmation of an org's upload shows in its 客户端代码 group (client extensions spec
|
|
129
|
+
* §5.4). Unknown -> 404 `catalog.item_not_found`.
|
|
120
130
|
*/
|
|
121
|
-
get(id: string): Promise<CatalogItemDetail & {
|
|
131
|
+
get(id: string): Promise<Omit<CatalogItemDetail, "versions"> & {
|
|
132
|
+
versions: (CatalogItemDetail["versions"][number] & {
|
|
133
|
+
client: ClientBuildSummary | null;
|
|
134
|
+
})[];
|
|
122
135
|
hasIcon: boolean;
|
|
136
|
+
suggestedAppearance: SuggestedAppearance | null;
|
|
123
137
|
}>;
|
|
138
|
+
/**
|
|
139
|
+
* 应用此插件的外观 (client extensions spec §6.5): sets the plugin's suggested theme as the platform
|
|
140
|
+
* default theme and each suggested replacement as the platform arrangement's `use` of its slot, in
|
|
141
|
+
* one transaction. `dryRun: true` answers the same changes and writes nothing. A theme that cannot
|
|
142
|
+
* be the platform default -> 400 `theme.not_available` (nothing written); a replacement not
|
|
143
|
+
* available on the platform is skipped. No such item, or no suggestion -> 404
|
|
144
|
+
* `catalog.item_not_found`.
|
|
145
|
+
*/
|
|
146
|
+
applyAppearance(principal: Principal, id: string, body: z.infer<typeof AppearanceApplyBody>): Promise<AppearanceResult>;
|
|
124
147
|
/**
|
|
125
148
|
* The item's icon (disabled items included; same response headers as the org side, see
|
|
126
149
|
* `sendCatalogIcon`). No icon -> 404 `catalog.icon_not_found`.
|
|
@@ -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, Req, Res, } from "@nestjs/common";
|
|
11
11
|
import { z } from "zod";
|
|
12
|
+
import { ClientBuilds } from "../client/client-builds.service.js";
|
|
12
13
|
import { ApiError } from "../errors.js";
|
|
13
14
|
import { PluginGuides } from "../extensions/plugin-guides.js";
|
|
14
15
|
import { PluginIcons } from "../extensions/plugin-icons.js";
|
|
@@ -16,6 +17,8 @@ import { CurrentPrincipal, patTokenId, requireUser, SuperAdmin, } from "../ident
|
|
|
16
17
|
import { createZodDto } from "../infra/validation/create-zod-dto.js";
|
|
17
18
|
import { ZBody, ZQuery } from "../infra/validation/zod-params.js";
|
|
18
19
|
import { AdminCatalogGuideQuery, CatalogItemsQuery, CatalogUpdateBody, CatalogUpdatePreviewBody, CatalogDisabledBody as DisabledBody, CatalogGitImportBody as GitBody, CatalogHash as Hash, CatalogNpmImportBody as NpmBody, CatalogScopeOrgs as Orgs, CatalogScope as Scope, CatalogScopeBody as ScopeBody, CatalogScopePreviewBody as ScopePreviewBody, CatalogShownBody as ShownBody, } from "./admin-catalog.schemas.js";
|
|
20
|
+
import { AppearanceApplyBody } from "./appearance.schemas.js";
|
|
21
|
+
import { AppearanceService, suggestedAppearanceOf, } from "./appearance.service.js";
|
|
19
22
|
import { CatalogService, } from "./catalog.service.js";
|
|
20
23
|
import { CatalogScopeService } from "./catalog-scope.service.js";
|
|
21
24
|
import { sendCatalogIcon } from "./org-catalog.controller.js";
|
|
@@ -65,6 +68,8 @@ class CatalogUpdatePreviewBodyDto extends createZodDto(CatalogUpdatePreviewBody,
|
|
|
65
68
|
}
|
|
66
69
|
class CatalogUpdateBodyDto extends createZodDto(CatalogUpdateBody, "CatalogUpdateBody") {
|
|
67
70
|
}
|
|
71
|
+
class AppearanceApplyBodyDto extends createZodDto(AppearanceApplyBody, "AppearanceApplyBody") {
|
|
72
|
+
}
|
|
68
73
|
/**
|
|
69
74
|
* The platform catalog (`/api/v1/admin/catalog/*`, super admins): plugin import (upload / npm
|
|
70
75
|
* prepare, then confirm, which installs the plugin's extension), the item list and detail, scope and
|
|
@@ -78,14 +83,18 @@ let AdminCatalogController = class AdminCatalogController {
|
|
|
78
83
|
scope;
|
|
79
84
|
updater;
|
|
80
85
|
icons;
|
|
86
|
+
appearance;
|
|
87
|
+
clientBuilds;
|
|
81
88
|
guides;
|
|
82
|
-
constructor(catalog, pluginImport, orgCatalog, scope, updater, icons, guides) {
|
|
89
|
+
constructor(catalog, pluginImport, orgCatalog, scope, updater, icons, appearance, clientBuilds, guides) {
|
|
83
90
|
this.catalog = catalog;
|
|
84
91
|
this.pluginImport = pluginImport;
|
|
85
92
|
this.orgCatalog = orgCatalog;
|
|
86
93
|
this.scope = scope;
|
|
87
94
|
this.updater = updater;
|
|
88
95
|
this.icons = icons;
|
|
96
|
+
this.appearance = appearance;
|
|
97
|
+
this.clientBuilds = clientBuilds;
|
|
89
98
|
this.guides = guides;
|
|
90
99
|
}
|
|
91
100
|
/**
|
|
@@ -180,12 +189,42 @@ let AdminCatalogController = class AdminCatalogController {
|
|
|
180
189
|
return { ...page, items: page.items.map((i) => ({ ...i, icon: icons.get(i.id) ?? null })) };
|
|
181
190
|
}
|
|
182
191
|
/**
|
|
183
|
-
*
|
|
184
|
-
* `
|
|
192
|
+
* The item with its versions, plus `hasIcon` (the latest version has an icon, served by `GET
|
|
193
|
+
* items/:id/icon`) and `suggestedAppearance` (the platform version's, null without one: the page
|
|
194
|
+
* offers 应用此插件的外观 when set). Each version carries `client`: its extension package's
|
|
195
|
+
* client build (files with sizes, total, client API version; null without one), what the
|
|
196
|
+
* permission confirmation of an org's upload shows in its 客户端代码 group (client extensions spec
|
|
197
|
+
* §5.4). Unknown -> 404 `catalog.item_not_found`.
|
|
185
198
|
*/
|
|
186
199
|
async get(id) {
|
|
187
200
|
const detail = await this.catalog.get(id);
|
|
188
|
-
|
|
201
|
+
const platform = detail.versions.find((v) => v.version === detail.item.platformVersion);
|
|
202
|
+
const [hasIcon, builds] = await Promise.all([
|
|
203
|
+
this.orgCatalog.hasIcon(id),
|
|
204
|
+
this.clientBuilds.summaries(detail.versions.flatMap((v) => (v.extensionSha256 === null ? [] : [v.extensionSha256]))),
|
|
205
|
+
]);
|
|
206
|
+
return {
|
|
207
|
+
...detail,
|
|
208
|
+
versions: detail.versions.map((v) => ({
|
|
209
|
+
...v,
|
|
210
|
+
client: v.extensionSha256 === null ? null : (builds.get(v.extensionSha256) ?? null),
|
|
211
|
+
})),
|
|
212
|
+
hasIcon,
|
|
213
|
+
suggestedAppearance: suggestedAppearanceOf(platform?.manifest),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* 应用此插件的外观 (client extensions spec §6.5): sets the plugin's suggested theme as the platform
|
|
218
|
+
* default theme and each suggested replacement as the platform arrangement's `use` of its slot, in
|
|
219
|
+
* one transaction. `dryRun: true` answers the same changes and writes nothing. A theme that cannot
|
|
220
|
+
* be the platform default -> 400 `theme.not_available` (nothing written); a replacement not
|
|
221
|
+
* available on the platform is skipped. No such item, or no suggestion -> 404
|
|
222
|
+
* `catalog.item_not_found`.
|
|
223
|
+
*/
|
|
224
|
+
applyAppearance(principal, id, body) {
|
|
225
|
+
const actor = requireUser(principal);
|
|
226
|
+
const tokenId = patTokenId(actor);
|
|
227
|
+
return this.appearance.apply({ platform: true }, id, { accountId: actor.account.id, ...(tokenId !== undefined && { tokenId }) }, body.dryRun === true);
|
|
189
228
|
}
|
|
190
229
|
/**
|
|
191
230
|
* The item's icon (disabled items included; same response headers as the org side, see
|
|
@@ -340,6 +379,13 @@ __decorate([
|
|
|
340
379
|
Get("items/:id"),
|
|
341
380
|
__param(0, Param("id"))
|
|
342
381
|
], AdminCatalogController.prototype, "get", null);
|
|
382
|
+
__decorate([
|
|
383
|
+
Post("items/:id/appearance"),
|
|
384
|
+
HttpCode(200),
|
|
385
|
+
__param(0, CurrentPrincipal()),
|
|
386
|
+
__param(1, Param("id")),
|
|
387
|
+
__param(2, ZBody(AppearanceApplyBodyDto))
|
|
388
|
+
], AdminCatalogController.prototype, "applyAppearance", null);
|
|
343
389
|
__decorate([
|
|
344
390
|
Get("items/:id/icon"),
|
|
345
391
|
__param(0, Param("id")),
|
|
@@ -416,6 +462,8 @@ AdminCatalogController = __decorate([
|
|
|
416
462
|
__param(3, Inject(CatalogScopeService)),
|
|
417
463
|
__param(4, Inject(PlatformPluginUpdater)),
|
|
418
464
|
__param(5, Inject(PluginIcons)),
|
|
419
|
-
__param(6, Inject(
|
|
465
|
+
__param(6, Inject(AppearanceService)),
|
|
466
|
+
__param(7, Inject(ClientBuilds)),
|
|
467
|
+
__param(8, Inject(PluginGuides))
|
|
420
468
|
], AdminCatalogController);
|
|
421
469
|
export { AdminCatalogController };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { RouteSchemas } from "../platform/route-schemas.js";
|
|
3
|
+
/**
|
|
4
|
+
* The body of both "apply this plugin's appearance" routes (client extensions spec §6.5):
|
|
5
|
+
* `dryRun: true` answers what would change and writes nothing (the confirm dialog's preview).
|
|
6
|
+
*/
|
|
7
|
+
export declare const AppearanceApplyBody: z.ZodObject<{
|
|
8
|
+
dryRun: z.ZodOptional<z.ZodBoolean>;
|
|
9
|
+
}, z.core.$strict>;
|
|
10
|
+
export declare const APPEARANCE_ROUTES: readonly RouteSchemas[];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { CatalogIdPathParams } from "./admin-catalog.schemas.js";
|
|
3
|
+
import { OrgExtensionParams } from "./org-extension.schemas.js";
|
|
4
|
+
/**
|
|
5
|
+
* The body of both "apply this plugin's appearance" routes (client extensions spec §6.5):
|
|
6
|
+
* `dryRun: true` answers what would change and writes nothing (the confirm dialog's preview).
|
|
7
|
+
*/
|
|
8
|
+
export const AppearanceApplyBody = z.object({ dryRun: z.boolean().optional() }).strict();
|
|
9
|
+
export const APPEARANCE_ROUTES = [
|
|
10
|
+
{
|
|
11
|
+
route: "POST /api/v1/admin/catalog/items/:id/appearance",
|
|
12
|
+
params: CatalogIdPathParams,
|
|
13
|
+
body: AppearanceApplyBody,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
route: "POST /api/v1/orgs/:orgId/extensions/:extension/appearance",
|
|
17
|
+
params: OrgExtensionParams,
|
|
18
|
+
body: AppearanceApplyBody,
|
|
19
|
+
},
|
|
20
|
+
];
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { type SuggestedAppearance } from "@capybara-hq/plugin-sdk";
|
|
2
|
+
import { ExtensionHost } from "../extensions/host.js";
|
|
3
|
+
import { SettingsService } from "../identity/settings.js";
|
|
4
|
+
import { SuperAdminLock } from "../identity/super-admin.js";
|
|
5
|
+
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
6
|
+
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
7
|
+
import { OrgLock } from "../orgs/org-lock.js";
|
|
8
|
+
import { OrgSettingsService } from "../orgs/settings.service.js";
|
|
9
|
+
/** Where the suggestion is applied: the platform defaults, or one org's. */
|
|
10
|
+
export type AppearanceLevel = {
|
|
11
|
+
platform: true;
|
|
12
|
+
} | {
|
|
13
|
+
orgId: string;
|
|
14
|
+
};
|
|
15
|
+
/** Why a replacement of the suggestion was left out (client extensions spec §6.5). */
|
|
16
|
+
export type AppearanceSkipReason = "unknown-slot" | "not-available" | "not-eligible";
|
|
17
|
+
/**
|
|
18
|
+
* What applying a suggestion changes at its level: the default theme (`from`: the theme in effect,
|
|
19
|
+
* null for none; `to`: the `platform_themes` id; null when the plugin suggests no theme or it is
|
|
20
|
+
* in effect already), each replacement slot's `use` (`to`: the contribution key
|
|
21
|
+
* `<extension>/<contribution>`; `from`: the key in effect, null for the built-in; a slot that
|
|
22
|
+
* already uses it is not listed) and what was skipped, in plugin.json order. At org level `from`
|
|
23
|
+
* is the org's own entry, else the platform's: `followsPlatform` says which (absent at platform
|
|
24
|
+
* level), and when the org follows a platform that already uses the suggestion nothing is pinned.
|
|
25
|
+
*/
|
|
26
|
+
export interface AppearanceResult {
|
|
27
|
+
theme: {
|
|
28
|
+
from: string | null;
|
|
29
|
+
to: string;
|
|
30
|
+
followsPlatform?: boolean;
|
|
31
|
+
} | null;
|
|
32
|
+
replacements: {
|
|
33
|
+
slot: string;
|
|
34
|
+
from: string | null;
|
|
35
|
+
to: string;
|
|
36
|
+
followsPlatform?: boolean;
|
|
37
|
+
}[];
|
|
38
|
+
skipped: {
|
|
39
|
+
what: string;
|
|
40
|
+
reason: AppearanceSkipReason;
|
|
41
|
+
}[];
|
|
42
|
+
}
|
|
43
|
+
/** Who applies it: the account, the PAT when one made the call, and extra audit detail. */
|
|
44
|
+
export interface AppearanceActor {
|
|
45
|
+
accountId: string;
|
|
46
|
+
tokenId?: string;
|
|
47
|
+
/** Merged into the settings writer's audit detail (the Platform tool's `via: "agent"`). */
|
|
48
|
+
auditDetail?: Record<string, unknown>;
|
|
49
|
+
}
|
|
50
|
+
/** The suggestion of a plugin's catalog version, or null (none, or not an object). */
|
|
51
|
+
export declare function suggestedAppearanceOf(manifest: unknown): SuggestedAppearance | null;
|
|
52
|
+
/**
|
|
53
|
+
* "Apply this plugin's appearance" (client extensions spec §6.5): one step that sets the plugin's
|
|
54
|
+
* suggested theme as the level's default theme and each suggested replacement as the level's `use`
|
|
55
|
+
* of its single slot. The writes go through the settings writers (`SettingsService.update` for the
|
|
56
|
+
* platform, `OrgSettingsService.update` for an org), so their validation, theme checks, lock order
|
|
57
|
+
* (the level's lock, then catalog item, theme row, `uiTheme` row) and audit apply, both settings in
|
|
58
|
+
* one call and one transaction: a theme that cannot be set (`theme.not_available`) refuses the
|
|
59
|
+
* whole call; a replacement that cannot be used where it is set is skipped, never refused (the
|
|
60
|
+
* arrangement keeps unknown keys anyway). A dry run computes the same answer and writes nothing.
|
|
61
|
+
*/
|
|
62
|
+
export declare class AppearanceService {
|
|
63
|
+
private readonly db;
|
|
64
|
+
readonly txHost: TransactionHost;
|
|
65
|
+
private readonly settings;
|
|
66
|
+
private readonly orgSettings;
|
|
67
|
+
private readonly adminLock;
|
|
68
|
+
private readonly orgLock;
|
|
69
|
+
private readonly host;
|
|
70
|
+
constructor(db: KernelDb, txHost: TransactionHost, settings: SettingsService, orgSettings: OrgSettingsService, adminLock: SuperAdminLock, orgLock: OrgLock, host: ExtensionHost);
|
|
71
|
+
/**
|
|
72
|
+
* The suggestions of the live plugins named `names`, from the version their components come from
|
|
73
|
+
* (`platform_synced_version`, else the latest); a plugin without one maps to null.
|
|
74
|
+
*/
|
|
75
|
+
suggestions(names: readonly string[]): Promise<Map<string, SuggestedAppearance | null>>;
|
|
76
|
+
/** The live catalog item `itemId` with its suggestion; else `catalog.item_not_found`. */
|
|
77
|
+
private platformItem;
|
|
78
|
+
/**
|
|
79
|
+
* The live plugin of extension `extension`, which must be on in the org, with its suggestion;
|
|
80
|
+
* else `extension.not_found`.
|
|
81
|
+
*/
|
|
82
|
+
private orgItem;
|
|
83
|
+
/**
|
|
84
|
+
* Applies (or, with `dryRun`, previews) the suggestion of `item` at `level`: for the platform,
|
|
85
|
+
* `item` is the catalog item id (`POST /api/v1/admin/catalog/items/:id/appearance`, super
|
|
86
|
+
* admin); for an org, the extension name (`POST /api/v1/orgs/:orgId/extensions/:extension/
|
|
87
|
+
* appearance`, `org.update`). The writers re-check the actor under their lock.
|
|
88
|
+
*/
|
|
89
|
+
apply(level: AppearanceLevel, item: string, actor: AppearanceActor, dryRun: boolean): Promise<AppearanceResult>;
|
|
90
|
+
/** Refuses (`theme.not_available`) a theme the level's writer would refuse; reads only. */
|
|
91
|
+
private assertThemeSettable;
|
|
92
|
+
private applyPlatform;
|
|
93
|
+
private applyOrg;
|
|
94
|
+
/**
|
|
95
|
+
* What applying `found` at `level` changes, from the stored settings (no writes): the level's
|
|
96
|
+
* own, and at org level the platform's it follows where it stores nothing.
|
|
97
|
+
*/
|
|
98
|
+
private plan;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The suggested appearance action: its own module, since it writes through the org settings
|
|
102
|
+
* writer (`OrgsModule`, which imports `CatalogModule`) and the deployment settings writer.
|
|
103
|
+
*/
|
|
104
|
+
export declare class AppearanceModule {
|
|
105
|
+
}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
8
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
9
|
+
};
|
|
10
|
+
import { parseSlotRef, UI_ARRANGEABLE_SLOTS, uiSlot, } from "@capybara-hq/plugin-sdk";
|
|
11
|
+
import { Inject, Injectable, Module } from "@nestjs/common";
|
|
12
|
+
import { ApiError } from "../errors.js";
|
|
13
|
+
import { ExtensionsModule } from "../extensions/extensions.module.js";
|
|
14
|
+
import { ExtensionHost } from "../extensions/host.js";
|
|
15
|
+
import { IdentityModule } from "../identity/identity.module.js";
|
|
16
|
+
import { readUiArrangement, readUiTheme, SettingsService } from "../identity/settings.js";
|
|
17
|
+
import { SuperAdminLock, SuperAdminLockModule } from "../identity/super-admin.js";
|
|
18
|
+
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
19
|
+
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
20
|
+
import { Transactional } from "../infra/database/transactional.decorator.js";
|
|
21
|
+
import { OrgLock, OrgLockModule } from "../orgs/org-lock.js";
|
|
22
|
+
import { OrgsModule } from "../orgs/orgs.module.js";
|
|
23
|
+
import { OrgSettingsService, readOrgSettings } from "../orgs/settings.service.js";
|
|
24
|
+
import { assertPlatformDefaultEligible, eligiblePlatformDefault, themeNotAvailable, themeOnIn, } from "../themes/theme-state.js";
|
|
25
|
+
import { ORG_ARRANGEABLE } from "../ui/arrangement.js";
|
|
26
|
+
import { inAuthorOrder } from "./catalog-items.js";
|
|
27
|
+
/** The suggestion of a plugin's catalog version, or null (none, or not an object). */
|
|
28
|
+
export function suggestedAppearanceOf(manifest) {
|
|
29
|
+
const sa = manifest?.suggestedAppearance;
|
|
30
|
+
return sa !== null && typeof sa === "object" ? sa : null;
|
|
31
|
+
}
|
|
32
|
+
const itemNotFound = () => new ApiError("catalog.item_not_found", "No such plugin, or it suggests no appearance");
|
|
33
|
+
const extensionNotFound = () => new ApiError("extension.not_found", "No such extension on in this organization, or its plugin suggests no appearance");
|
|
34
|
+
const useOf = (e) => e !== undefined && "use" in e ? e.use : null;
|
|
35
|
+
/**
|
|
36
|
+
* "Apply this plugin's appearance" (client extensions spec §6.5): one step that sets the plugin's
|
|
37
|
+
* suggested theme as the level's default theme and each suggested replacement as the level's `use`
|
|
38
|
+
* of its single slot. The writes go through the settings writers (`SettingsService.update` for the
|
|
39
|
+
* platform, `OrgSettingsService.update` for an org), so their validation, theme checks, lock order
|
|
40
|
+
* (the level's lock, then catalog item, theme row, `uiTheme` row) and audit apply, both settings in
|
|
41
|
+
* one call and one transaction: a theme that cannot be set (`theme.not_available`) refuses the
|
|
42
|
+
* whole call; a replacement that cannot be used where it is set is skipped, never refused (the
|
|
43
|
+
* arrangement keeps unknown keys anyway). A dry run computes the same answer and writes nothing.
|
|
44
|
+
*/
|
|
45
|
+
let AppearanceService = class AppearanceService {
|
|
46
|
+
db;
|
|
47
|
+
txHost;
|
|
48
|
+
settings;
|
|
49
|
+
orgSettings;
|
|
50
|
+
adminLock;
|
|
51
|
+
orgLock;
|
|
52
|
+
host;
|
|
53
|
+
constructor(db, txHost, settings, orgSettings, adminLock, orgLock, host) {
|
|
54
|
+
this.db = db;
|
|
55
|
+
this.txHost = txHost;
|
|
56
|
+
this.settings = settings;
|
|
57
|
+
this.orgSettings = orgSettings;
|
|
58
|
+
this.adminLock = adminLock;
|
|
59
|
+
this.orgLock = orgLock;
|
|
60
|
+
this.host = host;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The suggestions of the live plugins named `names`, from the version their components come from
|
|
64
|
+
* (`platform_synced_version`, else the latest); a plugin without one maps to null.
|
|
65
|
+
*/
|
|
66
|
+
async suggestions(names) {
|
|
67
|
+
const out = new Map(names.map((n) => [n, null]));
|
|
68
|
+
if (names.length === 0)
|
|
69
|
+
return out;
|
|
70
|
+
const rows = await this.db
|
|
71
|
+
.selectFrom("catalog_items as i")
|
|
72
|
+
.innerJoin("catalog_item_versions as v", (j) => j
|
|
73
|
+
.onRef("v.item_id", "=", "i.id")
|
|
74
|
+
.on((eb) => eb("v.version", "=", eb.fn.coalesce("i.platform_synced_version", "i.latest_version"))))
|
|
75
|
+
.select(["i.name", "v.manifest", "v.appearance_order"])
|
|
76
|
+
.where("i.name", "in", [...names])
|
|
77
|
+
.where("i.deleted_at", "is", null)
|
|
78
|
+
.execute();
|
|
79
|
+
for (const r of rows) {
|
|
80
|
+
out.set(r.name, suggestedAppearanceOf(inAuthorOrder(r.manifest, r.appearance_order)));
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
/** The live catalog item `itemId` with its suggestion; else `catalog.item_not_found`. */
|
|
85
|
+
async platformItem(itemId) {
|
|
86
|
+
if (!/^[0-9a-f-]{36}$/i.test(itemId))
|
|
87
|
+
throw itemNotFound();
|
|
88
|
+
const row = await this.db
|
|
89
|
+
.selectFrom("catalog_items")
|
|
90
|
+
.select(["id", "name"])
|
|
91
|
+
.where("id", "=", itemId)
|
|
92
|
+
.where("deleted_at", "is", null)
|
|
93
|
+
.executeTakeFirst();
|
|
94
|
+
const suggestion = row && (await this.suggestions([row.name])).get(row.name);
|
|
95
|
+
if (!row || !suggestion)
|
|
96
|
+
throw itemNotFound();
|
|
97
|
+
return { id: row.id, name: row.name, suggestion };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The live plugin of extension `extension`, which must be on in the org, with its suggestion;
|
|
101
|
+
* else `extension.not_found`.
|
|
102
|
+
*/
|
|
103
|
+
async orgItem(orgId, extension) {
|
|
104
|
+
if (!(await this.host.enabledPlugins(orgId)).has(extension))
|
|
105
|
+
throw extensionNotFound();
|
|
106
|
+
const row = await this.db
|
|
107
|
+
.selectFrom("catalog_items")
|
|
108
|
+
.select(["id", "name"])
|
|
109
|
+
.where("name", "=", extension)
|
|
110
|
+
.where("deleted_at", "is", null)
|
|
111
|
+
.executeTakeFirst();
|
|
112
|
+
const suggestion = row && (await this.suggestions([row.name])).get(row.name);
|
|
113
|
+
if (!row || !suggestion)
|
|
114
|
+
throw extensionNotFound();
|
|
115
|
+
return { id: row.id, name: row.name, suggestion };
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Applies (or, with `dryRun`, previews) the suggestion of `item` at `level`: for the platform,
|
|
119
|
+
* `item` is the catalog item id (`POST /api/v1/admin/catalog/items/:id/appearance`, super
|
|
120
|
+
* admin); for an org, the extension name (`POST /api/v1/orgs/:orgId/extensions/:extension/
|
|
121
|
+
* appearance`, `org.update`). The writers re-check the actor under their lock.
|
|
122
|
+
*/
|
|
123
|
+
async apply(level, item, actor, dryRun) {
|
|
124
|
+
if (dryRun) {
|
|
125
|
+
const found = "orgId" in level ? await this.orgItem(level.orgId, item) : await this.platformItem(item);
|
|
126
|
+
const plan = await this.plan(level, found);
|
|
127
|
+
if (plan.theme !== undefined)
|
|
128
|
+
await this.assertThemeSettable(level, plan.theme);
|
|
129
|
+
return plan.result;
|
|
130
|
+
}
|
|
131
|
+
return "orgId" in level
|
|
132
|
+
? this.applyOrg(level.orgId, item, actor)
|
|
133
|
+
: this.applyPlatform(item, actor);
|
|
134
|
+
}
|
|
135
|
+
/** Refuses (`theme.not_available`) a theme the level's writer would refuse; reads only. */
|
|
136
|
+
async assertThemeSettable(level, themeId) {
|
|
137
|
+
if ("orgId" in level) {
|
|
138
|
+
if (!(await themeOnIn(this.db.db, level.orgId, themeId))) {
|
|
139
|
+
throw themeNotAvailable(themeId, "in this organization");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
await assertPlatformDefaultEligible(this.db.db, themeId);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async applyPlatform(itemId, actor) {
|
|
147
|
+
// The writer's own lock first, so what the plan reads (the stored theme and arrangement) is
|
|
148
|
+
// what the write replaces; the writer takes it again (re-entrant) and then the theme rows
|
|
149
|
+
await this.adminLock.lockAndAuthorize(actor.accountId);
|
|
150
|
+
const found = await this.platformItem(itemId);
|
|
151
|
+
const plan = await this.plan({ platform: true }, found);
|
|
152
|
+
if (plan.theme === undefined && Object.keys(plan.arrangement).length === 0)
|
|
153
|
+
return plan.result;
|
|
154
|
+
await this.settings.update({
|
|
155
|
+
...(plan.theme !== undefined && { uiTheme: { id: plan.theme } }),
|
|
156
|
+
...(Object.keys(plan.arrangement).length > 0 && { uiArrangement: plan.arrangement }),
|
|
157
|
+
}, actor.accountId, actor.tokenId, { ...actor.auditDetail, appearance: { itemId: found.id, plugin: found.name } });
|
|
158
|
+
return plan.result;
|
|
159
|
+
}
|
|
160
|
+
async applyOrg(orgId, extension, actor) {
|
|
161
|
+
// The org lock first (as the writer takes it): `personal` and the stored arrangement are read
|
|
162
|
+
// under it, so the write keeps the lock setting it read
|
|
163
|
+
await this.orgLock.lockAndAuthorize(orgId, actor.accountId, "org.update");
|
|
164
|
+
const found = await this.orgItem(orgId, extension);
|
|
165
|
+
const plan = await this.plan({ orgId }, found);
|
|
166
|
+
if (plan.theme === undefined && Object.keys(plan.arrangement).length === 0)
|
|
167
|
+
return plan.result;
|
|
168
|
+
const stored = await readOrgSettings(this.db.db, orgId);
|
|
169
|
+
await this.orgSettings.update(orgId, {
|
|
170
|
+
...(plan.theme !== undefined && {
|
|
171
|
+
theme: { id: plan.theme, personal: stored.theme?.personal ?? "allowed" },
|
|
172
|
+
}),
|
|
173
|
+
...(Object.keys(plan.arrangement).length > 0 && { uiArrangement: plan.arrangement }),
|
|
174
|
+
}, actor.accountId, actor.tokenId, { ...actor.auditDetail, appearance: { itemId: found.id, plugin: found.name } });
|
|
175
|
+
return plan.result;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* What applying `found` at `level` changes, from the stored settings (no writes): the level's
|
|
179
|
+
* own, and at org level the platform's it follows where it stores nothing.
|
|
180
|
+
*/
|
|
181
|
+
async plan(level, found) {
|
|
182
|
+
const { suggestion } = found;
|
|
183
|
+
const [platformTheme, platformArrangement] = await Promise.all([
|
|
184
|
+
readUiTheme(this.db.db).then((t) => t.id),
|
|
185
|
+
readUiArrangement(this.db.db),
|
|
186
|
+
]);
|
|
187
|
+
const org = "orgId" in level ? await readOrgSettings(this.db.db, level.orgId) : null;
|
|
188
|
+
// the theme in effect: an org without its own theme (id null) follows the platform default,
|
|
189
|
+
// which renders only while it is eligible (else the org sees the built-in, as ThemesService
|
|
190
|
+
// resolves it)
|
|
191
|
+
const orgTheme = org === null ? undefined : (org.theme?.id ?? null);
|
|
192
|
+
const themeFollows = orgTheme === null;
|
|
193
|
+
const followed = themeFollows && platformTheme !== null
|
|
194
|
+
? (await eligiblePlatformDefault(this.db.db, platformTheme)) !== null
|
|
195
|
+
? platformTheme
|
|
196
|
+
: null
|
|
197
|
+
: platformTheme;
|
|
198
|
+
const themeInEffect = orgTheme ?? followed;
|
|
199
|
+
/** The `use` in effect for a single slot, and whether an org follows the platform's. */
|
|
200
|
+
const inEffect = (slot) => {
|
|
201
|
+
if (org === null)
|
|
202
|
+
return { use: useOf(platformArrangement[slot]) };
|
|
203
|
+
const own = org.uiArrangement?.[slot];
|
|
204
|
+
return own !== undefined
|
|
205
|
+
? { use: useOf(own), follows: false }
|
|
206
|
+
: { use: useOf(platformArrangement[slot]), follows: true };
|
|
207
|
+
};
|
|
208
|
+
const follows = (f) => (f === undefined ? {} : { followsPlatform: f });
|
|
209
|
+
const result = { theme: null, replacements: [], skipped: [] };
|
|
210
|
+
const plan = { result, arrangement: {} };
|
|
211
|
+
if (suggestion.theme !== undefined) {
|
|
212
|
+
const row = await this.db
|
|
213
|
+
.selectFrom("platform_themes")
|
|
214
|
+
.select("id")
|
|
215
|
+
.where("catalog_item_id", "=", found.id)
|
|
216
|
+
.where("name", "=", suggestion.theme)
|
|
217
|
+
.where("deleted_at", "is", null)
|
|
218
|
+
.executeTakeFirst();
|
|
219
|
+
if (!row) {
|
|
220
|
+
throw themeNotAvailable(suggestion.theme, "orgId" in level ? "in this organization" : "as the platform default");
|
|
221
|
+
}
|
|
222
|
+
if (row.id !== themeInEffect) {
|
|
223
|
+
plan.theme = row.id;
|
|
224
|
+
result.theme = {
|
|
225
|
+
from: themeInEffect,
|
|
226
|
+
to: row.id,
|
|
227
|
+
...follows(org === null ? undefined : themeFollows),
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// in plugin.json order (`suggestions` restores it from `appearance_order`)
|
|
232
|
+
const entries = Object.entries(suggestion.replacements ?? {});
|
|
233
|
+
if (entries.length === 0)
|
|
234
|
+
return plan;
|
|
235
|
+
const enabled = "orgId" in level ? await this.host.enabledPlugins(level.orgId) : null;
|
|
236
|
+
const loaded = this.host
|
|
237
|
+
.uiContributions()
|
|
238
|
+
.filter((c) => c.plugin === found.name &&
|
|
239
|
+
c.contribution.aliasOf === undefined &&
|
|
240
|
+
(enabled === null || enabled.has(c.plugin)));
|
|
241
|
+
const arrangeable = "orgId" in level ? ORG_ARRANGEABLE : UI_ARRANGEABLE_SLOTS;
|
|
242
|
+
const planned = new Set();
|
|
243
|
+
for (const [ref, id] of entries) {
|
|
244
|
+
const parsed = parseSlotRef(ref);
|
|
245
|
+
const known = parsed === null ? undefined : uiSlot(parsed.id);
|
|
246
|
+
if (parsed === null || known === undefined || parsed.version > known.version) {
|
|
247
|
+
result.skipped.push({ what: ref, reason: "unknown-slot" });
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
const slot = known.id;
|
|
251
|
+
// a second ref to one slot (`shell.brand` and `shell.brand@1`; the SDK refuses it at parse)
|
|
252
|
+
if (known.cardinality !== "single" || !arrangeable.includes(slot) || planned.has(slot)) {
|
|
253
|
+
result.skipped.push({ what: ref, reason: "not-eligible" });
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
const c = loaded.find((l) => l.slot.id === slot && l.contribution.id === id);
|
|
257
|
+
if (c === undefined) {
|
|
258
|
+
result.skipped.push({ what: ref, reason: "not-available" });
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (c.contribution.kind !== "component") {
|
|
262
|
+
result.skipped.push({ what: ref, reason: "not-eligible" });
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
planned.add(slot);
|
|
266
|
+
const key = `${found.name}/${id}`;
|
|
267
|
+
const now = inEffect(slot);
|
|
268
|
+
if (now.use === key)
|
|
269
|
+
continue;
|
|
270
|
+
plan.arrangement[slot] = { use: key };
|
|
271
|
+
result.replacements.push({ slot, from: now.use, to: key, ...follows(now.follows) });
|
|
272
|
+
}
|
|
273
|
+
return plan;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
__decorate([
|
|
277
|
+
Transactional()
|
|
278
|
+
], AppearanceService.prototype, "applyPlatform", null);
|
|
279
|
+
__decorate([
|
|
280
|
+
Transactional()
|
|
281
|
+
], AppearanceService.prototype, "applyOrg", null);
|
|
282
|
+
AppearanceService = __decorate([
|
|
283
|
+
Injectable(),
|
|
284
|
+
__param(0, Inject(KernelDb)),
|
|
285
|
+
__param(1, Inject(TransactionHost)),
|
|
286
|
+
__param(2, Inject(SettingsService)),
|
|
287
|
+
__param(3, Inject(OrgSettingsService)),
|
|
288
|
+
__param(4, Inject(SuperAdminLock)),
|
|
289
|
+
__param(5, Inject(OrgLock)),
|
|
290
|
+
__param(6, Inject(ExtensionHost))
|
|
291
|
+
], AppearanceService);
|
|
292
|
+
export { AppearanceService };
|
|
293
|
+
/**
|
|
294
|
+
* The suggested appearance action: its own module, since it writes through the org settings
|
|
295
|
+
* writer (`OrgsModule`, which imports `CatalogModule`) and the deployment settings writer.
|
|
296
|
+
*/
|
|
297
|
+
let AppearanceModule = class AppearanceModule {
|
|
298
|
+
};
|
|
299
|
+
AppearanceModule = __decorate([
|
|
300
|
+
Module({
|
|
301
|
+
imports: [IdentityModule, OrgsModule, SuperAdminLockModule, OrgLockModule, ExtensionsModule],
|
|
302
|
+
providers: [AppearanceService],
|
|
303
|
+
exports: [AppearanceService],
|
|
304
|
+
})
|
|
305
|
+
], AppearanceModule);
|
|
306
|
+
export { AppearanceModule };
|