diff --git a/bun.lockb b/bun.lockb index 551ea4f..a7c3064 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/packages/generator/package.json b/packages/generator/package.json index 6d42444..a28caec 100644 --- a/packages/generator/package.json +++ b/packages/generator/package.json @@ -23,7 +23,8 @@ "@prisma/sdk": "4.0.0", "fp-ts": "^2.16.2", "lodash": "^4.17.21", - "pluralize": "^8.0.0" + "pluralize": "^8.0.0", + "valibot": "^0.30.0" }, "devDependencies": { "@types/lodash": "^4.14.202", diff --git a/packages/generator/src/lib/strip-json-comments.ts b/packages/generator/src/lib/strip-json-comments.ts new file mode 100644 index 0000000..c079abe --- /dev/null +++ b/packages/generator/src/lib/strip-json-comments.ts @@ -0,0 +1,136 @@ +// @ts-nocheck + +/** + * From https://github.com/sindresorhus/strip-json-comments/commit/f9a628340cf4134500352122cf42ab40b9de6734 + */ + +const singleComment = Symbol('singleComment') +const multiComment = Symbol('multiComment') + +const stripWithoutWhitespace = () => '' +const stripWithWhitespace = (string, start, end) => + string.slice(start, end).replace(/\S/g, ' ') + +const isEscaped = (jsonString, quotePosition) => { + let index = quotePosition - 1 + let backslashCount = 0 + + while (jsonString[index] === '\\') { + index -= 1 + backslashCount += 1 + } + + return Boolean(backslashCount % 2) +} + +export default function stripJsonComments( + jsonString, + { whitespace = true, trailingCommas = false } = {} +) { + if (typeof jsonString !== 'string') { + throw new TypeError( + `Expected argument \`jsonString\` to be a \`string\`, got \`${typeof jsonString}\`` + ) + } + + const strip = whitespace ? stripWithWhitespace : stripWithoutWhitespace + + let isInsideString = false + let isInsideComment = false + let offset = 0 + let buffer = '' + let result = '' + let commaIndex = -1 + + for (let index = 0; index < jsonString.length; index++) { + const currentCharacter = jsonString[index] + const nextCharacter = jsonString[index + 1] + + if (!isInsideComment && currentCharacter === '"') { + // Enter or exit string + const escaped = isEscaped(jsonString, index) + if (!escaped) { + isInsideString = !isInsideString + } + } + + if (isInsideString) { + continue + } + + if (!isInsideComment && currentCharacter + nextCharacter === '//') { + // Enter single-line comment + buffer += jsonString.slice(offset, index) + offset = index + isInsideComment = singleComment + index++ + } else if ( + isInsideComment === singleComment && + currentCharacter + nextCharacter === '\r\n' + ) { + // Exit single-line comment via \r\n + index++ + isInsideComment = false + buffer += strip(jsonString, offset, index) + offset = index + continue + } else if (isInsideComment === singleComment && currentCharacter === '\n') { + // Exit single-line comment via \n + isInsideComment = false + buffer += strip(jsonString, offset, index) + offset = index + } else if (!isInsideComment && currentCharacter + nextCharacter === '/*') { + // Enter multiline comment + buffer += jsonString.slice(offset, index) + offset = index + isInsideComment = multiComment + index++ + continue + } else if ( + isInsideComment === multiComment && + currentCharacter + nextCharacter === '*/' + ) { + // Exit multiline comment + index++ + isInsideComment = false + buffer += strip(jsonString, offset, index + 1) + offset = index + 1 + continue + } else if (trailingCommas && !isInsideComment) { + if (commaIndex !== -1) { + if (currentCharacter === '}' || currentCharacter === ']') { + // Strip trailing comma + buffer += jsonString.slice(offset, index) + result += strip(buffer, 0, 1) + buffer.slice(1) + buffer = '' + offset = index + commaIndex = -1 + } else if ( + currentCharacter !== ' ' && + currentCharacter !== '\t' && + currentCharacter !== '\r' && + currentCharacter !== '\n' + ) { + // Hit non-whitespace following a comma; comma is not trailing + buffer += jsonString.slice(offset, index) + offset = index + commaIndex = -1 + } + } else if (currentCharacter === ',') { + // Flush buffer prior to this point, and save new comma index + result += buffer + jsonString.slice(offset, index) + buffer = '' + offset = index + commaIndex = index + } + } + } + + return ( + result + + buffer + + (isInsideComment + ? strip(jsonString.slice(offset)) + : jsonString.slice(offset)) + ) +} diff --git a/packages/generator/src/shared/generator-context.ts b/packages/generator/src/shared/generator-context.ts index 3a5843f..efc40c3 100644 --- a/packages/generator/src/shared/generator-context.ts +++ b/packages/generator/src/shared/generator-context.ts @@ -1,7 +1,9 @@ import fs from 'node:fs' import path from 'node:path' import type { GeneratorOptions } from '@prisma/generator-helper' +import { object, safeParse, string } from 'valibot' import { getModuleResolution } from '~/lib/config' +import stripJsonComments from '~/lib/strip-json-comments' type GeneratorContext = { moduleResolution?: string @@ -30,8 +32,20 @@ function resolveModuleResolution(options: GeneratorOptions) { const tsConfigPath = findTsConfig() if (!tsConfigPath) return - const tsConfig = JSON.parse(fs.readFileSync(tsConfigPath, 'utf-8')) - return tsConfig?.compilerOptions?.moduleResolution + const parsing = safeParse(TsConfig, readTsConfig(tsConfigPath)) + if (!parsing.success) return + + return parsing.output.compilerOptions.moduleResolution +} + +const TsConfig = object({ + compilerOptions: object({ + moduleResolution: string(), + }), +}) + +function readTsConfig(tsConfigPath: string) { + return JSON.parse(stripJsonComments(fs.readFileSync(tsConfigPath, 'utf-8'))) } function findTsConfig() {