diff --git a/packages/void/package.json b/packages/void/package.json index 06e1aadad..834b994ab 100644 --- a/packages/void/package.json +++ b/packages/void/package.json @@ -180,6 +180,7 @@ "drizzle-valibot": "^0.4.2", "drizzle-zod": "^0.8.3", "hono": "^4.12.9", + "jsonc-parser": "3.3.1", "pg": "^8.20.0", "wrangler": "catalog:" }, diff --git a/packages/void/src/cli/init.ts b/packages/void/src/cli/init.ts index 38443b718..bb8160134 100644 --- a/packages/void/src/cli/init.ts +++ b/packages/void/src/cli/init.ts @@ -7,6 +7,7 @@ import { unlinkSync, } from "node:fs"; import { basename, dirname, join } from "node:path"; +import { parse, type ParseError } from "jsonc-parser"; import { parseArgs } from "node:util"; import { intro, outro, log, confirm, isCancel, cancel, note, select } from "@clack/prompts"; import { ensureProjectTsconfig } from "../codegen/project-tsconfig"; @@ -362,9 +363,10 @@ function setupWranglerConfig(root: string, framework: DetectedFramework): void { if (existsSync(p)) { configPath = p; try { - // Use dynamic import-free JSONC parsing — strip comments, then JSON.parse const raw = readFileSync(p, "utf-8"); - parsed = JSON.parse(raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "")); + const errors: ParseError[] = []; + parsed = parse(raw, errors); + if (errors.length > 0) throw new SyntaxError("Failed to parse JSONC"); } catch { // unparseable — start fresh } diff --git a/packages/void/src/cli/wrangler.ts b/packages/void/src/cli/wrangler.ts index 33388609b..3d7723e32 100644 --- a/packages/void/src/cli/wrangler.ts +++ b/packages/void/src/cli/wrangler.ts @@ -1,5 +1,6 @@ import { readFileSync, writeFileSync, existsSync } from "node:fs"; import { join } from "node:path"; +import { parse, type ParseError } from "jsonc-parser"; import type { InferredBindings } from "../inference/walk"; import type { VoidConfig } from "../config"; import type { WebSocketRouteDefinition } from "../router/websocket"; @@ -210,41 +211,11 @@ export function ensureWranglerNodejsAlsFlag(root: string): boolean { return true; } -/** - * Minimal JSONC parser — strips single-line (`//`) and multi-line comments - * before parsing as JSON. Handles comments inside strings correctly. - */ function parseJsonc(text: string): Record { - let result = ""; - let i = 0; - while (i < text.length) { - // String literal — copy verbatim - if (text[i] === '"') { - const start = i; - i++; // skip opening quote - while (i < text.length && text[i] !== '"') { - if (text[i] === "\\") i++; // skip escaped char - i++; - } - i++; // skip closing quote - result += text.slice(start, i); - continue; - } - // Single-line comment - if (text[i] === "/" && text[i + 1] === "/") { - i += 2; - while (i < text.length && text[i] !== "\n") i++; - continue; - } - // Multi-line comment - if (text[i] === "/" && text[i + 1] === "*") { - i += 2; - while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) i++; - i += 2; // skip */ - continue; - } - result += text[i]; - i++; + const errors: ParseError[] = []; + const result = parse(text, errors); + if (errors.length > 0) { + throw new SyntaxError("Failed to parse JSONC"); } - return JSON.parse(result); + return result; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e9a56c2cc..50f167191 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -271,6 +271,9 @@ importers: hono: specifier: ^4.12.9 version: 4.12.9 + jsonc-parser: + specifier: 3.3.1 + version: 3.3.1 pg: specifier: ^8.20.0 version: 8.20.0 @@ -5631,6 +5634,9 @@ packages: engines: {node: '>=6'} hasBin: true + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} @@ -12728,6 +12734,8 @@ snapshots: json5@2.2.3: {} + jsonc-parser@3.3.1: {} + kind-of@6.0.3: {} kleur@4.1.5: {}