@ontrails/trails 1.0.0-beta.18 → 1.0.0-beta.19
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/CHANGELOG.md +117 -0
- package/README.md +7 -10
- package/package.json +13 -12
- package/src/app.ts +14 -4
- package/src/cli.ts +16 -0
- package/src/lifecycle-source-io.ts +33 -0
- package/src/project-writes.ts +62 -5
- package/src/retired-topo-command.ts +36 -0
- package/src/run-adapter-check.ts +76 -0
- package/src/run-collision.ts +1 -0
- package/src/trails/adapter-check.ts +244 -0
- package/src/trails/add-surface.ts +18 -18
- package/src/trails/add-trail.ts +3 -2
- package/src/trails/add-verify.ts +30 -6
- package/src/trails/{topo-compile.ts → compile.ts} +16 -8
- package/src/trails/completions-complete.ts +1 -1
- package/src/trails/create-adapter.ts +1084 -0
- package/src/trails/create-scaffold.ts +243 -29
- package/src/trails/create.ts +118 -17
- package/src/trails/deprecate.ts +59 -0
- package/src/trails/dev-clean.ts +2 -2
- package/src/trails/dev-reset.ts +2 -2
- package/src/trails/dev-stats.ts +1 -1
- package/src/trails/doctor.ts +56 -0
- package/src/trails/draft-promote.ts +1 -0
- package/src/trails/guide.ts +2 -2
- package/src/trails/revise.ts +53 -0
- package/src/trails/run-example.ts +12 -7
- package/src/trails/run-examples.ts +3 -3
- package/src/trails/run.ts +7 -4
- package/src/trails/survey.ts +332 -25
- package/src/trails/topo-history.ts +1 -1
- package/src/trails/topo-output-schemas.ts +30 -1
- package/src/trails/topo-pin.ts +3 -2
- package/src/trails/topo-read-support.ts +49 -8
- package/src/trails/topo-reports.ts +39 -22
- package/src/trails/topo-store-support.ts +62 -16
- package/src/trails/topo-support.ts +1 -1
- package/src/trails/topo-unpin.ts +2 -2
- package/src/trails/topo.ts +2 -2
- package/src/trails/{topo-verify.ts → validate.ts} +7 -7
- package/src/trails/version-lifecycle-support.ts +945 -0
- package/src/trails/warden-guide.ts +8 -0
- package/src/trails/warden.ts +18 -2
- package/src/versions.ts +4 -1
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
DETOUR_MAX_ATTEMPTS_CAP,
|
|
3
3
|
deriveCliPath,
|
|
4
4
|
filterSurfaceTrails,
|
|
5
|
+
isArchivedTrailVersionEntry,
|
|
5
6
|
zodToJsonSchema,
|
|
6
7
|
} from '@ontrails/core';
|
|
7
8
|
import type { AnyTrail, Signal, Topo } from '@ontrails/core';
|
|
@@ -16,6 +17,7 @@ import type {
|
|
|
16
17
|
TopoGraphEntry,
|
|
17
18
|
TopoGraphFieldOverride,
|
|
18
19
|
TopoGraphLayerReference,
|
|
20
|
+
TopoGraphVersionEntry,
|
|
19
21
|
} from '@ontrails/topographer';
|
|
20
22
|
import { z } from 'zod';
|
|
21
23
|
|
|
@@ -196,7 +198,7 @@ export interface TrailDetailReport {
|
|
|
196
198
|
| null;
|
|
197
199
|
readonly examples: readonly unknown[];
|
|
198
200
|
readonly fieldOverrides: readonly TopoGraphFieldOverride[];
|
|
199
|
-
readonly
|
|
201
|
+
readonly composes: readonly string[];
|
|
200
202
|
readonly fires: readonly string[];
|
|
201
203
|
readonly governance: Readonly<Record<string, unknown>> | null;
|
|
202
204
|
readonly id: string;
|
|
@@ -211,6 +213,9 @@ export interface TrailDetailReport {
|
|
|
211
213
|
readonly resources: readonly string[];
|
|
212
214
|
readonly surfaceProjections: readonly ShippedSurfaceProjection[];
|
|
213
215
|
readonly surfaces: readonly string[];
|
|
216
|
+
readonly supports: readonly number[];
|
|
217
|
+
readonly version: number | null;
|
|
218
|
+
readonly versions: Readonly<Record<string, TopoGraphVersionEntry>>;
|
|
214
219
|
}
|
|
215
220
|
|
|
216
221
|
export interface SignalDetailReport {
|
|
@@ -231,13 +236,20 @@ export interface SignalDetailReport {
|
|
|
231
236
|
readonly producers: readonly string[];
|
|
232
237
|
}
|
|
233
238
|
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
239
|
+
const countLiveTrailVersionExamples = (trail: AnyTrail): number => {
|
|
240
|
+
let count = 0;
|
|
241
|
+
for (const entry of Object.values(trail.versions ?? {})) {
|
|
242
|
+
if (isArchivedTrailVersionEntry(entry)) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
count += entry.examples?.length ?? 0;
|
|
237
246
|
}
|
|
238
|
-
return
|
|
247
|
+
return count;
|
|
239
248
|
};
|
|
240
249
|
|
|
250
|
+
export const countTrailExamples = (trail: AnyTrail): number =>
|
|
251
|
+
(trail.examples?.length ?? 0) + countLiveTrailVersionExamples(trail);
|
|
252
|
+
|
|
241
253
|
const detectFeatures = (
|
|
242
254
|
app: Topo
|
|
243
255
|
): {
|
|
@@ -246,18 +258,12 @@ const detectFeatures = (
|
|
|
246
258
|
hasOutputSchemas: boolean;
|
|
247
259
|
hasResources: boolean;
|
|
248
260
|
} => {
|
|
249
|
-
const trails = [...app.trails.values()]
|
|
250
|
-
(item) => item as unknown as Record<string, unknown>
|
|
251
|
-
);
|
|
261
|
+
const trails = [...app.trails.values()];
|
|
252
262
|
return {
|
|
253
|
-
hasDetours: trails.some((
|
|
254
|
-
hasExamples: trails.some((
|
|
255
|
-
hasOutputSchemas: trails.some((
|
|
256
|
-
hasResources: trails.some(
|
|
257
|
-
(r) =>
|
|
258
|
-
Array.isArray(r['resources']) &&
|
|
259
|
-
(r['resources'] as unknown[]).length > 0
|
|
260
|
-
),
|
|
263
|
+
hasDetours: trails.some((trail) => trail.detours.length > 0),
|
|
264
|
+
hasExamples: trails.some((trail) => countTrailExamples(trail) > 0),
|
|
265
|
+
hasOutputSchemas: trails.some((trail) => trail.output !== undefined),
|
|
266
|
+
hasResources: trails.some((trail) => trail.resources.length > 0),
|
|
261
267
|
};
|
|
262
268
|
};
|
|
263
269
|
|
|
@@ -386,11 +392,7 @@ export const deriveSurveyList = (app: Topo): SurveyListReport => {
|
|
|
386
392
|
const safety = safetyLabel(
|
|
387
393
|
item as unknown as { intent?: 'read' | 'write' | 'destroy' }
|
|
388
394
|
);
|
|
389
|
-
const examples =
|
|
390
|
-
(item as unknown as { examples?: unknown[] }).examples
|
|
391
|
-
)
|
|
392
|
-
? (item as unknown as { examples: unknown[] }).examples.length
|
|
393
|
-
: 0;
|
|
395
|
+
const examples = countTrailExamples(item);
|
|
394
396
|
|
|
395
397
|
return {
|
|
396
398
|
activatedBy: trailActivation.activatedBy,
|
|
@@ -657,6 +659,14 @@ const deriveResolvedSurfaceProjections = (
|
|
|
657
659
|
: deriveShippedSurfaceProjectionsForTrail(app, trail, topoGraph);
|
|
658
660
|
};
|
|
659
661
|
|
|
662
|
+
const deriveResolvedTrailVersionDetail = (
|
|
663
|
+
topoEntry: TopoGraphEntry | undefined
|
|
664
|
+
): Pick<TrailDetailReport, 'supports' | 'version' | 'versions'> => ({
|
|
665
|
+
supports: topoEntry?.supports ?? [],
|
|
666
|
+
version: topoEntry?.version ?? null,
|
|
667
|
+
versions: topoEntry?.versions ?? {},
|
|
668
|
+
});
|
|
669
|
+
|
|
660
670
|
const deriveResolvedTrailGraphDetail = (
|
|
661
671
|
app: Topo | undefined,
|
|
662
672
|
trailId: string,
|
|
@@ -676,6 +686,9 @@ const deriveResolvedTrailGraphDetail = (
|
|
|
676
686
|
| 'output'
|
|
677
687
|
| 'surfaceProjections'
|
|
678
688
|
| 'surfaces'
|
|
689
|
+
| 'supports'
|
|
690
|
+
| 'version'
|
|
691
|
+
| 'versions'
|
|
679
692
|
> => {
|
|
680
693
|
const topoGraph =
|
|
681
694
|
topoGraphOverride ?? (app === undefined ? undefined : deriveTopoGraph(app));
|
|
@@ -714,6 +727,7 @@ const deriveResolvedTrailGraphDetail = (
|
|
|
714
727
|
topoGraph
|
|
715
728
|
),
|
|
716
729
|
surfaces: topoEntry?.surfaces ?? [],
|
|
730
|
+
...deriveResolvedTrailVersionDetail(topoEntry),
|
|
717
731
|
};
|
|
718
732
|
};
|
|
719
733
|
|
|
@@ -765,9 +779,9 @@ export const deriveTrailDetail = (
|
|
|
765
779
|
topo: topoLayerNames,
|
|
766
780
|
trail: trailLayerNames,
|
|
767
781
|
},
|
|
782
|
+
composes: item.composes.toSorted(),
|
|
768
783
|
contourDetails: graphDetail.contourDetails,
|
|
769
784
|
contours: graphDetail.contours,
|
|
770
|
-
crosses: item.crosses.toSorted(),
|
|
771
785
|
description: item.description ?? null,
|
|
772
786
|
detours: formatTrailDetours(item),
|
|
773
787
|
examples: item.examples ?? [],
|
|
@@ -784,7 +798,10 @@ export const deriveTrailDetail = (
|
|
|
784
798
|
pattern: item.pattern ?? null,
|
|
785
799
|
resources: item.resources.map((resource) => resource.id).toSorted(),
|
|
786
800
|
safety,
|
|
801
|
+
supports: graphDetail.supports,
|
|
787
802
|
surfaceProjections: graphDetail.surfaceProjections,
|
|
788
803
|
surfaces: graphDetail.surfaces,
|
|
804
|
+
version: graphDetail.version,
|
|
805
|
+
versions: graphDetail.versions,
|
|
789
806
|
};
|
|
790
807
|
};
|
|
@@ -9,6 +9,7 @@ import { Database } from 'bun:sqlite';
|
|
|
9
9
|
|
|
10
10
|
import type { Topo } from '@ontrails/core';
|
|
11
11
|
import {
|
|
12
|
+
ConflictError,
|
|
12
13
|
deriveTrailsDir,
|
|
13
14
|
InternalError,
|
|
14
15
|
openWriteTrailsDb,
|
|
@@ -20,7 +21,15 @@ import type {
|
|
|
20
21
|
TopoSnapshot,
|
|
21
22
|
} from '@ontrails/topographer';
|
|
22
23
|
import type { StoredTopoExport } from '@ontrails/topographer/backend-support';
|
|
23
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
annotateTopoGraphForces,
|
|
26
|
+
carryForwardTopoGraphForces,
|
|
27
|
+
deriveTopoGraphDiff,
|
|
28
|
+
deriveTopoGraphHash,
|
|
29
|
+
readTopoGraph,
|
|
30
|
+
writeLockManifest,
|
|
31
|
+
writeTopoGraph,
|
|
32
|
+
} from '@ontrails/topographer';
|
|
24
33
|
import {
|
|
25
34
|
createStoredTopoSnapshot,
|
|
26
35
|
getStoredTopoExport,
|
|
@@ -85,19 +94,46 @@ export const deriveCurrentTopoExport = (
|
|
|
85
94
|
|
|
86
95
|
const writeStoredExportArtifacts = async (
|
|
87
96
|
storedExport: StoredTopoExport,
|
|
88
|
-
trailsDir: string
|
|
97
|
+
trailsDir: string,
|
|
98
|
+
options?: { readonly force?: boolean | undefined }
|
|
89
99
|
): Promise<Pick<TopoExportReport, 'hash' | 'lockPath' | 'topoPath'>> => {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
100
|
+
const previousTopo = await readTopoGraph({ dir: trailsDir });
|
|
101
|
+
const nextTopo = JSON.parse(storedExport.topoGraphJson) as TopoGraph;
|
|
102
|
+
const diff =
|
|
103
|
+
previousTopo === null
|
|
104
|
+
? undefined
|
|
105
|
+
: deriveTopoGraphDiff(previousTopo, nextTopo);
|
|
106
|
+
if (diff !== undefined && diff.breaking.length > 0 && !options?.force) {
|
|
107
|
+
throw new ConflictError(
|
|
108
|
+
`Topo contains ${diff.breaking.length} breaking change(s). Add a version entry, revert the change, or rerun with --force.`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const topoGraphBase =
|
|
113
|
+
previousTopo === null
|
|
114
|
+
? nextTopo
|
|
115
|
+
: carryForwardTopoGraphForces(previousTopo, nextTopo);
|
|
116
|
+
const topoGraph =
|
|
117
|
+
diff === undefined || diff.breaking.length === 0
|
|
118
|
+
? topoGraphBase
|
|
119
|
+
: annotateTopoGraphForces(topoGraphBase, diff.breaking);
|
|
120
|
+
const hash = deriveTopoGraphHash(topoGraph);
|
|
121
|
+
const lockManifest = {
|
|
122
|
+
...(JSON.parse(storedExport.lockManifestJson) as LockManifest),
|
|
123
|
+
artifacts: [
|
|
124
|
+
{
|
|
125
|
+
path: 'topo.lock',
|
|
126
|
+
role: 'topo',
|
|
127
|
+
sha256: hash,
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
} satisfies LockManifest;
|
|
131
|
+
|
|
132
|
+
const topoPath = await writeTopoGraph(topoGraph, { dir: trailsDir });
|
|
133
|
+
const lockPath = await writeLockManifest(lockManifest, { dir: trailsDir });
|
|
98
134
|
|
|
99
135
|
return {
|
|
100
|
-
hash
|
|
136
|
+
hash,
|
|
101
137
|
lockPath,
|
|
102
138
|
topoPath,
|
|
103
139
|
};
|
|
@@ -105,7 +141,7 @@ const writeStoredExportArtifacts = async (
|
|
|
105
141
|
|
|
106
142
|
export const exportCurrentTopo = async (
|
|
107
143
|
app: Topo,
|
|
108
|
-
options?: { readonly rootDir?: string }
|
|
144
|
+
options?: { readonly force?: boolean | undefined; readonly rootDir?: string }
|
|
109
145
|
): Promise<Result<TopoExportReport, Error>> => {
|
|
110
146
|
const rootDir = deriveRootDir(options?.rootDir);
|
|
111
147
|
const db = openWriteTrailsDb({ rootDir });
|
|
@@ -117,10 +153,20 @@ export const exportCurrentTopo = async (
|
|
|
117
153
|
}
|
|
118
154
|
|
|
119
155
|
const { snapshot, storedExport } = persisted.value;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
156
|
+
let artifacts: Pick<TopoExportReport, 'hash' | 'lockPath' | 'topoPath'>;
|
|
157
|
+
try {
|
|
158
|
+
artifacts = await writeStoredExportArtifacts(
|
|
159
|
+
storedExport,
|
|
160
|
+
deriveTrailsDir({ rootDir }),
|
|
161
|
+
{ force: options?.force }
|
|
162
|
+
);
|
|
163
|
+
} catch (error: unknown) {
|
|
164
|
+
return Result.err(
|
|
165
|
+
error instanceof Error
|
|
166
|
+
? error
|
|
167
|
+
: new InternalError('Unable to write topo artifacts')
|
|
168
|
+
);
|
|
169
|
+
}
|
|
124
170
|
return Result.ok({ ...artifacts, snapshot });
|
|
125
171
|
} finally {
|
|
126
172
|
db.close();
|
package/src/trails/topo-unpin.ts
CHANGED
|
@@ -20,7 +20,7 @@ export const topoUnpinTrail = trail('topo.unpin', {
|
|
|
20
20
|
|
|
21
21
|
const rootDirResult = resolveTrailRootDir(input.rootDir, ctx.cwd);
|
|
22
22
|
if (rootDirResult.isErr()) {
|
|
23
|
-
return
|
|
23
|
+
return rootDirResult;
|
|
24
24
|
}
|
|
25
25
|
const rootDir = rootDirResult.value;
|
|
26
26
|
return Result.ok(
|
|
@@ -57,5 +57,5 @@ export const topoUnpinTrail = trail('topo.unpin', {
|
|
|
57
57
|
removed: z.boolean(),
|
|
58
58
|
snapshot: topoSnapshotOutput.optional(),
|
|
59
59
|
}),
|
|
60
|
-
permit: { scopes: ['topo:
|
|
60
|
+
permit: { scopes: ['topo:write'] },
|
|
61
61
|
});
|
package/src/trails/topo.ts
CHANGED
|
@@ -76,12 +76,12 @@ export const topoTrail = trail('topo', {
|
|
|
76
76
|
blaze: async (input, ctx) => {
|
|
77
77
|
const rootDirResult = resolveTrailRootDir(input.rootDir, ctx.cwd);
|
|
78
78
|
if (rootDirResult.isErr()) {
|
|
79
|
-
return
|
|
79
|
+
return rootDirResult;
|
|
80
80
|
}
|
|
81
81
|
const rootDir = rootDirResult.value;
|
|
82
82
|
const leaseResult = await tryLoadFreshAppLease(input.module, rootDir);
|
|
83
83
|
if (leaseResult.isErr()) {
|
|
84
|
-
return
|
|
84
|
+
return leaseResult;
|
|
85
85
|
}
|
|
86
86
|
const lease = leaseResult.value;
|
|
87
87
|
try {
|
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { trail } from '@ontrails/core';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
|
|
4
4
|
import { tryLoadFreshAppLease } from './load-app.js';
|
|
5
5
|
import { resolveTrailRootDir } from './root-dir.js';
|
|
6
|
-
import {
|
|
6
|
+
import { validateCurrentTopo } from './topo-read-support.js';
|
|
7
7
|
|
|
8
|
-
export const
|
|
8
|
+
export const validateTrail = trail('validate', {
|
|
9
9
|
blaze: async (input, ctx) => {
|
|
10
10
|
const rootDirResult = resolveTrailRootDir(input.rootDir, ctx.cwd);
|
|
11
11
|
if (rootDirResult.isErr()) {
|
|
12
|
-
return
|
|
12
|
+
return rootDirResult;
|
|
13
13
|
}
|
|
14
14
|
const rootDir = rootDirResult.value;
|
|
15
15
|
const leaseResult = await tryLoadFreshAppLease(input.module, rootDir);
|
|
16
16
|
if (leaseResult.isErr()) {
|
|
17
|
-
return
|
|
17
|
+
return leaseResult;
|
|
18
18
|
}
|
|
19
19
|
const lease = leaseResult.value;
|
|
20
20
|
try {
|
|
21
|
-
return await
|
|
21
|
+
return await validateCurrentTopo(lease.app, { rootDir });
|
|
22
22
|
} finally {
|
|
23
23
|
lease.release();
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
|
-
description: '
|
|
26
|
+
description: 'Validate that committed topo artifacts match the current topo',
|
|
27
27
|
input: z.object({
|
|
28
28
|
module: z.string().optional().describe('Path to the app module'),
|
|
29
29
|
rootDir: z.string().optional().describe('Workspace root directory'),
|