Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
57b1880
setup workspace
amotarao Dec 30, 2025
eb6cfa0
setup schema
amotarao Dec 30, 2025
c84d217
add min-max example
amotarao Dec 30, 2025
60dd648
implement schema validation with zod
amotarao Dec 30, 2025
04271e8
rename to microcms-api-schema-schema
amotarao Dec 30, 2025
6d2fcb6
add enum validation for field options
amotarao Dec 30, 2025
86bfc84
update min-max
amotarao Dec 30, 2025
543e409
add JSON Schema generation on build
amotarao Dec 30, 2025
25ed77a
v0.0.0
amotarao Dec 30, 2025
5f3e498
rename apiSchemaSchema
amotarao Dec 30, 2025
8e3a094
add test step to CI workflow
amotarao Dec 30, 2025
55f120e
test, test:watch 分離
amotarao Dec 31, 2025
58eeb3c
__dirname
amotarao Dec 31, 2025
ac49f23
refactor: test.each を使って schema-examples テストの重複を解消
amotarao Dec 31, 2025
82ff054
rename fn
amotarao Dec 31, 2025
db7ae12
rename to microcms-api-schema-schema
amotarao Jan 1, 2026
aefb637
feat: type-check スクリプトを追加し CI に組み込み
amotarao Jan 2, 2026
d778f66
docs: README の説明を API スキーマのスキーマとして明確化
amotarao Jan 3, 2026
19a2b1e
rename pkg
amotarao Jan 3, 2026
63e82e6
Merge branch 'main' into feature/setup-schema-package
amotarao Jan 7, 2026
703ce0a
schema.json を root に出力
amotarao Jan 7, 2026
bfbb03e
zod を peerDependencies 化, v3,4 対応
amotarao Jan 7, 2026
2d40a30
Merge branch 'main' into feature/setup-schema-package
amotarao Jan 7, 2026
0da1815
add prepare script
amotarao Jan 7, 2026
3167022
v0.0.1-alpha.0
amotarao Jan 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
pull_request:

jobs:
format-check:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
Expand All @@ -24,3 +24,9 @@ jobs:

- name: Check format
run: pnpm format --check

- name: Type check
run: pnpm --filter microcms-api-schema-schema type-check

- name: Run tests
run: pnpm --filter microcms-api-schema-schema test
2 changes: 2 additions & 0 deletions packages/microcms-api-schema-schema/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/schema.json
dist
26 changes: 26 additions & 0 deletions packages/microcms-api-schema-schema/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# microcms-api-schema-schema

microCMS の API スキーマを定義するためのスキーマライブラリ

## 概要

このパッケージは、microCMS の API スキーマを TypeScript で型安全に定義し、Zod によるランタイムバリデーションを提供します。

## インストール

```bash
pnpm add microcms-api-schema-schema
```

## 開発

```bash
# ビルド
pnpm build

# 開発モード(watch)
pnpm dev

# テスト
pnpm test
```
42 changes: 41 additions & 1 deletion packages/microcms-api-schema-schema/package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,49 @@
{
"name": "@plainbrew/microcms-api-schema-schema",
"version": "0.0.0",
"version": "0.0.1-alpha.0",
"description": "",
"license": "MIT",
"files": [
"dist",
"schema.json",
"src"
],
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./schema.json": "./schema.json"
},
"publishConfig": {
"access": "public"
},
"scripts": {
"dev": "tsc --watch",
"build": "pnpm build:tsc && pnpm build:schema",
"build:tsc": "tsc",
"build:schema": "node scripts/generate-json-schema.ts",
"type-check": "tsc --noEmit",
"test": "vitest run",
"test:watch": "vitest",
"prepare": "pnpm run build"
},
"devDependencies": {
"@types/node": "^24.10.4",
"typescript": "^5.7.3",
"vitest": "^4.0.16",
"zod": "^4.2.1"
},
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
},
"peerDependenciesMeta": {
"zod": {
"optional": true
}
}
Comment on lines +40 to 47
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify zod import paths in schema.ts and related files

