Skip to content

Commit 08a065a

Browse files
committed
Merge remote-tracking branch 'origin/dev' into chore/claude-md
2 parents 4efba44 + a27a132 commit 08a065a

File tree

6 files changed

+274
-2
lines changed

6 files changed

+274
-2
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
name: Claude Code Review
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize]
6+
# Optional: Only run on specific file changes
7+
# paths:
8+
# - "src/**/*.ts"
9+
# - "src/**/*.tsx"
10+
# - "src/**/*.js"
11+
# - "src/**/*.jsx"
12+
13+
jobs:
14+
claude-review:
15+
# Optional: Filter by PR author
16+
# if: |
17+
# github.event.pull_request.user.login == 'external-contributor' ||
18+
# github.event.pull_request.user.login == 'new-developer' ||
19+
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
20+
21+
runs-on: ubuntu-latest
22+
permissions:
23+
contents: read
24+
pull-requests: read
25+
issues: read
26+
id-token: write
27+
28+
steps:
29+
- name: Checkout repository
30+
uses: actions/checkout@v4
31+
with:
32+
fetch-depth: 1
33+
34+
- name: Run Claude Code Review
35+
id: claude-review
36+
uses: anthropics/claude-code-action@beta
37+
with:
38+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
39+
40+
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4)
41+
# model: "claude-opus-4-20250514"
42+
43+
# Direct prompt for automated review (no @claude mention needed)
44+
direct_prompt: |
45+
Please review this pull request and provide feedback on:
46+
- Code quality and best practices
47+
- Potential bugs or issues
48+
- Performance considerations
49+
- Security concerns
50+
- Test coverage
51+
52+
Be constructive and helpful in your feedback.
53+
54+
# Optional: Use sticky comments to make Claude reuse the same comment on subsequent pushes to the same PR
55+
# use_sticky_comment: true
56+
57+
# Optional: Customize review based on file types
58+
# direct_prompt: |
59+
# Review this PR focusing on:
60+
# - For TypeScript files: Type safety and proper interface usage
61+
# - For API endpoints: Security, input validation, and error handling
62+
# - For React components: Performance, accessibility, and best practices
63+
# - For tests: Coverage, edge cases, and test quality
64+
65+
# Optional: Different prompts for different authors
66+
# direct_prompt: |
67+
# ${{ github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' &&
68+
# 'Welcome! Please review this PR from a first-time contributor. Be encouraging and provide detailed explanations for any suggestions.' ||
69+
# 'Please provide a thorough code review focusing on our coding standards and best practices.' }}
70+
71+
# Optional: Add specific tools for running tests or linting
72+
# allowed_tools: "Bash(npm run test),Bash(npm run lint),Bash(npm run typecheck)"
73+
74+
# Optional: Skip review for certain conditions
75+
# if: |
76+
# !contains(github.event.pull_request.title, '[skip-review]') &&
77+
# !contains(github.event.pull_request.title, '[WIP]')
78+

.github/workflows/claude.yml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
name: Claude Code
2+
3+
on:
4+
issue_comment:
5+
types: [created]
6+
pull_request_review_comment:
7+
types: [created]
8+
issues:
9+
types: [opened, assigned]
10+
pull_request_review:
11+
types: [submitted]
12+
13+
jobs:
14+
claude:
15+
if: |
16+
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
17+
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
18+
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
19+
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
20+
runs-on: ubuntu-latest
21+
permissions:
22+
contents: read
23+
pull-requests: read
24+
issues: read
25+
id-token: write
26+
actions: read # Required for Claude to read CI results on PRs
27+
steps:
28+
- name: Checkout repository
29+
uses: actions/checkout@v4
30+
with:
31+
fetch-depth: 1
32+
33+
- name: Run Claude Code
34+
id: claude
35+
uses: anthropics/claude-code-action@beta
36+
with:
37+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
38+
39+
# This is an optional setting that allows Claude to read CI results on PRs
40+
additional_permissions: |
41+
actions: read
42+
43+
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4)
44+
# model: "claude-opus-4-20250514"
45+
46+
# Optional: Customize the trigger phrase (default: @claude)
47+
# trigger_phrase: "/claude"
48+
49+
# Optional: Trigger when specific user is assigned to an issue
50+
# assignee_trigger: "claude-bot"
51+
52+
# Optional: Allow Claude to run specific commands
53+
# allowed_tools: "Bash(npm install),Bash(npm run build),Bash(npm run test:*),Bash(npm run lint:*)"
54+
55+
# Optional: Add custom instructions for Claude to customize its behavior for your project
56+
# custom_instructions: |
57+
# Follow our coding standards
58+
# Ensure all new code has tests
59+
# Use TypeScript for new files
60+
61+
# Optional: Custom environment variables for Claude
62+
# claude_env: |
63+
# NODE_ENV: test
64+

packages/cli/src/actions/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ import { run as generate } from './generate';
33
import { run as info } from './info';
44
import { run as init } from './init';
55
import { run as migrate } from './migrate';
6+
import { run as validate } from './validate';
67

