diff --git a/packages/cli/src/registry/validation.startup.test.ts b/packages/cli/src/registry/validation.startup.test.ts new file mode 100644 index 0000000000..87c95083d6 --- /dev/null +++ b/packages/cli/src/registry/validation.startup.test.ts @@ -0,0 +1,24 @@ +import { expect, it, vi } from "vitest"; + +const compiles = vi.hoisted(() => ({ count: 0 })); + +vi.mock("ajv/dist/2020.js", async (importOriginal) => { + const real = await importOriginal(); + const compile = real.Ajv2020.prototype.compile; + real.Ajv2020.prototype.compile = function (this: unknown, ...args: unknown[]) { + compiles.count += 1; + return Reflect.apply(compile, this, args); + } as typeof compile; + return { ...real }; +}); + +it("compiles the registry schemas on first use, not when the module is imported", async () => { + const { validRegistryItem } = await import("./validation.js"); + expect(compiles.count).toBe(0); + + validRegistryItem({}, "demo", "hyperframes:component"); + expect(compiles.count).toBe(2); + + validRegistryItem({}, "demo", "hyperframes:component"); + expect(compiles.count).toBe(2); +}); diff --git a/packages/cli/src/registry/validation.ts b/packages/cli/src/registry/validation.ts index cad89a406e..974924ae5a 100644 --- a/packages/cli/src/registry/validation.ts +++ b/packages/cli/src/registry/validation.ts @@ -3,9 +3,14 @@ import registrySchema from "@hyperframes/core/schemas/registry.json"; import itemSchema from "@hyperframes/core/schemas/registry-item.json"; import type { RegistryManifest, RegistryItem, ItemType } from "@hyperframes/core"; -const ajv = new Ajv2020({ strict: false, validateFormats: false }); -const manifestShape = ajv.compile(registrySchema); -const itemShape = ajv.compile(itemSchema); +function compileShapes() { + const ajv = new Ajv2020({ strict: false, validateFormats: false }); + return { + manifest: ajv.compile(registrySchema), + item: ajv.compile(itemSchema), + }; +} +let shapesCompiledOnFirstUse: ReturnType | undefined; export function validRegistryName(name: string): boolean { return name.length <= 128 && /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(name); @@ -13,7 +18,7 @@ export function validRegistryName(name: string): boolean { export function validRegistryManifest(value: unknown): value is RegistryManifest { return ( - manifestShape(value) && + (shapesCompiledOnFirstUse ??= compileShapes()).manifest(value) && value.items.length <= 10_000 && value.items.every((item) => validRegistryName(item.name)) ); @@ -25,7 +30,7 @@ export function validRegistryItem( type: ItemType, ): value is RegistryItem { return ( - itemShape(value) && + (shapesCompiledOnFirstUse ??= compileShapes()).item(value) && value.name === name && value.type === type && validRegistryName(value.name) &&