@ontrails/trails 1.0.0-beta.1 → 1.0.0-beta.11
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-lint.log +1 -1
- package/CHANGELOG.md +154 -0
- package/bin/trails.ts +0 -0
- package/dist/src/clack.d.ts.map +1 -1
- package/dist/src/clack.js +22 -0
- package/dist/src/clack.js.map +1 -1
- package/dist/src/trails/add-surface.js +9 -9
- package/dist/src/trails/add-surface.js.map +1 -1
- package/dist/src/trails/add-trail.d.ts +1 -2
- package/dist/src/trails/add-trail.d.ts.map +1 -1
- package/dist/src/trails/add-trail.js +16 -24
- package/dist/src/trails/add-trail.js.map +1 -1
- package/dist/src/trails/add-verify.js +9 -9
- package/dist/src/trails/add-verify.js.map +1 -1
- package/dist/src/trails/create-scaffold.js +25 -25
- package/dist/src/trails/create-scaffold.js.map +1 -1
- package/dist/src/trails/create.d.ts +1 -1
- package/dist/src/trails/create.js +10 -10
- package/dist/src/trails/create.js.map +1 -1
- package/dist/src/trails/guide.js +12 -12
- package/dist/src/trails/guide.js.map +1 -1
- package/dist/src/trails/survey.d.ts +41 -2
- package/dist/src/trails/survey.d.ts.map +1 -1
- package/dist/src/trails/survey.js +141 -33
- package/dist/src/trails/survey.js.map +1 -1
- package/dist/src/trails/warden.d.ts +1 -1
- package/dist/src/trails/warden.js +28 -28
- package/dist/src/trails/warden.js.map +1 -1
- package/package.json +9 -9
- package/src/__tests__/create.test.ts +7 -7
- package/src/__tests__/guide.test.ts +4 -4
- package/src/__tests__/survey.test.ts +69 -14
- package/src/__tests__/warden.test.ts +2 -2
- package/src/clack.ts +22 -0
- package/src/trails/add-surface.ts +9 -9
- package/src/trails/add-trail.ts +16 -25
- package/src/trails/add-verify.ts +9 -9
- package/src/trails/create-scaffold.ts +27 -27
- package/src/trails/create.ts +10 -10
- package/src/trails/guide.ts +14 -14
- package/src/trails/survey.ts +232 -44
- package/src/trails/warden.ts +33 -33
package/src/trails/survey.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { Result, trail } from '@ontrails/core';
|
|
|
10
10
|
import type { DiffResult } from '@ontrails/schema';
|
|
11
11
|
import {
|
|
12
12
|
diffSurfaceMaps,
|
|
13
|
+
generateOpenApiSpec,
|
|
13
14
|
generateSurfaceMap,
|
|
14
15
|
hashSurfaceMap,
|
|
15
16
|
readSurfaceMap,
|
|
@@ -29,15 +30,46 @@ export interface BriefReport {
|
|
|
29
30
|
readonly version: string;
|
|
30
31
|
readonly contractVersion: string;
|
|
31
32
|
readonly features: {
|
|
33
|
+
readonly services: boolean;
|
|
32
34
|
readonly outputSchemas: boolean;
|
|
33
35
|
readonly examples: boolean;
|
|
34
36
|
readonly detours: boolean;
|
|
35
|
-
readonly hikes: boolean;
|
|
36
37
|
readonly events: boolean;
|
|
37
38
|
};
|
|
38
39
|
readonly trails: number;
|
|
39
|
-
readonly hikes: number;
|
|
40
40
|
readonly events: number;
|
|
41
|
+
readonly services: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface SurveyListReport {
|
|
45
|
+
readonly count: number;
|
|
46
|
+
readonly entries: readonly {
|
|
47
|
+
readonly examples: number;
|
|
48
|
+
readonly id: string;
|
|
49
|
+
readonly kind: string;
|
|
50
|
+
readonly safety: string;
|
|
51
|
+
}[];
|
|
52
|
+
readonly serviceCount: number;
|
|
53
|
+
readonly services: readonly {
|
|
54
|
+
readonly description: string | null;
|
|
55
|
+
readonly health: 'available' | 'none';
|
|
56
|
+
readonly id: string;
|
|
57
|
+
readonly kind: 'service';
|
|
58
|
+
readonly lifetime: 'singleton';
|
|
59
|
+
readonly usedBy: readonly string[];
|
|
60
|
+
}[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface TrailDetailReport {
|
|
64
|
+
readonly description: string | null;
|
|
65
|
+
readonly detours: Trail<unknown, unknown>['detours'] | null;
|
|
66
|
+
readonly examples: readonly unknown[];
|
|
67
|
+
readonly follow: readonly string[];
|
|
68
|
+
readonly id: string;
|
|
69
|
+
readonly intent: 'read' | 'write' | 'destroy';
|
|
70
|
+
readonly kind: string;
|
|
71
|
+
readonly safety: string;
|
|
72
|
+
readonly services: readonly string[];
|
|
41
73
|
}
|
|
42
74
|
|
|
43
75
|
/** Check if a trail has a specific feature. */
|
|
@@ -51,7 +83,12 @@ const trailHas = (raw: Record<string, unknown>, key: string): boolean => {
|
|
|
51
83
|
/** Detect which features are used across trails. */
|
|
52
84
|
const detectFeatures = (
|
|
53
85
|
app: Topo
|
|
54
|
-
): {
|
|
86
|
+
): {
|
|
87
|
+
hasDetours: boolean;
|
|
88
|
+
hasExamples: boolean;
|
|
89
|
+
hasOutputSchemas: boolean;
|
|
90
|
+
hasServices: boolean;
|
|
91
|
+
} => {
|
|
55
92
|
const trails = [...app.trails.values()].map(
|
|
56
93
|
(item) => item as unknown as Record<string, unknown>
|
|
57
94
|
);
|
|
@@ -59,12 +96,17 @@ const detectFeatures = (
|
|
|
59
96
|
hasDetours: trails.some((r) => trailHas(r, 'detours')),
|
|
60
97
|
hasExamples: trails.some((r) => trailHas(r, 'examples')),
|
|
61
98
|
hasOutputSchemas: trails.some((r) => trailHas(r, 'output')),
|
|
99
|
+
hasServices: trails.some(
|
|
100
|
+
(r) =>
|
|
101
|
+
Array.isArray(r['services']) && (r['services'] as unknown[]).length > 0
|
|
102
|
+
),
|
|
62
103
|
};
|
|
63
104
|
};
|
|
64
105
|
|
|
65
106
|
/** Generate a compact capability report for the given topo. */
|
|
66
107
|
export const generateBriefReport = (app: Topo): BriefReport => {
|
|
67
|
-
const { hasDetours, hasExamples, hasOutputSchemas } =
|
|
108
|
+
const { hasDetours, hasExamples, hasOutputSchemas, hasServices } =
|
|
109
|
+
detectFeatures(app);
|
|
68
110
|
|
|
69
111
|
return {
|
|
70
112
|
contractVersion: '2026-03',
|
|
@@ -73,11 +115,11 @@ export const generateBriefReport = (app: Topo): BriefReport => {
|
|
|
73
115
|
detours: hasDetours,
|
|
74
116
|
events: app.events.size > 0,
|
|
75
117
|
examples: hasExamples,
|
|
76
|
-
hikes: app.hikes.size > 0,
|
|
77
118
|
outputSchemas: hasOutputSchemas,
|
|
119
|
+
services: hasServices,
|
|
78
120
|
},
|
|
79
|
-
hikes: app.hikes.size,
|
|
80
121
|
name: app.name,
|
|
122
|
+
services: app.services.size,
|
|
81
123
|
trails: app.trails.size,
|
|
82
124
|
version: '0.1.0',
|
|
83
125
|
};
|
|
@@ -88,23 +130,60 @@ export const generateBriefReport = (app: Topo): BriefReport => {
|
|
|
88
130
|
// ---------------------------------------------------------------------------
|
|
89
131
|
|
|
90
132
|
const safetyLabel = (entry: {
|
|
91
|
-
|
|
92
|
-
destructive?: boolean;
|
|
133
|
+
intent?: 'read' | 'write' | 'destroy';
|
|
93
134
|
}): string => {
|
|
94
|
-
if (entry.
|
|
95
|
-
return '
|
|
135
|
+
if (entry.intent === 'destroy') {
|
|
136
|
+
return 'destroy';
|
|
96
137
|
}
|
|
97
|
-
if (entry.
|
|
98
|
-
return '
|
|
138
|
+
if (entry.intent === 'read') {
|
|
139
|
+
return 'read';
|
|
99
140
|
}
|
|
100
141
|
return '-';
|
|
101
142
|
};
|
|
102
143
|
|
|
103
|
-
const
|
|
144
|
+
const buildServiceUsage = (
|
|
145
|
+
app: Topo
|
|
146
|
+
): ReadonlyMap<string, readonly string[]> => {
|
|
147
|
+
const usage = new Map<string, string[]>();
|
|
148
|
+
|
|
149
|
+
for (const trailDef of app.list()) {
|
|
150
|
+
for (const declaredService of trailDef.services) {
|
|
151
|
+
const users = usage.get(declaredService.id) ?? [];
|
|
152
|
+
users.push(trailDef.id);
|
|
153
|
+
usage.set(declaredService.id, users);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return new Map(
|
|
158
|
+
[...usage.entries()].map(([id, users]) => [id, users.toSorted()] as const)
|
|
159
|
+
);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const serviceHealthStatus = (service: {
|
|
163
|
+
health?: unknown;
|
|
164
|
+
}): 'available' | 'none' =>
|
|
165
|
+
service.health === undefined ? 'none' : 'available';
|
|
166
|
+
|
|
167
|
+
const formatServiceList = (app: Topo): SurveyListReport['services'] => {
|
|
168
|
+
const usage = buildServiceUsage(app);
|
|
169
|
+
return app
|
|
170
|
+
.listServices()
|
|
171
|
+
.map((service) => ({
|
|
172
|
+
description: service.description ?? null,
|
|
173
|
+
health: serviceHealthStatus(service),
|
|
174
|
+
id: service.id,
|
|
175
|
+
kind: service.kind,
|
|
176
|
+
lifetime: 'singleton' as const,
|
|
177
|
+
usedBy: usage.get(service.id) ?? [],
|
|
178
|
+
}))
|
|
179
|
+
.toSorted((a, b) => a.id.localeCompare(b.id));
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
export const generateSurveyList = (app: Topo): SurveyListReport => {
|
|
104
183
|
const items = app.list();
|
|
105
184
|
const entries = items.map((item) => {
|
|
106
185
|
const safety = safetyLabel(
|
|
107
|
-
item as unknown as {
|
|
186
|
+
item as unknown as { intent?: 'read' | 'write' | 'destroy' }
|
|
108
187
|
);
|
|
109
188
|
const examples = Array.isArray(
|
|
110
189
|
(item as unknown as { examples?: unknown[] }).examples
|
|
@@ -120,7 +199,14 @@ const formatTrailList = (app: Topo): object => {
|
|
|
120
199
|
};
|
|
121
200
|
});
|
|
122
201
|
|
|
123
|
-
|
|
202
|
+
const services = formatServiceList(app);
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
count: items.length,
|
|
206
|
+
entries,
|
|
207
|
+
serviceCount: services.length,
|
|
208
|
+
services,
|
|
209
|
+
};
|
|
124
210
|
};
|
|
125
211
|
|
|
126
212
|
/**
|
|
@@ -130,18 +216,37 @@ const formatTrailList = (app: Topo): object => {
|
|
|
130
216
|
* surface-map entry. The two serve different audiences (human display vs
|
|
131
217
|
* machine-diffable surface map) so they are kept separate.
|
|
132
218
|
*/
|
|
133
|
-
const
|
|
219
|
+
export const generateTrailDetail = (
|
|
220
|
+
item: Trail<unknown, unknown>
|
|
221
|
+
): TrailDetailReport => {
|
|
134
222
|
const safety = safetyLabel(
|
|
135
|
-
item as unknown as {
|
|
223
|
+
item as unknown as { intent?: 'read' | 'write' | 'destroy' }
|
|
136
224
|
);
|
|
137
225
|
|
|
138
226
|
return {
|
|
139
227
|
description: item.description ?? null,
|
|
140
228
|
detours: item.detours ?? null,
|
|
141
229
|
examples: item.examples ?? [],
|
|
230
|
+
follow: item.follow.toSorted(),
|
|
142
231
|
id: item.id,
|
|
232
|
+
intent: item.intent,
|
|
143
233
|
kind: item.kind,
|
|
144
234
|
safety,
|
|
235
|
+
services: item.services.map((service) => service.id).toSorted(),
|
|
236
|
+
};
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const formatServiceDetail = (app: Topo, serviceId: string): object => {
|
|
240
|
+
const item = app.getService(serviceId);
|
|
241
|
+
const usedBy = buildServiceUsage(app).get(serviceId) ?? [];
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
description: item?.description ?? null,
|
|
245
|
+
health: item ? serviceHealthStatus(item) : 'none',
|
|
246
|
+
id: serviceId,
|
|
247
|
+
kind: 'service',
|
|
248
|
+
lifetime: 'singleton',
|
|
249
|
+
usedBy,
|
|
145
250
|
};
|
|
146
251
|
};
|
|
147
252
|
|
|
@@ -184,10 +289,13 @@ const buildSurveyDetail = (
|
|
|
184
289
|
trailId: string
|
|
185
290
|
): Result<object, Error> => {
|
|
186
291
|
const item = app.get(trailId);
|
|
187
|
-
if (
|
|
188
|
-
return Result.
|
|
292
|
+
if (item) {
|
|
293
|
+
return Result.ok(generateTrailDetail(item as Trail<unknown, unknown>));
|
|
294
|
+
}
|
|
295
|
+
if (app.getService(trailId)) {
|
|
296
|
+
return Result.ok(formatServiceDetail(app, trailId));
|
|
189
297
|
}
|
|
190
|
-
return Result.
|
|
298
|
+
return Result.err(new Error(`Trail or service not found: ${trailId}`));
|
|
191
299
|
};
|
|
192
300
|
|
|
193
301
|
const buildSurveyGenerate = async (
|
|
@@ -200,6 +308,55 @@ const buildSurveyGenerate = async (
|
|
|
200
308
|
return Result.ok({ hash, lockPath, mapPath });
|
|
201
309
|
};
|
|
202
310
|
|
|
311
|
+
interface SurveyInput {
|
|
312
|
+
breakingOnly: boolean;
|
|
313
|
+
brief: boolean;
|
|
314
|
+
diff?: string | undefined;
|
|
315
|
+
generate: boolean;
|
|
316
|
+
openapi: boolean;
|
|
317
|
+
trailId?: string | undefined;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
type SurveyMode = 'brief' | 'detail' | 'diff' | 'generate' | 'list' | 'openapi';
|
|
321
|
+
|
|
322
|
+
/** Ordered mode checks — first truthy predicate wins, otherwise 'list'. */
|
|
323
|
+
const modeChecks: readonly [(input: SurveyInput) => boolean, SurveyMode][] = [
|
|
324
|
+
[(i) => i.brief, 'brief'],
|
|
325
|
+
[(i) => Boolean(i.diff), 'diff'],
|
|
326
|
+
[(i) => Boolean(i.trailId), 'detail'],
|
|
327
|
+
[(i) => i.generate, 'generate'],
|
|
328
|
+
[(i) => i.openapi, 'openapi'],
|
|
329
|
+
];
|
|
330
|
+
|
|
331
|
+
/** Determine which survey mode was requested, falling back to 'list'. */
|
|
332
|
+
const resolveSurveyMode = (input: SurveyInput): SurveyMode =>
|
|
333
|
+
modeChecks.find(([predicate]) => predicate(input))?.[1] ?? 'list';
|
|
334
|
+
|
|
335
|
+
type SurveyHandler = (
|
|
336
|
+
app: Topo,
|
|
337
|
+
input: SurveyInput
|
|
338
|
+
) => Result<object, Error> | Promise<Result<object, Error>>;
|
|
339
|
+
|
|
340
|
+
/** Handlers keyed by survey mode. */
|
|
341
|
+
const surveyHandlers: Record<SurveyMode, SurveyHandler> = {
|
|
342
|
+
brief: (app) => Result.ok(generateBriefReport(app)),
|
|
343
|
+
detail: (app, input) => buildSurveyDetail(app, input.trailId ?? ''),
|
|
344
|
+
diff: (app, input) => buildSurveyDiff(app, input.breakingOnly),
|
|
345
|
+
generate: (app) => buildSurveyGenerate(app),
|
|
346
|
+
list: (app) => Result.ok(generateSurveyList(app)),
|
|
347
|
+
openapi: (app) => Result.ok(generateOpenApiSpec(app)),
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
/** Dispatch to the appropriate survey sub-command based on input flags. */
|
|
351
|
+
const dispatchSurvey = (
|
|
352
|
+
app: Topo,
|
|
353
|
+
input: SurveyInput
|
|
354
|
+
): Result<object, Error> | Promise<Result<object, Error>> => {
|
|
355
|
+
const mode = resolveSurveyMode(input);
|
|
356
|
+
const handler = surveyHandlers[mode];
|
|
357
|
+
return handler(app, input);
|
|
358
|
+
};
|
|
359
|
+
|
|
203
360
|
// ---------------------------------------------------------------------------
|
|
204
361
|
// Trail definition
|
|
205
362
|
// ---------------------------------------------------------------------------
|
|
@@ -217,28 +374,12 @@ export const surveyTrail = trail('survey', {
|
|
|
217
374
|
input: { brief: true },
|
|
218
375
|
name: 'Brief capability report',
|
|
219
376
|
},
|
|
377
|
+
{
|
|
378
|
+
description: 'Generate an OpenAPI 3.1 specification for the topo',
|
|
379
|
+
input: { openapi: true },
|
|
380
|
+
name: 'OpenAPI spec',
|
|
381
|
+
},
|
|
220
382
|
],
|
|
221
|
-
implementation: async (input, ctx) => {
|
|
222
|
-
const app = await loadApp(input.module, ctx.cwd ?? '.');
|
|
223
|
-
|
|
224
|
-
if (input.brief) {
|
|
225
|
-
return Result.ok(generateBriefReport(app));
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (input.diff) {
|
|
229
|
-
return await buildSurveyDiff(app, input.breakingOnly);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (input.trailId) {
|
|
233
|
-
return buildSurveyDetail(app, input.trailId);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (input.generate) {
|
|
237
|
-
return await buildSurveyGenerate(app);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
return Result.ok(formatTrailList(app));
|
|
241
|
-
},
|
|
242
383
|
input: z.object({
|
|
243
384
|
breakingOnly: z
|
|
244
385
|
.boolean()
|
|
@@ -254,8 +395,10 @@ export const surveyTrail = trail('survey', {
|
|
|
254
395
|
.string()
|
|
255
396
|
.default('./src/app.ts')
|
|
256
397
|
.describe('Path to the app module'),
|
|
398
|
+
openapi: z.boolean().default(false).describe('Output OpenAPI 3.1 spec'),
|
|
257
399
|
trailId: z.string().optional().describe('Trail ID for detail view'),
|
|
258
400
|
}),
|
|
401
|
+
intent: 'read',
|
|
259
402
|
output: z.union([
|
|
260
403
|
z.object({
|
|
261
404
|
count: z.number(),
|
|
@@ -267,6 +410,17 @@ export const surveyTrail = trail('survey', {
|
|
|
267
410
|
safety: z.string(),
|
|
268
411
|
})
|
|
269
412
|
),
|
|
413
|
+
serviceCount: z.number(),
|
|
414
|
+
services: z.array(
|
|
415
|
+
z.object({
|
|
416
|
+
description: z.string().nullable(),
|
|
417
|
+
health: z.enum(['available', 'none']),
|
|
418
|
+
id: z.string(),
|
|
419
|
+
kind: z.literal('service'),
|
|
420
|
+
lifetime: z.literal('singleton'),
|
|
421
|
+
usedBy: z.array(z.string()),
|
|
422
|
+
})
|
|
423
|
+
),
|
|
270
424
|
}),
|
|
271
425
|
z.object({
|
|
272
426
|
contractVersion: z.string(),
|
|
@@ -275,11 +429,11 @@ export const surveyTrail = trail('survey', {
|
|
|
275
429
|
detours: z.boolean(),
|
|
276
430
|
events: z.boolean(),
|
|
277
431
|
examples: z.boolean(),
|
|
278
|
-
hikes: z.boolean(),
|
|
279
432
|
outputSchemas: z.boolean(),
|
|
433
|
+
services: z.boolean(),
|
|
280
434
|
}),
|
|
281
|
-
hikes: z.number(),
|
|
282
435
|
name: z.string(),
|
|
436
|
+
services: z.number(),
|
|
283
437
|
trails: z.number(),
|
|
284
438
|
version: z.string(),
|
|
285
439
|
}),
|
|
@@ -293,15 +447,49 @@ export const surveyTrail = trail('survey', {
|
|
|
293
447
|
description: z.unknown().nullable(),
|
|
294
448
|
detours: z.unknown().nullable(),
|
|
295
449
|
examples: z.array(z.unknown()),
|
|
450
|
+
follow: z.array(z.string()),
|
|
296
451
|
id: z.string(),
|
|
452
|
+
intent: z.enum(['read', 'write', 'destroy']),
|
|
297
453
|
kind: z.string(),
|
|
298
454
|
safety: z.string(),
|
|
455
|
+
services: z.array(z.string()),
|
|
456
|
+
}),
|
|
457
|
+
z.object({
|
|
458
|
+
description: z.string().nullable(),
|
|
459
|
+
health: z.enum(['available', 'none']),
|
|
460
|
+
id: z.string(),
|
|
461
|
+
kind: z.literal('service'),
|
|
462
|
+
lifetime: z.literal('singleton'),
|
|
463
|
+
usedBy: z.array(z.string()),
|
|
299
464
|
}),
|
|
300
465
|
z.object({
|
|
301
466
|
hash: z.string(),
|
|
302
467
|
lockPath: z.string(),
|
|
303
468
|
mapPath: z.string(),
|
|
304
469
|
}),
|
|
470
|
+
z.object({
|
|
471
|
+
components: z.object({
|
|
472
|
+
schemas: z.record(z.string(), z.unknown()),
|
|
473
|
+
}),
|
|
474
|
+
info: z.object({
|
|
475
|
+
description: z.string().optional(),
|
|
476
|
+
title: z.string(),
|
|
477
|
+
version: z.string(),
|
|
478
|
+
}),
|
|
479
|
+
openapi: z.literal('3.1.0'),
|
|
480
|
+
paths: z.record(z.string(), z.record(z.string(), z.unknown())),
|
|
481
|
+
servers: z
|
|
482
|
+
.array(
|
|
483
|
+
z.object({
|
|
484
|
+
description: z.string().optional(),
|
|
485
|
+
url: z.string(),
|
|
486
|
+
})
|
|
487
|
+
)
|
|
488
|
+
.optional(),
|
|
489
|
+
}),
|
|
305
490
|
]),
|
|
306
|
-
|
|
491
|
+
run: async (input, ctx) => {
|
|
492
|
+
const app = await loadApp(input.module, ctx.cwd ?? '.');
|
|
493
|
+
return dispatchSurvey(app, input);
|
|
494
|
+
},
|
|
307
495
|
});
|
package/src/trails/warden.ts
CHANGED
|
@@ -37,38 +37,6 @@ export const wardenTrail = trail('warden', {
|
|
|
37
37
|
name: 'GitHub Actions annotations',
|
|
38
38
|
},
|
|
39
39
|
],
|
|
40
|
-
implementation: async (input, ctx) => {
|
|
41
|
-
const rootDir = input.rootDir ?? ctx.cwd ?? process.cwd();
|
|
42
|
-
// oxlint-disable-next-line prefer-await-to-then -- catch converts rejection to undefined cleanly
|
|
43
|
-
const topo = await loadApp('./src/app.ts', rootDir).catch(
|
|
44
|
-
(): undefined => undefined
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
const report = await runWarden({
|
|
48
|
-
driftOnly: input.driftOnly,
|
|
49
|
-
lintOnly: input.lintOnly,
|
|
50
|
-
rootDir,
|
|
51
|
-
topo,
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
const formatters: Record<string, (r: typeof report) => string> = {
|
|
55
|
-
github: formatGitHubAnnotations,
|
|
56
|
-
json: formatJson,
|
|
57
|
-
summary: formatSummary,
|
|
58
|
-
text: formatWardenReport,
|
|
59
|
-
};
|
|
60
|
-
const formatter = formatters[input.format] ?? formatWardenReport;
|
|
61
|
-
const formatted = formatter(report);
|
|
62
|
-
|
|
63
|
-
return Result.ok({
|
|
64
|
-
diagnostics: report.diagnostics,
|
|
65
|
-
drift: report.drift,
|
|
66
|
-
errorCount: report.errorCount,
|
|
67
|
-
formatted,
|
|
68
|
-
passed: report.passed,
|
|
69
|
-
warnCount: report.warnCount,
|
|
70
|
-
});
|
|
71
|
-
},
|
|
72
40
|
input: z.object({
|
|
73
41
|
driftOnly: z.boolean().default(false).describe('Only run drift detection'),
|
|
74
42
|
format: z
|
|
@@ -78,6 +46,7 @@ export const wardenTrail = trail('warden', {
|
|
|
78
46
|
lintOnly: z.boolean().default(false).describe('Only run lint rules'),
|
|
79
47
|
rootDir: z.string().optional().describe('Root directory to scan'),
|
|
80
48
|
}),
|
|
49
|
+
intent: 'read',
|
|
81
50
|
output: z.object({
|
|
82
51
|
diagnostics: z.array(
|
|
83
52
|
z.object({
|
|
@@ -100,5 +69,36 @@ export const wardenTrail = trail('warden', {
|
|
|
100
69
|
passed: z.boolean(),
|
|
101
70
|
warnCount: z.number(),
|
|
102
71
|
}),
|
|
103
|
-
|
|
72
|
+
run: async (input, ctx) => {
|
|
73
|
+
const rootDir = input.rootDir ?? ctx.cwd ?? process.cwd();
|
|
74
|
+
// oxlint-disable-next-line prefer-await-to-then -- catch converts rejection to undefined cleanly
|
|
75
|
+
const topo = await loadApp('./src/app.ts', rootDir).catch(
|
|
76
|
+
(): undefined => undefined
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const report = await runWarden({
|
|
80
|
+
driftOnly: input.driftOnly,
|
|
81
|
+
lintOnly: input.lintOnly,
|
|
82
|
+
rootDir,
|
|
83
|
+
topo,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const formatters: Record<string, (r: typeof report) => string> = {
|
|
87
|
+
github: formatGitHubAnnotations,
|
|
88
|
+
json: formatJson,
|
|
89
|
+
summary: formatSummary,
|
|
90
|
+
text: formatWardenReport,
|
|
91
|
+
};
|
|
92
|
+
const formatter = formatters[input.format] ?? formatWardenReport;
|
|
93
|
+
const formatted = formatter(report);
|
|
94
|
+
|
|
95
|
+
return Result.ok({
|
|
96
|
+
diagnostics: report.diagnostics,
|
|
97
|
+
drift: report.drift,
|
|
98
|
+
errorCount: report.errorCount,
|
|
99
|
+
formatted,
|
|
100
|
+
passed: report.passed,
|
|
101
|
+
warnCount: report.warnCount,
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
104
|
});
|