feat: move snippets, keybinding actions and macros out of core into plugins

This commit is contained in:
LukeGus committed 2026-09-29 11:39:14 -05:00
1 parent d9bba10612
commit e0c30a4e6f
262 files changed
+9091 -10744

No files matched your search

@@ -686,3 +686,76 @@ describe("contributes.http.legacyRedirects", () => {
expect(errors).toMatch(/extra/);
});
});
describe("contributes.http.adminImpersonation", () => {
it("accepts a boolean and refuses anything else", () => {
expect(
validateManifest(
base({ contributes: { http: { adminImpersonation: true } } }),
),
).toEqual([]);
expect(
validateManifest(
base({ contributes: { http: { adminImpersonation: "yes" } } }),
).join(),
).toMatch(/adminImpersonation" must be a boolean/);
});
});
describe("contributes.keybindingActions", () => {
const withActions = (keybindingActions: unknown) =>
validateManifest(base({ contributes: { keybindingActions } }));
it("accepts actions with typed parameters", () => {
expect(
withActions([
{ id: "sample.run" },
{
id: "sample.send",
params: {
itemId: {
type: "string",
required: true,
pattern: "^[0-9]+$",
maxLength: 20,
syncEntity: "items",
},
enter: { type: "boolean" },
},
},
]),
).toEqual([]);
});
it("refuses the shell's own actions, duplicates and bad ids", () => {
const errors = withActions([
{ id: "nextTab" },
{ id: "sample.run" },
{ id: "sample.run" },
{ id: "bad id" },
]).join();
expect(errors).toMatch(/one of the shell's own actions/);
expect(errors).toMatch(/duplicates "sample.run"/);
expect(errors).toMatch(/letters, digits, dots and dashes/);
});
it("refuses bad parameters", () => {
const errors = withActions([
{
id: "sample.run",
params: {
type: { type: "string" },
a: { type: "number" },
b: { type: "string", pattern: "(" },
c: { type: "boolean", syncEntity: "items" },
d: { type: "string", extra: 1 },
},
},
]).join();
expect(errors).toMatch(/params.type is not a valid parameter name/);
expect(errors).toMatch(/a.type must be "string" or "boolean"/);
expect(errors).toMatch(/b.pattern must be a regular expression/);
expect(errors).toMatch(/c.syncEntity needs a string parameter/);
expect(errors).toMatch(/d/);
});
});
@@ -9,7 +9,11 @@ import {
resetSyncRegistry,
unregisterByOwner,
} from "../../plugins/sync-registry.js";
import { registerCoreSyncEntities } from "../../sync/entities.js";
import {
mapKeybindingReferences,
registerCoreSyncEntities,
} from "../../sync/entities.js";
import { setKeybindingActionSource } from "../../database/routes/keybinding-validation.js";
beforeEach(() => {
resetSyncRegistry();
@@ -197,14 +201,51 @@ describe("core sync entities", () => {
);
});
it("translates every snippet a host points at", () => {
const fields = (getEntity("hosts")?.references ?? [])
.filter((reference) => reference.entityType === "commandSnippet")
.map((reference) => reference.field);
expect(fields).toEqual([
"quickActions[].snippetId",
"terminalConfig.startupSnippetId",
it("leaves plugin data out of the host row, which travels in pluginSettings", () => {
const hosts = getEntity("hosts");
expect(
(hosts?.references ?? []).map((reference) => reference.entityType),
).not.toContain("commandSnippet");
expect(hosts?.readOnlyFields).toContain("quickActions");
});
it("translates a keybinding parameter that names a sync entity", async () => {
setKeybindingActionSource(() => [
{
pluginId: "fixture",
id: "runThing",
params: {
thingId: { type: "string", syncEntity: "things" },
label: { type: "string" },
},
},
]);
try {
const row = {
customKeybindings: JSON.stringify([
{ id: "a", action: { type: "runThing", thingId: "4", label: "4" } },
{ id: "b", action: { type: "nextTab" } },
]),
};
const out = await mapKeybindingReferences(row, async (entity, value) =>
entity === "things" && value === "4" ? "sync-4" : null,
);
const bindings = JSON.parse(out.customKeybindings as string);
expect(bindings[0].action).toEqual({
type: "runThing",
thingId: "sync-4",
label: "4",
});
expect(bindings[1].action).toEqual({ type: "nextTab" });
expect(
await mapKeybindingReferences(
{ customKeybindings: null },
async () => "x",
),
).toEqual({ customKeybindings: null });
} finally {
setKeybindingActionSource(() => []);
}
});
it("encrypts host and credential secrets", () => {
@@ -326,6 +326,10 @@ function upgradeChecks(current: () => Booted, bootIndex: number) {
containerRuntime: "podman",
});
expect(await host("ai")).toMatchObject({ enableAiAssistant: true });
expect(await host("snippets")).toMatchObject({
startupSnippetId: 1,
quickActions: [{ name: "d4-uptime", snippetId: 1 }],
});
expect(await host("wake-on-lan")).toMatchObject({
macAddress: "AA:BB:CC:DD:EE:04",
broadcastAddress: "10.4.0.255",
@@ -473,6 +477,11 @@ function upgradeChecks(current: () => Booted, bootIndex: number) {
terminalDefaults: { fontSize: 18, cursorBlink: false },
customThemes: [expect.objectContaining({ id: "d4-theme" })],
commandAutocomplete: true,
macros: [expect.objectContaining({ id: "d4-macro", name: "d4 macro" })],
});
expect(await settings(booted, "snippets", "user")).toMatchObject({
confirmExecution: true,
foldersCollapsed: false,
});
expect(await settings(booted, "step-ca", "admin")).toMatchObject({
caUrl: "https://ca.d4.example",