@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
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { type OnModuleInit } from "@nestjs/common";
|
|
2
|
+
import { type AppearanceResult, AppearanceService } from "../../catalog/appearance.service.js";
|
|
2
3
|
import { CatalogService } from "../../catalog/catalog.service.js";
|
|
3
4
|
import { CatalogScopeService } from "../../catalog/catalog-scope.service.js";
|
|
4
5
|
import { PluginImport, type PluginReview } from "../../catalog/plugin-import.js";
|
|
5
6
|
import { KernelDb } from "../../infra/database/kernel-db.js";
|
|
7
|
+
import { ThemesService } from "../../themes/themes.service.js";
|
|
6
8
|
import { PlatformOperations } from "../registry.js";
|
|
7
9
|
/** `plugins.set-shown` on a plugin an org owns. */
|
|
8
10
|
export declare const ORG_OWNED = "An org owns this plugin and manages who sees it; the super admin changes that in the admin console, not here.";
|
|
@@ -41,6 +43,20 @@ export declare const reviewBrief: (r: PluginReview) => {
|
|
|
41
43
|
resolved: string | null;
|
|
42
44
|
}[];
|
|
43
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* The card of an "apply this plugin's appearance" operation (client extensions spec §6.5), from the
|
|
48
|
+
* route's dry run: the theme it sets (its name), the slots it replaces and how many were skipped.
|
|
49
|
+
*/
|
|
50
|
+
export declare function appearanceSummary(level: "platform" | "org", plugin: {
|
|
51
|
+
zh: string;
|
|
52
|
+
en: string;
|
|
53
|
+
}, r: AppearanceResult, theme: {
|
|
54
|
+
zh: string;
|
|
55
|
+
en: string;
|
|
56
|
+
} | null): {
|
|
57
|
+
zh: string;
|
|
58
|
+
en: string;
|
|
59
|
+
};
|
|
44
60
|
/**
|
|
45
61
|
* The super admin's platform catalog operations of the `Platform` tool (spec §14.2, §14.5 item
|
|
46
62
|
* 1): prepare an import from npm or git and read its review (a person confirms it on the web: the
|
|
@@ -55,7 +71,9 @@ export declare class CatalogAdminOperations implements OnModuleInit {
|
|
|
55
71
|
private readonly scope;
|
|
56
72
|
private readonly pluginImport;
|
|
57
73
|
private readonly kdb;
|
|
58
|
-
|
|
74
|
+
private readonly appearance;
|
|
75
|
+
private readonly themes;
|
|
76
|
+
constructor(registry: PlatformOperations, catalog: CatalogService, scope: CatalogScopeService, pluginImport: PluginImport, kdb: KernelDb, appearance: AppearanceService, themes: ThemesService);
|
|
59
77
|
/** The precheck refusal of another org's private plugin (the phase-1 rule), else null. */
|
|
60
78
|
private otherOrgs;
|
|
61
79
|
/** Whether an org owns the item (its owner org manages it, not the super admin's agent). */
|
|
@@ -11,11 +11,13 @@ import { ToolError } from "@capybara-hq/plugin-sdk";
|
|
|
11
11
|
import { Inject, Injectable } from "@nestjs/common";
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
import { CatalogDisabledBody, CatalogGitImportBody, CatalogIdPathParams, CatalogNpmImportBody, CatalogScope, CatalogScopeBody, CatalogScopeOrgs, CatalogShownBody, CatalogStagingPathParams, } from "../../catalog/admin-catalog.schemas.js";
|
|
14
|
+
import { AppearanceService } from "../../catalog/appearance.service.js";
|
|
14
15
|
import { CatalogService } from "../../catalog/catalog.service.js";
|
|
15
16
|
import { CatalogScopeService } from "../../catalog/catalog-scope.service.js";
|
|
16
17
|
import { PluginImport } from "../../catalog/plugin-import.js";
|
|
17
18
|
import { ApiError } from "../../errors.js";
|
|
18
19
|
import { KernelDb } from "../../infra/database/kernel-db.js";
|
|
20
|
+
import { ThemesService } from "../../themes/themes.service.js";
|
|
19
21
|
import { defineOperation } from "../operation.js";
|
|
20
22
|
import { PlatformOperations } from "../registry.js";
|
|
21
23
|
import { precheckBy, viaAgent } from "./errors.js";
|
|
@@ -64,6 +66,37 @@ export const reviewBrief = (r) => ({
|
|
|
64
66
|
const conflictText = (cs) => cs
|
|
65
67
|
.map((c) => `skill "${c.name}" is already used in org ${c.orgName} (${c.plugins.length > 0 ? `${c.plugins.length} plugin(s)` : "its own skill"})`)
|
|
66
68
|
.join("; ");
|
|
69
|
+
/**
|
|
70
|
+
* The card of an "apply this plugin's appearance" operation (client extensions spec §6.5), from the
|
|
71
|
+
* route's dry run: the theme it sets (its name), the slots it replaces and how many were skipped.
|
|
72
|
+
*/
|
|
73
|
+
export function appearanceSummary(level, plugin, r, theme) {
|
|
74
|
+
const zh = [];
|
|
75
|
+
const en = [];
|
|
76
|
+
if (r.theme !== null && theme !== null) {
|
|
77
|
+
zh.push(`${level === "platform" ? "平台" : "本组织"}默认主题改为「${theme.zh}」`);
|
|
78
|
+
en.push(`${level === "platform" ? "platform" : "org"} default theme becomes "${theme.en}"`);
|
|
79
|
+
}
|
|
80
|
+
if (r.replacements.length > 0) {
|
|
81
|
+
const slots = r.replacements.map((x) => x.slot).join(", ");
|
|
82
|
+
zh.push(`替换 ${slots}`);
|
|
83
|
+
en.push(`replaces ${slots}`);
|
|
84
|
+
}
|
|
85
|
+
if (r.skipped.length > 0) {
|
|
86
|
+
zh.push(`跳过 ${r.skipped.length} 项`);
|
|
87
|
+
en.push(`${r.skipped.length} skipped`);
|
|
88
|
+
}
|
|
89
|
+
if (zh.length === 0) {
|
|
90
|
+
return {
|
|
91
|
+
zh: `插件「${plugin.zh}」的外观已经应用,没有改动`,
|
|
92
|
+
en: `The appearance of plugin "${plugin.en}" is applied already; nothing changes`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
zh: `应用插件「${plugin.zh}」的外观:${zh.join(";")}`,
|
|
97
|
+
en: `Apply the appearance of plugin "${plugin.en}": ${en.join("; ")}`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
67
100
|
/** A scope preview as the admin console shows it (orgs named); conflicting plugins counted. */
|
|
68
101
|
const previewBrief = (p) => ({
|
|
69
102
|
affected: p.affected,
|
|
@@ -93,12 +126,16 @@ let CatalogAdminOperations = class CatalogAdminOperations {
|
|
|
93
126
|
scope;
|
|
94
127
|
pluginImport;
|
|
95
128
|
kdb;
|
|
96
|
-
|
|
129
|
+
appearance;
|
|
130
|
+
themes;
|
|
131
|
+
constructor(registry, catalog, scope, pluginImport, kdb, appearance, themes) {
|
|
97
132
|
this.registry = registry;
|
|
98
133
|
this.catalog = catalog;
|
|
99
134
|
this.scope = scope;
|
|
100
135
|
this.pluginImport = pluginImport;
|
|
101
136
|
this.kdb = kdb;
|
|
137
|
+
this.appearance = appearance;
|
|
138
|
+
this.themes = themes;
|
|
102
139
|
}
|
|
103
140
|
/** The precheck refusal of another org's private plugin (the phase-1 rule), else null. */
|
|
104
141
|
async otherOrgs(itemId, orgId) {
|
|
@@ -585,6 +622,50 @@ let CatalogAdminOperations = class CatalogAdminOperations {
|
|
|
585
622
|
return { deleted: input.itemId };
|
|
586
623
|
},
|
|
587
624
|
}),
|
|
625
|
+
defineOperation({
|
|
626
|
+
id: "plugins.platform-apply-appearance",
|
|
627
|
+
description: "Apply a platform plugin's suggested appearance (super admin; the admin console's 应用此插件的外观): in one step, its theme becomes the platform default theme and each component it suggests replaces the built-in block of its slot (shell.brand, conversation.empty, conversation.composer.frame) in the platform arrangement; orgs that set their own keep theirs. The card shows exactly what changes; a theme that cannot be the platform default (scope not all, or off in an org) refuses the whole call, a component that is not available is skipped. Asked every time, never remembered.",
|
|
628
|
+
input: z.object({ itemId: ItemId }).strict(),
|
|
629
|
+
example: { itemId: "3f1c2a9e-5b7d-4e21-9c0a-1d2e3f4a5b6c" },
|
|
630
|
+
authz: { superAdmin: true },
|
|
631
|
+
mirrors: ["POST /api/v1/admin/catalog/items/:id/appearance"],
|
|
632
|
+
renames: { itemId: "id" },
|
|
633
|
+
// the route's dry run is this operation's card and precheck, never a confirmed call
|
|
634
|
+
omits: ["dryRun"],
|
|
635
|
+
confirm: "platform",
|
|
636
|
+
rememberable: false,
|
|
637
|
+
errors: [
|
|
638
|
+
{
|
|
639
|
+
code: "catalog.item_not_found",
|
|
640
|
+
next: "Check the plugin id with plugins.platform-list; only a plugin whose plugin.json has suggestedAppearance can be applied.",
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
code: "theme.not_available",
|
|
644
|
+
next: "The plugin's theme cannot be the platform default: give it scope all (plugins.component-scope-preview, then plugins.set-component-scope) and make sure no org switched it off, then apply again.",
|
|
645
|
+
},
|
|
646
|
+
],
|
|
647
|
+
precheck: async (i, ctx) => {
|
|
648
|
+
const other = await this.otherOrgs(i.itemId, ctx.orgId);
|
|
649
|
+
if (other !== null)
|
|
650
|
+
return other;
|
|
651
|
+
return precheckBy(() => this.appearance.apply({ platform: true }, i.itemId, { accountId: ctx.actorAccountId }, true));
|
|
652
|
+
},
|
|
653
|
+
summary: async (i, ctx) => {
|
|
654
|
+
const [n, r] = await Promise.all([
|
|
655
|
+
this.names(i.itemId),
|
|
656
|
+
this.appearance.apply({ platform: true }, i.itemId, { accountId: ctx.actorAccountId }, true),
|
|
657
|
+
]);
|
|
658
|
+
const theme = r.theme && localized(await this.themes.nameOf(r.theme.to), r.theme.to);
|
|
659
|
+
return appearanceSummary("platform", n, r, theme);
|
|
660
|
+
},
|
|
661
|
+
run: async (input, ctx) => {
|
|
662
|
+
await assertNotOtherOrgs(this.kdb.db, input.itemId, ctx.orgId);
|
|
663
|
+
return this.appearance.apply({ platform: true }, input.itemId, {
|
|
664
|
+
accountId: ctx.actorAccountId,
|
|
665
|
+
auditDetail: viaAgent("plugins.platform-apply-appearance", ctx),
|
|
666
|
+
}, false);
|
|
667
|
+
},
|
|
668
|
+
}),
|
|
588
669
|
]);
|
|
589
670
|
}
|
|
590
671
|
};
|
|
@@ -594,6 +675,8 @@ CatalogAdminOperations = __decorate([
|
|
|
594
675
|
__param(1, Inject(CatalogService)),
|
|
595
676
|
__param(2, Inject(CatalogScopeService)),
|
|
596
677
|
__param(3, Inject(PluginImport)),
|
|
597
|
-
__param(4, Inject(KernelDb))
|
|
678
|
+
__param(4, Inject(KernelDb)),
|
|
679
|
+
__param(5, Inject(AppearanceService)),
|
|
680
|
+
__param(6, Inject(ThemesService))
|
|
598
681
|
], CatalogAdminOperations);
|
|
599
682
|
export { CatalogAdminOperations };
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { type OnModuleInit } from "@nestjs/common";
|
|
2
|
+
import { AppearanceService } from "../../catalog/appearance.service.js";
|
|
2
3
|
import { CatalogScopeService } from "../../catalog/catalog-scope.service.js";
|
|
3
4
|
import { OrgCatalogService } from "../../catalog/org-catalog.service.js";
|
|
4
5
|
import { OrgPluginUploads } from "../../catalog/org-plugin-uploads.js";
|
|
5
6
|
import { OwnedItems } from "../../catalog/owned-items.js";
|
|
6
7
|
import { PluginInstaller } from "../../catalog/plugin-install.js";
|
|
7
8
|
import { KernelDb } from "../../infra/database/kernel-db.js";
|
|
9
|
+
import { ThemesService } from "../../themes/themes.service.js";
|
|
8
10
|
import { PlatformOperations } from "../registry.js";
|
|
9
11
|
/**
|
|
10
12
|
* The org's own plugin operations of the `Platform` tool (spec §14.2): the org's plugin installs
|
|
@@ -20,7 +22,11 @@ export declare class OrgPluginsOperations implements OnModuleInit {
|
|
|
20
22
|
private readonly owned;
|
|
21
23
|
private readonly scope;
|
|
22
24
|
private readonly kdb;
|
|
23
|
-
|
|
25
|
+
private readonly appearance;
|
|
26
|
+
private readonly themes;
|
|
27
|
+
constructor(registry: PlatformOperations, installer: PluginInstaller, orgCatalog: OrgCatalogService, uploads: OrgPluginUploads, owned: OwnedItems, scope: CatalogScopeService, kdb: KernelDb, appearance: AppearanceService, themes: ThemesService);
|
|
28
|
+
/** The display name of the live plugin of `extension`, or the name when it cannot be read. */
|
|
29
|
+
private pluginNames;
|
|
24
30
|
/** The org's own live plugin `id`, or null (another org's, a platform one, deleted, unknown). */
|
|
25
31
|
private ownedItem;
|
|
26
32
|
/** The precheck refusal of a plugin the org does not own. */
|
|
@@ -9,20 +9,23 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
9
9
|
};
|
|
10
10
|
import { Inject, Injectable } from "@nestjs/common";
|
|
11
11
|
import { z } from "zod";
|
|
12
|
+
import { AppearanceService } from "../../catalog/appearance.service.js";
|
|
12
13
|
import { CatalogScopeService } from "../../catalog/catalog-scope.service.js";
|
|
13
14
|
import { PluginInstallPathParams } from "../../catalog/org-catalog.schemas.js";
|
|
14
15
|
import { OrgCatalogService } from "../../catalog/org-catalog.service.js";
|
|
16
|
+
import { OrgExtensionParams } from "../../catalog/org-extension.schemas.js";
|
|
15
17
|
import { OrgPluginUploads } from "../../catalog/org-plugin-uploads.js";
|
|
16
18
|
import { OrgOwnedDeleteBody, OrgOwnedPluginParams, OrgOwnedShareableBody, OrgUploadStagingParams, } from "../../catalog/org-plugin-uploads.schemas.js";
|
|
17
19
|
import { OwnedItems } from "../../catalog/owned-items.js";
|
|
18
20
|
import { PluginInstaller } from "../../catalog/plugin-install.js";
|
|
19
21
|
import { KernelDb } from "../../infra/database/kernel-db.js";
|
|
22
|
+
import { ThemesService } from "../../themes/themes.service.js";
|
|
20
23
|
import { defineOperation } from "../operation.js";
|
|
21
24
|
import { PlatformOperations } from "../registry.js";
|
|
22
|
-
import { reviewBrief } from "./catalog-admin.ops.js";
|
|
25
|
+
import { appearanceSummary, reviewBrief } from "./catalog-admin.ops.js";
|
|
23
26
|
import { precheckBy, viaAgent } from "./errors.js";
|
|
24
27
|
import { Cursor, pageOf, startOf } from "./paging.js";
|
|
25
|
-
import { named } from "./summary.js";
|
|
28
|
+
import { localized, named, orElse } from "./summary.js";
|
|
26
29
|
const InstallId = PluginInstallPathParams.shape.installId.max(100);
|
|
27
30
|
/** An org-owned plugin's catalog item id (`:id` of the owned-plugins routes). */
|
|
28
31
|
const OwnedId = OrgOwnedPluginParams.shape.id;
|
|
@@ -51,7 +54,9 @@ let OrgPluginsOperations = class OrgPluginsOperations {
|
|
|
51
54
|
owned;
|
|
52
55
|
scope;
|
|
53
56
|
kdb;
|
|
54
|
-
|
|
57
|
+
appearance;
|
|
58
|
+
themes;
|
|
59
|
+
constructor(registry, installer, orgCatalog, uploads, owned, scope, kdb, appearance, themes) {
|
|
55
60
|
this.registry = registry;
|
|
56
61
|
this.installer = installer;
|
|
57
62
|
this.orgCatalog = orgCatalog;
|
|
@@ -59,6 +64,20 @@ let OrgPluginsOperations = class OrgPluginsOperations {
|
|
|
59
64
|
this.owned = owned;
|
|
60
65
|
this.scope = scope;
|
|
61
66
|
this.kdb = kdb;
|
|
67
|
+
this.appearance = appearance;
|
|
68
|
+
this.themes = themes;
|
|
69
|
+
}
|
|
70
|
+
/** The display name of the live plugin of `extension`, or the name when it cannot be read. */
|
|
71
|
+
pluginNames(extension) {
|
|
72
|
+
return orElse(async () => {
|
|
73
|
+
const row = await this.kdb.db
|
|
74
|
+
.selectFrom("catalog_items")
|
|
75
|
+
.select("display_name")
|
|
76
|
+
.where("name", "=", extension)
|
|
77
|
+
.where("deleted_at", "is", null)
|
|
78
|
+
.executeTakeFirst();
|
|
79
|
+
return localized(row?.display_name, extension);
|
|
80
|
+
}, { zh: named(extension), en: named(extension) });
|
|
62
81
|
}
|
|
63
82
|
/** The org's own live plugin `id`, or null (another org's, a platform one, deleted, unknown). */
|
|
64
83
|
async ownedItem(orgId, id) {
|
|
@@ -313,6 +332,44 @@ let OrgPluginsOperations = class OrgPluginsOperations {
|
|
|
313
332
|
return { withdrawn: input.itemId };
|
|
314
333
|
},
|
|
315
334
|
}),
|
|
335
|
+
defineOperation({
|
|
336
|
+
id: "plugins.apply-appearance",
|
|
337
|
+
description: "Apply a plugin's suggested appearance in this org (org admins; the 扩展 page's 应用此插件的外观), for a plugin whose extension is on here: in one step, its theme becomes the org's default theme (whether members may pick their own stays as it is) and each component it suggests replaces the built-in block of its slot (shell.brand, conversation.empty, conversation.composer.frame) in the org's arrangement. The card shows exactly what changes; a theme that is not on in the org refuses the whole call, a component that is not available is skipped. Asked every time, never remembered.",
|
|
338
|
+
input: z.object({ extension: OrgExtensionParams.shape.extension }).strict(),
|
|
339
|
+
example: { extension: "theme-cyberpunk" },
|
|
340
|
+
authz: { orgAction: "org.update" },
|
|
341
|
+
mirrors: ["POST /api/v1/orgs/:orgId/extensions/:extension/appearance"],
|
|
342
|
+
// the route's dry run is this operation's card and precheck, never a confirmed call
|
|
343
|
+
omits: ["dryRun"],
|
|
344
|
+
confirm: "org",
|
|
345
|
+
rememberable: false,
|
|
346
|
+
errors: [
|
|
347
|
+
{
|
|
348
|
+
code: "extension.not_found",
|
|
349
|
+
next: "Check the extension with extensions.list: it must be on in this org and its plugin must suggest an appearance.",
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
code: "theme.not_available",
|
|
353
|
+
next: {
|
|
354
|
+
orgAdmin: "The plugin's theme is not on in this org: switch it on in the org's appearance settings on the web, or ask the super admin to offer it to this org, then apply again.",
|
|
355
|
+
member: "Ask an org admin to switch the plugin's theme on in this org first.",
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
],
|
|
359
|
+
precheck: (i, ctx) => precheckBy(() => this.appearance.apply({ orgId: ctx.orgId }, i.extension, { accountId: ctx.actorAccountId }, true)),
|
|
360
|
+
summary: async (i, ctx) => {
|
|
361
|
+
const [n, r] = await Promise.all([
|
|
362
|
+
this.pluginNames(i.extension),
|
|
363
|
+
this.appearance.apply({ orgId: ctx.orgId }, i.extension, { accountId: ctx.actorAccountId }, true),
|
|
364
|
+
]);
|
|
365
|
+
const theme = r.theme && localized(await this.themes.nameIn(ctx.orgId, r.theme.to), r.theme.to);
|
|
366
|
+
return appearanceSummary("org", n, r, theme);
|
|
367
|
+
},
|
|
368
|
+
run: async (input, ctx) => this.appearance.apply({ orgId: ctx.orgId }, input.extension, {
|
|
369
|
+
accountId: ctx.actorAccountId,
|
|
370
|
+
auditDetail: viaAgent("plugins.apply-appearance", ctx),
|
|
371
|
+
}, false),
|
|
372
|
+
}),
|
|
316
373
|
]);
|
|
317
374
|
}
|
|
318
375
|
};
|
|
@@ -324,6 +381,8 @@ OrgPluginsOperations = __decorate([
|
|
|
324
381
|
__param(3, Inject(OrgPluginUploads)),
|
|
325
382
|
__param(4, Inject(OwnedItems)),
|
|
326
383
|
__param(5, Inject(CatalogScopeService)),
|
|
327
|
-
__param(6, Inject(KernelDb))
|
|
384
|
+
__param(6, Inject(KernelDb)),
|
|
385
|
+
__param(7, Inject(AppearanceService)),
|
|
386
|
+
__param(8, Inject(ThemesService))
|
|
328
387
|
], OrgPluginsOperations);
|
|
329
388
|
export { OrgPluginsOperations };
|
|
@@ -39,7 +39,7 @@ let OrgSettingsOperations = class OrgSettingsOperations {
|
|
|
39
39
|
this.registry.register([
|
|
40
40
|
defineOperation({
|
|
41
41
|
id: "org-settings.get",
|
|
42
|
-
description: "Read this org's settings: toolSearch, locale, sandboxToolPolicy, platformTool (on, read-only or off), sandboxEgress and wakeOnBackgroundTask (absent means it follows the deployment), theme (the org's default theme id, null or absent following the platform, and personal: allowed or locked), uiArrangement (per
|
|
42
|
+
description: "Read this org's settings: toolSearch, locale, sandboxToolPolicy, platformTool (on, read-only or off), sandboxEgress and wakeOnBackgroundTask (absent means it follows the deployment), theme (the org's default theme id, null or absent following the platform, and personal: allowed or locked), uiArrangement (per list slot, the extension contributions the org hides and their order; per single slot, the contribution that replaces the built-in as use, null for the built-in; a slot not listed follows the platform), the deployment's sandbox egress default and the sandbox cpu / memory limits in effect.",
|
|
43
43
|
input: z.object({}).strict(),
|
|
44
44
|
example: {},
|
|
45
45
|
authz: { orgAction: "org.read" },
|
|
@@ -53,7 +53,7 @@ let OrgSettingsOperations = class OrgSettingsOperations {
|
|
|
53
53
|
}),
|
|
54
54
|
defineOperation({
|
|
55
55
|
id: "org-settings.update",
|
|
56
|
-
description: "Change this org's settings (org admins). Give only the settings to change; null clears sandboxEgress or wakeOnBackgroundTask so they follow the deployment again. theme: { id, personal } sets the org's default theme (a theme id from themes.list, or null to follow the platform) and whether members may pick their own (allowed) or not (locked); null clears it. uiArrangement: { <slot>: { hidden, order } } replaces that slot's entry, where hidden and
|
|
56
|
+
description: "Change this org's settings (org admins). Give only the settings to change; null clears sandboxEgress or wakeOnBackgroundTask so they follow the deployment again. theme: { id, personal } sets the org's default theme (a theme id from themes.list, or null to follow the platform) and whether members may pick their own (allowed) or not (locked); null clears it. uiArrangement: { <slot>: { hidden, order } } (a list slot) or { <slot>: { use } } (a single slot: shell.brand, conversation.empty, conversation.composer.frame) replaces that slot's entry, where hidden, order and use name contribution keys <extension>/<contribution> (hidden ones are not listed in the web UI but their pages still open by URL; use null is the built-in); null for a slot makes it follow the platform again; platform.page is refused. Setting platformTool to off or read-only takes effect from the next call and cuts this Platform tool off in the org, so say so before asking; an org admin switches it back on in the web settings.",
|
|
57
57
|
input: OrgSettingsPatchSchema.refine((i) => Object.values(i).some((v) => v !== undefined), {
|
|
58
58
|
message: "give at least one setting",
|
|
59
59
|
}),
|
|
@@ -22,7 +22,7 @@ let UiOperations = class UiOperations {
|
|
|
22
22
|
this.registry.register([
|
|
23
23
|
defineOperation({
|
|
24
24
|
id: "ui.slots",
|
|
25
|
-
description: "List the UI slots an extension can contribute to (id, version, where it is, scope and args, accepted kinds view / embed, minimum role, anchors). Use a slot id as a ui contribution's `slot` in a plugin manifest.",
|
|
25
|
+
description: "List the UI slots an extension can contribute to (id, version, where it is, scope and args, accepted kinds view / embed / component, cardinality list or single, minimum role, anchors). Use a slot id as a ui contribution's `slot` in a plugin manifest.",
|
|
26
26
|
input: z.object({}).strict(),
|
|
27
27
|
example: {},
|
|
28
28
|
// any signed-in person may call the route; every run's actor is a member of this org
|
|
@@ -7,6 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
import { Module } from "@nestjs/common";
|
|
8
8
|
import { AgentsModule } from "../agents/agents.module.js";
|
|
9
9
|
import { BackgroundModule } from "../background/background.module.js";
|
|
10
|
+
import { AppearanceModule } from "../catalog/appearance.service.js";
|
|
10
11
|
import { CatalogModule } from "../catalog/catalog.module.js";
|
|
11
12
|
import { ChannelsModule } from "../channels/channels.module.js";
|
|
12
13
|
import { ConversationsModule } from "../conversations/conversations.module.js";
|
|
@@ -114,6 +115,7 @@ PlatformOpsModule = __decorate([
|
|
|
114
115
|
AgentsModule,
|
|
115
116
|
OrgsModule,
|
|
116
117
|
CatalogModule,
|
|
118
|
+
AppearanceModule,
|
|
117
119
|
ExtensionsModule,
|
|
118
120
|
ToolsModule,
|
|
119
121
|
ConversationsModule,
|
|
@@ -4,6 +4,7 @@ import { AGENT_ROUTES } from "../agents/agents.schemas.js";
|
|
|
4
4
|
import { AGENT_TEMPLATE_ROUTES } from "../agents/templates.schemas.js";
|
|
5
5
|
import { BACKGROUND_ROUTES } from "../background/background.schemas.js";
|
|
6
6
|
import { ADMIN_CATALOG_ROUTES } from "../catalog/admin-catalog.schemas.js";
|
|
7
|
+
import { APPEARANCE_ROUTES } from "../catalog/appearance.schemas.js";
|
|
7
8
|
import { COMPONENT_SCOPE_ROUTES } from "../catalog/component-scope.schemas.js";
|
|
8
9
|
import { MARKETPLACE_ROUTES } from "../catalog/marketplaces.schemas.js";
|
|
9
10
|
import { ORG_CATALOG_ROUTES } from "../catalog/org-catalog.schemas.js";
|
|
@@ -66,6 +67,8 @@ export const ROUTE_SCHEMAS = [
|
|
|
66
67
|
...ME_ROUTES,
|
|
67
68
|
// themes (themes spec §9)
|
|
68
69
|
...THEME_ROUTES,
|
|
70
|
+
// a plugin's suggested appearance (client extensions spec §6.5)
|
|
71
|
+
...APPEARANCE_ROUTES,
|
|
69
72
|
// setup guide checks (setup guides v2 §10.3, §12)
|
|
70
73
|
...SETUP_CHECK_ROUTES,
|
|
71
74
|
...SETUP_GUIDE_ROUTES,
|
|
@@ -14,6 +14,8 @@ import { PlatformMigration } from "../catalog/platform-migration.js";
|
|
|
14
14
|
import { PluginScopeWorkersModule } from "../catalog/scope-sync-workers.js";
|
|
15
15
|
import { ChannelsModule, ChannelWorkersModule } from "../channels/channels.module.js";
|
|
16
16
|
import { GatewayCheckJob } from "../channels/supervisor.js";
|
|
17
|
+
import { ClientModule } from "../client/client.module.js";
|
|
18
|
+
import { ClientReportsCron } from "../client/client-reports.cron.js";
|
|
17
19
|
import { AttachmentsModule } from "../conversations/attachments.module.js";
|
|
18
20
|
import { AttachmentCleanupJob } from "../conversations/attachments.service.js";
|
|
19
21
|
import { EventLogModule } from "../conversations/event-log.js";
|
|
@@ -55,6 +57,7 @@ let WorkerModule = WorkerModule_1 = class WorkerModule {
|
|
|
55
57
|
module: WorkerModule_1,
|
|
56
58
|
imports: [
|
|
57
59
|
KernelScheduleModule,
|
|
60
|
+
ClientModule,
|
|
58
61
|
// the queue consumers (their worker options come from the startup config)
|
|
59
62
|
RuntimeWorkersModule.forRoot(config),
|
|
60
63
|
EvalWorkersModule.forRoot(config),
|
|
@@ -87,6 +90,7 @@ let WorkerModule = WorkerModule_1 = class WorkerModule {
|
|
|
87
90
|
BackgroundTaskPollJob,
|
|
88
91
|
ToolJobs,
|
|
89
92
|
InitialPasswordCleanupJob,
|
|
93
|
+
ClientReportsCron,
|
|
90
94
|
CatalogJobs,
|
|
91
95
|
PlatformMigration,
|
|
92
96
|
DirectoryScanJob,
|
|
@@ -7,6 +7,7 @@ import { type ParsedTheme } from "@capybara-hq/plugin-sdk";
|
|
|
7
7
|
* the theme's families first, then the built-in stack. Every stored value is re-validated (token
|
|
8
8
|
* names against the overridable list, values against the SDK grammar, radius 0-16, families,
|
|
9
9
|
* paths, weights, styles, roles); one that fails is left out, and `onSkipped` (when given and
|
|
10
|
-
* something was skipped) is called once with the paths of every skipped value.
|
|
10
|
+
* something was skipped) is called once with the paths of every skipped value. The whole
|
|
11
|
+
* stylesheet is one `@layer theme { … }` block (client extensions spec §7.2).
|
|
11
12
|
*/
|
|
12
13
|
export declare function renderThemeCss(theme: ParsedTheme, assetUrl: (pluginPath: string) => string, onSkipped?: (paths: string[]) => void): string;
|
|
@@ -73,7 +73,8 @@ const declarations = (d, extra = []) => [...extra, ...[...d].map(([k, v]) => `${
|
|
|
73
73
|
* the theme's families first, then the built-in stack. Every stored value is re-validated (token
|
|
74
74
|
* names against the overridable list, values against the SDK grammar, radius 0-16, families,
|
|
75
75
|
* paths, weights, styles, roles); one that fails is left out, and `onSkipped` (when given and
|
|
76
|
-
* something was skipped) is called once with the paths of every skipped value.
|
|
76
|
+
* something was skipped) is called once with the paths of every skipped value. The whole
|
|
77
|
+
* stylesheet is one `@layer theme { … }` block (client extensions spec §7.2).
|
|
77
78
|
*/
|
|
78
79
|
export function renderThemeCss(theme, assetUrl, onSkipped) {
|
|
79
80
|
const skipped = [];
|
|
@@ -128,5 +129,7 @@ export function renderThemeCss(theme, assetUrl, onSkipped) {
|
|
|
128
129
|
rules.push(`:root { ${stacks.join(" ")} }`);
|
|
129
130
|
if (skipped.length > 0)
|
|
130
131
|
onSkipped?.(skipped);
|
|
131
|
-
|
|
132
|
+
// One block in the `theme` cascade layer (client extensions spec §7.2): the design stylesheet
|
|
133
|
+
// declares the order design, theme, extensions; an unlayered theme would beat plugin CSS.
|
|
134
|
+
return `@layer theme {\n${rules.join("\n")}\n}\n`;
|
|
132
135
|
}
|
|
@@ -22,9 +22,11 @@ export declare class ThemeDeliveryService {
|
|
|
22
22
|
/**
|
|
23
23
|
* The stylesheet of version `version` of theme `id` ({@link renderThemeCss}, asset URLs from
|
|
24
24
|
* {@link themeUrl}); a stored value that fails re-validation is left out with one warning per
|
|
25
|
-
* render naming every skipped value. Unknown theme or version
|
|
25
|
+
* render naming every skipped value. Unknown theme or version, or a `revision` other than the
|
|
26
|
+
* current `r<THEME_CSS_REVISION>` (an older one promised another format; a later one belongs to a
|
|
27
|
+
* newer replica): 404 `theme.not_found`, never cached.
|
|
26
28
|
*/
|
|
27
|
-
stylesheet(id: string, version: string): Promise<string>;
|
|
29
|
+
stylesheet(id: string, version: string, revision: string): Promise<string>;
|
|
28
30
|
/**
|
|
29
31
|
* Asset `file` (a path inside the plugin, as stored: `fonts/MiSans.woff2`) of version `version`
|
|
30
32
|
* of theme `id`: only a font or logo the version's definition names. Unknown theme, version or
|
|
@@ -15,7 +15,7 @@ import { KERNEL_LOGGER } from "../infra/tokens.js";
|
|
|
15
15
|
import { packagePath } from "../packages/tgz.js";
|
|
16
16
|
import { StorageService } from "../storage/storage.service.js";
|
|
17
17
|
import { renderThemeCss } from "./render-css.js";
|
|
18
|
-
import { themeUrl } from "./theme-state.js";
|
|
18
|
+
import { THEME_CSS_REVISION, themeUrl } from "./theme-state.js";
|
|
19
19
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
20
20
|
/** A version number as it appears in a delivery URL: a positive int4. */
|
|
21
21
|
const VERSION_RE = /^[1-9]\d{0,9}$/;
|
|
@@ -61,9 +61,14 @@ let ThemeDeliveryService = class ThemeDeliveryService {
|
|
|
61
61
|
/**
|
|
62
62
|
* The stylesheet of version `version` of theme `id` ({@link renderThemeCss}, asset URLs from
|
|
63
63
|
* {@link themeUrl}); a stored value that fails re-validation is left out with one warning per
|
|
64
|
-
* render naming every skipped value. Unknown theme or version
|
|
64
|
+
* render naming every skipped value. Unknown theme or version, or a `revision` other than the
|
|
65
|
+
* current `r<THEME_CSS_REVISION>` (an older one promised another format; a later one belongs to a
|
|
66
|
+
* newer replica): 404 `theme.not_found`, never cached.
|
|
65
67
|
*/
|
|
66
|
-
async stylesheet(id, version) {
|
|
68
|
+
async stylesheet(id, version, revision) {
|
|
69
|
+
if (revision !== `r${THEME_CSS_REVISION}`) {
|
|
70
|
+
throw notFound(`Theme ${id} version ${version} stylesheet revision ${revision}`);
|
|
71
|
+
}
|
|
67
72
|
const { definition } = await this.version(id, version);
|
|
68
73
|
return renderThemeCss(definition, (path) => themeUrl(id, version, path), (skipped) => this.log.warn("theme stylesheet: stored values failed re-validation and were left out", {
|
|
69
74
|
themeId: id,
|
|
@@ -6,6 +6,11 @@ import { ApiError } from "../errors.js";
|
|
|
6
6
|
export interface ThemeRef {
|
|
7
7
|
/** The `platform_themes` id (what settings and picks store). */
|
|
8
8
|
id: string;
|
|
9
|
+
/**
|
|
10
|
+
* `"<plugin>/<theme file id>"`: the name the web's `theme=` state token and `useTheme()` carry
|
|
11
|
+
* (client extensions spec §7.3). Stable across versions, unlike `id`.
|
|
12
|
+
*/
|
|
13
|
+
key: string;
|
|
9
14
|
/** Its current version. */
|
|
10
15
|
version: string;
|
|
11
16
|
name: LocalizedName;
|
|
@@ -19,7 +24,7 @@ export interface ThemeRef {
|
|
|
19
24
|
wordmark: string | null;
|
|
20
25
|
faviconUrl: string | null;
|
|
21
26
|
} | null;
|
|
22
|
-
/** `/api/v1/themes/<id>/<version>/theme.css`. */
|
|
27
|
+
/** `/api/v1/themes/<id>/<version>/r<THEME_CSS_REVISION>/theme.css`. */
|
|
23
28
|
cssUrl: string;
|
|
24
29
|
}
|
|
25
30
|
/** The four colors a theme picker draws per scheme. */
|
|
@@ -34,24 +39,40 @@ export type ListedTheme = ThemeRef & {
|
|
|
34
39
|
swatches: Partial<Record<ThemeScheme, ThemeSwatch>>;
|
|
35
40
|
};
|
|
36
41
|
/**
|
|
37
|
-
* The
|
|
42
|
+
* The render revision of theme stylesheets, a segment of the stylesheet URL (`r<N>/theme.css`).
|
|
43
|
+
* The stylesheet is cached as immutable for a year, keyed by its URL: bump this whenever
|
|
44
|
+
* `renderThemeCss`'s output format changes, so no browser keeps an older format under a URL
|
|
45
|
+
* that now means the new one. The delivery route serves only the current revision (404
|
|
46
|
+
* `theme.not_found` otherwise). History: (none, `<version>/theme.css`) plain rules; 2: one
|
|
47
|
+
* `@layer theme { … }` block (client extensions spec §7.2).
|
|
48
|
+
*/
|
|
49
|
+
export declare const THEME_CSS_REVISION = 2;
|
|
50
|
+
/**
|
|
51
|
+
* The delivery URL of a theme version (spec §8): its stylesheet (with the render revision,
|
|
52
|
+
* {@link THEME_CSS_REVISION}), or with `asset` (a path inside the
|
|
38
53
|
* plugin as the theme file names it, e.g. `./fonts/MiSans.woff2`) that asset, as one path segment
|
|
39
54
|
* (`assets/fonts%2FMiSans.woff2`; the route decodes it back to the stored path `fonts/MiSans.woff2`).
|
|
40
55
|
* The one builder of these URLs: the listing, the stylesheet and the asset routes all use it.
|
|
41
56
|
*/
|
|
42
57
|
export declare function themeUrl(id: string, version: string, asset?: string): string;
|
|
43
|
-
/**
|
|
44
|
-
|
|
58
|
+
/**
|
|
59
|
+
* The {@link ThemeRef} of version `version` of theme `id` of plugin `plugin` (null only for a row
|
|
60
|
+
* without provenance: the key is then the file id alone).
|
|
61
|
+
*/
|
|
62
|
+
export declare function themeRefOf(id: string, version: string, def: ParsedTheme, plugin: string | null): ThemeRef;
|
|
45
63
|
/**
|
|
46
64
|
* The swatches of each scheme the theme defines: its overrides merged onto the current built-in
|
|
47
65
|
* values (a stored definition holds only the theme file's own values, themes spec §6).
|
|
48
66
|
*/
|
|
49
67
|
export declare function themeSwatches(def: ParsedTheme): Partial<Record<ThemeScheme, ThemeSwatch>>;
|
|
50
|
-
/**
|
|
51
|
-
export
|
|
68
|
+
/** A live theme's current definition and its plugin (null: no provenance). */
|
|
69
|
+
export interface ThemeDefinition {
|
|
52
70
|
version: string;
|
|
53
71
|
definition: ParsedTheme;
|
|
54
|
-
|
|
72
|
+
plugin: string | null;
|
|
73
|
+
}
|
|
74
|
+
/** The current definitions of live themes `ids`, by id. */
|
|
75
|
+
export declare function themeDefinitions(db: KernelSchemaDb, ids: readonly string[]): Promise<Map<string, ThemeDefinition>>;
|
|
55
76
|
/** The live themes on in the org, sorted by id. */
|
|
56
77
|
export declare function themesOnIn(db: KernelSchemaDb, orgId: string): Promise<ComponentRow[]>;
|
|
57
78
|
/** The live theme `id` if it can be the platform default, else null. */
|
|
@@ -9,7 +9,17 @@ import { packagePath } from "../packages/tgz.js";
|
|
|
9
9
|
/** The schemes in the order they are listed. */
|
|
10
10
|
const SCHEMES = ["dark", "light"];
|
|
11
11
|
/**
|
|
12
|
-
* The
|
|
12
|
+
* The render revision of theme stylesheets, a segment of the stylesheet URL (`r<N>/theme.css`).
|
|
13
|
+
* The stylesheet is cached as immutable for a year, keyed by its URL: bump this whenever
|
|
14
|
+
* `renderThemeCss`'s output format changes, so no browser keeps an older format under a URL
|
|
15
|
+
* that now means the new one. The delivery route serves only the current revision (404
|
|
16
|
+
* `theme.not_found` otherwise). History: (none, `<version>/theme.css`) plain rules; 2: one
|
|
17
|
+
* `@layer theme { … }` block (client extensions spec §7.2).
|
|
18
|
+
*/
|
|
19
|
+
export const THEME_CSS_REVISION = 2;
|
|
20
|
+
/**
|
|
21
|
+
* The delivery URL of a theme version (spec §8): its stylesheet (with the render revision,
|
|
22
|
+
* {@link THEME_CSS_REVISION}), or with `asset` (a path inside the
|
|
13
23
|
* plugin as the theme file names it, e.g. `./fonts/MiSans.woff2`) that asset, as one path segment
|
|
14
24
|
* (`assets/fonts%2FMiSans.woff2`; the route decodes it back to the stored path `fonts/MiSans.woff2`).
|
|
15
25
|
* The one builder of these URLs: the listing, the stylesheet and the asset routes all use it.
|
|
@@ -17,14 +27,18 @@ const SCHEMES = ["dark", "light"];
|
|
|
17
27
|
export function themeUrl(id, version, asset) {
|
|
18
28
|
const base = `/api/v1/themes/${encodeURIComponent(id)}/${encodeURIComponent(version)}`;
|
|
19
29
|
return asset === undefined
|
|
20
|
-
? `${base}/theme.css`
|
|
30
|
+
? `${base}/r${THEME_CSS_REVISION}/theme.css`
|
|
21
31
|
: `${base}/assets/${encodeURIComponent(packagePath(asset))}`;
|
|
22
32
|
}
|
|
23
|
-
/**
|
|
24
|
-
|
|
33
|
+
/**
|
|
34
|
+
* The {@link ThemeRef} of version `version` of theme `id` of plugin `plugin` (null only for a row
|
|
35
|
+
* without provenance: the key is then the file id alone).
|
|
36
|
+
*/
|
|
37
|
+
export function themeRefOf(id, version, def, plugin) {
|
|
25
38
|
const url = (ref) => (ref === undefined ? null : themeUrl(id, version, ref));
|
|
26
39
|
return {
|
|
27
40
|
id,
|
|
41
|
+
key: plugin === null ? def.id : `${plugin}/${def.id}`,
|
|
28
42
|
version,
|
|
29
43
|
name: def.name,
|
|
30
44
|
schemes: SCHEMES.filter((s) => def.schemes[s] !== undefined),
|
|
@@ -62,12 +76,16 @@ export async function themeDefinitions(db, ids) {
|
|
|
62
76
|
const rows = await db
|
|
63
77
|
.selectFrom("platform_themes as p")
|
|
64
78
|
.innerJoin("platform_theme_versions as v", (j) => j.onRef("v.theme_id", "=", "p.id").onRef("v.version", "=", "p.current_version"))
|
|
65
|
-
.select(["p.id", "p.current_version", "v.definition"])
|
|
79
|
+
.select(["p.id", "p.current_version", "p.plugin_name", "v.definition"])
|
|
66
80
|
.where("p.id", "in", [...ids])
|
|
67
81
|
.where("p.deleted_at", "is", null)
|
|
68
82
|
.execute();
|
|
69
83
|
for (const r of rows) {
|
|
70
|
-
out.set(r.id, {
|
|
84
|
+
out.set(r.id, {
|
|
85
|
+
version: String(r.current_version),
|
|
86
|
+
definition: r.definition,
|
|
87
|
+
plugin: r.plugin_name,
|
|
88
|
+
});
|
|
71
89
|
}
|
|
72
90
|
return out;
|
|
73
91
|
}
|
|
@@ -10,8 +10,13 @@ import { ThemesService } from "./themes.service.js";
|
|
|
10
10
|
export declare class ThemeDeliveryController {
|
|
11
11
|
private readonly delivery;
|
|
12
12
|
constructor(delivery: ThemeDeliveryService);
|
|
13
|
-
/**
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* The version's stylesheet: scheme overrides, `@font-face` rules and font stacks, in the render
|
|
15
|
+
* revision `revision` (`r<THEME_CSS_REVISION>`; only the current one is served). The URL without
|
|
16
|
+
* a revision (before revision 2) has no route: 404, so a browser's cached unlayered copy is never
|
|
17
|
+
* answered with the new format under an immutable cache.
|
|
18
|
+
*/
|
|
19
|
+
css(id: string, version: string, revision: string, reply: FastifyReply): Promise<void>;
|
|
15
20
|
/**
|
|
16
21
|
* A font (`font/woff2`) or logo (`image/svg+xml`, sanitized at import, served under a CSP
|
|
17
22
|
* sandbox; `image/png`) the version names; `file` is its plugin path as one segment
|