fix(custom-widgets): parse fenced imports safely
This commit is contained in:
3 files changed
+63
-30
No files matched your search
@@ -8,10 +8,34 @@ export interface ImportReview {
|
||||
hasActions: boolean;
|
||||
}
|
||||
|
||||
function extractFencedBlock(text: string, acceptedLanguages: readonly string[]): string | undefined {
|
||||
let cursor = 0;
|
||||
|
||||
while (cursor < text.length) {
|
||||
const fenceStart = text.indexOf("```", cursor);
|
||||
if (fenceStart === -1) return undefined;
|
||||
|
||||
const headerStart = fenceStart + 3;
|
||||
const headerEnd = text.indexOf("\n", headerStart);
|
||||
if (headerEnd === -1) return undefined;
|
||||
|
||||
const contentStart = headerEnd + 1;
|
||||
const fenceEnd = text.indexOf("```", contentStart);
|
||||
if (fenceEnd === -1) return undefined;
|
||||
|
||||
const language = text.slice(headerStart, headerEnd).trim().toLowerCase();
|
||||
if (acceptedLanguages.includes(language)) return text.slice(contentStart, fenceEnd);
|
||||
|
||||
cursor = fenceEnd + 3;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function parseCustomWidgetClipboard(text: string): Record<string, unknown> | null {
|
||||
try {
|
||||
const jsonBlock = text.match(/```(?:json)?\s*([\s\S]*?)```/iu)?.[1];
|
||||
const jsxBlock = text.match(/```(?:jsx|tsx)\s*([\s\S]*?)```/iu)?.[1]?.trim();
|
||||
const jsonBlock = extractFencedBlock(text, ["", "json"]);
|
||||
const jsxBlock = extractFencedBlock(text, ["jsx", "tsx"])?.trim();
|
||||
const value: unknown = JSON.parse(jsonBlock ?? text);
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
||||
const widget = value as Record<string, unknown>;
|
||||
|
||||
@@ -53,4 +53,16 @@ ${JSON.stringify(widget, null, 2)}
|
||||
'<Stack gap="sm">\n <Text>{data.name}</Text>\n</Stack>',
|
||||
);
|
||||
});
|
||||
|
||||
it("accepts case-insensitive fenced languages and CRLF line endings", () => {
|
||||
const result = parseCustomWidgetClipboard(
|
||||
`\`\`\`JSON\r\n${JSON.stringify(widget)}\r\n\`\`\`\r\n\`\`\`TSX\r\n<Text>{data.name}</Text>\r\n\`\`\``,
|
||||
);
|
||||
|
||||
expect((result?.displayConfig as Record<string, unknown> | undefined)?.template).toBe("<Text>{data.name}</Text>");
|
||||
});
|
||||
|
||||
it("rejects an unterminated fence with a large whitespace payload", () => {
|
||||
expect(parseCustomWidgetClipboard(`\`\`\`json\n${" ".repeat(250_000)}`)).toBeNull();
|
||||
});
|
||||
});
|
||||
Generated
+25
-28
@@ -1755,7 +1755,7 @@ importers:
|
||||
version: link:../../tooling/typescript
|
||||
'@types/node':
|
||||
specifier: 'catalog:'
|
||||
version: 24.13.2
|
||||
version: 24.13.3
|
||||
'@types/react':
|
||||
specifier: 'catalog:'
|
||||
version: 19.2.17
|
||||
@@ -1773,7 +1773,7 @@ importers:
|
||||
version: 6.0.3
|
||||
vitest:
|
||||
specifier: 'catalog:'
|
||||
version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(jsdom@29.1.1(@noble/hashes@2.0.1))(msw@2.14.6(@types/node@24.13.2)(typescript@6.0.3))(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.16))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0))
|
||||
version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@24.13.3)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(jsdom@29.1.1(@noble/hashes@2.0.1))(msw@2.14.6(@types/node@24.13.3)(typescript@6.0.3))(vite@8.1.3(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.16))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0))
|
||||
|
||||
packages/db:
|
||||
dependencies:
|
||||
@@ -18918,7 +18918,7 @@ snapshots:
|
||||
'@jest/schemas': 29.6.3
|
||||
'@types/istanbul-lib-coverage': 2.0.6
|
||||
'@types/istanbul-reports': 3.0.4
|
||||
'@types/node': 24.13.2
|
||||
'@types/node': 24.13.3
|
||||
'@types/yargs': 17.0.35
|
||||
chalk: 4.1.2
|
||||
|
||||
@@ -20501,7 +20501,7 @@ snapshots:
|
||||
'@scalar/icons@0.7.3(typescript@6.0.3)':
|
||||
dependencies:
|
||||
'@phosphor-icons/core': 2.1.1
|
||||
'@types/node': 24.13.2
|
||||
'@types/node': 24.13.3
|
||||
chalk: 5.6.2
|
||||
vue: 3.5.35(typescript@6.0.3)
|
||||
transitivePeerDependencies:
|
||||
@@ -21476,11 +21476,11 @@ snapshots:
|
||||
'@types/body-parser@1.19.5':
|
||||
dependencies:
|
||||
'@types/connect': 3.4.38
|
||||
'@types/node': 24.13.2
|
||||
'@types/node': 24.13.3
|
||||
|
||||
'@types/bonjour@3.5.13':
|
||||
dependencies:
|
||||
'@types/node': 24.13.2
|
||||
'@types/node': 24.13.3
|
||||
|
||||
'@types/chai@5.2.2':
|
||||
dependencies:
|
||||
@@ -21491,11 +21491,11 @@ snapshots:
|
||||
'@types/connect-history-api-fallback@1.5.4':
|
||||
dependencies:
|
||||
'@types/express-serve-static-core': 4.19.5
|
||||
'@types/node': 24.13.2
|
||||
'@types/node': 24.13.3
|
||||
|
||||
'@types/connect@3.4.38':
|
||||
dependencies:
|
||||
'@types/node': 24.13.2
|
||||
'@types/node': 24.13.3
|
||||
|
||||
'@types/cookies@0.9.2':
|
||||
dependencies:
|
||||
@@ -21641,13 +21641,13 @@ snapshots:
|
||||
|
||||
'@types/docker-modem@3.0.6':
|
||||
dependencies:
|
||||
'@types/node': 24.13.2
|
||||
'@types/node': 24.13.3
|
||||
'@types/ssh2': 1.15.1
|
||||
|
||||
'@types/dockerode@4.0.1':
|
||||
dependencies:
|
||||
'@types/docker-modem': 3.0.6
|
||||
'@types/node': 24.13.2
|
||||
'@types/node': 24.13.3
|
||||
'@types/ssh2': 1.15.1
|
||||
|
||||
'@types/estree-jsx@1.0.5':
|
||||
@@ -21658,7 +21658,7 @@ snapshots:
|
||||
|
||||
'@types/express-serve-static-core@4.19.5':
|
||||
dependencies:
|
||||
'@types/node': 24.13.2
|
||||
'@types/node': 24.13.3
|
||||
'@types/qs': 6.9.16
|
||||
'@types/range-parser': 1.2.7
|
||||
'@types/send': 0.17.4
|
||||
@@ -21690,7 +21690,7 @@ snapshots:
|
||||
|
||||
'@types/http-proxy@1.17.17':
|
||||
dependencies:
|
||||
'@types/node': 24.13.2
|
||||
'@types/node': 24.13.3
|
||||
|
||||
'@types/istanbul-lib-coverage@2.0.6': {}
|
||||
|
||||
@@ -21720,7 +21720,7 @@ snapshots:
|
||||
|
||||
'@types/node-fetch@2.6.13':
|
||||
dependencies:
|
||||
'@types/node': 24.13.2
|
||||
'@types/node': 24.13.3
|
||||
form-data: 4.0.6
|
||||
|
||||
'@types/node-unifi@2.5.1(patch_hash=5e6ae51e2a17a7f9729bfa30b0eb3d0842a5810ac6db47603ab4a6efa1ed84c5)':
|
||||
@@ -21784,14 +21784,14 @@ snapshots:
|
||||
|
||||
'@types/sax@1.2.7':
|
||||
dependencies:
|
||||
'@types/node': 24.13.2
|
||||
'@types/node': 24.13.3
|
||||
|
||||
'@types/semver@7.5.8': {}
|
||||
|
||||
'@types/send@0.17.4':
|
||||
dependencies:
|
||||
'@types/mime': 1.3.5
|
||||
'@types/node': 24.13.2
|
||||
'@types/node': 24.13.3
|
||||
|
||||
'@types/serve-index@1.9.4':
|
||||
dependencies:
|
||||
@@ -21800,7 +21800,7 @@ snapshots:
|
||||
'@types/serve-static@1.15.7':
|
||||
dependencies:
|
||||
'@types/http-errors': 2.0.4
|
||||
'@types/node': 24.13.2
|
||||
'@types/node': 24.13.3
|
||||
'@types/send': 0.17.4
|
||||
|
||||
'@types/set-cookie-parser@2.4.10':
|
||||
@@ -21810,15 +21810,15 @@ snapshots:
|
||||
|
||||
'@types/sockjs@0.3.36':
|
||||
dependencies:
|
||||
'@types/node': 24.13.2
|
||||
'@types/node': 24.13.3
|
||||
|
||||
'@types/ssh2-streams@0.1.12':
|
||||
dependencies:
|
||||
'@types/node': 24.13.2
|
||||
'@types/node': 24.13.3
|
||||
|
||||
'@types/ssh2@0.5.52':
|
||||
dependencies:
|
||||
'@types/node': 24.13.2
|
||||
'@types/node': 24.13.3
|
||||
'@types/ssh2-streams': 0.1.12
|
||||
|
||||
'@types/ssh2@1.15.1':
|
||||
@@ -21830,7 +21830,7 @@ snapshots:
|
||||
|
||||
'@types/stream-buffers@3.0.7':
|
||||
dependencies:
|
||||
'@types/node': 24.13.2
|
||||
'@types/node': 24.13.3
|
||||
|
||||
'@types/triple-beam@1.3.5': {}
|
||||
|
||||
@@ -21855,7 +21855,7 @@ snapshots:
|
||||
|
||||
'@types/xml2js@0.4.14':
|
||||
dependencies:
|
||||
'@types/node': 24.13.2
|
||||
'@types/node': 24.13.3
|
||||
|
||||
'@types/yargs-parser@21.0.3': {}
|
||||
|
||||
@@ -21979,7 +21979,6 @@ snapshots:
|
||||
optionalDependencies:
|
||||
msw: 2.14.6(@types/node@24.13.3)(typescript@6.0.3)
|
||||
vite: 8.1.3(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.16))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0)
|
||||
optional: true
|
||||
|
||||
'@vitest/pretty-format@4.1.9':
|
||||
dependencies:
|
||||
@@ -24144,7 +24143,7 @@ snapshots:
|
||||
|
||||
eval@0.1.8:
|
||||
dependencies:
|
||||
'@types/node': 24.13.2
|
||||
'@types/node': 24.13.3
|
||||
require-like: 0.1.2
|
||||
|
||||
event-target-shim@5.0.1: {}
|
||||
@@ -25450,7 +25449,7 @@ snapshots:
|
||||
jest-util@29.7.0:
|
||||
dependencies:
|
||||
'@jest/types': 29.6.3
|
||||
'@types/node': 24.13.2
|
||||
'@types/node': 24.13.3
|
||||
chalk: 4.1.2
|
||||
ci-info: 3.9.0
|
||||
graceful-fs: 4.2.11
|
||||
@@ -25464,7 +25463,7 @@ snapshots:
|
||||
|
||||
jest-worker@29.7.0:
|
||||
dependencies:
|
||||
'@types/node': 24.13.2
|
||||
'@types/node': 24.13.3
|
||||
jest-util: 29.7.0
|
||||
merge-stream: 2.0.0
|
||||
supports-color: 8.1.1
|
||||
@@ -27867,7 +27866,7 @@ snapshots:
|
||||
'@protobufjs/path': 1.1.2
|
||||
'@protobufjs/pool': 1.1.0
|
||||
'@protobufjs/utf8': 1.1.0
|
||||
'@types/node': 24.13.2
|
||||
'@types/node': 24.13.3
|
||||
long: 5.3.2
|
||||
|
||||
proxmox-api@1.1.1:
|
||||
@@ -29874,7 +29873,6 @@ snapshots:
|
||||
terser: 5.46.1
|
||||
tsx: 4.21.0
|
||||
yaml: 2.9.0
|
||||
optional: true
|
||||
|
||||
vitest@4.1.9(@opentelemetry/api@1.9.0)(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(jsdom@29.1.1(@noble/hashes@2.0.1))(msw@2.14.6(@types/node@24.13.3)(typescript@6.0.3))(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.0(postcss@8.5.16))(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0)):
|
||||
dependencies:
|
||||
@@ -29937,7 +29935,6 @@ snapshots:
|
||||
jsdom: 29.1.1(@noble/hashes@2.0.1)
|
||||
transitivePeerDependencies:
|
||||
- msw
|
||||
optional: true
|
||||
|
||||
vue-component-type-helpers@3.3.4: {}
|
||||
|
||||
|
||||
Reference in new issue
Block a user