diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 358c6dc..fa2d9da 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,6 +25,8 @@ jobs: run: bun install - name: run type check run: bun run types + - name: run schema scheck + run: bun run schemas - name: setup data fixture run: mkdir -p site/src/generated && cp site/src/lib/server/__fixtures__/data.json site/src/generated/data.json - name: run unit tests diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7b54dd0 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "json.schemas": [ + { + "fileMatch": ["/site/src/content/enabler/*.json"], + "url": "./site/src/schemas/enabler.json" + } + ] +} diff --git a/bun.lockb b/bun.lockb index 32a1f4c..7995679 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 9ebc300..670dd59 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,9 @@ "build": "cd site && bun run build", "preview": "cd site && bun run preview", "test": "TZ=America/New_York vitest", - "types": "tsc --noEmit" + "types": "tsc --noEmit", + "gen-schemas": "bun run scripts/zod-json-schema.ts", + "schemas": "bun run ajv validate -s site/src/schemas/enabler -d \"site/src/content/enabler/*.json\"" }, "devDependencies": { "@types/bun": "latest" @@ -21,10 +23,12 @@ "typescript": "^5.0.0" }, "dependencies": { + "ajv-cli": "^5.0.0", "d3-node": "^3.0.0", "date-fns": "^3.3.0", "point-at-length": "^1.1.0", "reading-time": "^1.5.0", - "vitest": "^1.2.1" + "vitest": "^1.2.1", + "zod-to-json-schema": "^3.22.4" } } diff --git a/scripts/zod-json-schema.ts b/scripts/zod-json-schema.ts new file mode 100644 index 0000000..8acc021 --- /dev/null +++ b/scripts/zod-json-schema.ts @@ -0,0 +1,21 @@ +import fs from "fs"; +// @ts-expect-error bun types not up to date on alpha api (?) +import { $ } from "bun"; +import { zodToJsonSchema } from "zod-to-json-schema"; + +const zodFiles = await $`ls site/src/schemas`; +const modules = zodFiles.stdout + .toString() + .split("\n") + .map((line: string) => line.trim()); + +modules.forEach((fileName: string) => { + if (fileName) { + const { schema, name } = require(`../site/src/schemas/${fileName}`); + const jsonSchema = zodToJsonSchema(schema, name); + fs.writeFileSync( + `site/src/schemas/${name}.json`, + JSON.stringify(jsonSchema, null, 2) + ); + } +}); diff --git a/site/src/content/config.ts b/site/src/content/config.ts index 9cee21e..fc32f24 100644 --- a/site/src/content/config.ts +++ b/site/src/content/config.ts @@ -1,9 +1,9 @@ import { defineCollection } from "astro:content"; -import { enablerSchema } from "../schemas/enabler"; +import { schema } from "../schemas/enabler"; export const collections = { enabler: defineCollection({ type: "data", - schema: enablerSchema, + schema, }), }; diff --git a/site/src/content/enabler/anthony-housefather.json b/site/src/content/enabler/anthony-housefather.json index 6359f71..91c57e3 100644 --- a/site/src/content/enabler/anthony-housefather.json +++ b/site/src/content/enabler/anthony-housefather.json @@ -10,8 +10,10 @@ "href": "" }, { - "text": "Marking 100 days since Hamas attacked Israel and brutally murdered 1200 people, including eight Canadians. 100 days since Hamas took 240 hostages, and still holds over 130 in captivity. The world may have moved on since October 7, but we will #NeverForget. #BringThemHomeNow", - "author": "@marcomendicino", + "quote": { + "text": "Marking 100 days since Hamas attacked Israel and brutally murdered 1200 people, including eight Canadians. 100 days since Hamas took 240 hostages, and still holds over 130 in captivity. The world may have moved on since October 7, but we will #NeverForget. #BringThemHomeNow", + "author": "@marcomendicino" + }, "date": "2024-01-14T17:19:00.000Z", "type": "twitter", "href": "" diff --git a/site/src/schemas/README.md b/site/src/schemas/README.md new file mode 100644 index 0000000..ab7be47 --- /dev/null +++ b/site/src/schemas/README.md @@ -0,0 +1,3 @@ +# zod schemas + +Astro uses Zod to enforce content JSON schema. We use JSON schemas to make editing easier, and you can generate the json schemas in this folder with `bun run gen-schemas` from the root of the repo. diff --git a/site/src/schemas/enabler.json b/site/src/schemas/enabler.json new file mode 100644 index 0000000..a22405d --- /dev/null +++ b/site/src/schemas/enabler.json @@ -0,0 +1,89 @@ +{ + "$ref": "#/definitions/enabler", + "definitions": { + "enabler": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "image": { + "type": "string" + }, + "posts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "twitter" + ] + }, + "href": { + "type": "string" + }, + "text": { + "type": "string" + }, + "date": { + "type": "string" + }, + "image": { + "type": "string" + }, + "imageCaption": { + "type": "string" + }, + "commentary": { + "type": "string" + }, + "skip": { + "type": "boolean" + }, + "quote": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "author": { + "type": "string" + }, + "image": { + "type": "string" + }, + "imageCaption": { + "type": "string" + } + }, + "required": [ + "text", + "author" + ], + "additionalProperties": false + } + }, + "required": [ + "type", + "href", + "date" + ], + "additionalProperties": false + } + } + }, + "required": [ + "name", + "bio", + "posts" + ], + "additionalProperties": false + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/site/src/schemas/enabler.ts b/site/src/schemas/enabler.ts index 9b18358..bc958eb 100644 --- a/site/src/schemas/enabler.ts +++ b/site/src/schemas/enabler.ts @@ -1,6 +1,10 @@ -import { z } from "astro:content"; +import { z } from "zod"; -export const enablerSchema = z.object({ +/** + * run "bun run gen-schemas" to update the matching JSON schema if changed + */ + +export const schema = z.object({ name: z.string(), bio: z.string(), image: z.string().optional(), @@ -26,5 +30,7 @@ export const enablerSchema = z.object({ ), }); -export type Enabler = z.infer; +export const name = "enabler"; + +export type Enabler = z.infer; export type EnablerPost = Enabler["posts"][0];