7-
export { db, generate, info, init, migrate };
8+
export { db, generate, info, init, migrate, validate };
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import colors from 'colors';
2+
import { getSchemaFile, loadSchemaDocument } from './action-utils';
3+
4+
type Options = {
5+
schema?: string;
6+
};
7+
8+
/**
9+
* CLI action for validating schema without generation
10+
*/
11+
export async function run(options: Options) {
12+
const schemaFile = getSchemaFile(options.schema);
13+
14+
try {
15+
await loadSchemaDocument(schemaFile);
16+
console.log(colors.green('✓ Schema validation completed successfully.'));
17+
} catch (error) {
18+
console.error(colors.red('✗ Schema validation failed.'));
19+
// Re-throw to maintain CLI exit code behavior
20+
throw error;
21+
}
22+
}

packages/cli/src/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ const initAction = async (projectPath: string): Promise<void> => {
2424
await actions.init(projectPath);
2525
};
2626

27+
const validateAction = async (options: Parameters<typeof actions.validate>[0]): Promise<void> => {
28+
await actions.validate(options);
29+
};
30+
2731
export function createProgram() {
2832
const program = new Command('zenstack');
2933

@@ -35,7 +39,7 @@ export function createProgram() {
3539
.description(
3640
`${colors.bold.blue(
3741
'ζ',
38-
)} ZenStack is a Prisma power pack for building full-stack apps.\n\nDocumentation: https://zenstack.dev.`,
42+
)} ZenStack is a database access toolkit for TypeScript apps.\n\nDocumentation: https://zenstack.dev.`,
3943
)
4044
.showHelpAfterError()
4145
.showSuggestionAfterError();
@@ -115,6 +119,8 @@ export function createProgram() {
115119
.argument('[path]', 'project path', '.')
116120
.action(initAction);
117121

122+
program.command('validate').description('Validate a ZModel schema.').addOption(schemaOption).action(validateAction);
123+
118124
return program;
119125
}
120126

packages/cli/test/validate.test.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import fs from 'node:fs';
2+
import path from 'node:path';
3+
import { describe, expect, it } from 'vitest';
4+
import { createProject, runCli } from './utils';
5+
6+
const validModel = `
7+
model User {
8+
id String @id @default(cuid())
9+
email String @unique
10+
name String?
11+
posts Post[]
12+
}
13+
14+
model Post {
15+
id String @id @default(cuid())
16+
title String
17+
content String?
18+
author User @relation(fields: [authorId], references: [id])
19+
authorId String
20+
}
21+
`;
22+
23+
const invalidModel = `
24+
model User {
25+
id String @id @default(cuid())
26+
email String @unique
27+
posts Post[]
28+
}
29+
30+
model Post {
31+
id String @id @default(cuid())
32+
title String
33+
author User @relation(fields: [authorId], references: [id])
34+
// Missing authorId field - should cause validation error
35+
}
36+
`;
37+
38+
describe('CLI validate command test', () => {
39+
it('should validate a valid schema successfully', () => {
40+
const workDir = createProject(validModel);
41+
42+
// Should not throw an error
43+
expect(() => runCli('validate', workDir)).not.toThrow();
44+
});
45+
46+
it('should fail validation for invalid schema', () => {
47+
const workDir = createProject(invalidModel);
48+
49+
// Should throw an error due to validation failure
50+
expect(() => runCli('validate', workDir)).toThrow();
51+
});
52+
53+
it('should respect custom schema location', () => {
54+
const workDir = createProject(validModel);
55+
fs.renameSync(path.join(workDir, 'zenstack/schema.zmodel'), path.join(workDir, 'zenstack/custom.zmodel'));
56+
57+
// Should not throw an error when using custom schema path
58+
expect(() => runCli('validate --schema ./zenstack/custom.zmodel', workDir)).not.toThrow();
59+
});
60+
61+
it('should fail when schema file does not exist', () => {
62+
const workDir = createProject(validModel);
63+
64+
// Should throw an error when schema file doesn't exist
65+
expect(() => runCli('validate --schema ./nonexistent.zmodel', workDir)).toThrow();
66+
});
67+
68+
it('should respect package.json config', () => {
69+
const workDir = createProject(validModel);
70+
fs.mkdirSync(path.join(workDir, 'foo'));
71+
fs.renameSync(path.join(workDir, 'zenstack/schema.zmodel'), path.join(workDir, 'foo/schema.zmodel'));
72+
fs.rmdirSync(path.join(workDir, 'zenstack'));
73+
74+
const pkgJson = JSON.parse(fs.readFileSync(path.join(workDir, 'package.json'), 'utf8'));
75+
pkgJson.zenstack = {
76+
schema: './foo/schema.zmodel',
77+
};
78+
fs.writeFileSync(path.join(workDir, 'package.json'), JSON.stringify(pkgJson, null, 2));
79+
80+
// Should not throw an error when using package.json config
81+
expect(() => runCli('validate', workDir)).not.toThrow();
82+
});
83+
84+
it('should validate schema with syntax errors', () => {
85+
const modelWithSyntaxError = `
86+
datasource db {
87+
provider = "sqlite"
88+
url = "file:./dev.db"
89+
}
90+
91+
model User {
92+
id String @id @default(cuid())
93+
email String @unique
94+
// Missing closing brace - syntax error
95+
`;
96+
const workDir = createProject(modelWithSyntaxError, false);
97+
98+
// Should throw an error due to syntax error
99+
expect(() => runCli('validate', workDir)).toThrow();
100+
});
101+
});

0 commit comments

Comments
 (0)