UNPKG

create-validator-ts

Version:

Create JSON Schema validator from TypeScript.

186 lines (179 loc) 6.55 kB
import globWatch from "glob-watcher"; import fs from "node:fs/promises"; import * as globby from "globby"; import assert from "node:assert"; import { generateValidator, TsJsonSchemaGeneratorOptions } from "./create-validator-ts.js"; import { CodeGenerator } from "./default-code-generator.js"; import path from "node:path"; import { createCache } from "@file-cache/core"; import { createNpmPackageKey } from "@file-cache/npm"; import { pathToFileURL } from "node:url"; export type { GenerateValidatorCodeOptions, CodeGenerator } from "./default-code-generator.js"; // dynamic import wrapper // import() can not load Window file path // convert file path to file URL before import() // https://github.com/nodejs/node/issues/31710 export async function dynamicImport(targetPath: string) { const fileUrl = pathToFileURL(targetPath).href; return import(fileUrl); } export type CreateTSValidatorOptions = { cwd: string; verbose: boolean; tsconfigFilePath: string; codeGeneratorScript: string; targetGlobs: string[]; noCache?: boolean; // true by default } & TsJsonSchemaGeneratorOptions; export async function watchValidator(options: CreateTSValidatorOptions) { const { generator, generatorOptions = {} } = (await dynamicImport( path.resolve(options.cwd, options.codeGeneratorScript) )) as { generator: CodeGenerator; generatorOptions?: { extraTags?: string[]; }; }; const watcher = globWatch(options.targetGlobs, { ignoreInitial: true }); const { sortProps, strictTuples, encodeRefs, skipTypeCheck, additionalProperties } = options; return new Promise<void>((resolve, reject) => { watcher.on("change", async (filePath) => { const result = await generateValidator({ cwd: options.cwd, filePath: filePath, tsconfigFilePath: options.tsconfigFilePath, validatorGenerator: generator, extraTags: generatorOptions.extraTags || [], sortProps, strictTuples, encodeRefs, skipTypeCheck, additionalProperties }); if (!result) { return; } if (options.verbose) { console.log("Update validator: " + result.validatorFilePath); } return fs.writeFile(result.validatorFilePath, result.code, "utf-8"); }); watcher.on("close", () => { resolve(); }); watcher.on("error", (error) => { reject(error); }); }); } // --check: validate the difference current of source export async function testGeneratedValidator(options: CreateTSValidatorOptions) { const files = await globby.globby(options.targetGlobs, { cwd: options.cwd, absolute: true }); const { generator, generatorOptions = {} } = (await dynamicImport( path.resolve(options.cwd, options.codeGeneratorScript) )) as { generator: CodeGenerator; generatorOptions?: { extraTags?: string[]; }; }; const { sortProps, strictTuples, encodeRefs, skipTypeCheck, additionalProperties } = options; return Promise.all( files.map(async (filePath) => { const result = await generateValidator({ cwd: options.cwd, filePath: filePath, tsconfigFilePath: options.tsconfigFilePath, validatorGenerator: generator, extraTags: generatorOptions.extraTags || [], sortProps, strictTuples, encodeRefs, skipTypeCheck, additionalProperties }); if (!result) { return; } try { await fs.access(result.validatorFilePath); } catch { return; } const prevValidatorCode = await fs.readFile(result.validatorFilePath, { encoding: "utf-8" }); try { assert.strictEqual(result.code, prevValidatorCode); } catch (error) { console.error( "Found diff between types and validator.\nPlease update validator: $ npx create-validator-ts " + filePath ); throw error; } if (options.verbose) { console.log("OK: " + filePath); } }) ); } export async function createValidator(options: CreateTSValidatorOptions) { const { generator, generatorOptions = {} } = (await dynamicImport( path.resolve(options.cwd, options.codeGeneratorScript) )) as { generator: CodeGenerator; generatorOptions?: { extraTags?: string[]; }; }; const files = await globby.globby(options.targetGlobs, { cwd: options.cwd, absolute: true }); const { sortProps, strictTuples, encodeRefs, skipTypeCheck, additionalProperties } = options; const cache = await createCache({ name: "create-validator-ts", mode: "content", keys: [() => createNpmPackageKey(["create-validator-ts"]), () => JSON.stringify(options)], // disable cache by default noCache: options.noCache ?? true }); return Promise.all( files.map(async (filePath) => { const cacheStatus = await cache.getAndUpdateCache(filePath); if (!cacheStatus.changed) { if (options.verbose) { console.log("Cached: " + filePath); } return; } const result = await generateValidator({ cwd: options.cwd, filePath: filePath, tsconfigFilePath: options.tsconfigFilePath, validatorGenerator: generator, extraTags: generatorOptions.extraTags || [], sortProps, strictTuples, encodeRefs, skipTypeCheck, additionalProperties }); if (!result) { return; } if (options.verbose) { console.log("Create: " + filePath); } return fs.writeFile(result.validatorFilePath, result.code, "utf-8"); }) ).finally(() => { return cache.reconcile(); }); }