@capybara-hq/kernel 0.1.0-rc.10 → 0.1.0-rc.12
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 +3 -0
- package/dist/agents/agents.schemas.d.ts +3 -0
- package/dist/agents/agents.service.js +11 -1
- package/dist/agents/definition.d.ts +1 -0
- package/dist/agents/definition.js +7 -3
- package/dist/agents/org-templates.js +4 -0
- package/dist/agents/pending-connectors.service.js +1 -1
- package/dist/catalog/catalog-content.d.ts +33 -1
- package/dist/catalog/catalog-content.js +65 -2
- package/dist/catalog/catalog-items.js +1 -0
- package/dist/catalog/catalog-scope.service.d.ts +29 -3
- package/dist/catalog/catalog-scope.service.js +82 -10
- package/dist/catalog/catalog.service.d.ts +7 -5
- package/dist/catalog/catalog.service.js +26 -5
- package/dist/catalog/claude/translate.js +3 -0
- package/dist/catalog/component-scope.controller.d.ts +18 -6
- package/dist/catalog/component-scope.controller.js +36 -13
- package/dist/catalog/component-scope.schemas.d.ts +4 -0
- package/dist/catalog/component-scope.service.d.ts +59 -9
- package/dist/catalog/component-scope.service.js +156 -27
- package/dist/catalog/component-state.d.ts +20 -3
- package/dist/catalog/component-state.js +40 -14
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/catalog/org-catalog.service.js +3 -0
- package/dist/catalog/owned-items.js +7 -0
- package/dist/catalog/platform-components.d.ts +23 -3
- package/dist/catalog/platform-components.js +118 -9
- package/dist/catalog/platform-migration.js +2 -1
- package/dist/catalog/plugin-import.d.ts +24 -4
- package/dist/catalog/plugin-import.js +41 -7
- package/dist/catalog/plugin-install.d.ts +20 -1
- package/dist/catalog/plugin-install.js +71 -1
- package/dist/catalog/plugin-package.d.ts +41 -4
- package/dist/catalog/plugin-package.js +127 -7
- package/dist/catalog/plugin-upgrade.d.ts +13 -2
- package/dist/catalog/plugin-upgrade.js +124 -6
- package/dist/catalog/unshare.js +1 -0
- package/dist/conversations/conversations.service.d.ts +9 -0
- package/dist/conversations/conversations.service.js +9 -2
- package/dist/conversations/events.d.ts +1 -0
- package/dist/db/kernel-schema.d.ts +54 -4
- package/dist/errors.d.ts +27 -0
- package/dist/errors.js +27 -0
- package/dist/http/api.module.js +7 -0
- package/dist/identity/accounts.d.ts +11 -4
- package/dist/identity/accounts.js +23 -10
- package/dist/identity/me.controller.d.ts +10 -10
- package/dist/identity/me.controller.js +8 -6
- package/dist/identity/me.schemas.d.ts +13 -3
- package/dist/identity/me.schemas.js +10 -2
- package/dist/identity/settings.d.ts +27 -0
- package/dist/identity/settings.js +64 -0
- package/dist/orgs/settings.service.d.ts +31 -0
- package/dist/orgs/settings.service.js +31 -1
- package/dist/platform/exclusions.js +26 -0
- package/dist/platform/operation.js +6 -2
- package/dist/platform/ops/admin.ops.d.ts +3 -1
- package/dist/platform/ops/admin.ops.js +25 -7
- package/dist/platform/ops/agents.ops.js +19 -6
- package/dist/platform/ops/components.ops.js +1 -0
- package/dist/platform/ops/org-settings.ops.d.ts +3 -1
- package/dist/platform/ops/org-settings.ops.js +33 -8
- package/dist/platform/ops/orgs.ops.js +1 -0
- package/dist/platform/ops/platform.ops.d.ts +3 -1
- package/dist/platform/ops/platform.ops.js +43 -16
- package/dist/platform/ops/themes.ops.d.ts +14 -0
- package/dist/platform/ops/themes.ops.js +47 -0
- package/dist/platform/platform-ops.module.d.ts +2 -1
- package/dist/platform/platform-ops.module.js +5 -0
- package/dist/platform/route-schemas.js +3 -0
- package/dist/runtime/subagents/definition.d.ts +3 -2
- package/dist/runtime/subagents/definition.js +2 -1
- package/dist/themes/render-css.d.ts +12 -0
- package/dist/themes/render-css.js +132 -0
- package/dist/themes/theme-delivery.service.d.ts +34 -0
- package/dist/themes/theme-delivery.service.js +111 -0
- package/dist/themes/theme-state.d.ts +95 -0
- package/dist/themes/theme-state.js +203 -0
- package/dist/themes/theme-usage.d.ts +61 -0
- package/dist/themes/theme-usage.js +98 -0
- package/dist/themes/themes-public.controller.d.ts +28 -0
- package/dist/themes/themes-public.controller.js +92 -0
- package/dist/themes/themes.controller.d.ts +13 -0
- package/dist/themes/themes.controller.js +38 -0
- package/dist/themes/themes.module.d.ts +3 -0
- package/dist/themes/themes.module.js +21 -0
- package/dist/themes/themes.schemas.d.ts +3 -0
- package/dist/themes/themes.schemas.js +2 -0
- package/dist/themes/themes.service.d.ts +50 -0
- package/dist/themes/themes.service.js +99 -0
- package/dist/tools/connector-account-policies.d.ts +51 -0
- package/dist/tools/connector-account-policies.js +165 -0
- package/dist/tools/connectors.controller.d.ts +35 -4
- package/dist/tools/connectors.controller.js +66 -7
- package/dist/tools/connectors.schemas.d.ts +11 -0
- package/dist/tools/connectors.schemas.js +11 -0
- package/dist/tools/connectors.service.d.ts +2 -0
- package/dist/tools/connectors.service.js +1 -0
- package/dist/tools/pipeline.d.ts +1 -1
- package/dist/tools/policy.d.ts +23 -1
- package/dist/tools/policy.js +99 -24
- package/dist/tools/tools.module.js +3 -0
- package/package.json +3 -3
- package/src/migrations/0102_themes.sql +64 -0
- package/src/migrations/0103_ui_theme_setting.sql +7 -0
- package/src/migrations/0104_connector_tool_account_policies.sql +16 -0
|
@@ -0,0 +1,47 @@
|
|
|
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 { Inject, Injectable } from "@nestjs/common";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import { ThemesService } from "../../themes/themes.service.js";
|
|
13
|
+
import { defineOperation } from "../operation.js";
|
|
14
|
+
import { PlatformOperations } from "../registry.js";
|
|
15
|
+
/**
|
|
16
|
+
* Theme operations of the `Platform` tool (themes spec §9): the org's theme listing. Setting a
|
|
17
|
+
* theme goes through the settings operations (`platform.set-preferences`, `org-settings.update`,
|
|
18
|
+
* `admin.update-settings`).
|
|
19
|
+
*/
|
|
20
|
+
let ThemesOperations = class ThemesOperations {
|
|
21
|
+
registry;
|
|
22
|
+
themes;
|
|
23
|
+
constructor(registry, themes) {
|
|
24
|
+
this.registry = registry;
|
|
25
|
+
this.themes = themes;
|
|
26
|
+
}
|
|
27
|
+
onModuleInit() {
|
|
28
|
+
this.registry.register([
|
|
29
|
+
defineOperation({
|
|
30
|
+
id: "themes.list",
|
|
31
|
+
description: "List the themes on in this org (id, version, name, schemes, logo and the bg / panel / ink / accent swatches per scheme), the theme in effect for you here (resolved; null is the built-in theme) and the org's setting (its default theme id, null following the platform, and whether personal picks are allowed or locked). Use a theme id with platform.set-preferences to change your own theme, or with org-settings.update to change the org's default.",
|
|
32
|
+
input: z.object({}).strict(),
|
|
33
|
+
example: {},
|
|
34
|
+
authz: { orgAction: "org.read" },
|
|
35
|
+
mirrors: ["GET /api/v1/orgs/:orgId/themes"],
|
|
36
|
+
confirm: "read",
|
|
37
|
+
run: async (_input, ctx) => this.themes.forOrg(ctx.orgId, ctx.actorAccountId),
|
|
38
|
+
}),
|
|
39
|
+
]);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
ThemesOperations = __decorate([
|
|
43
|
+
Injectable(),
|
|
44
|
+
__param(0, Inject(PlatformOperations)),
|
|
45
|
+
__param(1, Inject(ThemesService))
|
|
46
|
+
], ThemesOperations);
|
|
47
|
+
export { ThemesOperations };
|
|
@@ -25,11 +25,12 @@ import { PluginsOperations } from "./ops/plugins.ops.js";
|
|
|
25
25
|
import { PreviewersOperations } from "./ops/previewers.ops.js";
|
|
26
26
|
import { RequestsOperations } from "./ops/requests.ops.js";
|
|
27
27
|
import { SkillsOperations } from "./ops/skills.ops.js";
|
|
28
|
+
import { ThemesOperations } from "./ops/themes.ops.js";
|
|
28
29
|
/**
|
|
29
30
|
* One provider per domain, each taking `PlatformOperations` as its first constructor argument:
|
|
30
31
|
* the parity test in `tools/checks` builds the operations from it without a Nest app.
|
|
31
32
|
*/
|
|
32
|
-
export declare const PLATFORM_OPERATION_PROVIDERS: readonly [typeof PlatformOperationsProvider, typeof SkillsOperations, typeof AgentsOperations, typeof AgentTemplatesOperations, typeof PluginsOperations, typeof MarketplacesOperations, typeof RequestsOperations, typeof ConnectorsOperations, typeof ExtensionsOperations, typeof ConversationsOperations, typeof ComponentsOperations, typeof AccountsOperations, typeof OrgsOperations, typeof AdminOperations, typeof CatalogAdminOperations, typeof OrgPluginsOperations, typeof ExtensionsAdminOperations, typeof ModelsOperations, typeof InboxOperations, typeof ApprovalsOperations, typeof ChannelsOperations, typeof OrgSettingsOperations, typeof EvalsOperations, typeof BackgroundOperations, typeof PreviewersOperations, typeof EnvironmentsOperations, typeof AgentSkillsOperations];
|
|
33
|
+
export declare const PLATFORM_OPERATION_PROVIDERS: readonly [typeof PlatformOperationsProvider, typeof SkillsOperations, typeof AgentsOperations, typeof AgentTemplatesOperations, typeof PluginsOperations, typeof MarketplacesOperations, typeof RequestsOperations, typeof ConnectorsOperations, typeof ExtensionsOperations, typeof ConversationsOperations, typeof ComponentsOperations, typeof AccountsOperations, typeof OrgsOperations, typeof AdminOperations, typeof CatalogAdminOperations, typeof OrgPluginsOperations, typeof ExtensionsAdminOperations, typeof ModelsOperations, typeof InboxOperations, typeof ApprovalsOperations, typeof ChannelsOperations, typeof OrgSettingsOperations, typeof EvalsOperations, typeof BackgroundOperations, typeof PreviewersOperations, typeof EnvironmentsOperations, typeof AgentSkillsOperations, typeof ThemesOperations];
|
|
33
34
|
/**
|
|
34
35
|
* The `Platform` tool's operations (ruling P2): one provider per domain, each registering its
|
|
35
36
|
* operations into `PlatformOperations` from `onModuleInit`. It imports the domain modules, which
|
|
@@ -20,6 +20,7 @@ import { ModelsModule } from "../models/models.module.js";
|
|
|
20
20
|
import { OrgsModule } from "../orgs/orgs.module.js";
|
|
21
21
|
import { ProvidersModule } from "../runtime/providers.js";
|
|
22
22
|
import { SkillsModule } from "../skills/skills.module.js";
|
|
23
|
+
import { ThemesModule } from "../themes/themes.module.js";
|
|
23
24
|
import { ToolsModule } from "../tools/tools.module.js";
|
|
24
25
|
import { AccountsOperations } from "./ops/accounts.ops.js";
|
|
25
26
|
import { AdminOperations } from "./ops/admin.ops.js";
|
|
@@ -48,6 +49,7 @@ import { PluginsOperations } from "./ops/plugins.ops.js";
|
|
|
48
49
|
import { PreviewersOperations } from "./ops/previewers.ops.js";
|
|
49
50
|
import { RequestsOperations } from "./ops/requests.ops.js";
|
|
50
51
|
import { SkillsOperations } from "./ops/skills.ops.js";
|
|
52
|
+
import { ThemesOperations } from "./ops/themes.ops.js";
|
|
51
53
|
import { PlatformModule } from "./platform.module.js";
|
|
52
54
|
/**
|
|
53
55
|
* One provider per domain, each taking `PlatformOperations` as its first constructor argument:
|
|
@@ -87,6 +89,8 @@ export const PLATFORM_OPERATION_PROVIDERS = [
|
|
|
87
89
|
// Task 11b: environments, agent skills
|
|
88
90
|
EnvironmentsOperations,
|
|
89
91
|
AgentSkillsOperations,
|
|
92
|
+
// themes (themes spec §9)
|
|
93
|
+
ThemesOperations,
|
|
90
94
|
];
|
|
91
95
|
/**
|
|
92
96
|
* The `Platform` tool's operations (ruling P2): one provider per domain, each registering its
|
|
@@ -115,6 +119,7 @@ PlatformOpsModule = __decorate([
|
|
|
115
119
|
EnvironmentsModule,
|
|
116
120
|
BackgroundModule,
|
|
117
121
|
FilesModule,
|
|
122
|
+
ThemesModule,
|
|
118
123
|
],
|
|
119
124
|
providers: [...PLATFORM_OPERATION_PROVIDERS],
|
|
120
125
|
})
|
|
@@ -20,6 +20,7 @@ import { INBOX_ROUTES } from "../inbox/inbox.schemas.js";
|
|
|
20
20
|
import { ORG_MODEL_ROUTES } from "../models/org-models.schemas.js";
|
|
21
21
|
import { ORG_ROUTES, ORG_SETTINGS_ROUTES } from "../orgs/orgs.schemas.js";
|
|
22
22
|
import { SKILL_ROUTES } from "../skills/skills.schemas.js";
|
|
23
|
+
import { THEME_ROUTES } from "../themes/themes.schemas.js";
|
|
23
24
|
import { APPROVAL_ROUTES } from "../tools/approvals.schemas.js";
|
|
24
25
|
import { CONNECTOR_ROUTES } from "../tools/connectors.schemas.js";
|
|
25
26
|
/**
|
|
@@ -60,4 +61,6 @@ export const ROUTE_SCHEMAS = [
|
|
|
60
61
|
...ENVIRONMENT_ROUTES,
|
|
61
62
|
// personal preferences (run context spec §3.4)
|
|
62
63
|
...ME_ROUTES,
|
|
64
|
+
// themes (themes spec §9)
|
|
65
|
+
...THEME_ROUTES,
|
|
63
66
|
];
|
|
@@ -34,9 +34,10 @@ export interface EffectiveDefinitionDeps {
|
|
|
34
34
|
type Policy = "ask" | "deny";
|
|
35
35
|
/**
|
|
36
36
|
* `base` with `over` applied on top, the stricter value winning per key (`deny` > `ask`). Values
|
|
37
|
-
* other than `ask` / `deny` (stored template data is not re-validated
|
|
37
|
+
* other than `ask` / `deny` (stored template data is not re-validated; an agent's connector-tool
|
|
38
|
+
* `allow`, which the policy check reads from the agent's current version) are skipped on both sides.
|
|
38
39
|
*/
|
|
39
|
-
export declare function stricterPolicies(base: Record<string,
|
|
40
|
+
export declare function stricterPolicies(base: Record<string, string>, over: Record<string, string>): Record<string, Policy>;
|
|
40
41
|
/** An org agent's version and definition as checked by its gates. */
|
|
41
42
|
export interface CheckedOrgAgent {
|
|
42
43
|
version: number;
|
|
@@ -23,7 +23,8 @@ const RANK = { ask: 1, deny: 2 };
|
|
|
23
23
|
const isPolicy = (p) => p === "ask" || p === "deny";
|
|
24
24
|
/**
|
|
25
25
|
* `base` with `over` applied on top, the stricter value winning per key (`deny` > `ask`). Values
|
|
26
|
-
* other than `ask` / `deny` (stored template data is not re-validated
|
|
26
|
+
* other than `ask` / `deny` (stored template data is not re-validated; an agent's connector-tool
|
|
27
|
+
* `allow`, which the policy check reads from the agent's current version) are skipped on both sides.
|
|
27
28
|
*/
|
|
28
29
|
export function stricterPolicies(base, over) {
|
|
29
30
|
const out = {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ParsedTheme } from "@capybara-hq/plugin-sdk";
|
|
2
|
+
/**
|
|
3
|
+
* The stylesheet of a stored theme (themes spec §8): each scheme's overrides under
|
|
4
|
+
* `:root[data-theme="<scheme>"]` (a single-scheme theme: its whole scheme, the same block under
|
|
5
|
+
* both selectors, with `color-scheme: <scheme>`), an `@font-face` per font (`src` from
|
|
6
|
+
* `assetUrl(<the font's path as stored>)`, the one URL builder), and each font role's stack with
|
|
7
|
+
* the theme's families first, then the built-in stack. Every stored value is re-validated (token
|
|
8
|
+
* names against the overridable list, values against the SDK grammar, radius 0-16, families,
|
|
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.
|
|
11
|
+
*/
|
|
12
|
+
export declare function renderThemeCss(theme: ParsedTheme, assetUrl: (pluginPath: string) => string, onSkipped?: (paths: string[]) => void): string;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// A theme version's stylesheet (themes spec §8), rendered from the stored, parsed theme. The
|
|
2
|
+
// definition was validated at import, but it is read back from a database row: every value is
|
|
3
|
+
// checked again here with the SDK's grammars before it is written into CSS, so a changed row cannot
|
|
4
|
+
// inject CSS. A value that fails is left out and reported.
|
|
5
|
+
import { canonicalThemeValue, isThemeAssetPath, isThemeFontFamily, THEME_FONTS, THEME_OVERRIDABLE, THEME_TOKENS, } from "@capybara-hq/plugin-sdk";
|
|
6
|
+
const SCHEMES = ["dark", "light"];
|
|
7
|
+
const GROUPS = ["color", "shadow"];
|
|
8
|
+
const OVERRIDABLE = {
|
|
9
|
+
color: new Set(THEME_OVERRIDABLE.color),
|
|
10
|
+
shadow: new Set(THEME_OVERRIDABLE.shadow),
|
|
11
|
+
radius: new Set(THEME_OVERRIDABLE.radius),
|
|
12
|
+
};
|
|
13
|
+
/** The CSS variable of a stored token name (the design's `tokens.css` names). */
|
|
14
|
+
const VARIABLE = {
|
|
15
|
+
color: (n) => `--cb-${n}`,
|
|
16
|
+
shadow: (n) => `--cb-${n}`,
|
|
17
|
+
radius: (n) => `--cb-radius-${n}`,
|
|
18
|
+
};
|
|
19
|
+
/** The CSS variable of each font role's stack. */
|
|
20
|
+
const STACK_VARIABLE = { mono: "--cb-font", prose: "--cb-font-prose" };
|
|
21
|
+
const isRecord = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
22
|
+
/**
|
|
23
|
+
* One scheme's declarations: the stored overrides that pass their checks, keyed by variable.
|
|
24
|
+
* `full`: start from every built-in value of `scheme` (a single-scheme theme writes the whole
|
|
25
|
+
* scheme, so the other selector draws it too); a skipped override then keeps the built-in value.
|
|
26
|
+
*/
|
|
27
|
+
function schemeDeclarations(theme, scheme, full, skip) {
|
|
28
|
+
const out = new Map();
|
|
29
|
+
if (full) {
|
|
30
|
+
// Built-in values come from the generated tokens (code, not stored data)
|
|
31
|
+
for (const g of GROUPS) {
|
|
32
|
+
for (const [n, v] of Object.entries(THEME_TOKENS[g]))
|
|
33
|
+
out.set(VARIABLE[g](n), v[scheme]);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const over = theme.schemes?.[scheme];
|
|
37
|
+
if (!isRecord(over))
|
|
38
|
+
return out;
|
|
39
|
+
for (const g of GROUPS) {
|
|
40
|
+
const values = over[g];
|
|
41
|
+
if (!isRecord(values))
|
|
42
|
+
continue;
|
|
43
|
+
for (const [n, raw] of Object.entries(values)) {
|
|
44
|
+
const v = OVERRIDABLE[g].has(n) ? canonicalThemeValue(g, raw) : undefined;
|
|
45
|
+
if (v === undefined)
|
|
46
|
+
skip(`schemes.${scheme}.${g}.${n}`);
|
|
47
|
+
else
|
|
48
|
+
out.set(VARIABLE[g](n), v);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const radius = over.radius;
|
|
52
|
+
if (isRecord(radius)) {
|
|
53
|
+
for (const [n, raw] of Object.entries(radius)) {
|
|
54
|
+
const ok = OVERRIDABLE.radius.has(n) &&
|
|
55
|
+
typeof raw === "number" &&
|
|
56
|
+
Number.isInteger(raw) &&
|
|
57
|
+
raw >= 0 &&
|
|
58
|
+
raw <= 16;
|
|
59
|
+
if (ok)
|
|
60
|
+
out.set(VARIABLE.radius(n), `${raw}px`);
|
|
61
|
+
else
|
|
62
|
+
skip(`schemes.${scheme}.radius.${n}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
const declarations = (d, extra = []) => [...extra, ...[...d].map(([k, v]) => `${k}: ${v};`)].join(" ");
|
|
68
|
+
/**
|
|
69
|
+
* The stylesheet of a stored theme (themes spec §8): each scheme's overrides under
|
|
70
|
+
* `:root[data-theme="<scheme>"]` (a single-scheme theme: its whole scheme, the same block under
|
|
71
|
+
* both selectors, with `color-scheme: <scheme>`), an `@font-face` per font (`src` from
|
|
72
|
+
* `assetUrl(<the font's path as stored>)`, the one URL builder), and each font role's stack with
|
|
73
|
+
* the theme's families first, then the built-in stack. Every stored value is re-validated (token
|
|
74
|
+
* names against the overridable list, values against the SDK grammar, radius 0-16, families,
|
|
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.
|
|
77
|
+
*/
|
|
78
|
+
export function renderThemeCss(theme, assetUrl, onSkipped) {
|
|
79
|
+
const skipped = [];
|
|
80
|
+
const skip = (p) => skipped.push(p);
|
|
81
|
+
const rules = [];
|
|
82
|
+
const single = SCHEMES.includes(theme.singleScheme)
|
|
83
|
+
? theme.singleScheme
|
|
84
|
+
: null;
|
|
85
|
+
if (single !== null) {
|
|
86
|
+
const body = declarations(schemeDeclarations(theme, single, true, skip), [
|
|
87
|
+
`color-scheme: ${single};`,
|
|
88
|
+
]);
|
|
89
|
+
for (const s of SCHEMES)
|
|
90
|
+
rules.push(`:root[data-theme="${s}"] { ${body} }`);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
for (const s of SCHEMES) {
|
|
94
|
+
const d = schemeDeclarations(theme, s, false, skip);
|
|
95
|
+
if (d.size > 0)
|
|
96
|
+
rules.push(`:root[data-theme="${s}"] { ${declarations(d)} }`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const families = { mono: [], prose: [] };
|
|
100
|
+
const fonts = theme.fonts;
|
|
101
|
+
(Array.isArray(fonts) ? fonts : []).forEach((f, i) => {
|
|
102
|
+
const font = isRecord(f) ? f : {};
|
|
103
|
+
const style = font.style ?? "normal";
|
|
104
|
+
const role = font.role;
|
|
105
|
+
const ok = isThemeFontFamily(font.family) &&
|
|
106
|
+
isThemeAssetPath(font.file) &&
|
|
107
|
+
/\.woff2$/i.test(font.file) &&
|
|
108
|
+
typeof font.weight === "number" &&
|
|
109
|
+
Number.isInteger(font.weight) &&
|
|
110
|
+
font.weight >= 100 &&
|
|
111
|
+
font.weight <= 900 &&
|
|
112
|
+
(style === "normal" || style === "italic") &&
|
|
113
|
+
(role === "mono" || role === "prose");
|
|
114
|
+
if (!ok) {
|
|
115
|
+
skip(`fonts.${i}`);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const family = font.family;
|
|
119
|
+
rules.push(`@font-face { font-family: "${family}"; src: url("${assetUrl(font.file)}") format("woff2"); font-weight: ${font.weight}; font-style: ${style}; font-display: swap; }`);
|
|
120
|
+
const list = families[role];
|
|
121
|
+
if (!list.includes(family))
|
|
122
|
+
list.push(family);
|
|
123
|
+
});
|
|
124
|
+
const stacks = ["mono", "prose"]
|
|
125
|
+
.filter((r) => families[r].length > 0)
|
|
126
|
+
.map((r) => `${STACK_VARIABLE[r]}: ${families[r].map((f) => `"${f}"`).join(", ")}, ${THEME_FONTS[r]};`);
|
|
127
|
+
if (stacks.length > 0)
|
|
128
|
+
rules.push(`:root { ${stacks.join(" ")} }`);
|
|
129
|
+
if (skipped.length > 0)
|
|
130
|
+
onSkipped?.(skipped);
|
|
131
|
+
return `${rules.join("\n")}\n`;
|
|
132
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
2
|
+
import type { KernelLogger } from "../log.js";
|
|
3
|
+
import { StorageService } from "../storage/storage.service.js";
|
|
4
|
+
/** A theme asset's bytes and content type. */
|
|
5
|
+
export interface ThemeAsset {
|
|
6
|
+
contentType: string;
|
|
7
|
+
data: Uint8Array;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Delivery of theme versions (themes spec §8): the stylesheet rendered from the stored definition
|
|
11
|
+
* and the stored assets. A version is identified by the theme id and version number only; a theme
|
|
12
|
+
* removed later (soft-deleted) still serves its versions, because their URLs are immutable and may
|
|
13
|
+
* be cached in open pages, and a theme's styles are not sensitive (§8, public access).
|
|
14
|
+
*/
|
|
15
|
+
export declare class ThemeDeliveryService {
|
|
16
|
+
private readonly db;
|
|
17
|
+
private readonly storage;
|
|
18
|
+
private readonly log;
|
|
19
|
+
constructor(db: KernelDb, storage: StorageService, log: KernelLogger);
|
|
20
|
+
/** The stored version row, or 404 `theme.not_found`. */
|
|
21
|
+
private version;
|
|
22
|
+
/**
|
|
23
|
+
* The stylesheet of version `version` of theme `id` ({@link renderThemeCss}, asset URLs from
|
|
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: 404 `theme.not_found`.
|
|
26
|
+
*/
|
|
27
|
+
stylesheet(id: string, version: string): Promise<string>;
|
|
28
|
+
/**
|
|
29
|
+
* Asset `file` (a path inside the plugin, as stored: `fonts/MiSans.woff2`) of version `version`
|
|
30
|
+
* of theme `id`: only a font or logo the version's definition names. Unknown theme, version or
|
|
31
|
+
* file: 404 `theme.not_found`.
|
|
32
|
+
*/
|
|
33
|
+
asset(id: string, version: string, file: string): Promise<ThemeAsset>;
|
|
34
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
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 { isThemeAssetPath } from "@capybara-hq/plugin-sdk";
|
|
11
|
+
import { Inject, Injectable } from "@nestjs/common";
|
|
12
|
+
import { ApiError } from "../errors.js";
|
|
13
|
+
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
14
|
+
import { KERNEL_LOGGER } from "../infra/tokens.js";
|
|
15
|
+
import { packagePath } from "../packages/tgz.js";
|
|
16
|
+
import { StorageService } from "../storage/storage.service.js";
|
|
17
|
+
import { renderThemeCss } from "./render-css.js";
|
|
18
|
+
import { themeUrl } from "./theme-state.js";
|
|
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
|
+
/** A version number as it appears in a delivery URL: a positive int4. */
|
|
21
|
+
const VERSION_RE = /^[1-9]\d{0,9}$/;
|
|
22
|
+
const INT4_MAX = 2_147_483_647;
|
|
23
|
+
/** The content type of each asset kind a theme names (fonts and logos). */
|
|
24
|
+
const ASSET_TYPES = [
|
|
25
|
+
[/\.woff2$/i, "font/woff2"],
|
|
26
|
+
[/\.svg$/i, "image/svg+xml"],
|
|
27
|
+
[/\.png$/i, "image/png"],
|
|
28
|
+
];
|
|
29
|
+
/** 404 `theme.not_found`. */
|
|
30
|
+
const notFound = (what) => new ApiError("theme.not_found", `${what} does not exist; reload the page to get the current theme`);
|
|
31
|
+
/**
|
|
32
|
+
* Delivery of theme versions (themes spec §8): the stylesheet rendered from the stored definition
|
|
33
|
+
* and the stored assets. A version is identified by the theme id and version number only; a theme
|
|
34
|
+
* removed later (soft-deleted) still serves its versions, because their URLs are immutable and may
|
|
35
|
+
* be cached in open pages, and a theme's styles are not sensitive (§8, public access).
|
|
36
|
+
*/
|
|
37
|
+
let ThemeDeliveryService = class ThemeDeliveryService {
|
|
38
|
+
db;
|
|
39
|
+
storage;
|
|
40
|
+
log;
|
|
41
|
+
constructor(db, storage, log) {
|
|
42
|
+
this.db = db;
|
|
43
|
+
this.storage = storage;
|
|
44
|
+
this.log = log;
|
|
45
|
+
}
|
|
46
|
+
/** The stored version row, or 404 `theme.not_found`. */
|
|
47
|
+
async version(id, version) {
|
|
48
|
+
if (!UUID_RE.test(id) || !VERSION_RE.test(version) || Number(version) > INT4_MAX) {
|
|
49
|
+
throw notFound(`Theme ${id} version ${version}`);
|
|
50
|
+
}
|
|
51
|
+
const row = await this.db
|
|
52
|
+
.selectFrom("platform_theme_versions")
|
|
53
|
+
.select(["definition", "object_prefix"])
|
|
54
|
+
.where("theme_id", "=", id)
|
|
55
|
+
.where("version", "=", Number(version))
|
|
56
|
+
.executeTakeFirst();
|
|
57
|
+
if (!row)
|
|
58
|
+
throw notFound(`Theme ${id} version ${version}`);
|
|
59
|
+
return { definition: row.definition, objectPrefix: row.object_prefix };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The stylesheet of version `version` of theme `id` ({@link renderThemeCss}, asset URLs from
|
|
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: 404 `theme.not_found`.
|
|
65
|
+
*/
|
|
66
|
+
async stylesheet(id, version) {
|
|
67
|
+
const { definition } = await this.version(id, version);
|
|
68
|
+
return renderThemeCss(definition, (path) => themeUrl(id, version, path), (skipped) => this.log.warn("theme stylesheet: stored values failed re-validation and were left out", {
|
|
69
|
+
themeId: id,
|
|
70
|
+
version,
|
|
71
|
+
skipped,
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Asset `file` (a path inside the plugin, as stored: `fonts/MiSans.woff2`) of version `version`
|
|
76
|
+
* of theme `id`: only a font or logo the version's definition names. Unknown theme, version or
|
|
77
|
+
* file: 404 `theme.not_found`.
|
|
78
|
+
*/
|
|
79
|
+
async asset(id, version, file) {
|
|
80
|
+
const v = await this.version(id, version);
|
|
81
|
+
const def = v.definition;
|
|
82
|
+
const named = [
|
|
83
|
+
...(Array.isArray(def.fonts) ? def.fonts.map((f) => f?.file) : []),
|
|
84
|
+
def.logo?.mark,
|
|
85
|
+
def.logo?.favicon,
|
|
86
|
+
]
|
|
87
|
+
.filter((p) => typeof p === "string")
|
|
88
|
+
.map(packagePath);
|
|
89
|
+
const type = ASSET_TYPES.find(([re]) => re.test(file))?.[1];
|
|
90
|
+
if (v.objectPrefix === null ||
|
|
91
|
+
type === undefined ||
|
|
92
|
+
!isThemeAssetPath(file) ||
|
|
93
|
+
!named.includes(file)) {
|
|
94
|
+
throw notFound(`Theme asset ${file}`);
|
|
95
|
+
}
|
|
96
|
+
this.storage.assertAvailable();
|
|
97
|
+
const got = await this.storage.deployment().get(`${v.objectPrefix}${file}`);
|
|
98
|
+
if (!got) {
|
|
99
|
+
this.log.warn("theme asset missing from object storage", { themeId: id, version, file });
|
|
100
|
+
throw notFound(`Theme asset ${file}`);
|
|
101
|
+
}
|
|
102
|
+
return { contentType: type, data: new Uint8Array(await new Response(got.body).arrayBuffer()) };
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
ThemeDeliveryService = __decorate([
|
|
106
|
+
Injectable(),
|
|
107
|
+
__param(0, Inject(KernelDb)),
|
|
108
|
+
__param(1, Inject(StorageService)),
|
|
109
|
+
__param(2, Inject(KERNEL_LOGGER))
|
|
110
|
+
], ThemeDeliveryService);
|
|
111
|
+
export { ThemeDeliveryService };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { type LocalizedName, type ParsedTheme, type ThemeScheme } from "@capybara-hq/plugin-sdk";
|
|
2
|
+
import { type ComponentRow } from "../catalog/component-state.js";
|
|
3
|
+
import type { KernelSchemaDb } from "../db/kernel-schema.js";
|
|
4
|
+
import { ApiError } from "../errors.js";
|
|
5
|
+
/** A theme as the web uses it (spec §8): what to load and how to draw the logo. */
|
|
6
|
+
export interface ThemeRef {
|
|
7
|
+
/** The `platform_themes` id (what settings and picks store). */
|
|
8
|
+
id: string;
|
|
9
|
+
/** Its current version. */
|
|
10
|
+
version: string;
|
|
11
|
+
name: LocalizedName;
|
|
12
|
+
/** The schemes the theme defines, `dark` first. */
|
|
13
|
+
schemes: ThemeScheme[];
|
|
14
|
+
/** The only scheme of a single-scheme theme, else null. */
|
|
15
|
+
singleScheme: ThemeScheme | null;
|
|
16
|
+
/** null: the theme has no logo (the built-in mark and wordmark). */
|
|
17
|
+
logo: {
|
|
18
|
+
markUrl: string | null;
|
|
19
|
+
wordmark: string | null;
|
|
20
|
+
faviconUrl: string | null;
|
|
21
|
+
} | null;
|
|
22
|
+
/** `/api/v1/themes/<id>/<version>/theme.css`. */
|
|
23
|
+
cssUrl: string;
|
|
24
|
+
}
|
|
25
|
+
/** The four colors a theme picker draws per scheme. */
|
|
26
|
+
export interface ThemeSwatch {
|
|
27
|
+
bg: string;
|
|
28
|
+
panel: string;
|
|
29
|
+
ink: string;
|
|
30
|
+
accent: string;
|
|
31
|
+
}
|
|
32
|
+
/** A theme of an org's listing: its reference and swatches for each scheme it defines. */
|
|
33
|
+
export type ListedTheme = ThemeRef & {
|
|
34
|
+
swatches: Partial<Record<ThemeScheme, ThemeSwatch>>;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* The delivery URL of a theme version (spec §8): its stylesheet, or with `asset` (a path inside the
|
|
38
|
+
* plugin as the theme file names it, e.g. `./fonts/MiSans.woff2`) that asset, as one path segment
|
|
39
|
+
* (`assets/fonts%2FMiSans.woff2`; the route decodes it back to the stored path `fonts/MiSans.woff2`).
|
|
40
|
+
* The one builder of these URLs: the listing, the stylesheet and the asset routes all use it.
|
|
41
|
+
*/
|
|
42
|
+
export declare function themeUrl(id: string, version: string, asset?: string): string;
|
|
43
|
+
/** The {@link ThemeRef} of version `version` of theme `id`. */
|
|
44
|
+
export declare function themeRefOf(id: string, version: string, def: ParsedTheme): ThemeRef;
|
|
45
|
+
/**
|
|
46
|
+
* The swatches of each scheme the theme defines: its overrides merged onto the current built-in
|
|
47
|
+
* values (a stored definition holds only the theme file's own values, themes spec §6).
|
|
48
|
+
*/
|
|
49
|
+
export declare function themeSwatches(def: ParsedTheme): Partial<Record<ThemeScheme, ThemeSwatch>>;
|
|
50
|
+
/** The current definitions of live themes `ids`, by id. */
|
|
51
|
+
export declare function themeDefinitions(db: KernelSchemaDb, ids: readonly string[]): Promise<Map<string, {
|
|
52
|
+
version: string;
|
|
53
|
+
definition: ParsedTheme;
|
|
54
|
+
}>>;
|
|
55
|
+
/** The live themes on in the org, sorted by id. */
|
|
56
|
+
export declare function themesOnIn(db: KernelSchemaDb, orgId: string): Promise<ComponentRow[]>;
|
|
57
|
+
/** The live theme `id` if it can be the platform default, else null. */
|
|
58
|
+
export declare function eligiblePlatformDefault(db: KernelSchemaDb, id: string): Promise<ComponentRow | null>;
|
|
59
|
+
/**
|
|
60
|
+
* 400 `theme.not_available`: theme `id` cannot be chosen where it was set; `orgs`: the live orgs
|
|
61
|
+
* where it is off (a platform default must be on in every org).
|
|
62
|
+
*/
|
|
63
|
+
export declare const themeNotAvailable: (id: string, where: string, orgs?: {
|
|
64
|
+
orgId: string;
|
|
65
|
+
orgName: string;
|
|
66
|
+
}[]) => ApiError;
|
|
67
|
+
/**
|
|
68
|
+
* Locks theme `id`'s catalog item, then its `platform_themes` row, FOR SHARE (the one theme lock
|
|
69
|
+
* order: catalog item, then theme row, then the `uiTheme` setting row; the scope, disable, switch
|
|
70
|
+
* and removal writers hold the item or theme row FOR UPDATE before they read `uiTheme`), so the
|
|
71
|
+
* check that follows reads a state no such writer changes before the caller commits.
|
|
72
|
+
*/
|
|
73
|
+
export declare function lockThemeShared(db: KernelSchemaDb, id: string): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Refuses (400 `theme.not_available`) a platform default that is not a platform-owned theme with
|
|
76
|
+
* effective scope `all`, live and not disabled, or that is off in some live org (an org switched it
|
|
77
|
+
* off, or does not have the plugin a `follow` theme comes with; details list those orgs): the
|
|
78
|
+
* default is locked on in every org and every org falls back to it (spec §7). Call it in the
|
|
79
|
+
* setting's transaction after {@link lockThemeShared} and the `uiTheme` row lock.
|
|
80
|
+
*/
|
|
81
|
+
export declare function assertPlatformDefaultEligible(db: KernelSchemaDb, id: string): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Refuses (400 `theme.not_available`) an org default that is not on in the org. Call it in the
|
|
84
|
+
* setting's transaction after the org lock: it locks the theme's rows FOR SHARE before reading.
|
|
85
|
+
*/
|
|
86
|
+
export declare function assertThemeOnIn(db: KernelSchemaDb, orgId: string, id: string): Promise<void>;
|
|
87
|
+
/** Whether theme `id` is on in the org (no locks: a read for display). */
|
|
88
|
+
export declare function themeOnIn(db: KernelSchemaDb, orgId: string, id: string): Promise<boolean>;
|
|
89
|
+
/** Whether theme `id` is on in at least one of the person's live orgs. */
|
|
90
|
+
export declare function themeOnForAccount(db: KernelSchemaDb, accountId: string, id: string): Promise<boolean>;
|
|
91
|
+
/**
|
|
92
|
+
* Refuses (400 `theme.not_available`) a person's pick that is on in none of their live orgs (the
|
|
93
|
+
* pick is one value across their orgs, spec §7; where it is off, the org's default applies).
|
|
94
|
+
*/
|
|
95
|
+
export declare function assertThemeOnForAccount(db: KernelSchemaDb, accountId: string, id: string): Promise<void>;
|