@ontrails/trails 1.0.0-beta.14 → 1.0.0-beta.15
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 +13 -0
- package/__tests__/examples.test.ts +29 -4
- package/dist/src/app.js +2 -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 +4 -3
- package/dist/src/cli.js.map +1 -1
- package/dist/src/trails/add-surface.d.ts +3 -3
- package/dist/src/trails/add-surface.d.ts.map +1 -1
- package/dist/src/trails/add-surface.js +46 -24
- package/dist/src/trails/add-surface.js.map +1 -1
- package/dist/src/trails/add-trail.d.ts +3 -1
- package/dist/src/trails/add-trail.d.ts.map +1 -1
- package/dist/src/trails/add-trail.js +38 -11
- package/dist/src/trails/add-trail.js.map +1 -1
- package/dist/src/trails/add-trailhead.d.ts +1 -1
- package/dist/src/trails/add-trailhead.d.ts.map +1 -1
- package/dist/src/trails/add-trailhead.js +4 -4
- package/dist/src/trails/add-verify.d.ts +1 -1
- package/dist/src/trails/add-verify.d.ts.map +1 -1
- package/dist/src/trails/add-verify.js +7 -6
- package/dist/src/trails/add-verify.js.map +1 -1
- package/dist/src/trails/create-scaffold.d.ts +1 -1
- package/dist/src/trails/create-scaffold.d.ts.map +1 -1
- package/dist/src/trails/create-scaffold.js +10 -3
- package/dist/src/trails/create-scaffold.js.map +1 -1
- package/dist/src/trails/create.d.ts +10 -14
- package/dist/src/trails/create.d.ts.map +1 -1
- package/dist/src/trails/create.js +42 -37
- package/dist/src/trails/create.js.map +1 -1
- package/dist/src/trails/dev-clean.d.ts +4 -4
- package/dist/src/trails/dev-clean.d.ts.map +1 -1
- package/dist/src/trails/dev-clean.js +22 -21
- package/dist/src/trails/dev-clean.js.map +1 -1
- package/dist/src/trails/dev-reset.d.ts +1 -1
- package/dist/src/trails/dev-reset.d.ts.map +1 -1
- package/dist/src/trails/dev-reset.js +3 -2
- package/dist/src/trails/dev-reset.js.map +1 -1
- package/dist/src/trails/dev-stats.d.ts +4 -4
- package/dist/src/trails/dev-stats.d.ts.map +1 -1
- package/dist/src/trails/dev-stats.js +19 -19
- package/dist/src/trails/dev-stats.js.map +1 -1
- package/dist/src/trails/dev-support.d.ts +17 -17
- package/dist/src/trails/dev-support.d.ts.map +1 -1
- package/dist/src/trails/dev-support.js +60 -57
- package/dist/src/trails/dev-support.js.map +1 -1
- package/dist/src/trails/draft-promote.d.ts +1 -1
- package/dist/src/trails/draft-promote.d.ts.map +1 -1
- package/dist/src/trails/draft-promote.js +30 -16
- package/dist/src/trails/draft-promote.js.map +1 -1
- package/dist/src/trails/guide.d.ts +2 -2
- package/dist/src/trails/guide.d.ts.map +1 -1
- package/dist/src/trails/guide.js +1 -4
- package/dist/src/trails/guide.js.map +1 -1
- package/dist/src/trails/load-app.d.ts +7 -1
- package/dist/src/trails/load-app.d.ts.map +1 -1
- package/dist/src/trails/load-app.js +374 -26
- package/dist/src/trails/load-app.js.map +1 -1
- package/dist/src/trails/survey.d.ts +5 -5
- package/dist/src/trails/survey.d.ts.map +1 -1
- package/dist/src/trails/survey.js +51 -29
- package/dist/src/trails/survey.js.map +1 -1
- package/dist/src/trails/topo-export.d.ts +5 -4
- package/dist/src/trails/topo-export.d.ts.map +1 -1
- package/dist/src/trails/topo-export.js +4 -7
- package/dist/src/trails/topo-export.js.map +1 -1
- package/dist/src/trails/topo-history.d.ts +6 -10
- package/dist/src/trails/topo-history.d.ts.map +1 -1
- package/dist/src/trails/topo-history.js +7 -8
- package/dist/src/trails/topo-history.js.map +1 -1
- package/dist/src/trails/topo-pin.d.ts +5 -9
- package/dist/src/trails/topo-pin.d.ts.map +1 -1
- package/dist/src/trails/topo-pin.js +5 -9
- package/dist/src/trails/topo-pin.js.map +1 -1
- package/dist/src/trails/topo-read-support.d.ts +12 -8
- package/dist/src/trails/topo-read-support.d.ts.map +1 -1
- package/dist/src/trails/topo-read-support.js +71 -82
- package/dist/src/trails/topo-read-support.js.map +1 -1
- package/dist/src/trails/topo-reports.d.ts +15 -11
- package/dist/src/trails/topo-reports.d.ts.map +1 -1
- package/dist/src/trails/topo-reports.js +40 -34
- package/dist/src/trails/topo-reports.js.map +1 -1
- package/dist/src/trails/topo-show.d.ts +4 -4
- package/dist/src/trails/topo-show.d.ts.map +1 -1
- package/dist/src/trails/topo-show.js +8 -12
- package/dist/src/trails/topo-show.js.map +1 -1
- package/dist/src/trails/topo-store-support.d.ts +2 -2
- package/dist/src/trails/topo-store-support.d.ts.map +1 -1
- package/dist/src/trails/topo-store-support.js +22 -22
- package/dist/src/trails/topo-store-support.js.map +1 -1
- package/dist/src/trails/topo-support.d.ts +19 -30
- package/dist/src/trails/topo-support.d.ts.map +1 -1
- package/dist/src/trails/topo-support.js +63 -96
- package/dist/src/trails/topo-support.js.map +1 -1
- package/dist/src/trails/topo-unpin.d.ts +9 -4
- package/dist/src/trails/topo-unpin.d.ts.map +1 -1
- package/dist/src/trails/topo-unpin.js +9 -4
- package/dist/src/trails/topo-unpin.js.map +1 -1
- package/dist/src/trails/topo-verify.d.ts +2 -2
- package/dist/src/trails/topo-verify.d.ts.map +1 -1
- package/dist/src/trails/topo-verify.js +1 -5
- package/dist/src/trails/topo-verify.js.map +1 -1
- package/dist/src/trails/topo.d.ts +2 -2
- package/dist/src/trails/topo.d.ts.map +1 -1
- package/dist/src/trails/topo.js +6 -10
- package/dist/src/trails/topo.js.map +1 -1
- package/dist/src/trails/warden.d.ts +3 -2
- package/dist/src/trails/warden.d.ts.map +1 -1
- package/dist/src/trails/warden.js +11 -2
- package/dist/src/trails/warden.js.map +1 -1
- package/dist/src/versions.d.ts +12 -0
- package/dist/src/versions.d.ts.map +1 -0
- package/dist/src/versions.js +23 -0
- package/dist/src/versions.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -8
- package/src/__tests__/add-trail.test.ts +97 -0
- package/src/__tests__/create.test.ts +91 -27
- package/src/__tests__/guide.test.ts +10 -5
- package/src/__tests__/load-app.test.ts +365 -4
- package/src/__tests__/survey.test.ts +143 -67
- package/src/__tests__/topo-dev.test.ts +48 -46
- package/src/app.ts +2 -2
- package/src/clack.ts +1 -1
- package/src/cli.ts +4 -3
- package/src/trails/add-surface.ts +150 -0
- package/src/trails/add-trail.ts +46 -10
- package/src/trails/add-verify.ts +11 -6
- package/src/trails/create-scaffold.ts +16 -3
- package/src/trails/create.ts +76 -71
- package/src/trails/dev-clean.ts +25 -21
- package/src/trails/dev-reset.ts +3 -2
- package/src/trails/dev-stats.ts +22 -19
- package/src/trails/dev-support.ts +88 -86
- package/src/trails/draft-promote.ts +70 -35
- package/src/trails/guide.ts +1 -4
- package/src/trails/load-app.ts +511 -32
- package/src/trails/survey.ts +60 -35
- package/src/trails/topo-export.ts +5 -9
- package/src/trails/topo-history.ts +8 -10
- package/src/trails/topo-pin.ts +9 -13
- package/src/trails/topo-read-support.ts +103 -106
- package/src/trails/topo-reports.ts +54 -47
- package/src/trails/topo-show.ts +8 -12
- package/src/trails/topo-store-support.ts +36 -28
- package/src/trails/topo-support.ts +90 -134
- package/src/trails/topo-unpin.ts +11 -6
- package/src/trails/topo-verify.ts +1 -5
- package/src/trails/topo.ts +6 -10
- package/src/trails/warden.ts +12 -3
- package/src/versions.ts +43 -0
- package/tsconfig.tests.json +10 -0
- package/src/trails/add-trailhead.ts +0 -121
|
@@ -1,65 +1,69 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Read-only topo store consumer helpers.
|
|
3
3
|
*
|
|
4
|
-
* Extracted from topo-support.ts
|
|
5
|
-
* keeping
|
|
4
|
+
* Extracted from topo-support.ts to isolate read-only store consumer helpers,
|
|
5
|
+
* keeping module boundaries clean.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { existsSync } from 'node:fs';
|
|
9
9
|
import { join } from 'node:path';
|
|
10
10
|
|
|
11
|
-
import type { Topo } from '@ontrails/core';
|
|
12
11
|
import {
|
|
13
12
|
ConflictError,
|
|
14
13
|
createTopoStore,
|
|
15
14
|
InternalError,
|
|
15
|
+
listTopoSnapshots,
|
|
16
16
|
NotFoundError,
|
|
17
17
|
Result,
|
|
18
18
|
} from '@ontrails/core';
|
|
19
|
-
import type {
|
|
20
|
-
import { listTopoSaves } from '@ontrails/core/internal/topo-saves';
|
|
19
|
+
import type { Topo, TopoSnapshot } from '@ontrails/core';
|
|
21
20
|
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
resolveTrailsDir,
|
|
21
|
+
deriveTrailsDbPath,
|
|
22
|
+
deriveTrailsDir,
|
|
25
23
|
} from '@ontrails/core/internal/trails-db';
|
|
26
|
-
import {
|
|
24
|
+
import { readSurfaceLockData } from '@ontrails/schema';
|
|
27
25
|
|
|
28
26
|
import type { BriefReport, SurveyListReport } from './topo-reports.js';
|
|
29
27
|
import type { TopoSummaryReport, TopoVerifyReport } from './topo-support.js';
|
|
30
28
|
import { REPORT_CONTRACT_VERSION, REPORT_VERSION } from './topo-constants.js';
|
|
31
29
|
import {
|
|
32
|
-
|
|
30
|
+
createCurrentTopoSnapshot,
|
|
31
|
+
deriveRootDir,
|
|
33
32
|
LOCK_PATH,
|
|
34
|
-
resolveRootDir,
|
|
35
33
|
} from './topo-support.js';
|
|
36
34
|
|
|
37
35
|
// ---------------------------------------------------------------------------
|
|
38
36
|
// Internal types
|
|
39
37
|
// ---------------------------------------------------------------------------
|
|
40
38
|
|
|
41
|
-
interface
|
|
42
|
-
readonly detours?:
|
|
43
|
-
|
|
39
|
+
interface StoredSurfaceMapEntry {
|
|
40
|
+
readonly detours?: readonly {
|
|
41
|
+
readonly on: string;
|
|
42
|
+
readonly maxAttempts: number;
|
|
43
|
+
}[];
|
|
44
|
+
readonly kind: 'resource' | 'signal' | 'trail';
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
interface CurrentTrailDetail {
|
|
47
48
|
readonly crosses: string[];
|
|
48
49
|
readonly description: string | null;
|
|
49
|
-
readonly detours:
|
|
50
|
+
readonly detours:
|
|
51
|
+
| readonly { readonly on: string; readonly maxAttempts: number }[]
|
|
52
|
+
| null;
|
|
50
53
|
readonly examples: unknown[];
|
|
51
54
|
readonly id: string;
|
|
52
55
|
readonly intent: 'destroy' | 'read' | 'write';
|
|
53
56
|
readonly kind: string;
|
|
54
|
-
readonly
|
|
57
|
+
readonly pattern: string | null;
|
|
58
|
+
readonly resources: string[];
|
|
55
59
|
readonly safety: string;
|
|
56
60
|
}
|
|
57
61
|
|
|
58
|
-
interface
|
|
62
|
+
interface CurrentResourceDetail {
|
|
59
63
|
readonly description: string | null;
|
|
60
64
|
readonly health: 'available' | 'none';
|
|
61
65
|
readonly id: string;
|
|
62
|
-
readonly kind: '
|
|
66
|
+
readonly kind: 'resource';
|
|
63
67
|
readonly lifetime: 'singleton';
|
|
64
68
|
readonly usedBy: string[];
|
|
65
69
|
}
|
|
@@ -68,18 +72,17 @@ interface CurrentProvisionDetail {
|
|
|
68
72
|
// Store helpers
|
|
69
73
|
// ---------------------------------------------------------------------------
|
|
70
74
|
|
|
71
|
-
const topoStoreRef = (
|
|
75
|
+
const topoStoreRef = (snapshotId: string) => ({ snapshotId }) as const;
|
|
72
76
|
|
|
73
77
|
const hasCommittedLock = (trailsDir: string): boolean =>
|
|
74
|
-
existsSync(join(trailsDir, 'trails.lock'))
|
|
75
|
-
existsSync(join(trailsDir, 'trailhead.lock'));
|
|
78
|
+
existsSync(join(trailsDir, 'trails.lock'));
|
|
76
79
|
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
): readonly
|
|
80
|
+
const readSurfaceEntries = (
|
|
81
|
+
surfaceMapJson: string
|
|
82
|
+
): readonly StoredSurfaceMapEntry[] =>
|
|
80
83
|
(
|
|
81
|
-
JSON.parse(
|
|
82
|
-
readonly entries: readonly
|
|
84
|
+
JSON.parse(surfaceMapJson) as {
|
|
85
|
+
readonly entries: readonly StoredSurfaceMapEntry[];
|
|
83
86
|
}
|
|
84
87
|
).entries;
|
|
85
88
|
|
|
@@ -87,32 +90,30 @@ const buildBriefReportFromStore = (
|
|
|
87
90
|
app: Topo,
|
|
88
91
|
store: ReturnType<typeof createTopoStore>,
|
|
89
92
|
ref: ReturnType<typeof topoStoreRef>,
|
|
90
|
-
|
|
93
|
+
snapshot: TopoSnapshot
|
|
91
94
|
): BriefReport => {
|
|
92
|
-
const trails = store.trails.list({
|
|
95
|
+
const trails = store.trails.list({ snapshot: ref });
|
|
93
96
|
const exportRecord = store.exports.get(ref);
|
|
94
97
|
const trailEntries =
|
|
95
98
|
exportRecord === undefined
|
|
96
99
|
? []
|
|
97
|
-
:
|
|
100
|
+
: readSurfaceEntries(exportRecord.surfaceMapJson).filter(
|
|
98
101
|
(entry) => entry.kind === 'trail'
|
|
99
102
|
);
|
|
100
103
|
|
|
101
104
|
return {
|
|
102
105
|
contractVersion: REPORT_CONTRACT_VERSION,
|
|
103
106
|
features: {
|
|
104
|
-
detours: trailEntries.some(
|
|
105
|
-
(entry) => (Object.keys(entry.detours ?? {}).length ?? 0) > 0
|
|
106
|
-
),
|
|
107
|
+
detours: trailEntries.some((entry) => (entry.detours ?? []).length > 0),
|
|
107
108
|
examples: trails.some((trail) => trail.hasExamples),
|
|
108
109
|
outputSchemas: trails.some((trail) => trail.hasOutput),
|
|
109
|
-
|
|
110
|
-
signals:
|
|
110
|
+
resources: snapshot.resourceCount > 0,
|
|
111
|
+
signals: snapshot.signalCount > 0,
|
|
111
112
|
},
|
|
112
113
|
name: app.name,
|
|
113
|
-
|
|
114
|
-
signals:
|
|
115
|
-
trails:
|
|
114
|
+
resources: snapshot.resourceCount,
|
|
115
|
+
signals: snapshot.signalCount,
|
|
116
|
+
trails: snapshot.trailCount,
|
|
116
117
|
version: REPORT_VERSION,
|
|
117
118
|
};
|
|
118
119
|
};
|
|
@@ -121,8 +122,8 @@ const buildSurveyListFromStore = (
|
|
|
121
122
|
store: ReturnType<typeof createTopoStore>,
|
|
122
123
|
ref: ReturnType<typeof topoStoreRef>
|
|
123
124
|
): SurveyListReport => {
|
|
124
|
-
const trails = store.trails.list({
|
|
125
|
-
const
|
|
125
|
+
const trails = store.trails.list({ snapshot: ref });
|
|
126
|
+
const resources = store.resources.list({ snapshot: ref });
|
|
126
127
|
|
|
127
128
|
return {
|
|
128
129
|
count: trails.length,
|
|
@@ -132,43 +133,46 @@ const buildSurveyListFromStore = (
|
|
|
132
133
|
kind: trail.kind,
|
|
133
134
|
safety: trail.safety,
|
|
134
135
|
})),
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
description:
|
|
138
|
-
health:
|
|
139
|
-
id:
|
|
140
|
-
kind:
|
|
141
|
-
lifetime:
|
|
142
|
-
usedBy:
|
|
136
|
+
resourceCount: resources.length,
|
|
137
|
+
resources: resources.map((resource) => ({
|
|
138
|
+
description: resource.description,
|
|
139
|
+
health: resource.health,
|
|
140
|
+
id: resource.id,
|
|
141
|
+
kind: resource.kind,
|
|
142
|
+
lifetime: resource.lifetime,
|
|
143
|
+
usedBy: resource.usedBy,
|
|
143
144
|
})),
|
|
144
145
|
};
|
|
145
146
|
};
|
|
146
147
|
|
|
147
148
|
const buildTrailDetailFromStore = (
|
|
148
|
-
detail:
|
|
149
|
+
detail: NonNullable<
|
|
150
|
+
ReturnType<ReturnType<typeof createTopoStore>['trails']['get']>
|
|
151
|
+
>
|
|
149
152
|
): CurrentTrailDetail => ({
|
|
150
|
-
crosses: [...
|
|
151
|
-
description: detail
|
|
152
|
-
detours: detail
|
|
153
|
-
examples: [...
|
|
154
|
-
id: detail
|
|
155
|
-
intent: detail
|
|
156
|
-
kind: detail
|
|
157
|
-
|
|
158
|
-
|
|
153
|
+
crosses: [...detail.crosses],
|
|
154
|
+
description: detail.description,
|
|
155
|
+
detours: detail.detours,
|
|
156
|
+
examples: [...detail.examples],
|
|
157
|
+
id: detail.id,
|
|
158
|
+
intent: detail.intent,
|
|
159
|
+
kind: detail.kind,
|
|
160
|
+
pattern: detail.pattern,
|
|
161
|
+
resources: [...detail.resources],
|
|
162
|
+
safety: detail.safety,
|
|
159
163
|
});
|
|
160
164
|
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
ReturnType<ReturnType<typeof createTopoStore>['
|
|
165
|
+
const buildResourceDetailFromStore = (
|
|
166
|
+
resource: NonNullable<
|
|
167
|
+
ReturnType<ReturnType<typeof createTopoStore>['resources']['get']>
|
|
164
168
|
>
|
|
165
|
-
):
|
|
166
|
-
description:
|
|
167
|
-
health:
|
|
168
|
-
id:
|
|
169
|
-
kind:
|
|
170
|
-
lifetime:
|
|
171
|
-
usedBy: [...
|
|
169
|
+
): CurrentResourceDetail => ({
|
|
170
|
+
description: resource.description,
|
|
171
|
+
health: resource.health,
|
|
172
|
+
id: resource.id,
|
|
173
|
+
kind: resource.kind,
|
|
174
|
+
lifetime: resource.lifetime,
|
|
175
|
+
usedBy: [...resource.usedBy],
|
|
172
176
|
});
|
|
173
177
|
|
|
174
178
|
// ---------------------------------------------------------------------------
|
|
@@ -178,8 +182,9 @@ const buildProvisionDetailFromStore = (
|
|
|
178
182
|
/**
|
|
179
183
|
* Run a read callback against the latest topo store state.
|
|
180
184
|
*
|
|
181
|
-
* Uses the most recent existing
|
|
182
|
-
* when no prior
|
|
185
|
+
* Uses the most recent existing snapshot when available, only creating a new
|
|
186
|
+
* snapshot when no prior snapshot exists. This avoids unbounded snapshot
|
|
187
|
+
* accumulation from
|
|
183
188
|
* read-only operations like survey, guide, and show.
|
|
184
189
|
*/
|
|
185
190
|
const withCurrentTopoStore = <T>(
|
|
@@ -188,24 +193,14 @@ const withCurrentTopoStore = <T>(
|
|
|
188
193
|
read: (
|
|
189
194
|
store: ReturnType<typeof createTopoStore>,
|
|
190
195
|
ref: ReturnType<typeof topoStoreRef>,
|
|
191
|
-
|
|
196
|
+
snapshot: TopoSnapshot
|
|
192
197
|
) => T
|
|
193
198
|
): T => {
|
|
194
|
-
const
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
const db = openReadTrailsDb({ rootDir });
|
|
198
|
-
try {
|
|
199
|
-
return listTopoSaves(db)[0];
|
|
200
|
-
} finally {
|
|
201
|
-
db.close();
|
|
202
|
-
}
|
|
203
|
-
})()
|
|
204
|
-
: undefined;
|
|
205
|
-
|
|
206
|
-
const save = existingSave ?? createCurrentTopoSave(app, { rootDir });
|
|
199
|
+
const [existingSnapshot] = listTopoSnapshots({ limit: 1, rootDir });
|
|
200
|
+
const snapshot =
|
|
201
|
+
existingSnapshot ?? createCurrentTopoSnapshot(app, { rootDir });
|
|
207
202
|
const store = createTopoStore({ rootDir });
|
|
208
|
-
return read(store, topoStoreRef(
|
|
203
|
+
return read(store, topoStoreRef(snapshot.id), snapshot);
|
|
209
204
|
};
|
|
210
205
|
|
|
211
206
|
// ---------------------------------------------------------------------------
|
|
@@ -216,11 +211,11 @@ export const buildTopoSummary = (
|
|
|
216
211
|
app: Topo,
|
|
217
212
|
options?: { readonly rootDir?: string }
|
|
218
213
|
): TopoSummaryReport => {
|
|
219
|
-
const rootDir =
|
|
220
|
-
const trailsDir =
|
|
221
|
-
return withCurrentTopoStore(app, rootDir, (store, ref,
|
|
222
|
-
app: buildBriefReportFromStore(app, store, ref,
|
|
223
|
-
dbPath:
|
|
214
|
+
const rootDir = deriveRootDir(options?.rootDir);
|
|
215
|
+
const trailsDir = deriveTrailsDir({ rootDir });
|
|
216
|
+
return withCurrentTopoStore(app, rootDir, (store, ref, snapshot) => ({
|
|
217
|
+
app: buildBriefReportFromStore(app, store, ref, snapshot),
|
|
218
|
+
dbPath: deriveTrailsDbPath({ rootDir }),
|
|
224
219
|
list: buildSurveyListFromStore(store, ref),
|
|
225
220
|
lockExists: hasCommittedLock(trailsDir),
|
|
226
221
|
lockPath: LOCK_PATH,
|
|
@@ -231,9 +226,9 @@ export const buildCurrentTopoBrief = (
|
|
|
231
226
|
app: Topo,
|
|
232
227
|
options?: { readonly rootDir?: string }
|
|
233
228
|
): BriefReport => {
|
|
234
|
-
const rootDir =
|
|
235
|
-
return withCurrentTopoStore(app, rootDir, (store, ref,
|
|
236
|
-
buildBriefReportFromStore(app, store, ref,
|
|
229
|
+
const rootDir = deriveRootDir(options?.rootDir);
|
|
230
|
+
return withCurrentTopoStore(app, rootDir, (store, ref, snapshot) =>
|
|
231
|
+
buildBriefReportFromStore(app, store, ref, snapshot)
|
|
237
232
|
);
|
|
238
233
|
};
|
|
239
234
|
|
|
@@ -241,7 +236,7 @@ export const buildCurrentTopoList = (
|
|
|
241
236
|
app: Topo,
|
|
242
237
|
options?: { readonly rootDir?: string }
|
|
243
238
|
): SurveyListReport => {
|
|
244
|
-
const rootDir =
|
|
239
|
+
const rootDir = deriveRootDir(options?.rootDir);
|
|
245
240
|
return withCurrentTopoStore(app, rootDir, (store, ref) =>
|
|
246
241
|
buildSurveyListFromStore(store, ref)
|
|
247
242
|
);
|
|
@@ -256,9 +251,9 @@ export const buildCurrentGuideEntries = (
|
|
|
256
251
|
readonly id: string;
|
|
257
252
|
readonly kind: string;
|
|
258
253
|
}[] => {
|
|
259
|
-
const rootDir =
|
|
254
|
+
const rootDir = deriveRootDir(options?.rootDir);
|
|
260
255
|
return withCurrentTopoStore(app, rootDir, (store, ref) =>
|
|
261
|
-
store.trails.list({
|
|
256
|
+
store.trails.list({ snapshot: ref }).map((trail) => ({
|
|
262
257
|
description: trail.description ?? '(no description)',
|
|
263
258
|
exampleCount: trail.exampleCount,
|
|
264
259
|
id: trail.id,
|
|
@@ -271,18 +266,18 @@ export const buildCurrentTopoDetail = (
|
|
|
271
266
|
app: Topo,
|
|
272
267
|
id: string,
|
|
273
268
|
options?: { readonly rootDir?: string }
|
|
274
|
-
):
|
|
275
|
-
const rootDir =
|
|
269
|
+
): CurrentResourceDetail | CurrentTrailDetail | undefined => {
|
|
270
|
+
const rootDir = deriveRootDir(options?.rootDir);
|
|
276
271
|
return withCurrentTopoStore(app, rootDir, (store, ref) => {
|
|
277
|
-
const trail = store.trails.get(id, {
|
|
272
|
+
const trail = store.trails.get(id, { snapshot: ref });
|
|
278
273
|
if (trail !== undefined) {
|
|
279
274
|
return buildTrailDetailFromStore(trail);
|
|
280
275
|
}
|
|
281
276
|
|
|
282
|
-
const
|
|
283
|
-
return
|
|
277
|
+
const resource = store.resources.get(id, { snapshot: ref });
|
|
278
|
+
return resource === undefined
|
|
284
279
|
? undefined
|
|
285
|
-
:
|
|
280
|
+
: buildResourceDetailFromStore(resource);
|
|
286
281
|
});
|
|
287
282
|
};
|
|
288
283
|
|
|
@@ -290,9 +285,9 @@ export const verifyCurrentTopo = async (
|
|
|
290
285
|
app: Topo,
|
|
291
286
|
options?: { readonly rootDir?: string }
|
|
292
287
|
): Promise<Result<TopoVerifyReport, Error>> => {
|
|
293
|
-
const rootDir =
|
|
294
|
-
const committedLock = await
|
|
295
|
-
dir:
|
|
288
|
+
const rootDir = deriveRootDir(options?.rootDir);
|
|
289
|
+
const committedLock = await readSurfaceLockData({
|
|
290
|
+
dir: deriveTrailsDir({ rootDir }),
|
|
296
291
|
});
|
|
297
292
|
|
|
298
293
|
if (committedLock === null) {
|
|
@@ -306,12 +301,14 @@ export const verifyCurrentTopo = async (
|
|
|
306
301
|
const currentHash = withCurrentTopoStore(
|
|
307
302
|
app,
|
|
308
303
|
rootDir,
|
|
309
|
-
(store, ref) => store.exports.get(ref)?.
|
|
304
|
+
(store, ref) => store.exports.get(ref)?.surfaceHash
|
|
310
305
|
);
|
|
311
306
|
|
|
312
307
|
if (currentHash === undefined) {
|
|
313
308
|
return Result.err(
|
|
314
|
-
new InternalError(
|
|
309
|
+
new InternalError(
|
|
310
|
+
'No stored topo export found for the current topo snapshot'
|
|
311
|
+
)
|
|
315
312
|
);
|
|
316
313
|
}
|
|
317
314
|
|
|
@@ -7,7 +7,7 @@ export interface BriefReport {
|
|
|
7
7
|
readonly version: string;
|
|
8
8
|
readonly contractVersion: string;
|
|
9
9
|
readonly features: {
|
|
10
|
-
readonly
|
|
10
|
+
readonly resources: boolean;
|
|
11
11
|
readonly outputSchemas: boolean;
|
|
12
12
|
readonly examples: boolean;
|
|
13
13
|
readonly detours: boolean;
|
|
@@ -15,7 +15,7 @@ export interface BriefReport {
|
|
|
15
15
|
};
|
|
16
16
|
readonly trails: number;
|
|
17
17
|
readonly signals: number;
|
|
18
|
-
readonly
|
|
18
|
+
readonly resources: number;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export interface SurveyListReport {
|
|
@@ -26,12 +26,12 @@ export interface SurveyListReport {
|
|
|
26
26
|
readonly kind: string;
|
|
27
27
|
readonly safety: string;
|
|
28
28
|
}[];
|
|
29
|
-
readonly
|
|
30
|
-
readonly
|
|
29
|
+
readonly resourceCount: number;
|
|
30
|
+
readonly resources: readonly {
|
|
31
31
|
readonly description: string | null;
|
|
32
32
|
readonly health: 'available' | 'none';
|
|
33
33
|
readonly id: string;
|
|
34
|
-
readonly kind: '
|
|
34
|
+
readonly kind: 'resource';
|
|
35
35
|
readonly lifetime: 'singleton';
|
|
36
36
|
readonly usedBy: readonly string[];
|
|
37
37
|
}[];
|
|
@@ -39,18 +39,21 @@ export interface SurveyListReport {
|
|
|
39
39
|
|
|
40
40
|
export interface TrailDetailReport {
|
|
41
41
|
readonly description: string | null;
|
|
42
|
-
readonly detours:
|
|
42
|
+
readonly detours:
|
|
43
|
+
| readonly { readonly on: string; readonly maxAttempts: number }[]
|
|
44
|
+
| null;
|
|
43
45
|
readonly examples: readonly unknown[];
|
|
44
46
|
readonly crosses: readonly string[];
|
|
45
47
|
readonly id: string;
|
|
46
48
|
readonly intent: 'read' | 'write' | 'destroy';
|
|
47
49
|
readonly kind: string;
|
|
50
|
+
readonly pattern: string | null;
|
|
48
51
|
readonly safety: string;
|
|
49
|
-
readonly
|
|
52
|
+
readonly resources: readonly string[];
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
const trailHas = (raw: Record<string, unknown>, key: string): boolean => {
|
|
53
|
-
if (key === 'examples') {
|
|
56
|
+
if (key === 'examples' || key === 'detours') {
|
|
54
57
|
return Array.isArray(raw[key]) && (raw[key] as unknown[]).length > 0;
|
|
55
58
|
}
|
|
56
59
|
return Boolean(raw[key]);
|
|
@@ -62,7 +65,7 @@ const detectFeatures = (
|
|
|
62
65
|
hasDetours: boolean;
|
|
63
66
|
hasExamples: boolean;
|
|
64
67
|
hasOutputSchemas: boolean;
|
|
65
|
-
|
|
68
|
+
hasResources: boolean;
|
|
66
69
|
} => {
|
|
67
70
|
const trails = [...app.trails.values()].map(
|
|
68
71
|
(item) => item as unknown as Record<string, unknown>
|
|
@@ -71,16 +74,16 @@ const detectFeatures = (
|
|
|
71
74
|
hasDetours: trails.some((r) => trailHas(r, 'detours')),
|
|
72
75
|
hasExamples: trails.some((r) => trailHas(r, 'examples')),
|
|
73
76
|
hasOutputSchemas: trails.some((r) => trailHas(r, 'output')),
|
|
74
|
-
|
|
77
|
+
hasResources: trails.some(
|
|
75
78
|
(r) =>
|
|
76
|
-
Array.isArray(r['
|
|
77
|
-
(r['
|
|
79
|
+
Array.isArray(r['resources']) &&
|
|
80
|
+
(r['resources'] as unknown[]).length > 0
|
|
78
81
|
),
|
|
79
82
|
};
|
|
80
83
|
};
|
|
81
84
|
|
|
82
|
-
export const
|
|
83
|
-
const { hasDetours, hasExamples, hasOutputSchemas,
|
|
85
|
+
export const deriveBriefReport = (app: Topo): BriefReport => {
|
|
86
|
+
const { hasDetours, hasExamples, hasOutputSchemas, hasResources } =
|
|
84
87
|
detectFeatures(app);
|
|
85
88
|
|
|
86
89
|
return {
|
|
@@ -89,11 +92,11 @@ export const generateBriefReport = (app: Topo): BriefReport => {
|
|
|
89
92
|
detours: hasDetours,
|
|
90
93
|
examples: hasExamples,
|
|
91
94
|
outputSchemas: hasOutputSchemas,
|
|
92
|
-
|
|
95
|
+
resources: hasResources,
|
|
93
96
|
signals: app.signals.size > 0,
|
|
94
97
|
},
|
|
95
98
|
name: app.name,
|
|
96
|
-
|
|
99
|
+
resources: app.resources.size,
|
|
97
100
|
signals: app.signals.size,
|
|
98
101
|
trails: app.trails.size,
|
|
99
102
|
version: REPORT_VERSION,
|
|
@@ -115,16 +118,16 @@ const safetyLabel = (entry: {
|
|
|
115
118
|
return '-';
|
|
116
119
|
};
|
|
117
120
|
|
|
118
|
-
const
|
|
121
|
+
const buildResourceUsage = (
|
|
119
122
|
app: Topo
|
|
120
123
|
): ReadonlyMap<string, readonly string[]> => {
|
|
121
124
|
const usage = new Map<string, string[]>();
|
|
122
125
|
|
|
123
126
|
for (const trailDef of app.list()) {
|
|
124
|
-
for (const
|
|
125
|
-
const users = usage.get(
|
|
127
|
+
for (const declaredResource of trailDef.resources) {
|
|
128
|
+
const users = usage.get(declaredResource.id) ?? [];
|
|
126
129
|
users.push(trailDef.id);
|
|
127
|
-
usage.set(
|
|
130
|
+
usage.set(declaredResource.id, users);
|
|
128
131
|
}
|
|
129
132
|
}
|
|
130
133
|
|
|
@@ -133,44 +136,41 @@ const buildProvisionUsage = (
|
|
|
133
136
|
);
|
|
134
137
|
};
|
|
135
138
|
|
|
136
|
-
const
|
|
139
|
+
const resourceHealthStatus = (resource: {
|
|
137
140
|
health?: unknown;
|
|
138
141
|
}): 'available' | 'none' =>
|
|
139
|
-
|
|
142
|
+
resource.health === undefined ? 'none' : 'available';
|
|
140
143
|
|
|
141
|
-
export const
|
|
142
|
-
app
|
|
143
|
-
|
|
144
|
-
): object => {
|
|
145
|
-
const item = app.getProvision(provisionId);
|
|
146
|
-
const usedBy = buildProvisionUsage(app).get(provisionId) ?? [];
|
|
144
|
+
export const deriveResourceDetail = (app: Topo, resourceId: string): object => {
|
|
145
|
+
const item = app.getResource(resourceId);
|
|
146
|
+
const usedBy = buildResourceUsage(app).get(resourceId) ?? [];
|
|
147
147
|
|
|
148
148
|
return {
|
|
149
149
|
description: item?.description ?? null,
|
|
150
|
-
health: item ?
|
|
151
|
-
id:
|
|
152
|
-
kind: '
|
|
150
|
+
health: item ? resourceHealthStatus(item) : 'none',
|
|
151
|
+
id: resourceId,
|
|
152
|
+
kind: 'resource',
|
|
153
153
|
lifetime: 'singleton',
|
|
154
154
|
usedBy,
|
|
155
155
|
};
|
|
156
156
|
};
|
|
157
157
|
|
|
158
|
-
const
|
|
159
|
-
const usage =
|
|
158
|
+
const formatResourceList = (app: Topo): SurveyListReport['resources'] => {
|
|
159
|
+
const usage = buildResourceUsage(app);
|
|
160
160
|
return app
|
|
161
|
-
.
|
|
162
|
-
.map((
|
|
163
|
-
description:
|
|
164
|
-
health:
|
|
165
|
-
id:
|
|
166
|
-
kind:
|
|
161
|
+
.listResources()
|
|
162
|
+
.map((resource) => ({
|
|
163
|
+
description: resource.description ?? null,
|
|
164
|
+
health: resourceHealthStatus(resource),
|
|
165
|
+
id: resource.id,
|
|
166
|
+
kind: resource.kind,
|
|
167
167
|
lifetime: 'singleton' as const,
|
|
168
|
-
usedBy: usage.get(
|
|
168
|
+
usedBy: usage.get(resource.id) ?? [],
|
|
169
169
|
}))
|
|
170
170
|
.toSorted((a, b) => a.id.localeCompare(b.id));
|
|
171
171
|
};
|
|
172
172
|
|
|
173
|
-
export const
|
|
173
|
+
export const deriveSurveyList = (app: Topo): SurveyListReport => {
|
|
174
174
|
const items = app.list();
|
|
175
175
|
const entries = items.map((item) => {
|
|
176
176
|
const safety = safetyLabel(
|
|
@@ -190,17 +190,17 @@ export const generateSurveyList = (app: Topo): SurveyListReport => {
|
|
|
190
190
|
};
|
|
191
191
|
});
|
|
192
192
|
|
|
193
|
-
const
|
|
193
|
+
const resources = formatResourceList(app);
|
|
194
194
|
|
|
195
195
|
return {
|
|
196
196
|
count: items.length,
|
|
197
197
|
entries,
|
|
198
|
-
|
|
199
|
-
|
|
198
|
+
resourceCount: resources.length,
|
|
199
|
+
resources,
|
|
200
200
|
};
|
|
201
201
|
};
|
|
202
202
|
|
|
203
|
-
export const
|
|
203
|
+
export const deriveTrailDetail = (
|
|
204
204
|
item: Trail<unknown, unknown>
|
|
205
205
|
): TrailDetailReport => {
|
|
206
206
|
const safety = safetyLabel(
|
|
@@ -210,12 +210,19 @@ export const generateTrailDetail = (
|
|
|
210
210
|
return {
|
|
211
211
|
crosses: item.crosses.toSorted(),
|
|
212
212
|
description: item.description ?? null,
|
|
213
|
-
detours:
|
|
213
|
+
detours:
|
|
214
|
+
item.detours.length > 0
|
|
215
|
+
? item.detours.map((d) => ({
|
|
216
|
+
maxAttempts: d.maxAttempts ?? 1,
|
|
217
|
+
on: d.on.name,
|
|
218
|
+
}))
|
|
219
|
+
: null,
|
|
214
220
|
examples: item.examples ?? [],
|
|
215
221
|
id: item.id,
|
|
216
222
|
intent: item.intent,
|
|
217
223
|
kind: item.kind,
|
|
218
|
-
|
|
224
|
+
pattern: item.pattern ?? null,
|
|
225
|
+
resources: item.resources.map((resource) => resource.id).toSorted(),
|
|
219
226
|
safety,
|
|
220
227
|
};
|
|
221
228
|
};
|
package/src/trails/topo-show.ts
CHANGED
|
@@ -3,7 +3,6 @@ import { z } from 'zod';
|
|
|
3
3
|
|
|
4
4
|
import { loadApp } from './load-app.js';
|
|
5
5
|
import { buildCurrentTopoDetail } from './topo-read-support.js';
|
|
6
|
-
import { DEFAULT_APP_MODULE } from './topo-support.js';
|
|
7
6
|
|
|
8
7
|
const trailDetailOutput = z.object({
|
|
9
8
|
crosses: z.array(z.string()),
|
|
@@ -13,15 +12,15 @@ const trailDetailOutput = z.object({
|
|
|
13
12
|
id: z.string(),
|
|
14
13
|
intent: z.enum(['read', 'write', 'destroy']),
|
|
15
14
|
kind: z.string(),
|
|
16
|
-
|
|
15
|
+
resources: z.array(z.string()),
|
|
17
16
|
safety: z.string(),
|
|
18
17
|
});
|
|
19
18
|
|
|
20
|
-
const
|
|
19
|
+
const resourceDetailOutput = z.object({
|
|
21
20
|
description: z.string().nullable(),
|
|
22
21
|
health: z.enum(['available', 'none']),
|
|
23
22
|
id: z.string(),
|
|
24
|
-
kind: z.literal('
|
|
23
|
+
kind: z.literal('resource'),
|
|
25
24
|
lifetime: z.literal('singleton'),
|
|
26
25
|
usedBy: z.array(z.string()),
|
|
27
26
|
});
|
|
@@ -35,10 +34,10 @@ export const topoShowTrail = trail('topo.show', {
|
|
|
35
34
|
return Result.ok(detail);
|
|
36
35
|
}
|
|
37
36
|
return Result.err(
|
|
38
|
-
new NotFoundError(`Trail or
|
|
37
|
+
new NotFoundError(`Trail or resource not found: ${input.id}`)
|
|
39
38
|
);
|
|
40
39
|
},
|
|
41
|
-
description: 'Show detail for a current trail or
|
|
40
|
+
description: 'Show detail for a current trail or resource',
|
|
42
41
|
examples: [
|
|
43
42
|
{
|
|
44
43
|
input: { id: 'topo' },
|
|
@@ -46,13 +45,10 @@ export const topoShowTrail = trail('topo.show', {
|
|
|
46
45
|
},
|
|
47
46
|
],
|
|
48
47
|
input: z.object({
|
|
49
|
-
id: z.string().describe('Trail or
|
|
50
|
-
module: z
|
|
51
|
-
.string()
|
|
52
|
-
.default(DEFAULT_APP_MODULE)
|
|
53
|
-
.describe('Path to the app module'),
|
|
48
|
+
id: z.string().describe('Trail or resource ID to inspect'),
|
|
49
|
+
module: z.string().optional().describe('Path to the app module'),
|
|
54
50
|
rootDir: z.string().optional().describe('Workspace root directory'),
|
|
55
51
|
}),
|
|
56
52
|
intent: 'read',
|
|
57
|
-
output: z.union([trailDetailOutput,
|
|
53
|
+
output: z.union([trailDetailOutput, resourceDetailOutput]),
|
|
58
54
|
});
|