Skip to content

Commit b4acf2c

Browse files
committed
Refactor registry validation and update package scripts
- Updated the build script to use tsdown for improved build performance. - Refactored validation logic for registry templates to enhance error handling and maintainability. - Added new validation functions for template metadata and improved checks for declared files. - Updated test scripts to utilize vitest for better testing capabilities. - Adjusted TypeScript configuration to prevent emitting output files during development.
1 parent 5775517 commit b4acf2c

File tree

10 files changed

+1403
-70
lines changed

10 files changed

+1403
-70
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ package-lock.json
88
yarn.lock
99

1010
# TESTING
11-
/coverage
11+
coverage/
1212
*.lcov
1313
.nyc_output
1414

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
import { describe, it, expect, beforeEach, vi } from "vitest";
2+
import {
3+
validateTemplateMetadata,
4+
isValidRegistryTemplate,
5+
type ValidationContext,
6+
} from "./validator.js";
7+
import fs from "fs";
8+
import path from "path";
9+
10+
// Mock fs module
11+
vi.mock("fs");
12+
const mockedFs = vi.mocked(fs);
13+
14+
describe("validator", () => {
15+
let mockContext: ValidationContext;
16+
17+
beforeEach(() => {
18+
vi.clearAllMocks();
19+
mockContext = {
20+
templatesPath: "/mock/templates",
21+
templateName: "test-template",
22+
templateDir: "/mock/templates/test-template",
23+
};
24+
});
25+
26+
describe("isValidRegistryTemplate", () => {
27+
it("should return true when template meta file exists", () => {
28+
mockedFs.existsSync.mockReturnValue(true);
29+
30+
const result = isValidRegistryTemplate(
31+
"email/generic",
32+
"/mock/templates",
33+
);
34+
35+
expect(result).toBe(true);
36+
expect(mockedFs.existsSync).toHaveBeenCalledWith(
37+
"/mock/templates/email/generic/_meta.ts",
38+
);
39+
});
40+
41+
it("should return false when template meta file does not exist", () => {
42+
mockedFs.existsSync.mockReturnValue(false);
43+
44+
const result = isValidRegistryTemplate("nonexistent", "/mock/templates");
45+
46+
expect(result).toBe(false);
47+
expect(mockedFs.existsSync).toHaveBeenCalledWith(
48+
"/mock/templates/nonexistent/_meta.ts",
49+
);
50+
});
51+
});
52+
53+
describe("validateTemplateMetadata", () => {
54+
beforeEach(() => {
55+
// Mock readdir to return empty files by default
56+
mockedFs.readdirSync.mockReturnValue([]);
57+
});
58+
59+
it("should validate a basic valid template", () => {
60+
const validMeta = {
61+
type: "static",
62+
title: "Test Template",
63+
description: "A test template",
64+
category: "utility",
65+
registryType: "registry:lib",
66+
files: [],
67+
};
68+
69+
expect(() =>
70+
validateTemplateMetadata(validMeta, mockContext),
71+
).not.toThrow();
72+
});
73+
74+
it("should throw error for invalid metadata structure", () => {
75+
const invalidMeta = {
76+
type: "invalid-type",
77+
title: "Test",
78+
};
79+
80+
expect(() => validateTemplateMetadata(invalidMeta, mockContext)).toThrow(
81+
/Invalid metadata structure/,
82+
);
83+
});
84+
85+
it("should throw error for missing required fields", () => {
86+
const incompleteMeta = {
87+
type: "static",
88+
title: "Test",
89+
// missing description, category, registryType
90+
};
91+
92+
expect(() =>
93+
validateTemplateMetadata(incompleteMeta, mockContext),
94+
).toThrow(/Invalid metadata structure/);
95+
});
96+
97+
it("should validate files exist when declared", () => {
98+
const metaWithFiles = {
99+
type: "static",
100+
title: "Test Template",
101+
description: "A test template",
102+
category: "utility",
103+
registryType: "registry:lib",
104+
files: [
105+
{
106+
sourceFileName: "component.tsx",
107+
destinationPath: "components/component.tsx",
108+
type: "registry:component",
109+
},
110+
],
111+
};
112+
113+
// Mock that the file exists
114+
mockedFs.readdirSync.mockReturnValue(["component.tsx", "_meta.ts"]);
115+
116+
expect(() =>
117+
validateTemplateMetadata(metaWithFiles, mockContext),
118+
).not.toThrow();
119+
});
120+
121+
it("should throw error when declared file does not exist", () => {
122+
const metaWithMissingFile = {
123+
type: "static",
124+
title: "Test Template",
125+
description: "A test template",
126+
category: "utility",
127+
registryType: "registry:lib",
128+
files: [
129+
{
130+
sourceFileName: "missing.tsx",
131+
destinationPath: "components/missing.tsx",
132+
type: "registry:component",
133+
},
134+
],
135+
};
136+
137+
// Mock that the file doesn't exist
138+
mockedFs.readdirSync.mockReturnValue(["_meta.ts"]);
139+
140+
expect(() =>
141+
validateTemplateMetadata(metaWithMissingFile, mockContext),
142+
).toThrow(/Declared file 'missing.tsx' does not exist/);
143+
});
144+
145+
it("should throw error when declared file does not exist (caught first)", () => {
146+
const metaWithFiles = {
147+
type: "static",
148+
title: "Test Template",
149+
description: "A test template",
150+
category: "utility",
151+
registryType: "registry:lib",
152+
files: [
153+
{
154+
sourceFileName: "component.tsx",
155+
destinationPath: "components/component.tsx",
156+
type: "registry:component",
157+
},
158+
],
159+
};
160+
161+
// Mock empty directory (only _meta.ts) - this will trigger "file does not exist" first
162+
mockedFs.readdirSync.mockReturnValue(["_meta.ts"]);
163+
164+
expect(() =>
165+
validateTemplateMetadata(metaWithFiles, mockContext),
166+
).toThrow(/Declared file 'component.tsx' does not exist/);
167+
});
168+
169+
it("should allow templates with no files (dependency-only templates)", () => {
170+
const dependencyOnlyMeta = {
171+
type: "static",
172+
title: "Dependency Template",
173+
description: "A template that only adds dependencies",
174+
category: "utility",
175+
registryType: "registry:lib",
176+
files: [],
177+
dependencies: ["react", "react-dom"],
178+
};
179+
180+
// Mock empty directory
181+
mockedFs.readdirSync.mockReturnValue(["_meta.ts"]);
182+
183+
expect(() =>
184+
validateTemplateMetadata(dependencyOnlyMeta, mockContext),
185+
).not.toThrow();
186+
});
187+
188+
it("should validate proofkitDependencies exist", () => {
189+
const metaWithProofkitDeps = {
190+
type: "static",
191+
title: "Test Template",
192+
description: "A test template",
193+
category: "utility",
194+
registryType: "registry:lib",
195+
files: [],
196+
proofkitDependencies: ["react-email"],
197+
};
198+
199+
// Mock that react-email template exists
200+
mockedFs.existsSync.mockImplementation((filePath) => {
201+
return filePath === "/mock/templates/react-email/_meta.ts";
202+
});
203+
204+
expect(() =>
205+
validateTemplateMetadata(metaWithProofkitDeps, mockContext),
206+
).not.toThrow();
207+
});
208+
209+
it("should throw error for invalid proofkitDependencies", () => {
210+
const metaWithInvalidDeps = {
211+
type: "static",
212+
title: "Test Template",
213+
description: "A test template",
214+
category: "utility",
215+
registryType: "registry:lib",
216+
files: [],
217+
proofkitDependencies: ["nonexistent-template"],
218+
};
219+
220+
// Mock that template doesn't exist
221+
mockedFs.existsSync.mockReturnValue(false);
222+
223+
expect(() =>
224+
validateTemplateMetadata(metaWithInvalidDeps, mockContext),
225+
).toThrow(
226+
/Invalid proofkitDependencies reference 'nonexistent-template'/,
227+
);
228+
});
229+
230+
it("should validate proofkit registryDependencies", () => {
231+
const metaWithRegistryDeps = {
232+
type: "static",
233+
title: "Test Template",
234+
description: "A test template",
235+
category: "utility",
236+
registryType: "registry:lib",
237+
files: [],
238+
registryDependencies: [
239+
"{proofkit}/r/email/generic",
240+
"external-component", // non-proofkit, should be ignored
241+
],
242+
};
243+
244+
// Mock that email/generic template exists
245+
mockedFs.existsSync.mockImplementation((filePath) => {
246+
return filePath === "/mock/templates/email/generic/_meta.ts";
247+
});
248+
249+
expect(() =>
250+
validateTemplateMetadata(metaWithRegistryDeps, mockContext),
251+
).not.toThrow();
252+
});
253+
254+
it("should throw error for invalid proofkit registryDependencies", () => {
255+
const metaWithInvalidRegistryDeps = {
256+
type: "static",
257+
title: "Test Template",
258+
description: "A test template",
259+
category: "utility",
260+
registryType: "registry:lib",
261+
files: [],
262+
registryDependencies: ["{proofkit}/r/email/nonexistent"],
263+
};
264+
265+
// Mock that template doesn't exist
266+
mockedFs.existsSync.mockReturnValue(false);
267+
268+
expect(() =>
269+
validateTemplateMetadata(metaWithInvalidRegistryDeps, mockContext),
270+
).toThrow(
271+
/Invalid registryDependencies reference '{proofkit}\/r\/email\/nonexistent'/,
272+
);
273+
});
274+
275+
it("should ignore non-proofkit registryDependencies", () => {
276+
const metaWithMixedDeps = {
277+
type: "static",
278+
title: "Test Template",
279+
description: "A test template",
280+
category: "utility",
281+
registryType: "registry:lib",
282+
files: [],
283+
registryDependencies: [
284+
"shadcn/ui/button", // external, should be ignored
285+
"some-other-registry/component", // external, should be ignored
286+
],
287+
};
288+
289+
expect(() =>
290+
validateTemplateMetadata(metaWithMixedDeps, mockContext),
291+
).not.toThrow();
292+
});
293+
294+
it("should validate dynamic templates", () => {
295+
const dynamicMeta = {
296+
type: "dynamic",
297+
title: "Dynamic Template",
298+
description: "A dynamic template",
299+
category: "component",
300+
registryType: "registry:component",
301+
files: [],
302+
schema: {
303+
type: "object",
304+
properties: {
305+
name: { type: "string" },
306+
},
307+
},
308+
};
309+
310+
expect(() =>
311+
validateTemplateMetadata(dynamicMeta, mockContext),
312+
).not.toThrow();
313+
});
314+
315+
it("should validate all category types", () => {
316+
const categories = ["component", "page", "utility", "hook", "email"];
317+
318+
categories.forEach((category) => {
319+
const meta = {
320+
type: "static",
321+
title: "Test Template",
322+
description: "A test template",
323+
category,
324+
registryType: "registry:lib",
325+
files: [],
326+
};
327+
328+
expect(() => validateTemplateMetadata(meta, mockContext)).not.toThrow();
329+
});
330+
});
331+
332+
it("should validate all registryType values", () => {
333+
const registryTypes = [
334+
"registry:lib",
335+
"registry:component",
336+
"registry:hook",
337+
"registry:ui",
338+
"registry:file",
339+
"registry:page",
340+
];
341+
342+
registryTypes.forEach((registryType) => {
343+
const meta = {
344+
type: "static",
345+
title: "Test Template",
346+
description: "A test template",
347+
category: "utility",
348+
registryType,
349+
files: [],
350+
};
351+
352+
expect(() => validateTemplateMetadata(meta, mockContext)).not.toThrow();
353+
});
354+
});
355+
});
356+
});

0 commit comments

Comments
 (0)