claudient 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +42 -0
- package/CONTEXT.md +58 -0
- package/README.md +165 -0
- package/agents/build-resolvers/de/python-resolver.md +64 -0
- package/agents/build-resolvers/de/typescript-resolver.md +65 -0
- package/agents/build-resolvers/es/python-resolver.md +64 -0
- package/agents/build-resolvers/es/typescript-resolver.md +65 -0
- package/agents/build-resolvers/fr/python-resolver.md +64 -0
- package/agents/build-resolvers/fr/typescript-resolver.md +65 -0
- package/agents/build-resolvers/nl/python-resolver.md +64 -0
- package/agents/build-resolvers/nl/typescript-resolver.md +65 -0
- package/agents/build-resolvers/python-resolver.md +62 -0
- package/agents/build-resolvers/typescript-resolver.md +63 -0
- package/agents/core/architect.md +64 -0
- package/agents/core/code-reviewer.md +78 -0
- package/agents/core/de/architect.md +66 -0
- package/agents/core/de/code-reviewer.md +80 -0
- package/agents/core/de/planner.md +63 -0
- package/agents/core/de/security-reviewer.md +93 -0
- package/agents/core/es/architect.md +66 -0
- package/agents/core/es/code-reviewer.md +80 -0
- package/agents/core/es/planner.md +63 -0
- package/agents/core/es/security-reviewer.md +93 -0
- package/agents/core/fr/architect.md +66 -0
- package/agents/core/fr/code-reviewer.md +80 -0
- package/agents/core/fr/planner.md +63 -0
- package/agents/core/fr/security-reviewer.md +93 -0
- package/agents/core/nl/architect.md +66 -0
- package/agents/core/nl/code-reviewer.md +80 -0
- package/agents/core/nl/planner.md +63 -0
- package/agents/core/nl/security-reviewer.md +93 -0
- package/agents/core/planner.md +61 -0
- package/agents/core/security-reviewer.md +91 -0
- package/guides/agent-orchestration.md +231 -0
- package/guides/de/agent-orchestration.md +174 -0
- package/guides/de/getting-started.md +164 -0
- package/guides/de/hooks-cookbook.md +160 -0
- package/guides/de/memory-management.md +153 -0
- package/guides/de/security.md +180 -0
- package/guides/de/skill-authoring.md +214 -0
- package/guides/de/token-optimization.md +156 -0
- package/guides/es/agent-orchestration.md +174 -0
- package/guides/es/getting-started.md +164 -0
- package/guides/es/hooks-cookbook.md +160 -0
- package/guides/es/memory-management.md +153 -0
- package/guides/es/security.md +180 -0
- package/guides/es/skill-authoring.md +214 -0
- package/guides/es/token-optimization.md +156 -0
- package/guides/fr/agent-orchestration.md +174 -0
- package/guides/fr/getting-started.md +164 -0
- package/guides/fr/hooks-cookbook.md +227 -0
- package/guides/fr/memory-management.md +169 -0
- package/guides/fr/security.md +180 -0
- package/guides/fr/skill-authoring.md +214 -0
- package/guides/fr/token-optimization.md +158 -0
- package/guides/getting-started.md +164 -0
- package/guides/hooks-cookbook.md +423 -0
- package/guides/memory-management.md +192 -0
- package/guides/nl/agent-orchestration.md +174 -0
- package/guides/nl/getting-started.md +164 -0
- package/guides/nl/hooks-cookbook.md +160 -0
- package/guides/nl/memory-management.md +153 -0
- package/guides/nl/security.md +180 -0
- package/guides/nl/skill-authoring.md +214 -0
- package/guides/nl/token-optimization.md +156 -0
- package/guides/security.md +229 -0
- package/guides/skill-authoring.md +226 -0
- package/guides/token-optimization.md +169 -0
- package/hooks/lifecycle/cost-tracker.md +49 -0
- package/hooks/lifecycle/cost-tracker.sh +59 -0
- package/hooks/lifecycle/pre-compact-save.md +56 -0
- package/hooks/lifecycle/pre-compact-save.sh +37 -0
- package/hooks/lifecycle/session-start.md +50 -0
- package/hooks/lifecycle/session-start.sh +47 -0
- package/hooks/post-tool-use/audit-log.md +53 -0
- package/hooks/post-tool-use/audit-log.sh +53 -0
- package/hooks/post-tool-use/prettier.md +53 -0
- package/hooks/post-tool-use/prettier.sh +49 -0
- package/hooks/pre-tool-use/block-dangerous.md +48 -0
- package/hooks/pre-tool-use/block-dangerous.sh +76 -0
- package/hooks/pre-tool-use/git-push-confirm.md +46 -0
- package/hooks/pre-tool-use/git-push-confirm.sh +36 -0
- package/mcp/configs/github.json +11 -0
- package/mcp/configs/postgres.json +11 -0
- package/mcp/de/recommended-servers.md +170 -0
- package/mcp/es/recommended-servers.md +170 -0
- package/mcp/fr/recommended-servers.md +170 -0
- package/mcp/nl/recommended-servers.md +170 -0
- package/mcp/recommended-servers.md +168 -0
- package/package.json +45 -0
- package/prompts/project-starters/de/fastapi-project.md +62 -0
- package/prompts/project-starters/de/nextjs-project.md +82 -0
- package/prompts/project-starters/es/fastapi-project.md +62 -0
- package/prompts/project-starters/es/nextjs-project.md +82 -0
- package/prompts/project-starters/fastapi-project.md +60 -0
- package/prompts/project-starters/fr/fastapi-project.md +62 -0
- package/prompts/project-starters/fr/nextjs-project.md +82 -0
- package/prompts/project-starters/nextjs-project.md +80 -0
- package/prompts/project-starters/nl/fastapi-project.md +62 -0
- package/prompts/project-starters/nl/nextjs-project.md +82 -0
- package/prompts/system-prompts/ai-product.md +80 -0
- package/prompts/system-prompts/data-pipeline.md +76 -0
- package/prompts/system-prompts/de/ai-product.md +82 -0
- package/prompts/system-prompts/de/data-pipeline.md +78 -0
- package/prompts/system-prompts/de/saas-backend.md +71 -0
- package/prompts/system-prompts/es/ai-product.md +82 -0
- package/prompts/system-prompts/es/data-pipeline.md +78 -0
- package/prompts/system-prompts/es/saas-backend.md +71 -0
- package/prompts/system-prompts/fr/ai-product.md +82 -0
- package/prompts/system-prompts/fr/data-pipeline.md +78 -0
- package/prompts/system-prompts/fr/saas-backend.md +71 -0
- package/prompts/system-prompts/nl/ai-product.md +82 -0
- package/prompts/system-prompts/nl/data-pipeline.md +78 -0
- package/prompts/system-prompts/nl/saas-backend.md +71 -0
- package/prompts/system-prompts/saas-backend.md +69 -0
- package/prompts/task-specific/changelog.md +81 -0
- package/prompts/task-specific/de/changelog.md +83 -0
- package/prompts/task-specific/de/debugging.md +78 -0
- package/prompts/task-specific/de/pr-description.md +69 -0
- package/prompts/task-specific/debugging.md +76 -0
- package/prompts/task-specific/es/changelog.md +83 -0
- package/prompts/task-specific/es/debugging.md +78 -0
- package/prompts/task-specific/es/pr-description.md +69 -0
- package/prompts/task-specific/fr/changelog.md +83 -0
- package/prompts/task-specific/fr/debugging.md +78 -0
- package/prompts/task-specific/fr/pr-description.md +69 -0
- package/prompts/task-specific/nl/changelog.md +83 -0
- package/prompts/task-specific/nl/debugging.md +78 -0
- package/prompts/task-specific/nl/pr-description.md +69 -0
- package/prompts/task-specific/pr-description.md +67 -0
- package/rules/common/coding-style.md +45 -0
- package/rules/common/de/coding-style.md +47 -0
- package/rules/common/de/git.md +48 -0
- package/rules/common/de/performance.md +40 -0
- package/rules/common/de/security.md +45 -0
- package/rules/common/de/testing.md +45 -0
- package/rules/common/es/coding-style.md +47 -0
- package/rules/common/es/git.md +48 -0
- package/rules/common/es/performance.md +40 -0
- package/rules/common/es/security.md +45 -0
- package/rules/common/es/testing.md +45 -0
- package/rules/common/fr/coding-style.md +47 -0
- package/rules/common/fr/git.md +48 -0
- package/rules/common/fr/performance.md +40 -0
- package/rules/common/fr/security.md +45 -0
- package/rules/common/fr/testing.md +45 -0
- package/rules/common/git.md +46 -0
- package/rules/common/nl/coding-style.md +47 -0
- package/rules/common/nl/git.md +48 -0
- package/rules/common/nl/performance.md +40 -0
- package/rules/common/nl/security.md +45 -0
- package/rules/common/nl/testing.md +45 -0
- package/rules/common/performance.md +38 -0
- package/rules/common/security.md +43 -0
- package/rules/common/testing.md +43 -0
- package/rules/language-specific/de/go.md +48 -0
- package/rules/language-specific/de/python.md +38 -0
- package/rules/language-specific/de/typescript.md +51 -0
- package/rules/language-specific/es/go.md +48 -0
- package/rules/language-specific/es/python.md +38 -0
- package/rules/language-specific/es/typescript.md +51 -0
- package/rules/language-specific/fr/go.md +48 -0
- package/rules/language-specific/fr/python.md +38 -0
- package/rules/language-specific/fr/typescript.md +51 -0
- package/rules/language-specific/go.md +46 -0
- package/rules/language-specific/nl/go.md +48 -0
- package/rules/language-specific/nl/python.md +38 -0
- package/rules/language-specific/nl/typescript.md +51 -0
- package/rules/language-specific/python.md +36 -0
- package/rules/language-specific/typescript.md +49 -0
- package/scripts/cli.js +161 -0
- package/scripts/link-skills.sh +35 -0
- package/scripts/list-skills.sh +34 -0
- package/skills/ai-engineering/agent-construction.md +285 -0
- package/skills/ai-engineering/claude-api.md +248 -0
- package/skills/ai-engineering/de/agent-construction.md +287 -0
- package/skills/ai-engineering/de/claude-api.md +250 -0
- package/skills/ai-engineering/es/agent-construction.md +287 -0
- package/skills/ai-engineering/es/claude-api.md +250 -0
- package/skills/ai-engineering/fr/agent-construction.md +287 -0
- package/skills/ai-engineering/fr/claude-api.md +250 -0
- package/skills/ai-engineering/nl/agent-construction.md +287 -0
- package/skills/ai-engineering/nl/claude-api.md +250 -0
- package/skills/backend/dotnet/csharp.md +304 -0
- package/skills/backend/dotnet/de/csharp.md +306 -0
- package/skills/backend/dotnet/es/csharp.md +306 -0
- package/skills/backend/dotnet/fr/csharp.md +306 -0
- package/skills/backend/dotnet/nl/csharp.md +306 -0
- package/skills/backend/go/de/go.md +307 -0
- package/skills/backend/go/es/go.md +307 -0
- package/skills/backend/go/fr/go.md +307 -0
- package/skills/backend/go/go.md +305 -0
- package/skills/backend/go/nl/go.md +307 -0
- package/skills/backend/nodejs/de/nestjs.md +274 -0
- package/skills/backend/nodejs/de/nextjs.md +222 -0
- package/skills/backend/nodejs/es/nestjs.md +274 -0
- package/skills/backend/nodejs/es/nextjs.md +222 -0
- package/skills/backend/nodejs/fr/nestjs.md +274 -0
- package/skills/backend/nodejs/fr/nextjs.md +222 -0
- package/skills/backend/nodejs/nestjs.md +272 -0
- package/skills/backend/nodejs/nextjs.md +220 -0
- package/skills/backend/nodejs/nl/nestjs.md +274 -0
- package/skills/backend/nodejs/nl/nextjs.md +222 -0
- package/skills/backend/python/de/django.md +285 -0
- package/skills/backend/python/de/fastapi.md +244 -0
- package/skills/backend/python/django.md +283 -0
- package/skills/backend/python/es/django.md +285 -0
- package/skills/backend/python/es/fastapi.md +244 -0
- package/skills/backend/python/fastapi.md +242 -0
- package/skills/backend/python/fr/django.md +285 -0
- package/skills/backend/python/fr/fastapi.md +244 -0
- package/skills/backend/python/nl/django.md +285 -0
- package/skills/backend/python/nl/fastapi.md +244 -0
- package/skills/data-ml/dbt-data-pipelines.md +155 -0
- package/skills/data-ml/de/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/de/pandas-polars.md +147 -0
- package/skills/data-ml/de/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/es/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/es/pandas-polars.md +147 -0
- package/skills/data-ml/es/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/fr/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/fr/pandas-polars.md +147 -0
- package/skills/data-ml/fr/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/nl/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/nl/pandas-polars.md +147 -0
- package/skills/data-ml/nl/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/pandas-polars.md +145 -0
- package/skills/data-ml/pytorch-tensorflow.md +169 -0
- package/skills/database/de/graphql.md +181 -0
- package/skills/database/es/graphql.md +181 -0
- package/skills/database/fr/graphql.md +181 -0
- package/skills/database/graphql.md +179 -0
- package/skills/database/nl/graphql.md +181 -0
- package/skills/devops-infra/de/docker.md +133 -0
- package/skills/devops-infra/de/github-actions.md +179 -0
- package/skills/devops-infra/de/kubernetes.md +129 -0
- package/skills/devops-infra/de/terraform.md +130 -0
- package/skills/devops-infra/docker.md +131 -0
- package/skills/devops-infra/es/docker.md +133 -0
- package/skills/devops-infra/es/github-actions.md +179 -0
- package/skills/devops-infra/es/kubernetes.md +129 -0
- package/skills/devops-infra/es/terraform.md +130 -0
- package/skills/devops-infra/fr/docker.md +133 -0
- package/skills/devops-infra/fr/github-actions.md +179 -0
- package/skills/devops-infra/fr/kubernetes.md +129 -0
- package/skills/devops-infra/fr/terraform.md +130 -0
- package/skills/devops-infra/github-actions.md +177 -0
- package/skills/devops-infra/kubernetes.md +127 -0
- package/skills/devops-infra/nl/docker.md +133 -0
- package/skills/devops-infra/nl/github-actions.md +179 -0
- package/skills/devops-infra/nl/kubernetes.md +129 -0
- package/skills/devops-infra/nl/terraform.md +130 -0
- package/skills/devops-infra/terraform.md +128 -0
- package/skills/finance-payments/de/stripe.md +187 -0
- package/skills/finance-payments/es/stripe.md +187 -0
- package/skills/finance-payments/fr/stripe.md +187 -0
- package/skills/finance-payments/nl/stripe.md +187 -0
- package/skills/finance-payments/stripe.md +185 -0
- package/workflows/code-review.md +151 -0
- package/workflows/de/code-review.md +153 -0
- package/workflows/de/debugging-session.md +146 -0
- package/workflows/de/feature-development.md +155 -0
- package/workflows/de/new-project-bootstrap.md +175 -0
- package/workflows/de/refactor-safely.md +150 -0
- package/workflows/debugging-session.md +144 -0
- package/workflows/es/code-review.md +153 -0
- package/workflows/es/debugging-session.md +146 -0
- package/workflows/es/feature-development.md +155 -0
- package/workflows/es/new-project-bootstrap.md +175 -0
- package/workflows/es/refactor-safely.md +150 -0
- package/workflows/feature-development.md +153 -0
- package/workflows/fr/code-review.md +153 -0
- package/workflows/fr/debugging-session.md +146 -0
- package/workflows/fr/feature-development.md +155 -0
- package/workflows/fr/new-project-bootstrap.md +175 -0
- package/workflows/fr/refactor-safely.md +150 -0
- package/workflows/new-project-bootstrap.md +173 -0
- package/workflows/nl/code-review.md +153 -0
- package/workflows/nl/debugging-session.md +146 -0
- package/workflows/nl/feature-development.md +155 -0
- package/workflows/nl/new-project-bootstrap.md +175 -0
- package/workflows/nl/refactor-safely.md +150 -0
- package/workflows/refactor-safely.md +148 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# GraphQL Skill
|
|
2
|
+
|
|
3
|
+
## When to activate
|
|
4
|
+
- Designing a GraphQL schema (types, queries, mutations, subscriptions)
|
|
5
|
+
- Implementing resolvers in Node.js (Apollo Server, Pothos, GraphQL Yoga)
|
|
6
|
+
- Setting up Prisma with a GraphQL API
|
|
7
|
+
- Writing GraphQL queries and fragments for a frontend client
|
|
8
|
+
- Implementing cursor-based or offset pagination in GraphQL
|
|
9
|
+
- Setting up DataLoader for N+1 query prevention
|
|
10
|
+
- Adding authentication and authorization to a GraphQL API
|
|
11
|
+
- Debugging GraphQL performance or N+1 issues
|
|
12
|
+
|
|
13
|
+
## When NOT to use
|
|
14
|
+
- REST APIs where GraphQL adds complexity without benefit (simple CRUD, webhooks, file uploads)
|
|
15
|
+
- gRPC or event-driven systems
|
|
16
|
+
- Cases where the client always needs the full response (GraphQL's field selection adds no value)
|
|
17
|
+
|
|
18
|
+
## Instructions
|
|
19
|
+
|
|
20
|
+
### Schema design principles
|
|
21
|
+
```graphql
|
|
22
|
+
# Type names: PascalCase singular nouns
|
|
23
|
+
# Field names: camelCase
|
|
24
|
+
# Enums: SCREAMING_SNAKE_CASE values
|
|
25
|
+
|
|
26
|
+
type Order {
|
|
27
|
+
id: ID!
|
|
28
|
+
status: OrderStatus!
|
|
29
|
+
customer: Customer! # Nested type — always return the object, not just the ID
|
|
30
|
+
items: [OrderItem!]! # Non-null list of non-null items
|
|
31
|
+
totalAmount: Float!
|
|
32
|
+
createdAt: DateTime!
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
enum OrderStatus {
|
|
36
|
+
PENDING
|
|
37
|
+
COMPLETED
|
|
38
|
+
CANCELLED
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Queries: return nullable for single item (not found = null), non-null for lists
|
|
42
|
+
type Query {
|
|
43
|
+
order(id: ID!): Order # Nullable — null if not found
|
|
44
|
+
orders(filter: OrderFilter): [Order!]! # Non-null list
|
|
45
|
+
me: User # Nullable — null if not authenticated
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# Mutations: always return the mutated object plus an errors array
|
|
49
|
+
type Mutation {
|
|
50
|
+
createOrder(input: CreateOrderInput!): CreateOrderPayload!
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
type CreateOrderPayload {
|
|
54
|
+
order: Order # Null if mutation failed
|
|
55
|
+
errors: [UserError!]! # Empty if successful
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type UserError {
|
|
59
|
+
field: String
|
|
60
|
+
message: String!
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### N+1 prevention with DataLoader
|
|
65
|
+
```typescript
|
|
66
|
+
import DataLoader from 'dataloader';
|
|
67
|
+
|
|
68
|
+
// Create per-request — never singleton (request data isolation)
|
|
69
|
+
export function createLoaders() {
|
|
70
|
+
return {
|
|
71
|
+
customerLoader: new DataLoader<string, Customer>(async (ids) => {
|
|
72
|
+
const customers = await db.customer.findMany({
|
|
73
|
+
where: { id: { in: [...ids] } }
|
|
74
|
+
});
|
|
75
|
+
// Must return in same order as ids
|
|
76
|
+
const customerMap = new Map(customers.map(c => [c.id, c]));
|
|
77
|
+
return ids.map(id => customerMap.get(id) ?? new Error(`Customer ${id} not found`));
|
|
78
|
+
}),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Resolver — uses loader, not direct DB call
|
|
83
|
+
const resolvers = {
|
|
84
|
+
Order: {
|
|
85
|
+
customer: (order, _, { loaders }) => loaders.customerLoader.load(order.customerId),
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Cursor-based pagination (preferred over offset)
|
|
91
|
+
```graphql
|
|
92
|
+
type OrderConnection {
|
|
93
|
+
edges: [OrderEdge!]!
|
|
94
|
+
pageInfo: PageInfo!
|
|
95
|
+
totalCount: Int!
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
type OrderEdge {
|
|
99
|
+
node: Order!
|
|
100
|
+
cursor: String!
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
type PageInfo {
|
|
104
|
+
hasNextPage: Boolean!
|
|
105
|
+
hasPreviousPage: Boolean!
|
|
106
|
+
startCursor: String
|
|
107
|
+
endCursor: String
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
type Query {
|
|
111
|
+
orders(first: Int, after: String, last: Int, before: String): OrderConnection!
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Authorization pattern
|
|
116
|
+
```typescript
|
|
117
|
+
// Field-level authorization in resolver
|
|
118
|
+
const resolvers = {
|
|
119
|
+
Query: {
|
|
120
|
+
adminStats: (_, __, { user }) => {
|
|
121
|
+
if (!user || user.role !== 'ADMIN') {
|
|
122
|
+
throw new GraphQLError('Unauthorized', {
|
|
123
|
+
extensions: { code: 'UNAUTHORIZED' }
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return getAdminStats();
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
Order: {
|
|
130
|
+
// Object-level: only return sensitive fields to the order's owner
|
|
131
|
+
internalNotes: (order, _, { user }) => {
|
|
132
|
+
if (user?.id !== order.customerId && user?.role !== 'ADMIN') return null;
|
|
133
|
+
return order.internalNotes;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Prisma + GraphQL pattern
|
|
140
|
+
```typescript
|
|
141
|
+
// Resolver using Prisma — avoid over-fetching
|
|
142
|
+
const resolvers = {
|
|
143
|
+
Query: {
|
|
144
|
+
order: async (_, { id }, { prisma, user }) => {
|
|
145
|
+
const order = await prisma.order.findUnique({
|
|
146
|
+
where: { id },
|
|
147
|
+
select: {
|
|
148
|
+
id: true,
|
|
149
|
+
status: true,
|
|
150
|
+
customerId: true,
|
|
151
|
+
totalAmount: true,
|
|
152
|
+
createdAt: true,
|
|
153
|
+
// Do NOT select items here — let the items resolver handle it
|
|
154
|
+
// with DataLoader to avoid N+1
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
if (!order) return null;
|
|
158
|
+
if (order.customerId !== user?.id) throw new GraphQLError('Forbidden');
|
|
159
|
+
return order;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Example
|
|
166
|
+
|
|
167
|
+
**User:** Design a GraphQL schema and resolvers for a simple e-commerce API — products, orders, and customers. Include pagination, DataLoader for customers, and mutation error handling.
|
|
168
|
+
|
|
169
|
+
**Expected output:**
|
|
170
|
+
- Schema: `Product`, `Order`, `Customer`, `OrderConnection`, `UserError` types
|
|
171
|
+
- `Query.orders` with cursor pagination returning `OrderConnection`
|
|
172
|
+
- `Mutation.createOrder` returning `CreateOrderPayload` with errors array
|
|
173
|
+
- `Order.customer` resolver using DataLoader (not direct DB query)
|
|
174
|
+
- `createLoaders()` function per request, batching customer lookups by ID
|
|
175
|
+
- Auth check: only authenticated users can view their own orders
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
> **Work with us:** Claudient is backed by [Uitbreiden](https://uitbreiden.com/) — we build AI products and B2B solutions with developer communities. Building GraphQL APIs or AI-powered data layers? [uitbreiden.com](https://uitbreiden.com/)
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
> 🇳🇱 Dit is de Nederlandse vertaling. [Engelse versie](../graphql.md).
|
|
2
|
+
|
|
3
|
+
# GraphQL Skill
|
|
4
|
+
|
|
5
|
+
## Wanneer te activeren
|
|
6
|
+
- Een GraphQL-schema ontwerpen (typen, queries, mutaties, subscriptions)
|
|
7
|
+
- Resolvers implementeren in Node.js (Apollo Server, Pothos, GraphQL Yoga)
|
|
8
|
+
- Prisma instellen met een GraphQL API
|
|
9
|
+
- GraphQL-queries en -fragments schrijven voor een frontend-client
|
|
10
|
+
- Cursor-gebaseerde of offset-paginering implementeren in GraphQL
|
|
11
|
+
- DataLoader instellen voor N+1-querypreventie
|
|
12
|
+
- Authenticatie en autorisatie toevoegen aan een GraphQL API
|
|
13
|
+
- GraphQL-prestaties of N+1-problemen debuggen
|
|
14
|
+
|
|
15
|
+
## Wanneer NIET te gebruiken
|
|
16
|
+
- REST API's waar GraphQL complexiteit toevoegt zonder voordeel (eenvoudige CRUD, webhooks, bestandsuploads)
|
|
17
|
+
- gRPC of event-driven systemen
|
|
18
|
+
- Gevallen waarbij de client altijd de volledige respons nodig heeft (veldkeuze van GraphQL voegt geen waarde toe)
|
|
19
|
+
|
|
20
|
+
## Instructies
|
|
21
|
+
|
|
22
|
+
### Schema-ontwerpprincipes
|
|
23
|
+
```graphql
|
|
24
|
+
# Typenamen: PascalCase enkelvoudige zelfstandige naamwoorden
|
|
25
|
+
# Veldnamen: camelCase
|
|
26
|
+
# Enums: SCREAMING_SNAKE_CASE-waarden
|
|
27
|
+
|
|
28
|
+
type Order {
|
|
29
|
+
id: ID!
|
|
30
|
+
status: OrderStatus!
|
|
31
|
+
customer: Customer! # Genest type — geef altijd het object terug, niet alleen het ID
|
|
32
|
+
items: [OrderItem!]! # Niet-null lijst van niet-null items
|
|
33
|
+
totalAmount: Float!
|
|
34
|
+
createdAt: DateTime!
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
enum OrderStatus {
|
|
38
|
+
PENDING
|
|
39
|
+
COMPLETED
|
|
40
|
+
CANCELLED
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# Queries: retourneer nullable voor enkelvoudig item (niet gevonden = null), niet-null voor lijsten
|
|
44
|
+
type Query {
|
|
45
|
+
order(id: ID!): Order # Nullable — null als niet gevonden
|
|
46
|
+
orders(filter: OrderFilter): [Order!]! # Niet-null lijst
|
|
47
|
+
me: User # Nullable — null als niet geauthenticeerd
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Mutaties: geef altijd het gemuteerde object terug plus een errors-array
|
|
51
|
+
type Mutation {
|
|
52
|
+
createOrder(input: CreateOrderInput!): CreateOrderPayload!
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type CreateOrderPayload {
|
|
56
|
+
order: Order # Null als mutatie mislukte
|
|
57
|
+
errors: [UserError!]! # Leeg als geslaagd
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
type UserError {
|
|
61
|
+
field: String
|
|
62
|
+
message: String!
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### N+1-preventie met DataLoader
|
|
67
|
+
```typescript
|
|
68
|
+
import DataLoader from 'dataloader';
|
|
69
|
+
|
|
70
|
+
// Maak per request — nooit singleton (aanvraagdata-isolatie)
|
|
71
|
+
export function createLoaders() {
|
|
72
|
+
return {
|
|
73
|
+
customerLoader: new DataLoader<string, Customer>(async (ids) => {
|
|
74
|
+
const customers = await db.customer.findMany({
|
|
75
|
+
where: { id: { in: [...ids] } }
|
|
76
|
+
});
|
|
77
|
+
// Moet in dezelfde volgorde worden geretourneerd als ids
|
|
78
|
+
const customerMap = new Map(customers.map(c => [c.id, c]));
|
|
79
|
+
return ids.map(id => customerMap.get(id) ?? new Error(`Customer ${id} not found`));
|
|
80
|
+
}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Resolver — gebruikt loader, geen directe DB-aanroep
|
|
85
|
+
const resolvers = {
|
|
86
|
+
Order: {
|
|
87
|
+
customer: (order, _, { loaders }) => loaders.customerLoader.load(order.customerId),
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Cursor-gebaseerde paginering (voorkeur boven offset)
|
|
93
|
+
```graphql
|
|
94
|
+
type OrderConnection {
|
|
95
|
+
edges: [OrderEdge!]!
|
|
96
|
+
pageInfo: PageInfo!
|
|
97
|
+
totalCount: Int!
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
type OrderEdge {
|
|
101
|
+
node: Order!
|
|
102
|
+
cursor: String!
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
type PageInfo {
|
|
106
|
+
hasNextPage: Boolean!
|
|
107
|
+
hasPreviousPage: Boolean!
|
|
108
|
+
startCursor: String
|
|
109
|
+
endCursor: String
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
type Query {
|
|
113
|
+
orders(first: Int, after: String, last: Int, before: String): OrderConnection!
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Autorisatiepatroon
|
|
118
|
+
```typescript
|
|
119
|
+
// Veldniveau-autorisatie in resolver
|
|
120
|
+
const resolvers = {
|
|
121
|
+
Query: {
|
|
122
|
+
adminStats: (_, __, { user }) => {
|
|
123
|
+
if (!user || user.role !== 'ADMIN') {
|
|
124
|
+
throw new GraphQLError('Unauthorized', {
|
|
125
|
+
extensions: { code: 'UNAUTHORIZED' }
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return getAdminStats();
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
Order: {
|
|
132
|
+
// Objectniveau: geef gevoelige velden alleen terug aan de eigenaar van de bestelling
|
|
133
|
+
internalNotes: (order, _, { user }) => {
|
|
134
|
+
if (user?.id !== order.customerId && user?.role !== 'ADMIN') return null;
|
|
135
|
+
return order.internalNotes;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Prisma + GraphQL-patroon
|
|
142
|
+
```typescript
|
|
143
|
+
// Resolver met Prisma — vermijd over-fetching
|
|
144
|
+
const resolvers = {
|
|
145
|
+
Query: {
|
|
146
|
+
order: async (_, { id }, { prisma, user }) => {
|
|
147
|
+
const order = await prisma.order.findUnique({
|
|
148
|
+
where: { id },
|
|
149
|
+
select: {
|
|
150
|
+
id: true,
|
|
151
|
+
status: true,
|
|
152
|
+
customerId: true,
|
|
153
|
+
totalAmount: true,
|
|
154
|
+
createdAt: true,
|
|
155
|
+
// Selecteer hier GEEN items — laat de items-resolver dit afhandelen
|
|
156
|
+
// met DataLoader om N+1 te vermijden
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
if (!order) return null;
|
|
160
|
+
if (order.customerId !== user?.id) throw new GraphQLError('Forbidden');
|
|
161
|
+
return order;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Voorbeeld
|
|
168
|
+
|
|
169
|
+
**Gebruiker:** Ontwerp een GraphQL-schema en resolvers voor een eenvoudige e-commerce API — producten, bestellingen en klanten. Inclusief paginering, DataLoader voor klanten en afhandeling van mutatiefouten.
|
|
170
|
+
|
|
171
|
+
**Verwachte output:**
|
|
172
|
+
- Schema: typen `Product`, `Order`, `Customer`, `OrderConnection`, `UserError`
|
|
173
|
+
- `Query.orders` met cursor-paginering die `OrderConnection` retourneert
|
|
174
|
+
- `Mutation.createOrder` die `CreateOrderPayload` retourneert met errors-array
|
|
175
|
+
- `Order.customer`-resolver met DataLoader (geen directe DB-query)
|
|
176
|
+
- `createLoaders()`-functie per aanvraag, klant-lookups batchen op ID
|
|
177
|
+
- Auth-controle: alleen geauthenticeerde gebruikers kunnen hun eigen bestellingen bekijken
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
> **Werk met ons:** Claudient wordt ondersteund door [Uitbreiden](https://uitbreiden.com/) — we bouwen AI-producten en B2B-oplossingen met ontwikkelaarsgemeenschappen. GraphQL API's of AI-aangedreven datalaag bouwen? [uitbreiden.com](https://uitbreiden.com/)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
> 🇩🇪 Dies ist die deutsche Übersetzung. [Englische Version](../docker.md).
|
|
2
|
+
|
|
3
|
+
# Docker Skill
|
|
4
|
+
|
|
5
|
+
## Wann aktivieren
|
|
6
|
+
- Dockerfiles für die Produktion schreiben oder optimieren
|
|
7
|
+
- Multi-Stage-Builds zur Reduzierung der Image-Größe einrichten
|
|
8
|
+
- Docker Compose-Dateien für die lokale Entwicklung schreiben
|
|
9
|
+
- Container-Startfehler oder Layer-Cache-Probleme debuggen
|
|
10
|
+
- Nicht-Root-Benutzer, Health-Checks und Image-Sicherheit konfigurieren
|
|
11
|
+
- .dockerignore für effiziente Builds einrichten
|
|
12
|
+
- Build-Skripte oder CI/CD Docker-Build-Pipelines schreiben
|
|
13
|
+
|
|
14
|
+
## Wann NICHT verwenden
|
|
15
|
+
- Kubernetes-Manifeste (Kubernetes Skill verwenden)
|
|
16
|
+
- Buildpacks (Heroku, Cloud Native Buildpacks) — anderes Build-System
|
|
17
|
+
- Virtuelle Maschinen bereitstellen (andere Abstraktionsebene)
|
|
18
|
+
- Nix-basierte reproduzierbare Builds
|
|
19
|
+
|
|
20
|
+
## Anweisungen
|
|
21
|
+
|
|
22
|
+
### Produktions-Dockerfile-Struktur
|
|
23
|
+
Für kompilierte Sprachen und Node.js immer Multi-Stage-Builds verwenden:
|
|
24
|
+
|
|
25
|
+
```dockerfile
|
|
26
|
+
# Stage 1: Build
|
|
27
|
+
FROM node:20-alpine AS builder
|
|
28
|
+
WORKDIR /app
|
|
29
|
+
COPY package*.json ./
|
|
30
|
+
RUN npm ci --only=production
|
|
31
|
+
|
|
32
|
+
# Stage 2: Runtime — minimales Image
|
|
33
|
+
FROM node:20-alpine AS runtime
|
|
34
|
+
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
|
|
35
|
+
WORKDIR /app
|
|
36
|
+
COPY --from=builder /app/node_modules ./node_modules
|
|
37
|
+
COPY . .
|
|
38
|
+
USER appuser
|
|
39
|
+
EXPOSE 8080
|
|
40
|
+
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
|
41
|
+
CMD wget -qO- http://localhost:8080/healthz || exit 1
|
|
42
|
+
CMD ["node", "server.js"]
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Sicherheitsregeln
|
|
46
|
+
- Niemals als Root in der Produktion ausführen — immer einen Nicht-Root-Benutzer erstellen und zu diesem wechseln
|
|
47
|
+
- Niemals `latest`-Tag verwenden — auf eine spezifische Version oder einen Digest pinnen
|
|
48
|
+
- Alpine oder distroless-Basis-Images gegenüber vollem Debian/Ubuntu bevorzugen
|
|
49
|
+
- Niemals `.env`-Dateien in das Image kopieren — Secrets als Laufzeit-Umgebungsvariablen übergeben
|
|
50
|
+
- Images mit `docker scout` oder Trivy scannen, bevor sie in die Produktion gepusht werden
|
|
51
|
+
|
|
52
|
+
### Layer-Cache-Optimierung
|
|
53
|
+
Dockerfile-Anweisungen von am wenigsten bis am häufigsten geändert ordnen:
|
|
54
|
+
1. Basis-Image (ändert sich selten)
|
|
55
|
+
2. System-Abhängigkeiten (`apt-get`, `apk add`)
|
|
56
|
+
3. Paketmanager-Dateien (`package.json`, `requirements.txt`)
|
|
57
|
+
4. Paket-Installation (`npm ci`, `pip install`)
|
|
58
|
+
5. Anwendungscode (`COPY . .`) — ändert sich am häufigsten, muss zuletzt stehen
|
|
59
|
+
|
|
60
|
+
### .dockerignore — immer einschließen
|
|
61
|
+
```
|
|
62
|
+
node_modules/
|
|
63
|
+
.git/
|
|
64
|
+
.env
|
|
65
|
+
.env.*
|
|
66
|
+
*.md
|
|
67
|
+
Dockerfile*
|
|
68
|
+
docker-compose*
|
|
69
|
+
.dockerignore
|
|
70
|
+
coverage/
|
|
71
|
+
.nyc_output/
|
|
72
|
+
__pycache__/
|
|
73
|
+
*.pyc
|
|
74
|
+
.pytest_cache/
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Docker Compose für die lokale Entwicklung
|
|
78
|
+
```yaml
|
|
79
|
+
services:
|
|
80
|
+
app:
|
|
81
|
+
build:
|
|
82
|
+
context: .
|
|
83
|
+
target: builder # Build-Stage verwenden, nicht Runtime-Stage lokal
|
|
84
|
+
volumes:
|
|
85
|
+
- .:/app # Hot Reload
|
|
86
|
+
- /app/node_modules # Container node_modules nicht überschreiben
|
|
87
|
+
ports:
|
|
88
|
+
- "8080:8080"
|
|
89
|
+
environment:
|
|
90
|
+
- NODE_ENV=development
|
|
91
|
+
depends_on:
|
|
92
|
+
db:
|
|
93
|
+
condition: service_healthy
|
|
94
|
+
|
|
95
|
+
db:
|
|
96
|
+
image: postgres:16-alpine
|
|
97
|
+
environment:
|
|
98
|
+
POSTGRES_USER: app
|
|
99
|
+
POSTGRES_PASSWORD: dev_password # Nur Dev — niemals Produktion
|
|
100
|
+
POSTGRES_DB: appdb
|
|
101
|
+
ports:
|
|
102
|
+
- "5432:5432"
|
|
103
|
+
healthcheck:
|
|
104
|
+
test: ["CMD-SHELL", "pg_isready -U app -d appdb"]
|
|
105
|
+
interval: 5s
|
|
106
|
+
timeout: 5s
|
|
107
|
+
retries: 5
|
|
108
|
+
volumes:
|
|
109
|
+
- postgres_data:/var/lib/postgresql/data
|
|
110
|
+
|
|
111
|
+
volumes:
|
|
112
|
+
postgres_data:
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Häufige Build-Fehler
|
|
116
|
+
- `COPY` schlägt lautlos fehl, wenn die Quelle nicht existiert — prüfen, ob `.dockerignore` benötigte Dateien ausschließt
|
|
117
|
+
- Layer-Cache unerwartet invalidiert — prüfen, ob ein `COPY` vor den Install-Schritten geänderte Dateien einbezieht
|
|
118
|
+
- Zugriff verweigert zur Laufzeit — Dateibesitz beim Verwenden von `COPY --from` mit einem Nicht-Root-Benutzer prüfen
|
|
119
|
+
|
|
120
|
+
## Beispiel
|
|
121
|
+
|
|
122
|
+
**Benutzer:** Ein Produktions-Dockerfile für eine Python FastAPI-App mit Multi-Stage-Build, Nicht-Root-Benutzer und Health-Check schreiben.
|
|
123
|
+
|
|
124
|
+
**Erwartete Ausgabe:**
|
|
125
|
+
- Stage 1 (builder): `python:3.12-slim`, Abhängigkeiten mit `pip install --no-cache-dir` installieren
|
|
126
|
+
- Stage 2 (runtime): `python:3.12-slim`, Nicht-Root-Benutzer, nur Wheels/Deps vom Builder + App-Code kopieren
|
|
127
|
+
- `HEALTHCHECK` trifft `/healthz`-Endpunkt
|
|
128
|
+
- `CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]`
|
|
129
|
+
- `.dockerignore` deckt `__pycache__`, `.env`, `.git`, `*.pyc` ab
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
> **Mit uns arbeiten:** Claudient wird von [Uitbreiden](https://uitbreiden.com/) unterstützt — wir bauen KI-Produkte und B2B-Lösungen mit Entwickler-Communities. KI-Workloads containerisieren oder cloud-native Systeme bauen? [uitbreiden.com](https://uitbreiden.com/)
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
> 🇩🇪 Dies ist die deutsche Übersetzung. [Englische Version](../github-actions.md).
|
|
2
|
+
|
|
3
|
+
# GitHub Actions Skill
|
|
4
|
+
|
|
5
|
+
## Wann aktivieren
|
|
6
|
+
- CI/CD-Pipelines für Tests, Linting, Build und Deployment schreiben
|
|
7
|
+
- Matrix-Builds über mehrere Betriebssysteme oder Sprachversionen einrichten
|
|
8
|
+
- Umgebungsschutzregeln und Deployment-Gates konfigurieren
|
|
9
|
+
- Wiederverwendbare Workflows oder zusammengesetzte Actions schreiben
|
|
10
|
+
- Docker-Build und Push zu einer Container-Registry einrichten
|
|
11
|
+
- OIDC-Authentifizierung bei Cloud-Providern konfigurieren (keine langlebigen Secrets)
|
|
12
|
+
- Fehlschlagende Workflows debuggen oder Workflow-Syntaxfehler verstehen
|
|
13
|
+
- Caching für Abhängigkeiten einrichten (npm, pip, Go-Module)
|
|
14
|
+
|
|
15
|
+
## Wann NICHT verwenden
|
|
16
|
+
- GitLab CI, CircleCI, Jenkins — andere Pipeline-Systeme
|
|
17
|
+
- Lokale Entwicklungsautomatisierung (Makefile oder Skripte verwenden)
|
|
18
|
+
- Cron-Jobs, die nicht an ein Repository gebunden sind (Cloud-Scheduler verwenden)
|
|
19
|
+
|
|
20
|
+
## Anweisungen
|
|
21
|
+
|
|
22
|
+
### Workflow-Dateistruktur
|
|
23
|
+
```yaml
|
|
24
|
+
name: CI
|
|
25
|
+
|
|
26
|
+
on:
|
|
27
|
+
push:
|
|
28
|
+
branches: [main]
|
|
29
|
+
pull_request:
|
|
30
|
+
branches: [main]
|
|
31
|
+
|
|
32
|
+
# Explizite Berechtigungen — niemals Standard write-all verwenden
|
|
33
|
+
permissions:
|
|
34
|
+
contents: read
|
|
35
|
+
pull-requests: write # Nur wenn benötigt (z.B. Kommentare posten)
|
|
36
|
+
|
|
37
|
+
jobs:
|
|
38
|
+
test:
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v4
|
|
42
|
+
- name: Set up Node.js
|
|
43
|
+
uses: actions/setup-node@v4
|
|
44
|
+
with:
|
|
45
|
+
node-version: '20'
|
|
46
|
+
cache: 'npm'
|
|
47
|
+
- run: npm ci
|
|
48
|
+
- run: npm test
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Berechtigungen — immer explizit
|
|
52
|
+
Niemals `permissions: write-all` als Standard verwenden. Immer Mindestberechtigungen deklarieren:
|
|
53
|
+
```yaml
|
|
54
|
+
permissions:
|
|
55
|
+
contents: read # Repository lesen
|
|
56
|
+
packages: write # In GitHub Container Registry pushen
|
|
57
|
+
id-token: write # OIDC für Cloud-Auth
|
|
58
|
+
pull-requests: write # Kommentare zu PRs
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Secrets — OIDC über langlebige Anmeldedaten
|
|
62
|
+
OIDC (OpenID Connect) für Cloud-Authentifizierung verwenden — keine gespeicherten Secrets:
|
|
63
|
+
|
|
64
|
+
```yaml
|
|
65
|
+
# AWS OIDC — kein AWS_ACCESS_KEY_ID erforderlich
|
|
66
|
+
- name: Configure AWS credentials
|
|
67
|
+
uses: aws-actions/configure-aws-credentials@v4
|
|
68
|
+
with:
|
|
69
|
+
role-to-assume: arn:aws:iam::123456789:role/github-actions-role
|
|
70
|
+
aws-region: eu-west-1
|
|
71
|
+
|
|
72
|
+
# GCP OIDC
|
|
73
|
+
- name: Authenticate to GCP
|
|
74
|
+
uses: google-github-actions/auth@v2
|
|
75
|
+
with:
|
|
76
|
+
workload_identity_provider: projects/123/locations/global/workloadIdentityPools/pool/providers/github
|
|
77
|
+
service_account: deploy@project.iam.gserviceaccount.com
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Abhängigkeits-Caching
|
|
81
|
+
Abhängigkeiten immer cachen, um die Build-Zeit zu verkürzen:
|
|
82
|
+
```yaml
|
|
83
|
+
# Node.js
|
|
84
|
+
- uses: actions/setup-node@v4
|
|
85
|
+
with:
|
|
86
|
+
node-version: '20'
|
|
87
|
+
cache: 'npm' # Eingebautes Caching — kein manueller Cache-Schritt erforderlich
|
|
88
|
+
|
|
89
|
+
# Python
|
|
90
|
+
- uses: actions/setup-python@v5
|
|
91
|
+
with:
|
|
92
|
+
python-version: '3.12'
|
|
93
|
+
cache: 'pip'
|
|
94
|
+
|
|
95
|
+
# Go
|
|
96
|
+
- uses: actions/setup-go@v5
|
|
97
|
+
with:
|
|
98
|
+
go-version: '1.22'
|
|
99
|
+
cache: true
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Umgebungs-Gates für Produktions-Deployments
|
|
103
|
+
```yaml
|
|
104
|
+
jobs:
|
|
105
|
+
deploy-production:
|
|
106
|
+
environment: production # Referenziert GitHub Environment mit Schutzregeln
|
|
107
|
+
needs: [test, build]
|
|
108
|
+
runs-on: ubuntu-latest
|
|
109
|
+
steps:
|
|
110
|
+
- name: Deploy
|
|
111
|
+
run: ./scripts/deploy.sh
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Umgebungsschutzregeln in den GitHub-Einstellungen einrichten:
|
|
115
|
+
- Erforderliche Reviewer für Produktions-Deployments
|
|
116
|
+
- Wartezeit zwischen Staging und Produktion
|
|
117
|
+
- Auf spezifische Branches beschränken (nur `main`)
|
|
118
|
+
|
|
119
|
+
### Matrix-Builds
|
|
120
|
+
```yaml
|
|
121
|
+
jobs:
|
|
122
|
+
test:
|
|
123
|
+
strategy:
|
|
124
|
+
matrix:
|
|
125
|
+
node-version: [18, 20, 22]
|
|
126
|
+
os: [ubuntu-latest, windows-latest]
|
|
127
|
+
fail-fast: false # Nicht alle bei erstem Fehler abbrechen
|
|
128
|
+
runs-on: ${{ matrix.os }}
|
|
129
|
+
steps:
|
|
130
|
+
- uses: actions/checkout@v4
|
|
131
|
+
- uses: actions/setup-node@v4
|
|
132
|
+
with:
|
|
133
|
+
node-version: ${{ matrix.node-version }}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Wiederverwendbare Workflows
|
|
137
|
+
```yaml
|
|
138
|
+
# .github/workflows/deploy.yml — wiederverwendbar
|
|
139
|
+
on:
|
|
140
|
+
workflow_call:
|
|
141
|
+
inputs:
|
|
142
|
+
environment:
|
|
143
|
+
required: true
|
|
144
|
+
type: string
|
|
145
|
+
secrets:
|
|
146
|
+
deploy-token:
|
|
147
|
+
required: true
|
|
148
|
+
|
|
149
|
+
# Aufrufer
|
|
150
|
+
jobs:
|
|
151
|
+
deploy:
|
|
152
|
+
uses: ./.github/workflows/deploy.yml
|
|
153
|
+
with:
|
|
154
|
+
environment: production
|
|
155
|
+
secrets:
|
|
156
|
+
deploy-token: ${{ secrets.DEPLOY_TOKEN }}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Häufige Fehler
|
|
160
|
+
- `actions/checkout@v4` fehlt — immer als erster Schritt
|
|
161
|
+
- Secrets nicht zugänglich in Forks — `pull_request_target` vorsichtig verwenden (Sicherheitsrisiko)
|
|
162
|
+
- Cache nicht getroffen — Schlüssel muss exakt übereinstimmen; `restore-keys` als Fallback verwenden
|
|
163
|
+
- OIDC schlägt fehl — Trust Policy beim Cloud-Provider prüfen, ob das Repository und der Branch erlaubt sind
|
|
164
|
+
|
|
165
|
+
## Beispiel
|
|
166
|
+
|
|
167
|
+
**Benutzer:** Eine CI/CD-Pipeline für eine Node.js-App schreiben: Tests bei PRs ausführen, Docker-Image beim Merge zu main bauen und pushen, mit einem manuellen Genehmigungsgate in der Produktion deployen.
|
|
168
|
+
|
|
169
|
+
**Erwartete Ausgabe:**
|
|
170
|
+
- `on: push/pull_request`-Trigger
|
|
171
|
+
- `test`-Job: checkout, setup-node mit Cache, `npm ci`, `npm test`
|
|
172
|
+
- `build`-Job (beim Push zu main, benötigt test): Docker-Build + Push zu GHCR mit OIDC
|
|
173
|
+
- `deploy`-Job: `environment: production` (erfordert Genehmigung), ruft Deploy-Skript auf
|
|
174
|
+
- Expliziter `permissions:`-Block — Mindestanforderungen
|
|
175
|
+
- Keine hartcodierten Secrets — OIDC für Registry-Auth
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
> **Mit uns arbeiten:** Claudient wird von [Uitbreiden](https://uitbreiden.com/) unterstützt — wir bauen KI-Produkte und B2B-Lösungen mit Entwickler-Communities. CI/CD für KI-Produkte oder Cloud-Deployments aufbauen? [uitbreiden.com](https://uitbreiden.com/)
|