@ontrails/trails 1.0.0-beta.12 → 1.0.0-beta.14
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 +40 -12
- package/__tests__/examples.test.ts +14 -0
- package/dist/src/app.d.ts.map +1 -1
- package/dist/src/app.js +13 -2
- package/dist/src/app.js.map +1 -1
- package/dist/src/clack.d.ts +1 -1
- package/dist/src/clack.js +1 -1
- package/dist/src/cli.js +2 -2
- package/dist/src/cli.js.map +1 -1
- package/dist/src/trails/add-trail.js +13 -13
- package/dist/src/trails/add-trail.js.map +1 -1
- package/dist/src/trails/add-trailhead.d.ts +13 -0
- package/dist/src/trails/add-trailhead.d.ts.map +1 -0
- package/dist/src/trails/add-trailhead.js +88 -0
- package/dist/src/trails/add-trailhead.js.map +1 -0
- package/dist/src/trails/add-verify.js +10 -10
- package/dist/src/trails/add-verify.js.map +1 -1
- package/dist/src/trails/create-scaffold.js +26 -26
- package/dist/src/trails/create-scaffold.js.map +1 -1
- package/dist/src/trails/create.d.ts +6 -6
- package/dist/src/trails/create.d.ts.map +1 -1
- package/dist/src/trails/create.js +29 -29
- package/dist/src/trails/create.js.map +1 -1
- package/dist/src/trails/dev-clean.d.ts +9 -0
- package/dist/src/trails/dev-clean.d.ts.map +1 -0
- package/dist/src/trails/dev-clean.js +65 -0
- package/dist/src/trails/dev-clean.js.map +1 -0
- package/dist/src/trails/dev-reset.d.ts +6 -0
- package/dist/src/trails/dev-reset.d.ts.map +1 -0
- package/dist/src/trails/dev-reset.js +38 -0
- package/dist/src/trails/dev-reset.js.map +1 -0
- package/dist/src/trails/dev-stats.d.ts +7 -0
- package/dist/src/trails/dev-stats.d.ts.map +1 -0
- package/dist/src/trails/dev-stats.js +61 -0
- package/dist/src/trails/dev-stats.js.map +1 -0
- package/dist/src/trails/dev-support.d.ts +64 -0
- package/dist/src/trails/dev-support.d.ts.map +1 -0
- package/dist/src/trails/dev-support.js +178 -0
- package/dist/src/trails/dev-support.js.map +1 -0
- package/dist/src/trails/draft-promote.d.ts +18 -0
- package/dist/src/trails/draft-promote.d.ts.map +1 -0
- package/dist/src/trails/draft-promote.js +386 -0
- package/dist/src/trails/draft-promote.js.map +1 -0
- package/dist/src/trails/guide.d.ts +13 -3
- package/dist/src/trails/guide.d.ts.map +1 -1
- package/dist/src/trails/guide.js +21 -37
- package/dist/src/trails/guide.js.map +1 -1
- package/dist/src/trails/load-app.d.ts +3 -1
- package/dist/src/trails/load-app.d.ts.map +1 -1
- package/dist/src/trails/load-app.js +53 -10
- package/dist/src/trails/load-app.js.map +1 -1
- package/dist/src/trails/project.d.ts.map +1 -1
- package/dist/src/trails/project.js +14 -3
- package/dist/src/trails/project.js.map +1 -1
- package/dist/src/trails/survey.d.ts +4 -58
- package/dist/src/trails/survey.d.ts.map +1 -1
- package/dist/src/trails/survey.js +52 -173
- package/dist/src/trails/survey.js.map +1 -1
- package/dist/src/trails/topo-constants.d.ts +3 -0
- package/dist/src/trails/topo-constants.d.ts.map +1 -0
- package/dist/src/trails/topo-constants.js +3 -0
- package/dist/src/trails/topo-constants.js.map +1 -0
- package/dist/src/trails/topo-export.d.ts +18 -0
- package/dist/src/trails/topo-export.d.ts.map +1 -0
- package/dist/src/trails/topo-export.js +34 -0
- package/dist/src/trails/topo-export.js.map +1 -0
- package/dist/src/trails/topo-history.d.ts +24 -0
- package/dist/src/trails/topo-history.d.ts.map +1 -0
- package/dist/src/trails/topo-history.js +33 -0
- package/dist/src/trails/topo-history.js.map +1 -0
- package/dist/src/trails/topo-pin.d.ts +21 -0
- package/dist/src/trails/topo-pin.d.ts.map +1 -0
- package/dist/src/trails/topo-pin.js +35 -0
- package/dist/src/trails/topo-pin.js.map +1 -0
- package/dist/src/trails/topo-read-support.d.ts +54 -0
- package/dist/src/trails/topo-read-support.d.ts.map +1 -0
- package/dist/src/trails/topo-read-support.js +178 -0
- package/dist/src/trails/topo-read-support.js.map +1 -0
- package/dist/src/trails/topo-reports.d.ts +50 -0
- package/dist/src/trails/topo-reports.d.ts.map +1 -0
- package/dist/src/trails/topo-reports.js +122 -0
- package/dist/src/trails/topo-reports.js.map +1 -0
- package/dist/src/trails/topo-show.d.ts +23 -0
- package/dist/src/trails/topo-show.d.ts.map +1 -0
- package/dist/src/trails/topo-show.js +53 -0
- package/dist/src/trails/topo-show.js.map +1 -0
- package/dist/src/trails/topo-store-support.d.ts +13 -0
- package/dist/src/trails/topo-store-support.d.ts.map +1 -0
- package/dist/src/trails/topo-store-support.js +55 -0
- package/dist/src/trails/topo-store-support.js.map +1 -0
- package/dist/src/trails/topo-support.d.ts +87 -0
- package/dist/src/trails/topo-support.d.ts.map +1 -0
- package/dist/src/trails/topo-support.js +165 -0
- package/dist/src/trails/topo-support.js.map +1 -0
- package/dist/src/trails/topo-unpin.d.ts +15 -0
- package/dist/src/trails/topo-unpin.d.ts.map +1 -0
- package/dist/src/trails/topo-unpin.js +39 -0
- package/dist/src/trails/topo-unpin.js.map +1 -0
- package/dist/src/trails/topo-verify.d.ts +5 -0
- package/dist/src/trails/topo-verify.d.ts.map +1 -0
- package/dist/src/trails/topo-verify.js +28 -0
- package/dist/src/trails/topo-verify.js.map +1 -0
- package/dist/src/trails/topo.d.ts +5 -0
- package/dist/src/trails/topo.d.ts.map +1 -0
- package/dist/src/trails/topo.js +67 -0
- package/dist/src/trails/topo.js.map +1 -0
- package/dist/src/trails/warden.d.ts +1 -1
- package/dist/src/trails/warden.d.ts.map +1 -1
- package/dist/src/trails/warden.js +28 -27
- package/dist/src/trails/warden.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -7
- package/src/__tests__/create.test.ts +35 -33
- package/src/__tests__/draft-promote.test.ts +144 -0
- package/src/__tests__/guide.test.ts +4 -4
- package/src/__tests__/load-app.test.ts +43 -0
- package/src/__tests__/survey.test.ts +140 -55
- package/src/__tests__/topo-dev.test.ts +424 -0
- package/src/__tests__/warden.test.ts +2 -2
- package/src/app.ts +24 -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/dev-clean.ts +73 -0
- package/src/trails/dev-reset.ts +44 -0
- package/src/trails/dev-stats.ts +64 -0
- package/src/trails/dev-support.ts +326 -0
- package/src/trails/draft-promote.ts +704 -0
- package/src/trails/guide.ts +29 -44
- package/src/trails/load-app.ts +76 -13
- package/src/trails/project.ts +17 -3
- package/src/trails/survey.ts +80 -279
- package/src/trails/topo-constants.ts +2 -0
- package/src/trails/topo-export.ts +39 -0
- package/src/trails/topo-history.ts +40 -0
- package/src/trails/topo-pin.ts +42 -0
- package/src/trails/topo-read-support.ts +332 -0
- package/src/trails/topo-reports.ts +221 -0
- package/src/trails/topo-show.ts +58 -0
- package/src/trails/topo-store-support.ts +96 -0
- package/src/trails/topo-support.ts +274 -0
- package/src/trails/topo-unpin.ts +51 -0
- package/src/trails/topo-verify.ts +29 -0
- package/src/trails/topo.ts +73 -0
- package/src/trails/warden.ts +33 -32
package/src/trails/survey.ts
CHANGED
|
@@ -1,255 +1,45 @@
|
|
|
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
|
|
|
8
|
-
import type { Topo
|
|
9
|
-
import { Result, trail } from '@ontrails/core';
|
|
8
|
+
import type { Topo } from '@ontrails/core';
|
|
9
|
+
import { NotFoundError, 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
|
-
readSurfaceMap,
|
|
17
|
-
writeSurfaceLock,
|
|
18
|
-
writeSurfaceMap,
|
|
14
|
+
generateTrailheadMap,
|
|
15
|
+
readTrailheadMap,
|
|
19
16
|
} from '@ontrails/schema';
|
|
20
17
|
import { z } from 'zod';
|
|
21
18
|
|
|
22
19
|
import { loadApp } from './load-app.js';
|
|
20
|
+
import {
|
|
21
|
+
buildCurrentTopoBrief,
|
|
22
|
+
buildCurrentTopoDetail,
|
|
23
|
+
buildCurrentTopoList,
|
|
24
|
+
} from './topo-read-support.js';
|
|
25
|
+
import { exportCurrentTopo } from './topo-store-support.js';
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
formatProvisionDetail,
|
|
29
|
+
generateBriefReport,
|
|
30
|
+
generateSurveyList,
|
|
31
|
+
generateTrailDetail,
|
|
32
|
+
} from './topo-reports.js';
|
|
33
|
+
export type {
|
|
34
|
+
BriefReport,
|
|
35
|
+
SurveyListReport,
|
|
36
|
+
TrailDetailReport,
|
|
37
|
+
} from './topo-reports.js';
|
|
23
38
|
|
|
24
39
|
// ---------------------------------------------------------------------------
|
|
25
40
|
// Brief report (formerly scout)
|
|
26
41
|
// ---------------------------------------------------------------------------
|
|
27
42
|
|
|
28
|
-
export interface BriefReport {
|
|
29
|
-
readonly name: string;
|
|
30
|
-
readonly version: string;
|
|
31
|
-
readonly contractVersion: string;
|
|
32
|
-
readonly features: {
|
|
33
|
-
readonly services: boolean;
|
|
34
|
-
readonly outputSchemas: boolean;
|
|
35
|
-
readonly examples: boolean;
|
|
36
|
-
readonly detours: boolean;
|
|
37
|
-
readonly events: boolean;
|
|
38
|
-
};
|
|
39
|
-
readonly trails: number;
|
|
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[];
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** Check if a trail has a specific feature. */
|
|
76
|
-
const trailHas = (raw: Record<string, unknown>, key: string): boolean => {
|
|
77
|
-
if (key === 'examples') {
|
|
78
|
-
return Array.isArray(raw[key]) && (raw[key] as unknown[]).length > 0;
|
|
79
|
-
}
|
|
80
|
-
return Boolean(raw[key]);
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
/** Detect which features are used across trails. */
|
|
84
|
-
const detectFeatures = (
|
|
85
|
-
app: Topo
|
|
86
|
-
): {
|
|
87
|
-
hasDetours: boolean;
|
|
88
|
-
hasExamples: boolean;
|
|
89
|
-
hasOutputSchemas: boolean;
|
|
90
|
-
hasServices: boolean;
|
|
91
|
-
} => {
|
|
92
|
-
const trails = [...app.trails.values()].map(
|
|
93
|
-
(item) => item as unknown as Record<string, unknown>
|
|
94
|
-
);
|
|
95
|
-
return {
|
|
96
|
-
hasDetours: trails.some((r) => trailHas(r, 'detours')),
|
|
97
|
-
hasExamples: trails.some((r) => trailHas(r, 'examples')),
|
|
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
|
-
),
|
|
103
|
-
};
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
/** Generate a compact capability report for the given topo. */
|
|
107
|
-
export const generateBriefReport = (app: Topo): BriefReport => {
|
|
108
|
-
const { hasDetours, hasExamples, hasOutputSchemas, hasServices } =
|
|
109
|
-
detectFeatures(app);
|
|
110
|
-
|
|
111
|
-
return {
|
|
112
|
-
contractVersion: '2026-03',
|
|
113
|
-
events: app.events.size,
|
|
114
|
-
features: {
|
|
115
|
-
detours: hasDetours,
|
|
116
|
-
events: app.events.size > 0,
|
|
117
|
-
examples: hasExamples,
|
|
118
|
-
outputSchemas: hasOutputSchemas,
|
|
119
|
-
services: hasServices,
|
|
120
|
-
},
|
|
121
|
-
name: app.name,
|
|
122
|
-
services: app.services.size,
|
|
123
|
-
trails: app.trails.size,
|
|
124
|
-
version: '0.1.0',
|
|
125
|
-
};
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
// ---------------------------------------------------------------------------
|
|
129
|
-
// Formatting helpers
|
|
130
|
-
// ---------------------------------------------------------------------------
|
|
131
|
-
|
|
132
|
-
const safetyLabel = (entry: {
|
|
133
|
-
intent?: 'read' | 'write' | 'destroy';
|
|
134
|
-
}): string => {
|
|
135
|
-
if (entry.intent === 'destroy') {
|
|
136
|
-
return 'destroy';
|
|
137
|
-
}
|
|
138
|
-
if (entry.intent === 'read') {
|
|
139
|
-
return 'read';
|
|
140
|
-
}
|
|
141
|
-
return '-';
|
|
142
|
-
};
|
|
143
|
-
|
|
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 => {
|
|
183
|
-
const items = app.list();
|
|
184
|
-
const entries = items.map((item) => {
|
|
185
|
-
const safety = safetyLabel(
|
|
186
|
-
item as unknown as { intent?: 'read' | 'write' | 'destroy' }
|
|
187
|
-
);
|
|
188
|
-
const examples = Array.isArray(
|
|
189
|
-
(item as unknown as { examples?: unknown[] }).examples
|
|
190
|
-
)
|
|
191
|
-
? (item as unknown as { examples: unknown[] }).examples.length
|
|
192
|
-
: 0;
|
|
193
|
-
|
|
194
|
-
return {
|
|
195
|
-
examples,
|
|
196
|
-
id: item.id,
|
|
197
|
-
kind: item.kind,
|
|
198
|
-
safety,
|
|
199
|
-
};
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
const services = formatServiceList(app);
|
|
203
|
-
|
|
204
|
-
return {
|
|
205
|
-
count: items.length,
|
|
206
|
-
entries,
|
|
207
|
-
serviceCount: services.length,
|
|
208
|
-
services,
|
|
209
|
-
};
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Build a human-readable detail view for a single trail.
|
|
214
|
-
*
|
|
215
|
-
* Overlaps with `trailToEntry` in `@ontrails/schema` which builds the
|
|
216
|
-
* surface-map entry. The two serve different audiences (human display vs
|
|
217
|
-
* machine-diffable surface map) so they are kept separate.
|
|
218
|
-
*/
|
|
219
|
-
export const generateTrailDetail = (
|
|
220
|
-
item: Trail<unknown, unknown>
|
|
221
|
-
): TrailDetailReport => {
|
|
222
|
-
const safety = safetyLabel(
|
|
223
|
-
item as unknown as { intent?: 'read' | 'write' | 'destroy' }
|
|
224
|
-
);
|
|
225
|
-
|
|
226
|
-
return {
|
|
227
|
-
description: item.description ?? null,
|
|
228
|
-
detours: item.detours ?? null,
|
|
229
|
-
examples: item.examples ?? [],
|
|
230
|
-
follow: item.follow.toSorted(),
|
|
231
|
-
id: item.id,
|
|
232
|
-
intent: item.intent,
|
|
233
|
-
kind: item.kind,
|
|
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,
|
|
250
|
-
};
|
|
251
|
-
};
|
|
252
|
-
|
|
253
43
|
const formatDiff = (diff: DiffResult): object => ({
|
|
254
44
|
breaking: diff.breaking,
|
|
255
45
|
hasBreaking: diff.hasBreaking,
|
|
@@ -261,17 +51,17 @@ const buildSurveyDiff = async (
|
|
|
261
51
|
app: Topo,
|
|
262
52
|
breakingOnly: boolean
|
|
263
53
|
): Promise<Result<object, Error>> => {
|
|
264
|
-
const currentMap =
|
|
265
|
-
const previousMap = await
|
|
54
|
+
const currentMap = generateTrailheadMap(app);
|
|
55
|
+
const previousMap = await readTrailheadMap();
|
|
266
56
|
if (!previousMap) {
|
|
267
57
|
return Result.err(
|
|
268
|
-
new
|
|
269
|
-
'No previous
|
|
58
|
+
new NotFoundError(
|
|
59
|
+
'No previous trailhead map found. Run `trails topo export` first.'
|
|
270
60
|
)
|
|
271
61
|
);
|
|
272
62
|
}
|
|
273
63
|
|
|
274
|
-
const diff =
|
|
64
|
+
const diff = diffTrailheadMaps(previousMap, currentMap);
|
|
275
65
|
return Result.ok(
|
|
276
66
|
breakingOnly
|
|
277
67
|
? formatDiff({
|
|
@@ -286,26 +76,31 @@ const buildSurveyDiff = async (
|
|
|
286
76
|
|
|
287
77
|
const buildSurveyDetail = (
|
|
288
78
|
app: Topo,
|
|
289
|
-
trailId: string
|
|
79
|
+
trailId: string,
|
|
80
|
+
rootDir: string
|
|
290
81
|
): Result<object, Error> => {
|
|
291
|
-
const
|
|
292
|
-
if (
|
|
293
|
-
return Result.ok(
|
|
82
|
+
const detail = buildCurrentTopoDetail(app, trailId, { rootDir });
|
|
83
|
+
if (detail !== undefined) {
|
|
84
|
+
return Result.ok(detail);
|
|
294
85
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
return Result.err(new Error(`Trail or service not found: ${trailId}`));
|
|
86
|
+
return Result.err(
|
|
87
|
+
new NotFoundError(`Trail or provision not found: ${trailId}`)
|
|
88
|
+
);
|
|
299
89
|
};
|
|
300
90
|
|
|
301
91
|
const buildSurveyGenerate = async (
|
|
302
|
-
app: Topo
|
|
92
|
+
app: Topo,
|
|
93
|
+
rootDir: string
|
|
303
94
|
): Promise<Result<object, Error>> => {
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
return Result.ok({
|
|
95
|
+
const exported = await exportCurrentTopo(app, { rootDir });
|
|
96
|
+
if (exported.isErr()) {
|
|
97
|
+
return exported;
|
|
98
|
+
}
|
|
99
|
+
return Result.ok({
|
|
100
|
+
hash: exported.value.hash,
|
|
101
|
+
lockPath: exported.value.lockPath,
|
|
102
|
+
mapPath: exported.value.mapPath,
|
|
103
|
+
});
|
|
309
104
|
};
|
|
310
105
|
|
|
311
106
|
interface SurveyInput {
|
|
@@ -334,27 +129,32 @@ const resolveSurveyMode = (input: SurveyInput): SurveyMode =>
|
|
|
334
129
|
|
|
335
130
|
type SurveyHandler = (
|
|
336
131
|
app: Topo,
|
|
337
|
-
input: SurveyInput
|
|
132
|
+
input: SurveyInput,
|
|
133
|
+
rootDir: string
|
|
338
134
|
) => Result<object, Error> | Promise<Result<object, Error>>;
|
|
339
135
|
|
|
340
136
|
/** Handlers keyed by survey mode. */
|
|
341
137
|
const surveyHandlers: Record<SurveyMode, SurveyHandler> = {
|
|
342
|
-
brief: (app) =>
|
|
343
|
-
|
|
138
|
+
brief: (app, _input, rootDir) =>
|
|
139
|
+
Result.ok(buildCurrentTopoBrief(app, { rootDir })),
|
|
140
|
+
detail: (app, input, rootDir) =>
|
|
141
|
+
buildSurveyDetail(app, input.trailId ?? '', rootDir),
|
|
344
142
|
diff: (app, input) => buildSurveyDiff(app, input.breakingOnly),
|
|
345
|
-
generate: (app) => buildSurveyGenerate(app),
|
|
346
|
-
list: (app) =>
|
|
143
|
+
generate: (app, _input, rootDir) => buildSurveyGenerate(app, rootDir),
|
|
144
|
+
list: (app, _input, rootDir) =>
|
|
145
|
+
Result.ok(buildCurrentTopoList(app, { rootDir })),
|
|
347
146
|
openapi: (app) => Result.ok(generateOpenApiSpec(app)),
|
|
348
147
|
};
|
|
349
148
|
|
|
350
149
|
/** Dispatch to the appropriate survey sub-command based on input flags. */
|
|
351
150
|
const dispatchSurvey = (
|
|
352
151
|
app: Topo,
|
|
353
|
-
input: SurveyInput
|
|
152
|
+
input: SurveyInput,
|
|
153
|
+
rootDir: string
|
|
354
154
|
): Result<object, Error> | Promise<Result<object, Error>> => {
|
|
355
155
|
const mode = resolveSurveyMode(input);
|
|
356
156
|
const handler = surveyHandlers[mode];
|
|
357
|
-
return handler(app, input);
|
|
157
|
+
return handler(app, input, rootDir);
|
|
358
158
|
};
|
|
359
159
|
|
|
360
160
|
// ---------------------------------------------------------------------------
|
|
@@ -362,21 +162,26 @@ const dispatchSurvey = (
|
|
|
362
162
|
// ---------------------------------------------------------------------------
|
|
363
163
|
|
|
364
164
|
export const surveyTrail = trail('survey', {
|
|
165
|
+
blaze: async (input, ctx) => {
|
|
166
|
+
const rootDir = ctx.cwd ?? '.';
|
|
167
|
+
const app = await loadApp(input.module, rootDir);
|
|
168
|
+
return dispatchSurvey(app, input, rootDir);
|
|
169
|
+
},
|
|
365
170
|
description: 'Full topo introspection',
|
|
366
171
|
examples: [
|
|
367
172
|
{
|
|
368
|
-
description: 'Lists all registered trails with safety and
|
|
369
|
-
input: {},
|
|
173
|
+
description: 'Lists all registered trails with safety and trailhead info',
|
|
174
|
+
input: { module: './src/app.ts' },
|
|
370
175
|
name: 'List all trails',
|
|
371
176
|
},
|
|
372
177
|
{
|
|
373
178
|
description: 'Quick capability summary with counts and feature flags',
|
|
374
|
-
input: { brief: true },
|
|
179
|
+
input: { brief: true, module: './src/app.ts' },
|
|
375
180
|
name: 'Brief capability report',
|
|
376
181
|
},
|
|
377
182
|
{
|
|
378
183
|
description: 'Generate an OpenAPI 3.1 specification for the topo',
|
|
379
|
-
input: { openapi: true },
|
|
184
|
+
input: { module: './src/app.ts', openapi: true },
|
|
380
185
|
name: 'OpenAPI spec',
|
|
381
186
|
},
|
|
382
187
|
],
|
|
@@ -390,7 +195,7 @@ export const surveyTrail = trail('survey', {
|
|
|
390
195
|
generate: z
|
|
391
196
|
.boolean()
|
|
392
197
|
.default(false)
|
|
393
|
-
.describe('Generate
|
|
198
|
+
.describe('Generate trailhead map and lock file'),
|
|
394
199
|
module: z
|
|
395
200
|
.string()
|
|
396
201
|
.default('./src/app.ts')
|
|
@@ -410,13 +215,13 @@ export const surveyTrail = trail('survey', {
|
|
|
410
215
|
safety: z.string(),
|
|
411
216
|
})
|
|
412
217
|
),
|
|
413
|
-
|
|
414
|
-
|
|
218
|
+
provisionCount: z.number(),
|
|
219
|
+
provisions: z.array(
|
|
415
220
|
z.object({
|
|
416
221
|
description: z.string().nullable(),
|
|
417
222
|
health: z.enum(['available', 'none']),
|
|
418
223
|
id: z.string(),
|
|
419
|
-
kind: z.literal('
|
|
224
|
+
kind: z.literal('provision'),
|
|
420
225
|
lifetime: z.literal('singleton'),
|
|
421
226
|
usedBy: z.array(z.string()),
|
|
422
227
|
})
|
|
@@ -424,16 +229,16 @@ export const surveyTrail = trail('survey', {
|
|
|
424
229
|
}),
|
|
425
230
|
z.object({
|
|
426
231
|
contractVersion: z.string(),
|
|
427
|
-
events: z.number(),
|
|
428
232
|
features: z.object({
|
|
429
233
|
detours: z.boolean(),
|
|
430
|
-
events: z.boolean(),
|
|
431
234
|
examples: z.boolean(),
|
|
432
235
|
outputSchemas: z.boolean(),
|
|
433
|
-
|
|
236
|
+
provisions: z.boolean(),
|
|
237
|
+
signals: z.boolean(),
|
|
434
238
|
}),
|
|
435
239
|
name: z.string(),
|
|
436
|
-
|
|
240
|
+
provisions: z.number(),
|
|
241
|
+
signals: z.number(),
|
|
437
242
|
trails: z.number(),
|
|
438
243
|
version: z.string(),
|
|
439
244
|
}),
|
|
@@ -444,21 +249,21 @@ export const surveyTrail = trail('survey', {
|
|
|
444
249
|
warnings: z.array(z.unknown()),
|
|
445
250
|
}),
|
|
446
251
|
z.object({
|
|
252
|
+
crosses: z.array(z.string()),
|
|
447
253
|
description: z.unknown().nullable(),
|
|
448
254
|
detours: z.unknown().nullable(),
|
|
449
255
|
examples: z.array(z.unknown()),
|
|
450
|
-
follow: z.array(z.string()),
|
|
451
256
|
id: z.string(),
|
|
452
257
|
intent: z.enum(['read', 'write', 'destroy']),
|
|
453
258
|
kind: z.string(),
|
|
259
|
+
provisions: z.array(z.string()),
|
|
454
260
|
safety: z.string(),
|
|
455
|
-
services: z.array(z.string()),
|
|
456
261
|
}),
|
|
457
262
|
z.object({
|
|
458
263
|
description: z.string().nullable(),
|
|
459
264
|
health: z.enum(['available', 'none']),
|
|
460
265
|
id: z.string(),
|
|
461
|
-
kind: z.literal('
|
|
266
|
+
kind: z.literal('provision'),
|
|
462
267
|
lifetime: z.literal('singleton'),
|
|
463
268
|
usedBy: z.array(z.string()),
|
|
464
269
|
}),
|
|
@@ -488,8 +293,4 @@ export const surveyTrail = trail('survey', {
|
|
|
488
293
|
.optional(),
|
|
489
294
|
}),
|
|
490
295
|
]),
|
|
491
|
-
run: async (input, ctx) => {
|
|
492
|
-
const app = await loadApp(input.module, ctx.cwd ?? '.');
|
|
493
|
-
return dispatchSurvey(app, input);
|
|
494
|
-
},
|
|
495
296
|
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { trail } from '@ontrails/core';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
import { loadApp } from './load-app.js';
|
|
5
|
+
import { exportCurrentTopo } from './topo-store-support.js';
|
|
6
|
+
import {
|
|
7
|
+
DEFAULT_APP_MODULE,
|
|
8
|
+
isolatedExampleInput,
|
|
9
|
+
topoSaveOutput,
|
|
10
|
+
} from './topo-support.js';
|
|
11
|
+
|
|
12
|
+
export const topoExportTrail = trail('topo.export', {
|
|
13
|
+
blaze: async (input, ctx) => {
|
|
14
|
+
const rootDir = input.rootDir ?? ctx.cwd ?? process.cwd();
|
|
15
|
+
const app = await loadApp(input.module, rootDir);
|
|
16
|
+
return exportCurrentTopo(app, { rootDir });
|
|
17
|
+
},
|
|
18
|
+
description: 'Export the current topo to .trails artifacts',
|
|
19
|
+
examples: [
|
|
20
|
+
{
|
|
21
|
+
input: isolatedExampleInput('topo-export'),
|
|
22
|
+
name: 'Write the current topo export',
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
input: z.object({
|
|
26
|
+
module: z
|
|
27
|
+
.string()
|
|
28
|
+
.default(DEFAULT_APP_MODULE)
|
|
29
|
+
.describe('Path to the app module'),
|
|
30
|
+
rootDir: z.string().optional().describe('Workspace root directory'),
|
|
31
|
+
}),
|
|
32
|
+
intent: 'write',
|
|
33
|
+
output: z.object({
|
|
34
|
+
hash: z.string(),
|
|
35
|
+
lockPath: z.string(),
|
|
36
|
+
mapPath: z.string(),
|
|
37
|
+
save: topoSaveOutput,
|
|
38
|
+
}),
|
|
39
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Result, trail } from '@ontrails/core';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_TOPO_HISTORY_LIMIT,
|
|
6
|
+
isolatedExampleInput,
|
|
7
|
+
listTopoHistory,
|
|
8
|
+
topoPinOutput,
|
|
9
|
+
topoSaveOutput,
|
|
10
|
+
} from './topo-support.js';
|
|
11
|
+
|
|
12
|
+
export const topoHistoryTrail = trail('topo.history', {
|
|
13
|
+
blaze: (input, ctx) => {
|
|
14
|
+
const rootDir = input.rootDir ?? ctx.cwd ?? process.cwd();
|
|
15
|
+
return Result.ok(listTopoHistory({ limit: input.limit, rootDir }));
|
|
16
|
+
},
|
|
17
|
+
description: 'List saved topo metadata, including pins and recent autosaves',
|
|
18
|
+
examples: [
|
|
19
|
+
{
|
|
20
|
+
input: isolatedExampleInput('topo-history'),
|
|
21
|
+
name: 'Show topo history',
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
input: z.object({
|
|
25
|
+
limit: z
|
|
26
|
+
.number()
|
|
27
|
+
.default(DEFAULT_TOPO_HISTORY_LIMIT)
|
|
28
|
+
.describe('Maximum number of autosaves to return'),
|
|
29
|
+
rootDir: z.string().optional().describe('Workspace root directory'),
|
|
30
|
+
}),
|
|
31
|
+
intent: 'read',
|
|
32
|
+
output: z.object({
|
|
33
|
+
dbPath: z.string(),
|
|
34
|
+
limit: z.number(),
|
|
35
|
+
pinCount: z.number(),
|
|
36
|
+
pins: z.array(topoPinOutput),
|
|
37
|
+
saveCount: z.number(),
|
|
38
|
+
saves: z.array(topoSaveOutput),
|
|
39
|
+
}),
|
|
40
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Result, trail } from '@ontrails/core';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
import { loadApp } from './load-app.js';
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_APP_MODULE,
|
|
7
|
+
isolatedExampleInput,
|
|
8
|
+
pinCurrentTopo,
|
|
9
|
+
topoPinOutput,
|
|
10
|
+
topoSaveOutput,
|
|
11
|
+
} from './topo-support.js';
|
|
12
|
+
|
|
13
|
+
export const topoPinTrail = trail('topo.pin', {
|
|
14
|
+
blaze: async (input, ctx) => {
|
|
15
|
+
const rootDir = input.rootDir ?? ctx.cwd ?? process.cwd();
|
|
16
|
+
const app = await loadApp(input.module, rootDir);
|
|
17
|
+
return Result.ok(pinCurrentTopo(app, { name: input.name, rootDir }));
|
|
18
|
+
},
|
|
19
|
+
description: 'Pin the current topo under a durable name',
|
|
20
|
+
examples: [
|
|
21
|
+
{
|
|
22
|
+
input: {
|
|
23
|
+
...isolatedExampleInput('topo-pin'),
|
|
24
|
+
name: 'before-auth-refactor',
|
|
25
|
+
},
|
|
26
|
+
name: 'Pin the current topo',
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
input: z.object({
|
|
30
|
+
module: z
|
|
31
|
+
.string()
|
|
32
|
+
.default(DEFAULT_APP_MODULE)
|
|
33
|
+
.describe('Path to the app module'),
|
|
34
|
+
name: z.string().describe('Pin name'),
|
|
35
|
+
rootDir: z.string().optional().describe('Workspace root directory'),
|
|
36
|
+
}),
|
|
37
|
+
intent: 'write',
|
|
38
|
+
output: z.object({
|
|
39
|
+
pin: topoPinOutput,
|
|
40
|
+
save: topoSaveOutput,
|
|
41
|
+
}),
|
|
42
|
+
});
|