@ontrails/trails 1.0.0-beta.14 → 1.0.0-beta.16
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 +208 -0
- package/README.md +27 -0
- package/package.json +19 -8
- package/src/app.ts +17 -7
- package/src/clack.ts +1 -1
- package/src/cli.ts +304 -10
- package/src/completions.ts +240 -0
- package/src/load-app-mirror.ts +160 -0
- package/src/local-state-io.ts +153 -0
- package/src/project-writes.ts +320 -0
- package/src/run-collision.ts +125 -0
- package/src/run-completions-install.ts +179 -0
- package/src/run-example.ts +149 -0
- package/src/run-examples.ts +148 -0
- package/src/run-quiet.ts +75 -0
- package/src/run-trace.ts +273 -0
- package/src/run-warden.ts +39 -0
- package/src/run-watch.ts +432 -0
- package/src/scaffold-versions.generated.ts +12 -0
- package/src/trails/add-surface.ts +172 -0
- package/src/trails/add-trail.ts +73 -27
- package/src/trails/add-verify.ts +68 -23
- package/src/trails/completions-complete.ts +165 -0
- package/src/trails/completions.ts +47 -0
- package/src/trails/create-scaffold.ts +101 -35
- package/src/trails/create.ts +87 -74
- package/src/trails/dev-clean.ts +31 -22
- package/src/trails/dev-reset.ts +9 -3
- package/src/trails/dev-stats.ts +28 -20
- package/src/trails/dev-support.ts +109 -95
- package/src/trails/draft-promote.ts +351 -107
- package/src/trails/guide.ts +55 -38
- package/src/trails/load-app.ts +712 -38
- package/src/trails/root-dir.ts +21 -0
- package/src/trails/run-example.ts +482 -0
- package/src/trails/run-examples.ts +141 -0
- package/src/trails/run.ts +403 -0
- package/src/trails/survey.ts +517 -186
- package/src/trails/topo-activation.ts +385 -0
- package/src/trails/topo-compile.ts +55 -0
- package/src/trails/topo-history.ts +14 -11
- package/src/trails/topo-output-schemas.ts +175 -0
- package/src/trails/topo-pin.ts +25 -16
- package/src/trails/topo-read-support.ts +178 -238
- package/src/trails/topo-reports.ts +445 -63
- package/src/trails/topo-store-support.ts +67 -35
- package/src/trails/topo-support.ts +93 -147
- package/src/trails/topo-unpin.ts +17 -7
- package/src/trails/topo-verify.ts +19 -10
- package/src/trails/topo.ts +64 -31
- package/src/trails/warden-guide.ts +121 -0
- package/src/trails/warden.ts +137 -47
- package/src/versions.ts +28 -0
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-lint.log +0 -3
- package/.turbo/turbo-typecheck.log +0 -1
- package/__tests__/examples.test.ts +0 -20
- package/dist/bin/trails.d.ts +0 -3
- package/dist/bin/trails.d.ts.map +0 -1
- package/dist/bin/trails.js +0 -4
- package/dist/bin/trails.js.map +0 -1
- package/dist/src/app.d.ts +0 -2
- package/dist/src/app.d.ts.map +0 -1
- package/dist/src/app.js +0 -22
- package/dist/src/app.js.map +0 -1
- package/dist/src/clack.d.ts +0 -9
- package/dist/src/clack.d.ts.map +0 -1
- package/dist/src/clack.js +0 -84
- package/dist/src/clack.js.map +0 -1
- package/dist/src/cli.d.ts +0 -2
- package/dist/src/cli.d.ts.map +0 -1
- package/dist/src/cli.js +0 -13
- package/dist/src/cli.js.map +0 -1
- package/dist/src/trails/add-surface.d.ts +0 -13
- package/dist/src/trails/add-surface.d.ts.map +0 -1
- package/dist/src/trails/add-surface.js +0 -88
- package/dist/src/trails/add-surface.js.map +0 -1
- package/dist/src/trails/add-trail.d.ts +0 -10
- package/dist/src/trails/add-trail.d.ts.map +0 -1
- package/dist/src/trails/add-trail.js +0 -77
- package/dist/src/trails/add-trail.js.map +0 -1
- package/dist/src/trails/add-trailhead.d.ts +0 -13
- package/dist/src/trails/add-trailhead.d.ts.map +0 -1
- package/dist/src/trails/add-trailhead.js +0 -88
- package/dist/src/trails/add-trailhead.js.map +0 -1
- package/dist/src/trails/add-verify.d.ts +0 -10
- package/dist/src/trails/add-verify.d.ts.map +0 -1
- package/dist/src/trails/add-verify.js +0 -67
- package/dist/src/trails/add-verify.js.map +0 -1
- package/dist/src/trails/create-scaffold.d.ts +0 -15
- package/dist/src/trails/create-scaffold.d.ts.map +0 -1
- package/dist/src/trails/create-scaffold.js +0 -288
- package/dist/src/trails/create-scaffold.js.map +0 -1
- package/dist/src/trails/create.d.ts +0 -22
- package/dist/src/trails/create.d.ts.map +0 -1
- package/dist/src/trails/create.js +0 -121
- package/dist/src/trails/create.js.map +0 -1
- package/dist/src/trails/dev-clean.d.ts +0 -9
- package/dist/src/trails/dev-clean.d.ts.map +0 -1
- package/dist/src/trails/dev-clean.js +0 -65
- package/dist/src/trails/dev-clean.js.map +0 -1
- package/dist/src/trails/dev-reset.d.ts +0 -6
- package/dist/src/trails/dev-reset.d.ts.map +0 -1
- package/dist/src/trails/dev-reset.js +0 -38
- package/dist/src/trails/dev-reset.js.map +0 -1
- package/dist/src/trails/dev-stats.d.ts +0 -7
- package/dist/src/trails/dev-stats.d.ts.map +0 -1
- package/dist/src/trails/dev-stats.js +0 -61
- package/dist/src/trails/dev-stats.js.map +0 -1
- package/dist/src/trails/dev-support.d.ts +0 -64
- package/dist/src/trails/dev-support.d.ts.map +0 -1
- package/dist/src/trails/dev-support.js +0 -178
- package/dist/src/trails/dev-support.js.map +0 -1
- package/dist/src/trails/draft-promote.d.ts +0 -18
- package/dist/src/trails/draft-promote.d.ts.map +0 -1
- package/dist/src/trails/draft-promote.js +0 -386
- package/dist/src/trails/draft-promote.js.map +0 -1
- package/dist/src/trails/guide.d.ts +0 -21
- package/dist/src/trails/guide.d.ts.map +0 -1
- package/dist/src/trails/guide.js +0 -64
- package/dist/src/trails/guide.js.map +0 -1
- package/dist/src/trails/load-app.d.ts +0 -6
- package/dist/src/trails/load-app.d.ts.map +0 -1
- package/dist/src/trails/load-app.js +0 -67
- package/dist/src/trails/load-app.js.map +0 -1
- package/dist/src/trails/project.d.ts +0 -8
- package/dist/src/trails/project.d.ts.map +0 -1
- package/dist/src/trails/project.js +0 -54
- package/dist/src/trails/project.js.map +0 -1
- package/dist/src/trails/survey.d.ts +0 -18
- package/dist/src/trails/survey.d.ts.map +0 -1
- package/dist/src/trails/survey.js +0 -212
- package/dist/src/trails/survey.js.map +0 -1
- package/dist/src/trails/topo-constants.d.ts +0 -3
- package/dist/src/trails/topo-constants.d.ts.map +0 -1
- package/dist/src/trails/topo-constants.js +0 -3
- package/dist/src/trails/topo-constants.js.map +0 -1
- package/dist/src/trails/topo-export.d.ts +0 -18
- package/dist/src/trails/topo-export.d.ts.map +0 -1
- package/dist/src/trails/topo-export.js +0 -34
- package/dist/src/trails/topo-export.js.map +0 -1
- package/dist/src/trails/topo-history.d.ts +0 -24
- package/dist/src/trails/topo-history.d.ts.map +0 -1
- package/dist/src/trails/topo-history.js +0 -33
- package/dist/src/trails/topo-history.js.map +0 -1
- package/dist/src/trails/topo-pin.d.ts +0 -21
- package/dist/src/trails/topo-pin.d.ts.map +0 -1
- package/dist/src/trails/topo-pin.js +0 -35
- package/dist/src/trails/topo-pin.js.map +0 -1
- package/dist/src/trails/topo-read-support.d.ts +0 -54
- package/dist/src/trails/topo-read-support.d.ts.map +0 -1
- package/dist/src/trails/topo-read-support.js +0 -178
- package/dist/src/trails/topo-read-support.js.map +0 -1
- package/dist/src/trails/topo-reports.d.ts +0 -50
- package/dist/src/trails/topo-reports.d.ts.map +0 -1
- package/dist/src/trails/topo-reports.js +0 -122
- package/dist/src/trails/topo-reports.js.map +0 -1
- package/dist/src/trails/topo-show.d.ts +0 -23
- package/dist/src/trails/topo-show.d.ts.map +0 -1
- package/dist/src/trails/topo-show.js +0 -53
- package/dist/src/trails/topo-show.js.map +0 -1
- package/dist/src/trails/topo-store-support.d.ts +0 -13
- package/dist/src/trails/topo-store-support.d.ts.map +0 -1
- package/dist/src/trails/topo-store-support.js +0 -55
- package/dist/src/trails/topo-store-support.js.map +0 -1
- package/dist/src/trails/topo-support.d.ts +0 -87
- package/dist/src/trails/topo-support.d.ts.map +0 -1
- package/dist/src/trails/topo-support.js +0 -165
- package/dist/src/trails/topo-support.js.map +0 -1
- package/dist/src/trails/topo-unpin.d.ts +0 -15
- package/dist/src/trails/topo-unpin.d.ts.map +0 -1
- package/dist/src/trails/topo-unpin.js +0 -39
- package/dist/src/trails/topo-unpin.js.map +0 -1
- package/dist/src/trails/topo-verify.d.ts +0 -5
- package/dist/src/trails/topo-verify.d.ts.map +0 -1
- package/dist/src/trails/topo-verify.js +0 -28
- package/dist/src/trails/topo-verify.js.map +0 -1
- package/dist/src/trails/topo.d.ts +0 -5
- package/dist/src/trails/topo.d.ts.map +0 -1
- package/dist/src/trails/topo.js +0 -67
- package/dist/src/trails/topo.js.map +0 -1
- package/dist/src/trails/warden.d.ts +0 -19
- package/dist/src/trails/warden.d.ts.map +0 -1
- package/dist/src/trails/warden.js +0 -89
- package/dist/src/trails/warden.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/src/__tests__/create.test.ts +0 -351
- package/src/__tests__/draft-promote.test.ts +0 -144
- package/src/__tests__/guide.test.ts +0 -91
- package/src/__tests__/load-app.test.ts +0 -58
- package/src/__tests__/survey.test.ts +0 -301
- package/src/__tests__/topo-dev.test.ts +0 -424
- package/src/__tests__/warden.test.ts +0 -74
- package/src/trails/add-trailhead.ts +0 -121
- package/src/trails/topo-export.ts +0 -39
- package/src/trails/topo-show.ts +0 -58
- package/tsconfig.json +0 -9
package/src/trails/topo-pin.ts
CHANGED
|
@@ -1,42 +1,51 @@
|
|
|
1
1
|
import { Result, trail } from '@ontrails/core';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { tryLoadFreshAppLease } from './load-app.js';
|
|
5
|
+
import { resolveTrailRootDir } from './root-dir.js';
|
|
5
6
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
topoPinOutput,
|
|
10
|
-
topoSaveOutput,
|
|
7
|
+
createIsolatedExampleInput,
|
|
8
|
+
pinCurrentTopoSnapshot,
|
|
9
|
+
topoSnapshotOutput,
|
|
11
10
|
} from './topo-support.js';
|
|
12
11
|
|
|
13
12
|
export const topoPinTrail = trail('topo.pin', {
|
|
14
13
|
blaze: async (input, ctx) => {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
const rootDirResult = resolveTrailRootDir(input.rootDir, ctx.cwd);
|
|
15
|
+
if (rootDirResult.isErr()) {
|
|
16
|
+
return Result.err(rootDirResult.error);
|
|
17
|
+
}
|
|
18
|
+
const rootDir = rootDirResult.value;
|
|
19
|
+
const leaseResult = await tryLoadFreshAppLease(input.module, rootDir);
|
|
20
|
+
if (leaseResult.isErr()) {
|
|
21
|
+
return Result.err(leaseResult.error);
|
|
22
|
+
}
|
|
23
|
+
const lease = leaseResult.value;
|
|
24
|
+
try {
|
|
25
|
+
return Result.ok(
|
|
26
|
+
pinCurrentTopoSnapshot(lease.app, { name: input.name, rootDir })
|
|
27
|
+
);
|
|
28
|
+
} finally {
|
|
29
|
+
lease.release();
|
|
30
|
+
}
|
|
18
31
|
},
|
|
19
32
|
description: 'Pin the current topo under a durable name',
|
|
20
33
|
examples: [
|
|
21
34
|
{
|
|
22
35
|
input: {
|
|
23
|
-
...
|
|
36
|
+
...createIsolatedExampleInput('topo-pin'),
|
|
24
37
|
name: 'before-auth-refactor',
|
|
25
38
|
},
|
|
26
39
|
name: 'Pin the current topo',
|
|
27
40
|
},
|
|
28
41
|
],
|
|
29
42
|
input: z.object({
|
|
30
|
-
module: z
|
|
31
|
-
.string()
|
|
32
|
-
.default(DEFAULT_APP_MODULE)
|
|
33
|
-
.describe('Path to the app module'),
|
|
43
|
+
module: z.string().optional().describe('Path to the app module'),
|
|
34
44
|
name: z.string().describe('Pin name'),
|
|
35
45
|
rootDir: z.string().optional().describe('Workspace root directory'),
|
|
36
46
|
}),
|
|
37
47
|
intent: 'write',
|
|
38
48
|
output: z.object({
|
|
39
|
-
|
|
40
|
-
save: topoSaveOutput,
|
|
49
|
+
snapshot: topoSnapshotOutput,
|
|
41
50
|
}),
|
|
42
51
|
});
|
|
@@ -1,212 +1,91 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Read-only topo
|
|
2
|
+
* Read-only live topo consumer helpers.
|
|
3
3
|
*
|
|
4
|
-
* Extracted from topo-support.ts
|
|
5
|
-
* keeping
|
|
4
|
+
* Extracted from topo-support.ts to isolate read-only topo 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';
|
|
11
|
+
import type { Topo, TrailContext } from '@ontrails/core';
|
|
12
12
|
import {
|
|
13
13
|
ConflictError,
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
deriveTrailsDbPath,
|
|
15
|
+
deriveTrailsDir,
|
|
16
16
|
NotFoundError,
|
|
17
17
|
Result,
|
|
18
|
+
SURFACE_LAYER_NAMES_KEY,
|
|
19
|
+
ValidationError,
|
|
18
20
|
} from '@ontrails/core';
|
|
19
|
-
import
|
|
20
|
-
|
|
21
|
+
import { deriveTopoGraph, readLockManifest } from '@ontrails/topographer';
|
|
22
|
+
|
|
23
|
+
import type {
|
|
24
|
+
BriefReport,
|
|
25
|
+
SignalDetailReport,
|
|
26
|
+
SurfaceLayerNames,
|
|
27
|
+
SurveyListReport,
|
|
28
|
+
TrailDetailReport,
|
|
29
|
+
} from './topo-reports.js';
|
|
21
30
|
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
import type {
|
|
31
|
+
deriveBriefReport,
|
|
32
|
+
deriveResourceDetail,
|
|
33
|
+
deriveSignalDetail,
|
|
34
|
+
deriveSurveyList,
|
|
35
|
+
deriveTrailDetail,
|
|
36
|
+
} from './topo-reports.js';
|
|
37
|
+
import type { ActivationGraphReport } from './topo-activation.js';
|
|
38
|
+
import { deriveActivationGraph } from './topo-activation.js';
|
|
29
39
|
import type { TopoSummaryReport, TopoVerifyReport } from './topo-support.js';
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
createCurrentTopoSave,
|
|
33
|
-
LOCK_PATH,
|
|
34
|
-
resolveRootDir,
|
|
35
|
-
} from './topo-support.js';
|
|
36
|
-
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
|
-
// Internal types
|
|
39
|
-
// ---------------------------------------------------------------------------
|
|
40
|
+
import { deriveRootDir, LOCK_PATH } from './topo-support.js';
|
|
41
|
+
import { deriveCurrentTopoExport } from './topo-store-support.js';
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
readonly detours?: Readonly<Record<string, readonly string[]>>;
|
|
43
|
-
readonly kind: 'provision' | 'signal' | 'trail';
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
interface CurrentTrailDetail {
|
|
47
|
-
readonly crosses: string[];
|
|
48
|
-
readonly description: string | null;
|
|
49
|
-
readonly detours: Readonly<Record<string, readonly string[]>> | null;
|
|
50
|
-
readonly examples: unknown[];
|
|
51
|
-
readonly id: string;
|
|
52
|
-
readonly intent: 'destroy' | 'read' | 'write';
|
|
53
|
-
readonly kind: string;
|
|
54
|
-
readonly provisions: string[];
|
|
55
|
-
readonly safety: string;
|
|
56
|
-
}
|
|
43
|
+
export type CurrentTrailDetail = TrailDetailReport;
|
|
57
44
|
|
|
58
|
-
interface
|
|
45
|
+
export interface CurrentResourceDetail {
|
|
59
46
|
readonly description: string | null;
|
|
60
47
|
readonly health: 'available' | 'none';
|
|
61
48
|
readonly id: string;
|
|
62
|
-
readonly kind: '
|
|
49
|
+
readonly kind: 'resource';
|
|
63
50
|
readonly lifetime: 'singleton';
|
|
64
|
-
readonly usedBy: string[];
|
|
51
|
+
readonly usedBy: readonly string[];
|
|
65
52
|
}
|
|
66
53
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const topoStoreRef = (saveId: string) => ({ saveId }) as const;
|
|
72
|
-
|
|
73
|
-
const hasCommittedLock = (trailsDir: string): boolean =>
|
|
74
|
-
existsSync(join(trailsDir, 'trails.lock')) ||
|
|
75
|
-
existsSync(join(trailsDir, 'trailhead.lock'));
|
|
54
|
+
export type CurrentTopoDetail =
|
|
55
|
+
| CurrentResourceDetail
|
|
56
|
+
| CurrentTrailDetail
|
|
57
|
+
| SignalDetailReport;
|
|
76
58
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
JSON.parse(trailheadMapJson) as {
|
|
82
|
-
readonly entries: readonly StoredTrailheadMapEntry[];
|
|
83
|
-
}
|
|
84
|
-
).entries;
|
|
85
|
-
|
|
86
|
-
const buildBriefReportFromStore = (
|
|
87
|
-
app: Topo,
|
|
88
|
-
store: ReturnType<typeof createTopoStore>,
|
|
89
|
-
ref: ReturnType<typeof topoStoreRef>,
|
|
90
|
-
save: TopoSaveRecord
|
|
91
|
-
): BriefReport => {
|
|
92
|
-
const trails = store.trails.list({ save: ref });
|
|
93
|
-
const exportRecord = store.exports.get(ref);
|
|
94
|
-
const trailEntries =
|
|
95
|
-
exportRecord === undefined
|
|
96
|
-
? []
|
|
97
|
-
: readTrailheadEntries(exportRecord.trailheadMapJson).filter(
|
|
98
|
-
(entry) => entry.kind === 'trail'
|
|
99
|
-
);
|
|
59
|
+
export interface CurrentTopoMatch {
|
|
60
|
+
readonly kind: CurrentTopoDetail['kind'];
|
|
61
|
+
readonly detail: CurrentTopoDetail;
|
|
62
|
+
}
|
|
100
63
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
(entry) => (Object.keys(entry.detours ?? {}).length ?? 0) > 0
|
|
106
|
-
),
|
|
107
|
-
examples: trails.some((trail) => trail.hasExamples),
|
|
108
|
-
outputSchemas: trails.some((trail) => trail.hasOutput),
|
|
109
|
-
provisions: save.provisionCount > 0,
|
|
110
|
-
signals: save.signalCount > 0,
|
|
111
|
-
},
|
|
112
|
-
name: app.name,
|
|
113
|
-
provisions: save.provisionCount,
|
|
114
|
-
signals: save.signalCount,
|
|
115
|
-
trails: save.trailCount,
|
|
116
|
-
version: REPORT_VERSION,
|
|
117
|
-
};
|
|
118
|
-
};
|
|
64
|
+
export interface CurrentTopoReadOptions {
|
|
65
|
+
readonly rootDir?: string | undefined;
|
|
66
|
+
readonly surfaceLayerNames?: Partial<SurfaceLayerNames> | undefined;
|
|
67
|
+
}
|
|
119
68
|
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
ref: ReturnType<typeof topoStoreRef>
|
|
123
|
-
): SurveyListReport => {
|
|
124
|
-
const trails = store.trails.list({ save: ref });
|
|
125
|
-
const provisions = store.provisions.list({ save: ref });
|
|
69
|
+
const isStringArray = (value: unknown): value is readonly string[] =>
|
|
70
|
+
Array.isArray(value) && value.every((item) => typeof item === 'string');
|
|
126
71
|
|
|
72
|
+
export const readSurfaceLayerNamesFromContext = (
|
|
73
|
+
ctx: TrailContext
|
|
74
|
+
): Partial<SurfaceLayerNames> => {
|
|
75
|
+
const value = ctx.extensions?.[SURFACE_LAYER_NAMES_KEY];
|
|
76
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
77
|
+
return {};
|
|
78
|
+
}
|
|
79
|
+
const raw = value as Record<string, unknown>;
|
|
127
80
|
return {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
id: trail.id,
|
|
132
|
-
kind: trail.kind,
|
|
133
|
-
safety: trail.safety,
|
|
134
|
-
})),
|
|
135
|
-
provisionCount: provisions.length,
|
|
136
|
-
provisions: provisions.map((provision) => ({
|
|
137
|
-
description: provision.description,
|
|
138
|
-
health: provision.health,
|
|
139
|
-
id: provision.id,
|
|
140
|
-
kind: provision.kind,
|
|
141
|
-
lifetime: provision.lifetime,
|
|
142
|
-
usedBy: provision.usedBy,
|
|
143
|
-
})),
|
|
81
|
+
...(isStringArray(raw['cli']) ? { cli: raw['cli'] } : {}),
|
|
82
|
+
...(isStringArray(raw['http']) ? { http: raw['http'] } : {}),
|
|
83
|
+
...(isStringArray(raw['mcp']) ? { mcp: raw['mcp'] } : {}),
|
|
144
84
|
};
|
|
145
85
|
};
|
|
146
86
|
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
): CurrentTrailDetail => ({
|
|
150
|
-
crosses: [...(detail?.crosses ?? [])],
|
|
151
|
-
description: detail?.description ?? null,
|
|
152
|
-
detours: detail?.detours ?? null,
|
|
153
|
-
examples: [...(detail?.examples ?? [])],
|
|
154
|
-
id: detail?.id ?? '',
|
|
155
|
-
intent: detail?.intent ?? 'write',
|
|
156
|
-
kind: detail?.kind ?? 'trail',
|
|
157
|
-
provisions: [...(detail?.provisions ?? [])],
|
|
158
|
-
safety: detail?.safety ?? '-',
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
const buildProvisionDetailFromStore = (
|
|
162
|
-
provision: NonNullable<
|
|
163
|
-
ReturnType<ReturnType<typeof createTopoStore>['provisions']['get']>
|
|
164
|
-
>
|
|
165
|
-
): CurrentProvisionDetail => ({
|
|
166
|
-
description: provision.description,
|
|
167
|
-
health: provision.health,
|
|
168
|
-
id: provision.id,
|
|
169
|
-
kind: provision.kind,
|
|
170
|
-
lifetime: provision.lifetime,
|
|
171
|
-
usedBy: [...provision.usedBy],
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
// ---------------------------------------------------------------------------
|
|
175
|
-
// withCurrentTopoStore
|
|
176
|
-
// ---------------------------------------------------------------------------
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Run a read callback against the latest topo store state.
|
|
180
|
-
*
|
|
181
|
-
* Uses the most recent existing save when available, only creating a new save
|
|
182
|
-
* when no prior save exists. This avoids unbounded save accumulation from
|
|
183
|
-
* read-only operations like survey, guide, and show.
|
|
184
|
-
*/
|
|
185
|
-
const withCurrentTopoStore = <T>(
|
|
186
|
-
app: Topo,
|
|
187
|
-
rootDir: string,
|
|
188
|
-
read: (
|
|
189
|
-
store: ReturnType<typeof createTopoStore>,
|
|
190
|
-
ref: ReturnType<typeof topoStoreRef>,
|
|
191
|
-
save: TopoSaveRecord
|
|
192
|
-
) => T
|
|
193
|
-
): T => {
|
|
194
|
-
const dbPath = resolveTrailsDbPath({ rootDir });
|
|
195
|
-
const existingSave = existsSync(dbPath)
|
|
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 });
|
|
207
|
-
const store = createTopoStore({ rootDir });
|
|
208
|
-
return read(store, topoStoreRef(save.id), save);
|
|
209
|
-
};
|
|
87
|
+
const hasCommittedLock = (trailsDir: string): boolean =>
|
|
88
|
+
existsSync(join(trailsDir, 'trails.lock'));
|
|
210
89
|
|
|
211
90
|
// ---------------------------------------------------------------------------
|
|
212
91
|
// Public read-only consumers
|
|
@@ -216,115 +95,176 @@ export const buildTopoSummary = (
|
|
|
216
95
|
app: Topo,
|
|
217
96
|
options?: { readonly rootDir?: string }
|
|
218
97
|
): TopoSummaryReport => {
|
|
219
|
-
const rootDir =
|
|
220
|
-
const trailsDir =
|
|
221
|
-
return
|
|
222
|
-
app:
|
|
223
|
-
dbPath:
|
|
224
|
-
list:
|
|
98
|
+
const rootDir = deriveRootDir(options?.rootDir);
|
|
99
|
+
const trailsDir = deriveTrailsDir({ rootDir });
|
|
100
|
+
return {
|
|
101
|
+
app: deriveBriefReport(app),
|
|
102
|
+
dbPath: deriveTrailsDbPath({ rootDir }),
|
|
103
|
+
list: deriveSurveyList(app),
|
|
225
104
|
lockExists: hasCommittedLock(trailsDir),
|
|
226
105
|
lockPath: LOCK_PATH,
|
|
227
|
-
}
|
|
106
|
+
};
|
|
228
107
|
};
|
|
229
108
|
|
|
230
109
|
export const buildCurrentTopoBrief = (
|
|
231
110
|
app: Topo,
|
|
232
|
-
|
|
233
|
-
): BriefReport =>
|
|
234
|
-
const rootDir = resolveRootDir(options?.rootDir);
|
|
235
|
-
return withCurrentTopoStore(app, rootDir, (store, ref, save) =>
|
|
236
|
-
buildBriefReportFromStore(app, store, ref, save)
|
|
237
|
-
);
|
|
238
|
-
};
|
|
111
|
+
_options?: { readonly rootDir?: string }
|
|
112
|
+
): BriefReport => deriveBriefReport(app);
|
|
239
113
|
|
|
240
114
|
export const buildCurrentTopoList = (
|
|
241
115
|
app: Topo,
|
|
242
|
-
|
|
243
|
-
): SurveyListReport =>
|
|
244
|
-
const rootDir = resolveRootDir(options?.rootDir);
|
|
245
|
-
return withCurrentTopoStore(app, rootDir, (store, ref) =>
|
|
246
|
-
buildSurveyListFromStore(store, ref)
|
|
247
|
-
);
|
|
248
|
-
};
|
|
116
|
+
_options?: { readonly rootDir?: string }
|
|
117
|
+
): SurveyListReport => deriveSurveyList(app);
|
|
249
118
|
|
|
250
119
|
export const buildCurrentGuideEntries = (
|
|
251
120
|
app: Topo,
|
|
252
|
-
|
|
121
|
+
_options?: { readonly rootDir?: string }
|
|
253
122
|
): readonly {
|
|
254
123
|
readonly description: string;
|
|
255
124
|
readonly exampleCount: number;
|
|
256
125
|
readonly id: string;
|
|
257
|
-
readonly kind:
|
|
258
|
-
}[] =>
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
126
|
+
readonly kind: 'trail';
|
|
127
|
+
}[] =>
|
|
128
|
+
app
|
|
129
|
+
.list()
|
|
130
|
+
.map((trail) => ({
|
|
262
131
|
description: trail.description ?? '(no description)',
|
|
263
|
-
exampleCount: trail.
|
|
132
|
+
exampleCount: trail.examples?.length ?? 0,
|
|
264
133
|
id: trail.id,
|
|
265
|
-
kind: trail
|
|
134
|
+
kind: 'trail' as const,
|
|
266
135
|
}))
|
|
267
|
-
|
|
136
|
+
.toSorted((a, b) => a.id.localeCompare(b.id));
|
|
137
|
+
|
|
138
|
+
export const buildCurrentTrailDetail = (
|
|
139
|
+
app: Topo,
|
|
140
|
+
id: string,
|
|
141
|
+
options?: CurrentTopoReadOptions
|
|
142
|
+
): CurrentTrailDetail | undefined => {
|
|
143
|
+
const trail = app.get(id);
|
|
144
|
+
return trail === undefined
|
|
145
|
+
? undefined
|
|
146
|
+
: deriveTrailDetail(trail, app, undefined, {
|
|
147
|
+
surfaceLayerNames: options?.surfaceLayerNames,
|
|
148
|
+
});
|
|
268
149
|
};
|
|
269
150
|
|
|
151
|
+
export const buildCurrentResourceDetail = (
|
|
152
|
+
app: Topo,
|
|
153
|
+
id: string,
|
|
154
|
+
_options?: { readonly rootDir?: string }
|
|
155
|
+
): CurrentResourceDetail | undefined =>
|
|
156
|
+
app.getResource(id) === undefined
|
|
157
|
+
? undefined
|
|
158
|
+
: (deriveResourceDetail(app, id) as CurrentResourceDetail);
|
|
159
|
+
|
|
160
|
+
export const buildCurrentSignalDetail = (
|
|
161
|
+
app: Topo,
|
|
162
|
+
id: string,
|
|
163
|
+
_options?: { readonly rootDir?: string }
|
|
164
|
+
): SignalDetailReport | undefined => deriveSignalDetail(app, id);
|
|
165
|
+
|
|
270
166
|
export const buildCurrentTopoDetail = (
|
|
271
167
|
app: Topo,
|
|
272
168
|
id: string,
|
|
273
|
-
options?:
|
|
274
|
-
):
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
if (trail !== undefined) {
|
|
279
|
-
return buildTrailDetailFromStore(trail);
|
|
280
|
-
}
|
|
169
|
+
options?: CurrentTopoReadOptions
|
|
170
|
+
): CurrentTopoDetail | undefined =>
|
|
171
|
+
buildCurrentTrailDetail(app, id, options) ??
|
|
172
|
+
buildCurrentResourceDetail(app, id) ??
|
|
173
|
+
buildCurrentSignalDetail(app, id);
|
|
281
174
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
175
|
+
export const buildCurrentTopoMatches = (
|
|
176
|
+
app: Topo,
|
|
177
|
+
id: string,
|
|
178
|
+
options?: CurrentTopoReadOptions
|
|
179
|
+
): readonly CurrentTopoMatch[] => {
|
|
180
|
+
const matches: CurrentTopoMatch[] = [];
|
|
181
|
+
let activationGraph: ActivationGraphReport | undefined;
|
|
182
|
+
const getActivationGraph = (): ActivationGraphReport =>
|
|
183
|
+
(activationGraph ??= deriveActivationGraph(app));
|
|
184
|
+
let topoGraph: ReturnType<typeof deriveTopoGraph> | undefined;
|
|
185
|
+
const getTopoGraph = (): ReturnType<typeof deriveTopoGraph> =>
|
|
186
|
+
(topoGraph ??= deriveTopoGraph(app));
|
|
187
|
+
|
|
188
|
+
const trail = app.get(id);
|
|
189
|
+
if (trail !== undefined) {
|
|
190
|
+
matches.push({
|
|
191
|
+
detail: deriveTrailDetail(trail, app, getActivationGraph(), {
|
|
192
|
+
surfaceLayerNames: options?.surfaceLayerNames,
|
|
193
|
+
topoGraph: getTopoGraph(),
|
|
194
|
+
}),
|
|
195
|
+
kind: 'trail',
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const resource = buildCurrentResourceDetail(app, id);
|
|
200
|
+
if (resource !== undefined) {
|
|
201
|
+
matches.push({ detail: resource, kind: 'resource' });
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const signal = deriveSignalDetail(app, id, activationGraph);
|
|
205
|
+
if (signal !== undefined) {
|
|
206
|
+
matches.push({ detail: signal, kind: 'signal' });
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return matches;
|
|
287
210
|
};
|
|
288
211
|
|
|
289
212
|
export const verifyCurrentTopo = async (
|
|
290
213
|
app: Topo,
|
|
291
214
|
options?: { readonly rootDir?: string }
|
|
292
215
|
): Promise<Result<TopoVerifyReport, Error>> => {
|
|
293
|
-
const rootDir =
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
216
|
+
const rootDir = deriveRootDir(options?.rootDir);
|
|
217
|
+
let lockManifest: Awaited<ReturnType<typeof readLockManifest>>;
|
|
218
|
+
try {
|
|
219
|
+
lockManifest = await readLockManifest({
|
|
220
|
+
dir: deriveTrailsDir({ rootDir }),
|
|
221
|
+
});
|
|
222
|
+
} catch (error) {
|
|
223
|
+
const message =
|
|
224
|
+
error instanceof Error
|
|
225
|
+
? error.message
|
|
226
|
+
: 'Unable to read committed trails.lock manifest.';
|
|
227
|
+
return Result.err(
|
|
228
|
+
error instanceof Error
|
|
229
|
+
? new ValidationError(message, { cause: error })
|
|
230
|
+
: new ValidationError(message)
|
|
231
|
+
);
|
|
232
|
+
}
|
|
297
233
|
|
|
298
|
-
if (
|
|
234
|
+
if (lockManifest === null) {
|
|
299
235
|
return Result.err(
|
|
300
236
|
new NotFoundError(
|
|
301
|
-
'No committed trails.lock found. Run `trails topo
|
|
237
|
+
'No committed trails.lock found. Run `trails topo compile` first.'
|
|
302
238
|
)
|
|
303
239
|
);
|
|
304
240
|
}
|
|
305
241
|
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
242
|
+
const currentExport = deriveCurrentTopoExport(app, { rootDir });
|
|
243
|
+
if (currentExport.isErr()) {
|
|
244
|
+
return currentExport;
|
|
245
|
+
}
|
|
246
|
+
const currentHash = currentExport.value.topoGraphHash;
|
|
247
|
+
const topoArtifact = lockManifest.artifacts.find(
|
|
248
|
+
(artifact) => artifact.role === 'topo' && artifact.path === 'topo.lock'
|
|
310
249
|
);
|
|
311
|
-
|
|
312
|
-
if (currentHash === undefined) {
|
|
250
|
+
if (topoArtifact === undefined) {
|
|
313
251
|
return Result.err(
|
|
314
|
-
new
|
|
252
|
+
new NotFoundError(
|
|
253
|
+
'No topo.lock artifact found in trails.lock. Run `trails topo compile` first.'
|
|
254
|
+
)
|
|
315
255
|
);
|
|
316
256
|
}
|
|
317
257
|
|
|
318
|
-
if (
|
|
258
|
+
if (topoArtifact.sha256 !== currentHash) {
|
|
319
259
|
return Result.err(
|
|
320
260
|
new ConflictError(
|
|
321
|
-
'trails.lock is stale. Run `trails topo
|
|
261
|
+
'trails.lock is stale. Run `trails topo compile` to refresh it.'
|
|
322
262
|
)
|
|
323
263
|
);
|
|
324
264
|
}
|
|
325
265
|
|
|
326
266
|
return Result.ok({
|
|
327
|
-
committedHash:
|
|
267
|
+
committedHash: topoArtifact.sha256,
|
|
328
268
|
currentHash,
|
|
329
269
|
lockPath: LOCK_PATH,
|
|
330
270
|
stale: false,
|