@ontrails/trails 1.0.0-beta.11 → 1.0.0-beta.13
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 +35 -12
- package/package.json +7 -7
- package/src/__tests__/create.test.ts +35 -33
- package/src/__tests__/guide.test.ts +4 -4
- package/src/__tests__/survey.test.ts +55 -55
- package/src/__tests__/warden.test.ts +2 -2
- package/src/app.ts +2 -2
- package/src/clack.ts +1 -1
- package/src/cli.ts +2 -2
- package/src/trails/add-trail.ts +13 -13
- package/src/trails/{add-surface.ts → add-trailhead.ts} +39 -37
- package/src/trails/add-verify.ts +10 -10
- package/src/trails/create-scaffold.ts +28 -28
- package/src/trails/create.ts +42 -42
- package/src/trails/guide.ts +14 -14
- package/src/trails/survey.ts +83 -82
- package/src/trails/warden.ts +32 -32
package/src/trails/survey.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `survey` trail -- Full topo introspection.
|
|
3
3
|
*
|
|
4
|
-
* Lists trails, shows detail for individual trails, generates
|
|
4
|
+
* Lists trails, shows detail for individual trails, generates trailhead maps,
|
|
5
5
|
* and diffs against previous versions.
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -9,13 +9,13 @@ import type { Topo, Trail } from '@ontrails/core';
|
|
|
9
9
|
import { Result, trail } from '@ontrails/core';
|
|
10
10
|
import type { DiffResult } from '@ontrails/schema';
|
|
11
11
|
import {
|
|
12
|
-
|
|
12
|
+
diffTrailheadMaps,
|
|
13
13
|
generateOpenApiSpec,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
generateTrailheadMap,
|
|
15
|
+
hashTrailheadMap,
|
|
16
|
+
readTrailheadMap,
|
|
17
|
+
writeTrailheadLock,
|
|
18
|
+
writeTrailheadMap,
|
|
19
19
|
} from '@ontrails/schema';
|
|
20
20
|
import { z } from 'zod';
|
|
21
21
|
|
|
@@ -30,15 +30,15 @@ export interface BriefReport {
|
|
|
30
30
|
readonly version: string;
|
|
31
31
|
readonly contractVersion: string;
|
|
32
32
|
readonly features: {
|
|
33
|
-
readonly
|
|
33
|
+
readonly provisions: boolean;
|
|
34
34
|
readonly outputSchemas: boolean;
|
|
35
35
|
readonly examples: boolean;
|
|
36
36
|
readonly detours: boolean;
|
|
37
|
-
readonly
|
|
37
|
+
readonly signals: boolean;
|
|
38
38
|
};
|
|
39
39
|
readonly trails: number;
|
|
40
|
-
readonly
|
|
41
|
-
readonly
|
|
40
|
+
readonly signals: number;
|
|
41
|
+
readonly provisions: number;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
export interface SurveyListReport {
|
|
@@ -49,12 +49,12 @@ export interface SurveyListReport {
|
|
|
49
49
|
readonly kind: string;
|
|
50
50
|
readonly safety: string;
|
|
51
51
|
}[];
|
|
52
|
-
readonly
|
|
53
|
-
readonly
|
|
52
|
+
readonly provisionCount: number;
|
|
53
|
+
readonly provisions: readonly {
|
|
54
54
|
readonly description: string | null;
|
|
55
55
|
readonly health: 'available' | 'none';
|
|
56
56
|
readonly id: string;
|
|
57
|
-
readonly kind: '
|
|
57
|
+
readonly kind: 'provision';
|
|
58
58
|
readonly lifetime: 'singleton';
|
|
59
59
|
readonly usedBy: readonly string[];
|
|
60
60
|
}[];
|
|
@@ -64,12 +64,12 @@ export interface TrailDetailReport {
|
|
|
64
64
|
readonly description: string | null;
|
|
65
65
|
readonly detours: Trail<unknown, unknown>['detours'] | null;
|
|
66
66
|
readonly examples: readonly unknown[];
|
|
67
|
-
readonly
|
|
67
|
+
readonly crosses: readonly string[];
|
|
68
68
|
readonly id: string;
|
|
69
69
|
readonly intent: 'read' | 'write' | 'destroy';
|
|
70
70
|
readonly kind: string;
|
|
71
71
|
readonly safety: string;
|
|
72
|
-
readonly
|
|
72
|
+
readonly provisions: readonly string[];
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
/** Check if a trail has a specific feature. */
|
|
@@ -87,7 +87,7 @@ const detectFeatures = (
|
|
|
87
87
|
hasDetours: boolean;
|
|
88
88
|
hasExamples: boolean;
|
|
89
89
|
hasOutputSchemas: boolean;
|
|
90
|
-
|
|
90
|
+
hasProvisions: boolean;
|
|
91
91
|
} => {
|
|
92
92
|
const trails = [...app.trails.values()].map(
|
|
93
93
|
(item) => item as unknown as Record<string, unknown>
|
|
@@ -96,30 +96,31 @@ const detectFeatures = (
|
|
|
96
96
|
hasDetours: trails.some((r) => trailHas(r, 'detours')),
|
|
97
97
|
hasExamples: trails.some((r) => trailHas(r, 'examples')),
|
|
98
98
|
hasOutputSchemas: trails.some((r) => trailHas(r, 'output')),
|
|
99
|
-
|
|
99
|
+
hasProvisions: trails.some(
|
|
100
100
|
(r) =>
|
|
101
|
-
Array.isArray(r['
|
|
101
|
+
Array.isArray(r['provisions']) &&
|
|
102
|
+
(r['provisions'] as unknown[]).length > 0
|
|
102
103
|
),
|
|
103
104
|
};
|
|
104
105
|
};
|
|
105
106
|
|
|
106
107
|
/** Generate a compact capability report for the given topo. */
|
|
107
108
|
export const generateBriefReport = (app: Topo): BriefReport => {
|
|
108
|
-
const { hasDetours, hasExamples, hasOutputSchemas,
|
|
109
|
+
const { hasDetours, hasExamples, hasOutputSchemas, hasProvisions } =
|
|
109
110
|
detectFeatures(app);
|
|
110
111
|
|
|
111
112
|
return {
|
|
112
113
|
contractVersion: '2026-03',
|
|
113
|
-
events: app.events.size,
|
|
114
114
|
features: {
|
|
115
115
|
detours: hasDetours,
|
|
116
|
-
events: app.events.size > 0,
|
|
117
116
|
examples: hasExamples,
|
|
118
117
|
outputSchemas: hasOutputSchemas,
|
|
119
|
-
|
|
118
|
+
provisions: hasProvisions,
|
|
119
|
+
signals: app.signals.size > 0,
|
|
120
120
|
},
|
|
121
121
|
name: app.name,
|
|
122
|
-
|
|
122
|
+
provisions: app.provisions.size,
|
|
123
|
+
signals: app.signals.size,
|
|
123
124
|
trails: app.trails.size,
|
|
124
125
|
version: '0.1.0',
|
|
125
126
|
};
|
|
@@ -141,16 +142,16 @@ const safetyLabel = (entry: {
|
|
|
141
142
|
return '-';
|
|
142
143
|
};
|
|
143
144
|
|
|
144
|
-
const
|
|
145
|
+
const buildProvisionUsage = (
|
|
145
146
|
app: Topo
|
|
146
147
|
): ReadonlyMap<string, readonly string[]> => {
|
|
147
148
|
const usage = new Map<string, string[]>();
|
|
148
149
|
|
|
149
150
|
for (const trailDef of app.list()) {
|
|
150
|
-
for (const
|
|
151
|
-
const users = usage.get(
|
|
151
|
+
for (const declaredProvision of trailDef.provisions) {
|
|
152
|
+
const users = usage.get(declaredProvision.id) ?? [];
|
|
152
153
|
users.push(trailDef.id);
|
|
153
|
-
usage.set(
|
|
154
|
+
usage.set(declaredProvision.id, users);
|
|
154
155
|
}
|
|
155
156
|
}
|
|
156
157
|
|
|
@@ -159,22 +160,22 @@ const buildServiceUsage = (
|
|
|
159
160
|
);
|
|
160
161
|
};
|
|
161
162
|
|
|
162
|
-
const
|
|
163
|
+
const provisionHealthStatus = (provision: {
|
|
163
164
|
health?: unknown;
|
|
164
165
|
}): 'available' | 'none' =>
|
|
165
|
-
|
|
166
|
+
provision.health === undefined ? 'none' : 'available';
|
|
166
167
|
|
|
167
|
-
const
|
|
168
|
-
const usage =
|
|
168
|
+
const formatProvisionList = (app: Topo): SurveyListReport['provisions'] => {
|
|
169
|
+
const usage = buildProvisionUsage(app);
|
|
169
170
|
return app
|
|
170
|
-
.
|
|
171
|
-
.map((
|
|
172
|
-
description:
|
|
173
|
-
health:
|
|
174
|
-
id:
|
|
175
|
-
kind:
|
|
171
|
+
.listProvisions()
|
|
172
|
+
.map((provision) => ({
|
|
173
|
+
description: provision.description ?? null,
|
|
174
|
+
health: provisionHealthStatus(provision),
|
|
175
|
+
id: provision.id,
|
|
176
|
+
kind: provision.kind,
|
|
176
177
|
lifetime: 'singleton' as const,
|
|
177
|
-
usedBy: usage.get(
|
|
178
|
+
usedBy: usage.get(provision.id) ?? [],
|
|
178
179
|
}))
|
|
179
180
|
.toSorted((a, b) => a.id.localeCompare(b.id));
|
|
180
181
|
};
|
|
@@ -199,13 +200,13 @@ export const generateSurveyList = (app: Topo): SurveyListReport => {
|
|
|
199
200
|
};
|
|
200
201
|
});
|
|
201
202
|
|
|
202
|
-
const
|
|
203
|
+
const provisions = formatProvisionList(app);
|
|
203
204
|
|
|
204
205
|
return {
|
|
205
206
|
count: items.length,
|
|
206
207
|
entries,
|
|
207
|
-
|
|
208
|
-
|
|
208
|
+
provisionCount: provisions.length,
|
|
209
|
+
provisions,
|
|
209
210
|
};
|
|
210
211
|
};
|
|
211
212
|
|
|
@@ -213,8 +214,8 @@ export const generateSurveyList = (app: Topo): SurveyListReport => {
|
|
|
213
214
|
* Build a human-readable detail view for a single trail.
|
|
214
215
|
*
|
|
215
216
|
* Overlaps with `trailToEntry` in `@ontrails/schema` which builds the
|
|
216
|
-
*
|
|
217
|
-
* machine-diffable
|
|
217
|
+
* trailhead-map entry. The two serve different audiences (human display vs
|
|
218
|
+
* machine-diffable trailhead map) so they are kept separate.
|
|
218
219
|
*/
|
|
219
220
|
export const generateTrailDetail = (
|
|
220
221
|
item: Trail<unknown, unknown>
|
|
@@ -224,27 +225,27 @@ export const generateTrailDetail = (
|
|
|
224
225
|
);
|
|
225
226
|
|
|
226
227
|
return {
|
|
228
|
+
crosses: item.crosses.toSorted(),
|
|
227
229
|
description: item.description ?? null,
|
|
228
230
|
detours: item.detours ?? null,
|
|
229
231
|
examples: item.examples ?? [],
|
|
230
|
-
follow: item.follow.toSorted(),
|
|
231
232
|
id: item.id,
|
|
232
233
|
intent: item.intent,
|
|
233
234
|
kind: item.kind,
|
|
235
|
+
provisions: item.provisions.map((provision) => provision.id).toSorted(),
|
|
234
236
|
safety,
|
|
235
|
-
services: item.services.map((service) => service.id).toSorted(),
|
|
236
237
|
};
|
|
237
238
|
};
|
|
238
239
|
|
|
239
|
-
const
|
|
240
|
-
const item = app.
|
|
241
|
-
const usedBy =
|
|
240
|
+
const formatProvisionDetail = (app: Topo, provisionId: string): object => {
|
|
241
|
+
const item = app.getProvision(provisionId);
|
|
242
|
+
const usedBy = buildProvisionUsage(app).get(provisionId) ?? [];
|
|
242
243
|
|
|
243
244
|
return {
|
|
244
245
|
description: item?.description ?? null,
|
|
245
|
-
health: item ?
|
|
246
|
-
id:
|
|
247
|
-
kind: '
|
|
246
|
+
health: item ? provisionHealthStatus(item) : 'none',
|
|
247
|
+
id: provisionId,
|
|
248
|
+
kind: 'provision',
|
|
248
249
|
lifetime: 'singleton',
|
|
249
250
|
usedBy,
|
|
250
251
|
};
|
|
@@ -261,17 +262,17 @@ const buildSurveyDiff = async (
|
|
|
261
262
|
app: Topo,
|
|
262
263
|
breakingOnly: boolean
|
|
263
264
|
): Promise<Result<object, Error>> => {
|
|
264
|
-
const currentMap =
|
|
265
|
-
const previousMap = await
|
|
265
|
+
const currentMap = generateTrailheadMap(app);
|
|
266
|
+
const previousMap = await readTrailheadMap();
|
|
266
267
|
if (!previousMap) {
|
|
267
268
|
return Result.err(
|
|
268
269
|
new Error(
|
|
269
|
-
'No previous
|
|
270
|
+
'No previous trailhead map found. Run `trails survey generate` first.'
|
|
270
271
|
)
|
|
271
272
|
);
|
|
272
273
|
}
|
|
273
274
|
|
|
274
|
-
const diff =
|
|
275
|
+
const diff = diffTrailheadMaps(previousMap, currentMap);
|
|
275
276
|
return Result.ok(
|
|
276
277
|
breakingOnly
|
|
277
278
|
? formatDiff({
|
|
@@ -292,19 +293,19 @@ const buildSurveyDetail = (
|
|
|
292
293
|
if (item) {
|
|
293
294
|
return Result.ok(generateTrailDetail(item as Trail<unknown, unknown>));
|
|
294
295
|
}
|
|
295
|
-
if (app.
|
|
296
|
-
return Result.ok(
|
|
296
|
+
if (app.getProvision(trailId)) {
|
|
297
|
+
return Result.ok(formatProvisionDetail(app, trailId));
|
|
297
298
|
}
|
|
298
|
-
return Result.err(new Error(`Trail or
|
|
299
|
+
return Result.err(new Error(`Trail or provision not found: ${trailId}`));
|
|
299
300
|
};
|
|
300
301
|
|
|
301
302
|
const buildSurveyGenerate = async (
|
|
302
303
|
app: Topo
|
|
303
304
|
): Promise<Result<object, Error>> => {
|
|
304
|
-
const
|
|
305
|
-
const mapPath = await
|
|
306
|
-
const hash =
|
|
307
|
-
const lockPath = await
|
|
305
|
+
const trailheadMap = generateTrailheadMap(app);
|
|
306
|
+
const mapPath = await writeTrailheadMap(trailheadMap);
|
|
307
|
+
const hash = hashTrailheadMap(trailheadMap);
|
|
308
|
+
const lockPath = await writeTrailheadLock(hash);
|
|
308
309
|
return Result.ok({ hash, lockPath, mapPath });
|
|
309
310
|
};
|
|
310
311
|
|
|
@@ -362,21 +363,25 @@ const dispatchSurvey = (
|
|
|
362
363
|
// ---------------------------------------------------------------------------
|
|
363
364
|
|
|
364
365
|
export const surveyTrail = trail('survey', {
|
|
366
|
+
blaze: async (input, ctx) => {
|
|
367
|
+
const app = await loadApp(input.module, ctx.cwd ?? '.');
|
|
368
|
+
return dispatchSurvey(app, input);
|
|
369
|
+
},
|
|
365
370
|
description: 'Full topo introspection',
|
|
366
371
|
examples: [
|
|
367
372
|
{
|
|
368
|
-
description: 'Lists all registered trails with safety and
|
|
369
|
-
input: {},
|
|
373
|
+
description: 'Lists all registered trails with safety and trailhead info',
|
|
374
|
+
input: { module: './src/app.ts' },
|
|
370
375
|
name: 'List all trails',
|
|
371
376
|
},
|
|
372
377
|
{
|
|
373
378
|
description: 'Quick capability summary with counts and feature flags',
|
|
374
|
-
input: { brief: true },
|
|
379
|
+
input: { brief: true, module: './src/app.ts' },
|
|
375
380
|
name: 'Brief capability report',
|
|
376
381
|
},
|
|
377
382
|
{
|
|
378
383
|
description: 'Generate an OpenAPI 3.1 specification for the topo',
|
|
379
|
-
input: { openapi: true },
|
|
384
|
+
input: { module: './src/app.ts', openapi: true },
|
|
380
385
|
name: 'OpenAPI spec',
|
|
381
386
|
},
|
|
382
387
|
],
|
|
@@ -390,7 +395,7 @@ export const surveyTrail = trail('survey', {
|
|
|
390
395
|
generate: z
|
|
391
396
|
.boolean()
|
|
392
397
|
.default(false)
|
|
393
|
-
.describe('Generate
|
|
398
|
+
.describe('Generate trailhead map and lock file'),
|
|
394
399
|
module: z
|
|
395
400
|
.string()
|
|
396
401
|
.default('./src/app.ts')
|
|
@@ -410,13 +415,13 @@ export const surveyTrail = trail('survey', {
|
|
|
410
415
|
safety: z.string(),
|
|
411
416
|
})
|
|
412
417
|
),
|
|
413
|
-
|
|
414
|
-
|
|
418
|
+
provisionCount: z.number(),
|
|
419
|
+
provisions: z.array(
|
|
415
420
|
z.object({
|
|
416
421
|
description: z.string().nullable(),
|
|
417
422
|
health: z.enum(['available', 'none']),
|
|
418
423
|
id: z.string(),
|
|
419
|
-
kind: z.literal('
|
|
424
|
+
kind: z.literal('provision'),
|
|
420
425
|
lifetime: z.literal('singleton'),
|
|
421
426
|
usedBy: z.array(z.string()),
|
|
422
427
|
})
|
|
@@ -424,16 +429,16 @@ export const surveyTrail = trail('survey', {
|
|
|
424
429
|
}),
|
|
425
430
|
z.object({
|
|
426
431
|
contractVersion: z.string(),
|
|
427
|
-
events: z.number(),
|
|
428
432
|
features: z.object({
|
|
429
433
|
detours: z.boolean(),
|
|
430
|
-
events: z.boolean(),
|
|
431
434
|
examples: z.boolean(),
|
|
432
435
|
outputSchemas: z.boolean(),
|
|
433
|
-
|
|
436
|
+
provisions: z.boolean(),
|
|
437
|
+
signals: z.boolean(),
|
|
434
438
|
}),
|
|
435
439
|
name: z.string(),
|
|
436
|
-
|
|
440
|
+
provisions: z.number(),
|
|
441
|
+
signals: z.number(),
|
|
437
442
|
trails: z.number(),
|
|
438
443
|
version: z.string(),
|
|
439
444
|
}),
|
|
@@ -444,21 +449,21 @@ export const surveyTrail = trail('survey', {
|
|
|
444
449
|
warnings: z.array(z.unknown()),
|
|
445
450
|
}),
|
|
446
451
|
z.object({
|
|
452
|
+
crosses: z.array(z.string()),
|
|
447
453
|
description: z.unknown().nullable(),
|
|
448
454
|
detours: z.unknown().nullable(),
|
|
449
455
|
examples: z.array(z.unknown()),
|
|
450
|
-
follow: z.array(z.string()),
|
|
451
456
|
id: z.string(),
|
|
452
457
|
intent: z.enum(['read', 'write', 'destroy']),
|
|
453
458
|
kind: z.string(),
|
|
459
|
+
provisions: z.array(z.string()),
|
|
454
460
|
safety: z.string(),
|
|
455
|
-
services: z.array(z.string()),
|
|
456
461
|
}),
|
|
457
462
|
z.object({
|
|
458
463
|
description: z.string().nullable(),
|
|
459
464
|
health: z.enum(['available', 'none']),
|
|
460
465
|
id: z.string(),
|
|
461
|
-
kind: z.literal('
|
|
466
|
+
kind: z.literal('provision'),
|
|
462
467
|
lifetime: z.literal('singleton'),
|
|
463
468
|
usedBy: z.array(z.string()),
|
|
464
469
|
}),
|
|
@@ -488,8 +493,4 @@ export const surveyTrail = trail('survey', {
|
|
|
488
493
|
.optional(),
|
|
489
494
|
}),
|
|
490
495
|
]),
|
|
491
|
-
run: async (input, ctx) => {
|
|
492
|
-
const app = await loadApp(input.module, ctx.cwd ?? '.');
|
|
493
|
-
return dispatchSurvey(app, input);
|
|
494
|
-
},
|
|
495
496
|
});
|
package/src/trails/warden.ts
CHANGED
|
@@ -21,6 +21,38 @@ import { loadApp } from './load-app.js';
|
|
|
21
21
|
// ---------------------------------------------------------------------------
|
|
22
22
|
|
|
23
23
|
export const wardenTrail = trail('warden', {
|
|
24
|
+
blaze: async (input, ctx) => {
|
|
25
|
+
const rootDir = input.rootDir ?? ctx.cwd ?? process.cwd();
|
|
26
|
+
// oxlint-disable-next-line prefer-await-to-then -- catch converts rejection to undefined cleanly
|
|
27
|
+
const topo = await loadApp('./src/app.ts', rootDir).catch(
|
|
28
|
+
(): undefined => undefined
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const report = await runWarden({
|
|
32
|
+
driftOnly: input.driftOnly,
|
|
33
|
+
lintOnly: input.lintOnly,
|
|
34
|
+
rootDir,
|
|
35
|
+
topo,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const formatters: Record<string, (r: typeof report) => string> = {
|
|
39
|
+
github: formatGitHubAnnotations,
|
|
40
|
+
json: formatJson,
|
|
41
|
+
summary: formatSummary,
|
|
42
|
+
text: formatWardenReport,
|
|
43
|
+
};
|
|
44
|
+
const formatter = formatters[input.format] ?? formatWardenReport;
|
|
45
|
+
const formatted = formatter(report);
|
|
46
|
+
|
|
47
|
+
return Result.ok({
|
|
48
|
+
diagnostics: report.diagnostics,
|
|
49
|
+
drift: report.drift,
|
|
50
|
+
errorCount: report.errorCount,
|
|
51
|
+
formatted,
|
|
52
|
+
passed: report.passed,
|
|
53
|
+
warnCount: report.warnCount,
|
|
54
|
+
});
|
|
55
|
+
},
|
|
24
56
|
description: 'Run governance checks (lint + drift)',
|
|
25
57
|
examples: [
|
|
26
58
|
{
|
|
@@ -69,36 +101,4 @@ export const wardenTrail = trail('warden', {
|
|
|
69
101
|
passed: z.boolean(),
|
|
70
102
|
warnCount: z.number(),
|
|
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
|
});
|