echo "=== Searching for zod imports ==="
rg -n "from ['\"]zod" packages/microcms-api-schema-schema/src/

echo ""
echo "=== Searching for zod require statements ==="
rg -n "require\(['\"]zod" packages/microcms-api-schema-schema/src/

Repository: plainbrew/microcms-schema

Length of output: 229


peerDependenciesとインポートパスの不一致を修正してください。

schema.ts"zod/v4"からインポートしていますが、このエクスポートパスはZod 4にのみ存在します。一方、package.jsonのpeerDependenciesはZod v3と v4の両方を許可しているため、ユーザーがZod 3をインストールするとインポートエラーが発生します。

以下のいずれかの対応が必要です:

  1. v3との互換性を維持する場合: schema.tsのインポートを"zod"に変更
  2. v4のみをサポートする場合: peerDependenciesを"^4.0.0"のみに制限
🤖 Prompt for AI Agents
In @packages/microcms-api-schema-schema/package.json around lines 40 - 47, The
peerDependencies allow Zod v3 but schema.ts imports from the v4-only path
"zod/v4", causing runtime import errors for v3 users; either change the import
in schema.ts to import from "zod" (to maintain v3/v4 compatibility) or restrict
package.json peerDependencies to "^4.0.0" only (if you want to support v4
exclusively). Locate the import statement in schema.ts that references "zod/v4"
and update it to "zod" for compatibility, or update the peerDependencies entry
in package.json to "zod": "^4.0.0" to enforce v4-only support and remove the v3
range and optional metadata accordingly.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { writeFileSync, mkdirSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { z } from "zod";
import { apiSchemaSchema } from "../src/schema.ts";

const __dirname = dirname(fileURLToPath(import.meta.url));
const distDir = join(__dirname, "../");

mkdirSync(distDir, { recursive: true });

writeFileSync(
join(distDir, "schema.json"),
JSON.stringify(z.toJSONSchema(apiSchemaSchema), null, 2),
);

console.log("Generated schema.json");
1 change: 1 addition & 0 deletions packages/microcms-api-schema-schema/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { apiSchemaSchema, ApiSchemaSchema } from "./schema.js";
32 changes: 32 additions & 0 deletions packages/microcms-api-schema-schema/src/schema-examples.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, test } from "vitest";
import { apiSchemaSchema } from "./schema.js";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const SCHEMA_EXAMPLES_DIR = join(__dirname, "../../../schema-examples");

const schemaFiles = [
"official-categories.json",
"official-news.json",
"official-blogs.json",
"official-banner.json",
"min-max.json",
];

describe("schema-examples のファイルをバリデーション", () => {
test.each(schemaFiles)("%s", (filename) => {
const filePath = join(SCHEMA_EXAMPLES_DIR, filename);
const content = readFileSync(filePath, "utf-8");
const schema = JSON.parse(content);

const result = apiSchemaSchema.safeParse(schema);
if (!result.success) {
console.error(result.error);
}
expect(result.success).toBe(true);
});
});
200 changes: 200 additions & 0 deletions packages/microcms-api-schema-schema/src/schema.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import { describe, expect, test } from "vitest";
import { apiSchemaSchema } from "./schema.js";

