diff --git a/packages/lib-cheatsheet-local/src/app.tsx b/packages/lib-cheatsheet-local/src/app.tsx
index 3e05ee56ef..cecfae869c 100644
--- a/packages/lib-cheatsheet-local/src/app.tsx
+++ b/packages/lib-cheatsheet-local/src/app.tsx
@@ -16,5 +16,8 @@ declare global {
}
export function App() {
+ if (document.cheatsheetInfo == null) {
+ throw new Error("Cheatsheet info is not available on the document.");
+ }
return ;
}
diff --git a/packages/lib-node-common/package.json b/packages/lib-node-common/package.json
index ed84af279b..51c9ee9267 100644
--- a/packages/lib-node-common/package.json
+++ b/packages/lib-node-common/package.json
@@ -21,6 +21,7 @@
"devDependencies": {
"@types/js-yaml": "^4.0.9",
"@types/lodash-es": "^4.17.12",
+ "@types/node": "^24.13.4",
"js-yaml": "^5.4.1"
}
}
diff --git a/packages/lib-node-common/src/Cheatsheet.ts b/packages/lib-node-common/src/Cheatsheet.ts
index 9b7295b71d..a28d67c8d4 100644
--- a/packages/lib-node-common/src/Cheatsheet.ts
+++ b/packages/lib-node-common/src/Cheatsheet.ts
@@ -1,6 +1,5 @@
import { readFile, writeFile } from "node:fs/promises";
import path from "node:path";
-import { parse } from "node-html-parser";
import type { CheatsheetInfo, IDE } from "@cursorless/lib-common";
import {
getCheatsheetInfo,
@@ -9,6 +8,7 @@ import {
showWarning,
} from "@cursorless/lib-common";
import type { FileSystemTalonSpokenForms } from "./FileSystemTalonSpokenForms";
+import { injectCheatsheetInfo } from "./injectCheatsheetInfo";
interface CheatSheetCommandArgV0 {
version: 0;
@@ -47,12 +47,11 @@ export async function showCheatsheet(
);
const cheatsheetPath = path.join(ide.assetsRoot, "cheatsheet.html");
const cheatsheetContent = await readFile(cheatsheetPath, "utf8");
- const root = parse(cheatsheetContent);
- root.getElementById("cheatsheet-data")!.textContent =
- `document.cheatsheetInfo = ${JSON.stringify(cheatsheetInfo)};`;
-
- await writeFile(arg.outputPath, root.toString());
+ await writeFile(
+ arg.outputPath,
+ injectCheatsheetInfo(cheatsheetContent, cheatsheetInfo),
+ );
}
async function getCheatsheetInfoForCommand(
diff --git a/packages/lib-node-common/src/injectCheatsheetInfo.ts b/packages/lib-node-common/src/injectCheatsheetInfo.ts
new file mode 100644
index 0000000000..1f853dfa35
--- /dev/null
+++ b/packages/lib-node-common/src/injectCheatsheetInfo.ts
@@ -0,0 +1,21 @@
+import { parse, TextNode } from "node-html-parser";
+import type { CheatsheetInfo } from "@cursorless/lib-common";
+
+export function injectCheatsheetInfo(
+ cheatsheetContent: string,
+ cheatsheetInfo: CheatsheetInfo,
+): string {
+ const root = parse(cheatsheetContent);
+ const serializedCheatsheetInfo = JSON.stringify(cheatsheetInfo).replaceAll(
+ "<",
+ String.raw`\u003c`,
+ );
+
+ root
+ .getElementById("cheatsheet-data")!
+ .set_content(
+ new TextNode(`document.cheatsheetInfo = ${serializedCheatsheetInfo};`),
+ );
+
+ return root.toString();
+}
diff --git a/packages/lib-node-common/test/Cheatsheet.test.ts b/packages/lib-node-common/test/Cheatsheet.test.ts
new file mode 100644
index 0000000000..ced3beaf07
--- /dev/null
+++ b/packages/lib-node-common/test/Cheatsheet.test.ts
@@ -0,0 +1,41 @@
+import assert from "node:assert/strict";
+import { parse } from "node-html-parser";
+import type { CheatsheetInfo } from "@cursorless/lib-common";
+import { injectCheatsheetInfo } from "../src/injectCheatsheetInfo";
+
+suite("Cheatsheet", () => {
+ test("injects cheatsheet info as executable script text", () => {
+ const cheatsheetInfo: CheatsheetInfo = {
+ sections: [
+ {
+ id: "actions",
+ name: "Actions",
+ items: [
+ {
+ id: "test-action",
+ type: "action",
+ variations: [
+ {
+ spokenForm: "take ",
+ description: "Select A & B ",
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ };
+
+ const output = injectCheatsheetInfo(
+ '',
+ cheatsheetInfo,
+ );
+ const script = parse(output).getElementById("cheatsheet-data")!;
+
+ assert.equal(
+ script.rawText,
+ `document.cheatsheetInfo = ${JSON.stringify(cheatsheetInfo).replaceAll("<", String.raw`\u003c`)};`,
+ );
+ assert.doesNotMatch(script.rawText, /&(?:amp|lt|quot);/u);
+ });
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 514d1da9e3..b0fb903c63 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -515,6 +515,9 @@ importers:
'@types/lodash-es':
specifier: ^4.17.12
version: 4.17.12
+ '@types/node':
+ specifier: ^24.13.4
+ version: 24.13.4
js-yaml:
specifier: ^5.4.1
version: 5.4.1