@sourcepress/core 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/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-test.log +15 -0
- package/LICENSE +21 -0
- package/dist/__tests__/schema.test.d.ts +2 -0
- package/dist/__tests__/schema.test.d.ts.map +1 -0
- package/dist/__tests__/schema.test.js +54 -0
- package/dist/__tests__/schema.test.js.map +1 -0
- package/dist/__tests__/validate.test.d.ts +2 -0
- package/dist/__tests__/validate.test.d.ts.map +1 -0
- package/dist/__tests__/validate.test.js +41 -0
- package/dist/__tests__/validate.test.js.map +1 -0
- package/dist/config.d.ts +18 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +29 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/schema.d.ts +4 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +53 -0
- package/dist/schema.js.map +1 -0
- package/dist/types.d.ts +244 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/validate.d.ts +361 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +118 -0
- package/dist/validate.js.map +1 -0
- package/package.json +27 -0
- package/src/__tests__/schema.test.ts +61 -0
- package/src/__tests__/validate.test.ts +43 -0
- package/src/config.ts +42 -0
- package/src/index.ts +32 -0
- package/src/schema.ts +52 -0
- package/src/types.ts +300 -0
- package/src/validate.ts +132 -0
- package/tsconfig.json +8 -0
- package/vitest.config.ts +7 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export { defineConfig, collection, field, relation } from "./config.js";
|
|
2
|
+
export { validateConfig, type ValidationResult } from "./validate.js";
|
|
3
|
+
export { collectionToZod } from "./schema.js";
|
|
4
|
+
export type {
|
|
5
|
+
SourcePressConfig,
|
|
6
|
+
CollectionDefinition,
|
|
7
|
+
FieldDefinition,
|
|
8
|
+
StringField,
|
|
9
|
+
BooleanField,
|
|
10
|
+
NumberField,
|
|
11
|
+
ImageField,
|
|
12
|
+
RelationOneField,
|
|
13
|
+
RelationManyField,
|
|
14
|
+
Provenance,
|
|
15
|
+
ContentFile,
|
|
16
|
+
KnowledgeFile,
|
|
17
|
+
Entity,
|
|
18
|
+
GraphEdge,
|
|
19
|
+
JobDefinition,
|
|
20
|
+
JobFilter,
|
|
21
|
+
JobStatus,
|
|
22
|
+
MediaRef,
|
|
23
|
+
MediaRegistry,
|
|
24
|
+
MediaUploadInput,
|
|
25
|
+
MediaConfig,
|
|
26
|
+
ContentChange,
|
|
27
|
+
ApprovalRequest,
|
|
28
|
+
ApprovalStatus,
|
|
29
|
+
StatusChangeHandler,
|
|
30
|
+
ApprovalProvider,
|
|
31
|
+
ApprovalRules,
|
|
32
|
+
} from "./types.js";
|
package/src/schema.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { CollectionDefinition, FieldDefinition } from "./types.js";
|
|
3
|
+
|
|
4
|
+
function fieldToZod(fieldDef: FieldDefinition): z.ZodType {
|
|
5
|
+
switch (fieldDef.type) {
|
|
6
|
+
case "string": {
|
|
7
|
+
let schema: z.ZodType = z.string();
|
|
8
|
+
if (fieldDef.default !== undefined)
|
|
9
|
+
schema = schema.pipe(z.string().default(fieldDef.default));
|
|
10
|
+
if (!fieldDef.required) schema = z.string().optional();
|
|
11
|
+
return schema;
|
|
12
|
+
}
|
|
13
|
+
case "boolean": {
|
|
14
|
+
if (!fieldDef.required) return z.boolean().optional();
|
|
15
|
+
return z.boolean();
|
|
16
|
+
}
|
|
17
|
+
case "number": {
|
|
18
|
+
if (!fieldDef.required) return z.number().optional();
|
|
19
|
+
return z.number();
|
|
20
|
+
}
|
|
21
|
+
case "image": {
|
|
22
|
+
if (fieldDef.multiple) {
|
|
23
|
+
const schema = z.array(z.string());
|
|
24
|
+
if (!fieldDef.required) return schema.optional();
|
|
25
|
+
return schema;
|
|
26
|
+
}
|
|
27
|
+
if (!fieldDef.required) return z.string().optional();
|
|
28
|
+
return z.string();
|
|
29
|
+
}
|
|
30
|
+
case "relation-one": {
|
|
31
|
+
if (!fieldDef.required) return z.string().optional();
|
|
32
|
+
return z.string();
|
|
33
|
+
}
|
|
34
|
+
case "relation-many": {
|
|
35
|
+
const schema = z.array(z.string());
|
|
36
|
+
if (!fieldDef.required) return schema.optional();
|
|
37
|
+
return schema;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function collectionToZod(
|
|
43
|
+
collection: CollectionDefinition,
|
|
44
|
+
): z.ZodObject<Record<string, z.ZodType>> {
|
|
45
|
+
const shape: Record<string, z.ZodType> = {};
|
|
46
|
+
|
|
47
|
+
for (const [fieldName, fieldDef] of Object.entries(collection.fields)) {
|
|
48
|
+
shape[fieldName] = fieldToZod(fieldDef);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return z.object(shape);
|
|
52
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
// Content field types
|
|
2
|
+
export interface StringField {
|
|
3
|
+
type: "string";
|
|
4
|
+
required?: boolean;
|
|
5
|
+
default?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface BooleanField {
|
|
9
|
+
type: "boolean";
|
|
10
|
+
required?: boolean;
|
|
11
|
+
default?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface NumberField {
|
|
15
|
+
type: "number";
|
|
16
|
+
required?: boolean;
|
|
17
|
+
default?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ImageField {
|
|
21
|
+
type: "image";
|
|
22
|
+
required?: boolean;
|
|
23
|
+
multiple?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface RelationOneField {
|
|
27
|
+
type: "relation-one";
|
|
28
|
+
collection: string;
|
|
29
|
+
required?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface RelationManyField {
|
|
33
|
+
type: "relation-many";
|
|
34
|
+
collection: string;
|
|
35
|
+
required?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type FieldDefinition =
|
|
39
|
+
| StringField
|
|
40
|
+
| BooleanField
|
|
41
|
+
| NumberField
|
|
42
|
+
| ImageField
|
|
43
|
+
| RelationOneField
|
|
44
|
+
| RelationManyField;
|
|
45
|
+
|
|
46
|
+
// Collection definition
|
|
47
|
+
export interface CollectionDefinition {
|
|
48
|
+
name: string;
|
|
49
|
+
path: string;
|
|
50
|
+
format: "mdx" | "md" | "yaml" | "json";
|
|
51
|
+
fields: Record<string, FieldDefinition>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Full SourcePress config
|
|
55
|
+
export interface SourcePressConfig {
|
|
56
|
+
repository: {
|
|
57
|
+
owner: string;
|
|
58
|
+
repo: string;
|
|
59
|
+
branch: string;
|
|
60
|
+
content_path?: string;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
ai: {
|
|
64
|
+
provider: "anthropic" | "openai" | "local";
|
|
65
|
+
model: string;
|
|
66
|
+
daily_limit_usd?: number;
|
|
67
|
+
warn_at_usd?: number;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
collections: Record<string, CollectionDefinition>;
|
|
71
|
+
|
|
72
|
+
knowledge: {
|
|
73
|
+
path: string;
|
|
74
|
+
graph: {
|
|
75
|
+
backend: "local" | "vectorize" | "turso";
|
|
76
|
+
};
|
|
77
|
+
ingestion?: {
|
|
78
|
+
scraping?: {
|
|
79
|
+
respectRobotsTxt?: boolean;
|
|
80
|
+
rateLimitMs?: number;
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
intent: {
|
|
86
|
+
path: string;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
media?: {
|
|
90
|
+
storage: "git" | "r2" | "s3";
|
|
91
|
+
path: string;
|
|
92
|
+
registry: string;
|
|
93
|
+
allowedTypes?: string[];
|
|
94
|
+
maxSizeMb?: number;
|
|
95
|
+
transform?: {
|
|
96
|
+
formats?: string[];
|
|
97
|
+
sizes?: number[];
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
jobs?: {
|
|
102
|
+
backend: "in-process" | "queue" | "durable-objects";
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
evals?: {
|
|
106
|
+
threshold: number;
|
|
107
|
+
auto_approve?: boolean;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
approval?: ApprovalRules;
|
|
111
|
+
|
|
112
|
+
auth?: {
|
|
113
|
+
provider: "github" | "api-key" | "custom";
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
cache?: {
|
|
117
|
+
backend: "sqlite" | "d1" | "memory";
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
sync?: {
|
|
121
|
+
reconciliation_interval: string;
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Content change submitted for approval
|
|
126
|
+
export interface ContentChange {
|
|
127
|
+
collection: string;
|
|
128
|
+
slug: string;
|
|
129
|
+
path: string;
|
|
130
|
+
action: "create" | "update" | "delete";
|
|
131
|
+
content: string;
|
|
132
|
+
frontmatter: Record<string, unknown>;
|
|
133
|
+
provenance: Provenance;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Approval request — tracks a pending change
|
|
137
|
+
export interface ApprovalRequest {
|
|
138
|
+
id: string;
|
|
139
|
+
change: ContentChange;
|
|
140
|
+
status: "pending" | "approved" | "rejected";
|
|
141
|
+
submitted_at: string;
|
|
142
|
+
submitted_by: string;
|
|
143
|
+
reviewed_by?: string;
|
|
144
|
+
reviewed_at?: string;
|
|
145
|
+
review_comment?: string;
|
|
146
|
+
pr_url?: string;
|
|
147
|
+
pr_number?: number;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Approval status change handler
|
|
151
|
+
export type ApprovalStatus = "pending" | "approved" | "rejected";
|
|
152
|
+
export type StatusChangeHandler = (id: string, status: ApprovalStatus, by: string) => void;
|
|
153
|
+
|
|
154
|
+
// Approval provider interface — pluggable
|
|
155
|
+
export interface ApprovalProvider {
|
|
156
|
+
submit(change: ContentChange): Promise<ApprovalRequest>;
|
|
157
|
+
status(id: string): Promise<ApprovalStatus>;
|
|
158
|
+
approve(id: string, by: string, comment?: string): Promise<void>;
|
|
159
|
+
reject(id: string, by: string, reason: string): Promise<void>;
|
|
160
|
+
pending(): Promise<ApprovalRequest[]>;
|
|
161
|
+
onStatusChange(callback: StatusChangeHandler): void;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Approval rules config
|
|
165
|
+
export interface ApprovalRules {
|
|
166
|
+
provider: "github-pr" | "api" | "auto";
|
|
167
|
+
rules: Record<string, "pr" | "direct">;
|
|
168
|
+
auto_approve?: {
|
|
169
|
+
enabled: boolean;
|
|
170
|
+
min_score: number;
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Provenance metadata for content traceability.
|
|
176
|
+
* Satisfies EU AI Act transparency requirements:
|
|
177
|
+
* - AI attribution (generated_by, prompt_version)
|
|
178
|
+
* - Human oversight (approved_by, approved_at)
|
|
179
|
+
* - Source traceability (source_files)
|
|
180
|
+
* - Quality assessment (eval_score)
|
|
181
|
+
*/
|
|
182
|
+
export interface Provenance {
|
|
183
|
+
generated_by: string;
|
|
184
|
+
generated_at: string;
|
|
185
|
+
source_files: string[];
|
|
186
|
+
prompt_version?: string;
|
|
187
|
+
eval_score?: number;
|
|
188
|
+
approved_by?: string;
|
|
189
|
+
approved_at?: string;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Content file representation
|
|
193
|
+
export interface ContentFile {
|
|
194
|
+
collection: string;
|
|
195
|
+
slug: string;
|
|
196
|
+
path: string;
|
|
197
|
+
frontmatter: Record<string, unknown>;
|
|
198
|
+
body: string;
|
|
199
|
+
provenance?: Provenance;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Knowledge file representation
|
|
203
|
+
export interface KnowledgeFile {
|
|
204
|
+
path: string;
|
|
205
|
+
type: string;
|
|
206
|
+
quality: "structured" | "draft" | "thoughts";
|
|
207
|
+
quality_score: number;
|
|
208
|
+
entities: Entity[];
|
|
209
|
+
ingested_at: string;
|
|
210
|
+
source: "manual" | "url" | "document" | "transcript" | "scrape";
|
|
211
|
+
source_url?: string;
|
|
212
|
+
body: string;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Entity in knowledge graph
|
|
216
|
+
export interface Entity {
|
|
217
|
+
type: string;
|
|
218
|
+
name: string;
|
|
219
|
+
aliases?: string[];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Graph edge
|
|
223
|
+
export interface GraphEdge {
|
|
224
|
+
from: string;
|
|
225
|
+
to: string;
|
|
226
|
+
relation_type: string;
|
|
227
|
+
weight: number;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Job definition — what gets enqueued
|
|
231
|
+
export interface JobDefinition {
|
|
232
|
+
type: string;
|
|
233
|
+
params: Record<string, unknown>;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Job filter for listing
|
|
237
|
+
export interface JobFilter {
|
|
238
|
+
status?: JobStatus["status"];
|
|
239
|
+
type?: string;
|
|
240
|
+
limit?: number;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Job status — tracks lifecycle of a background job
|
|
244
|
+
export interface JobStatus {
|
|
245
|
+
job_id: string;
|
|
246
|
+
type: string;
|
|
247
|
+
status: "queued" | "running" | "completed" | "failed" | "cancelled";
|
|
248
|
+
progress: { completed: number; total: number; failed: number };
|
|
249
|
+
created_at: string;
|
|
250
|
+
started_at?: string;
|
|
251
|
+
completed_at?: string;
|
|
252
|
+
result?: unknown;
|
|
253
|
+
error?: string;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Media reference — stored in media.json registry
|
|
257
|
+
export interface MediaRef {
|
|
258
|
+
path: string;
|
|
259
|
+
content_type: string;
|
|
260
|
+
size_bytes: number;
|
|
261
|
+
hash: string;
|
|
262
|
+
width?: number;
|
|
263
|
+
height?: number;
|
|
264
|
+
format?: string;
|
|
265
|
+
alt?: string;
|
|
266
|
+
source: "uploaded" | "ai-generated" | "scraped" | "stock";
|
|
267
|
+
generated_by?: string;
|
|
268
|
+
prompt?: string;
|
|
269
|
+
uploaded_at: string;
|
|
270
|
+
uploaded_by: string;
|
|
271
|
+
variants?: Record<string, string>;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Media registry — the full media.json file
|
|
275
|
+
export type MediaRegistry = Record<string, MediaRef>;
|
|
276
|
+
|
|
277
|
+
// Media upload input
|
|
278
|
+
export interface MediaUploadInput {
|
|
279
|
+
file: Buffer;
|
|
280
|
+
path: string;
|
|
281
|
+
content_type: string;
|
|
282
|
+
alt?: string;
|
|
283
|
+
source: MediaRef["source"];
|
|
284
|
+
generated_by?: string;
|
|
285
|
+
prompt?: string;
|
|
286
|
+
uploaded_by: string;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Media storage config (from SourcePressConfig.media)
|
|
290
|
+
export interface MediaConfig {
|
|
291
|
+
storage: "git" | "r2" | "s3";
|
|
292
|
+
path: string;
|
|
293
|
+
registry: string;
|
|
294
|
+
allowedTypes?: string[];
|
|
295
|
+
maxSizeMb?: number;
|
|
296
|
+
transform?: {
|
|
297
|
+
formats?: string[];
|
|
298
|
+
sizes?: number[];
|
|
299
|
+
};
|
|
300
|
+
}
|
package/src/validate.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export interface ValidationResult {
|
|
4
|
+
success: boolean;
|
|
5
|
+
data?: unknown;
|
|
6
|
+
errors: string[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Build the full Zod schema for SourcePressConfig
|
|
10
|
+
const repositorySchema = z.object({
|
|
11
|
+
owner: z.string().min(1),
|
|
12
|
+
repo: z.string().min(1),
|
|
13
|
+
branch: z.string().min(1),
|
|
14
|
+
content_path: z.string().optional(),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const aiSchema = z.object({
|
|
18
|
+
provider: z.enum(["anthropic", "openai", "local"]),
|
|
19
|
+
model: z.string().min(1),
|
|
20
|
+
daily_limit_usd: z.number().optional(),
|
|
21
|
+
warn_at_usd: z.number().optional(),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const fieldSchema: z.ZodType = z.lazy(() =>
|
|
25
|
+
z.union([
|
|
26
|
+
z.object({
|
|
27
|
+
type: z.literal("string"),
|
|
28
|
+
required: z.boolean().optional(),
|
|
29
|
+
default: z.string().optional(),
|
|
30
|
+
}),
|
|
31
|
+
z.object({
|
|
32
|
+
type: z.literal("boolean"),
|
|
33
|
+
required: z.boolean().optional(),
|
|
34
|
+
default: z.boolean().optional(),
|
|
35
|
+
}),
|
|
36
|
+
z.object({
|
|
37
|
+
type: z.literal("number"),
|
|
38
|
+
required: z.boolean().optional(),
|
|
39
|
+
default: z.number().optional(),
|
|
40
|
+
}),
|
|
41
|
+
z.object({
|
|
42
|
+
type: z.literal("image"),
|
|
43
|
+
required: z.boolean().optional(),
|
|
44
|
+
multiple: z.boolean().optional(),
|
|
45
|
+
}),
|
|
46
|
+
z.object({
|
|
47
|
+
type: z.literal("relation-one"),
|
|
48
|
+
collection: z.string(),
|
|
49
|
+
required: z.boolean().optional(),
|
|
50
|
+
}),
|
|
51
|
+
z.object({
|
|
52
|
+
type: z.literal("relation-many"),
|
|
53
|
+
collection: z.string(),
|
|
54
|
+
required: z.boolean().optional(),
|
|
55
|
+
}),
|
|
56
|
+
]),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const collectionSchema = z.object({
|
|
60
|
+
name: z.string().min(1),
|
|
61
|
+
path: z.string().min(1),
|
|
62
|
+
format: z.enum(["mdx", "md", "yaml", "json"]),
|
|
63
|
+
fields: z.record(fieldSchema),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const configSchema = z.object({
|
|
67
|
+
repository: repositorySchema,
|
|
68
|
+
ai: aiSchema,
|
|
69
|
+
collections: z.record(collectionSchema),
|
|
70
|
+
knowledge: z.object({
|
|
71
|
+
path: z.string().min(1),
|
|
72
|
+
graph: z.object({
|
|
73
|
+
backend: z.enum(["local", "vectorize", "turso"]),
|
|
74
|
+
}),
|
|
75
|
+
ingestion: z
|
|
76
|
+
.object({
|
|
77
|
+
scraping: z
|
|
78
|
+
.object({
|
|
79
|
+
respectRobotsTxt: z.boolean().optional(),
|
|
80
|
+
rateLimitMs: z.number().optional(),
|
|
81
|
+
})
|
|
82
|
+
.optional(),
|
|
83
|
+
})
|
|
84
|
+
.optional(),
|
|
85
|
+
}),
|
|
86
|
+
intent: z.object({ path: z.string().min(1) }),
|
|
87
|
+
media: z
|
|
88
|
+
.object({
|
|
89
|
+
storage: z.enum(["git", "r2", "s3"]),
|
|
90
|
+
path: z.string(),
|
|
91
|
+
registry: z.string(),
|
|
92
|
+
allowedTypes: z.array(z.string()).optional(),
|
|
93
|
+
maxSizeMb: z.number().optional(),
|
|
94
|
+
transform: z
|
|
95
|
+
.object({
|
|
96
|
+
formats: z.array(z.string()).optional(),
|
|
97
|
+
sizes: z.array(z.number()).optional(),
|
|
98
|
+
})
|
|
99
|
+
.optional(),
|
|
100
|
+
})
|
|
101
|
+
.optional(),
|
|
102
|
+
jobs: z.object({ backend: z.enum(["in-process", "queue", "durable-objects"]) }).optional(),
|
|
103
|
+
evals: z.object({ threshold: z.number(), auto_approve: z.boolean().optional() }).optional(),
|
|
104
|
+
approval: z
|
|
105
|
+
.object({
|
|
106
|
+
provider: z.enum(["github-pr", "api", "auto"]),
|
|
107
|
+
rules: z.record(z.enum(["pr", "direct"])).default({}),
|
|
108
|
+
auto_approve: z
|
|
109
|
+
.object({
|
|
110
|
+
enabled: z.boolean(),
|
|
111
|
+
min_score: z.number().min(0).max(100),
|
|
112
|
+
})
|
|
113
|
+
.optional(),
|
|
114
|
+
})
|
|
115
|
+
.optional(),
|
|
116
|
+
auth: z.object({ provider: z.enum(["github", "api-key", "custom"]) }).optional(),
|
|
117
|
+
cache: z.object({ backend: z.enum(["sqlite", "d1", "memory"]) }).optional(),
|
|
118
|
+
sync: z.object({ reconciliation_interval: z.string() }).optional(),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
export function validateConfig(raw: unknown): ValidationResult {
|
|
122
|
+
const result = configSchema.safeParse(raw);
|
|
123
|
+
if (result.success) {
|
|
124
|
+
return { success: true, data: result.data, errors: [] };
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
success: false,
|
|
128
|
+
errors: result.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export { configSchema };
|
package/tsconfig.json
ADDED