describe("apiSchemaSchema", () => {
test("official-categories.json のスキーマをバリデーションできる", () => {
const schema = {
apiFields: [
{
fieldId: "name",
name: "カテゴリ名",
kind: "text",
required: false,
isUnique: false,
},
],
customFields: [],
};

const result = apiSchemaSchema.safeParse(schema);
expect(result.success).toBe(true);
});

test("official-news.json のスキーマをバリデーションできる", () => {
const schema = {
apiFields: [
{
fieldId: "title",
name: "タイトル",
kind: "text",
required: false,
isUnique: false,
},
{
fieldId: "content",
name: "内容",
kind: "richEditorV2",
required: false,
},
{
fieldId: "category",
name: "カテゴリ",
kind: "relation",
required: false,
},
],
customFields: [],
};

const result = apiSchemaSchema.safeParse(schema);
expect(result.success).toBe(true);
});

test("official-blogs.json のスキーマをバリデーションできる", () => {
const schema = {
apiFields: [
{
fieldId: "title",
name: "タイトル",
kind: "text",
required: false,
isUnique: false,
},
{
fieldId: "content",
name: "内容",
kind: "richEditorV2",
required: false,
},
{
fieldId: "eyecatch",
name: "アイキャッチ",
kind: "media",
required: false,
},
{
fieldId: "category",
name: "カテゴリ",
kind: "relation",
required: false,
},
],
customFields: [],
};

const result = apiSchemaSchema.safeParse(schema);
expect(result.success).toBe(true);
});

test("複雑なスキーマをバリデーションできる", () => {
const schema = {
apiFields: [
{
fieldId: "text_max",
name: "テキストフィールド 最大",
kind: "text",
description: "説明文",
required: true,
selectItems: [],
selectInitialValue: [],
multipleSelect: false,
textSizeLimitValidation: { textSize: { min: 1, max: 30 } },
patternMatchValidation: { regexp: { pattern: "\\d", flags: "gi" } },
isUnique: true,
},
{
fieldId: "rich_max",
name: "リッチエディタ 最大",
kind: "richEditorV2",
description: "説明文",
required: true,
richEditorV2Options: ["undo", "redo", "bold", "italic", "underline", "code"],
richEditorV2ColorList: [
{ id: "KDKsIv0jXZ", value: "rgb(255, 255, 255)" },
{ id: "vHyfNOIY51", value: "rgb(156, 102, 102)" },
],
richEditorV2HideColorPicker: true,
richEditorV2FontSizeList: [{ id: "Sp9MppHN43", name: "表示名", value: "120" }],
customClassList: [{ id: "nL3_cmKOxf", name: "表示名", value: "class名" }],
},
{
fieldId: "image_max",
name: "画像 最大",
kind: "media",
description: "説明文",
required: true,
imageSizeValidation: { imageSize: { width: 100, height: 100 } },
},
{
fieldId: "select_max",
name: "セレクトフィールド 最大",
kind: "select",
required: true,
selectItems: [
{ id: "viO1_AW73E", value: "item1" },
{ id: "6bnahbE--K", value: "item2" },
],
selectInitialValue: ["viO1_AW73E"],
multipleSelect: true,
},
{
fieldId: "relation_multi_max",
name: "複数参照 最大",
kind: "relationList",
description: "説明文",
required: true,
referenceDisplayItem: "eO9ST_qUoe",
relationListCountLimitValidation: {
relationListCount: { min: 1, max: 3 },
},
},
{
fieldId: "number_max",
name: "数字 最大",
kind: "number",
description: "説明文",
required: true,
numberSizeLimitValidation: { numberSize: { min: 1, max: 300 } },
},
],
customFields: [],
};

const result = apiSchemaSchema.safeParse(schema);
expect(result.success).toBe(true);
});

test("不正なスキーマはバリデーションエラーになる", () => {
const invalidSchema = {
apiFields: [
{
fieldId: "test",
// name が欠けている
kind: "text",
required: false,
},
],
customFields: [],
};

const result = apiSchemaSchema.safeParse(invalidSchema);
expect(result.success).toBe(false);
});

test("不正な kind はバリデーションエラーになる", () => {
const invalidSchema = {
apiFields: [
{
fieldId: "test",
name: "テスト",
kind: "invalid_kind",
required: false,
},
],
customFields: [],
};

const result = apiSchemaSchema.safeParse(invalidSchema);
expect(result.success).toBe(false);
});
});
Loading