@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.
Files changed (197) hide show
  1. package/CHANGELOG.md +208 -0
  2. package/README.md +27 -0
  3. package/package.json +19 -8
  4. package/src/app.ts +17 -7
  5. package/src/clack.ts +1 -1
  6. package/src/cli.ts +304 -10
  7. package/src/completions.ts +240 -0
  8. package/src/load-app-mirror.ts +160 -0
  9. package/src/local-state-io.ts +153 -0
  10. package/src/project-writes.ts +320 -0
  11. package/src/run-collision.ts +125 -0
  12. package/src/run-completions-install.ts +179 -0
  13. package/src/run-example.ts +149 -0
  14. package/src/run-examples.ts +148 -0
  15. package/src/run-quiet.ts +75 -0
  16. package/src/run-trace.ts +273 -0
  17. package/src/run-warden.ts +39 -0
  18. package/src/run-watch.ts +432 -0
  19. package/src/scaffold-versions.generated.ts +12 -0
  20. package/src/trails/add-surface.ts +172 -0
  21. package/src/trails/add-trail.ts +73 -27
  22. package/src/trails/add-verify.ts +68 -23
  23. package/src/trails/completions-complete.ts +165 -0
  24. package/src/trails/completions.ts +47 -0
  25. package/src/trails/create-scaffold.ts +101 -35
  26. package/src/trails/create.ts +87 -74
  27. package/src/trails/dev-clean.ts +31 -22
  28. package/src/trails/dev-reset.ts +9 -3
  29. package/src/trails/dev-stats.ts +28 -20
  30. package/src/trails/dev-support.ts +109 -95
  31. package/src/trails/draft-promote.ts +351 -107
  32. package/src/trails/guide.ts +55 -38
  33. package/src/trails/load-app.ts +712 -38
  34. package/src/trails/root-dir.ts +21 -0
  35. package/src/trails/run-example.ts +482 -0
  36. package/src/trails/run-examples.ts +141 -0
  37. package/src/trails/run.ts +403 -0
  38. package/src/trails/survey.ts +517 -186
  39. package/src/trails/topo-activation.ts +385 -0
  40. package/src/trails/topo-compile.ts +55 -0
  41. package/src/trails/topo-history.ts +14 -11
  42. package/src/trails/topo-output-schemas.ts +175 -0
  43. package/src/trails/topo-pin.ts +25 -16
  44. package/src/trails/topo-read-support.ts +178 -238
  45. package/src/trails/topo-reports.ts +445 -63
  46. package/src/trails/topo-store-support.ts +67 -35
  47. package/src/trails/topo-support.ts +93 -147
  48. package/src/trails/topo-unpin.ts +17 -7
  49. package/src/trails/topo-verify.ts +19 -10
  50. package/src/trails/topo.ts +64 -31
  51. package/src/trails/warden-guide.ts +121 -0
  52. package/src/trails/warden.ts +137 -47
  53. package/src/versions.ts +28 -0
  54. package/.turbo/turbo-build.log +0 -1
  55. package/.turbo/turbo-lint.log +0 -3
  56. package/.turbo/turbo-typecheck.log +0 -1
  57. package/__tests__/examples.test.ts +0 -20
  58. package/dist/bin/trails.d.ts +0 -3
  59. package/dist/bin/trails.d.ts.map +0 -1
  60. package/dist/bin/trails.js +0 -4
  61. package/dist/bin/trails.js.map +0 -1
  62. package/dist/src/app.d.ts +0 -2
  63. package/dist/src/app.d.ts.map +0 -1
  64. package/dist/src/app.js +0 -22
  65. package/dist/src/app.js.map +0 -1
  66. package/dist/src/clack.d.ts +0 -9
  67. package/dist/src/clack.d.ts.map +0 -1
  68. package/dist/src/clack.js +0 -84
  69. package/dist/src/clack.js.map +0 -1
  70. package/dist/src/cli.d.ts +0 -2
  71. package/dist/src/cli.d.ts.map +0 -1
  72. package/dist/src/cli.js +0 -13
  73. package/dist/src/cli.js.map +0 -1
  74. package/dist/src/trails/add-surface.d.ts +0 -13
  75. package/dist/src/trails/add-surface.d.ts.map +0 -1
  76. package/dist/src/trails/add-surface.js +0 -88
  77. package/dist/src/trails/add-surface.js.map +0 -1
  78. package/dist/src/trails/add-trail.d.ts +0 -10
  79. package/dist/src/trails/add-trail.d.ts.map +0 -1
  80. package/dist/src/trails/add-trail.js +0 -77
  81. package/dist/src/trails/add-trail.js.map +0 -1
  82. package/dist/src/trails/add-trailhead.d.ts +0 -13
  83. package/dist/src/trails/add-trailhead.d.ts.map +0 -1
  84. package/dist/src/trails/add-trailhead.js +0 -88
  85. package/dist/src/trails/add-trailhead.js.map +0 -1
  86. package/dist/src/trails/add-verify.d.ts +0 -10
  87. package/dist/src/trails/add-verify.d.ts.map +0 -1
  88. package/dist/src/trails/add-verify.js +0 -67
  89. package/dist/src/trails/add-verify.js.map +0 -1
  90. package/dist/src/trails/create-scaffold.d.ts +0 -15
  91. package/dist/src/trails/create-scaffold.d.ts.map +0 -1
  92. package/dist/src/trails/create-scaffold.js +0 -288
  93. package/dist/src/trails/create-scaffold.js.map +0 -1
  94. package/dist/src/trails/create.d.ts +0 -22
  95. package/dist/src/trails/create.d.ts.map +0 -1
  96. package/dist/src/trails/create.js +0 -121
  97. package/dist/src/trails/create.js.map +0 -1
  98. package/dist/src/trails/dev-clean.d.ts +0 -9
  99. package/dist/src/trails/dev-clean.d.ts.map +0 -1
  100. package/dist/src/trails/dev-clean.js +0 -65
  101. package/dist/src/trails/dev-clean.js.map +0 -1
  102. package/dist/src/trails/dev-reset.d.ts +0 -6
  103. package/dist/src/trails/dev-reset.d.ts.map +0 -1
  104. package/dist/src/trails/dev-reset.js +0 -38
  105. package/dist/src/trails/dev-reset.js.map +0 -1
  106. package/dist/src/trails/dev-stats.d.ts +0 -7
  107. package/dist/src/trails/dev-stats.d.ts.map +0 -1
  108. package/dist/src/trails/dev-stats.js +0 -61
  109. package/dist/src/trails/dev-stats.js.map +0 -1
  110. package/dist/src/trails/dev-support.d.ts +0 -64
  111. package/dist/src/trails/dev-support.d.ts.map +0 -1
  112. package/dist/src/trails/dev-support.js +0 -178
  113. package/dist/src/trails/dev-support.js.map +0 -1
  114. package/dist/src/trails/draft-promote.d.ts +0 -18
  115. package/dist/src/trails/draft-promote.d.ts.map +0 -1
  116. package/dist/src/trails/draft-promote.js +0 -386
  117. package/dist/src/trails/draft-promote.js.map +0 -1
  118. package/dist/src/trails/guide.d.ts +0 -21
  119. package/dist/src/trails/guide.d.ts.map +0 -1
  120. package/dist/src/trails/guide.js +0 -64
  121. package/dist/src/trails/guide.js.map +0 -1
  122. package/dist/src/trails/load-app.d.ts +0 -6
  123. package/dist/src/trails/load-app.d.ts.map +0 -1
  124. package/dist/src/trails/load-app.js +0 -67
  125. package/dist/src/trails/load-app.js.map +0 -1
  126. package/dist/src/trails/project.d.ts +0 -8
  127. package/dist/src/trails/project.d.ts.map +0 -1
  128. package/dist/src/trails/project.js +0 -54
  129. package/dist/src/trails/project.js.map +0 -1
  130. package/dist/src/trails/survey.d.ts +0 -18
  131. package/dist/src/trails/survey.d.ts.map +0 -1
  132. package/dist/src/trails/survey.js +0 -212
  133. package/dist/src/trails/survey.js.map +0 -1
  134. package/dist/src/trails/topo-constants.d.ts +0 -3
  135. package/dist/src/trails/topo-constants.d.ts.map +0 -1
  136. package/dist/src/trails/topo-constants.js +0 -3
  137. package/dist/src/trails/topo-constants.js.map +0 -1
  138. package/dist/src/trails/topo-export.d.ts +0 -18
  139. package/dist/src/trails/topo-export.d.ts.map +0 -1
  140. package/dist/src/trails/topo-export.js +0 -34
  141. package/dist/src/trails/topo-export.js.map +0 -1
  142. package/dist/src/trails/topo-history.d.ts +0 -24
  143. package/dist/src/trails/topo-history.d.ts.map +0 -1
  144. package/dist/src/trails/topo-history.js +0 -33
  145. package/dist/src/trails/topo-history.js.map +0 -1
  146. package/dist/src/trails/topo-pin.d.ts +0 -21
  147. package/dist/src/trails/topo-pin.d.ts.map +0 -1
  148. package/dist/src/trails/topo-pin.js +0 -35
  149. package/dist/src/trails/topo-pin.js.map +0 -1
  150. package/dist/src/trails/topo-read-support.d.ts +0 -54
  151. package/dist/src/trails/topo-read-support.d.ts.map +0 -1
  152. package/dist/src/trails/topo-read-support.js +0 -178
  153. package/dist/src/trails/topo-read-support.js.map +0 -1
  154. package/dist/src/trails/topo-reports.d.ts +0 -50
  155. package/dist/src/trails/topo-reports.d.ts.map +0 -1
  156. package/dist/src/trails/topo-reports.js +0 -122
  157. package/dist/src/trails/topo-reports.js.map +0 -1
  158. package/dist/src/trails/topo-show.d.ts +0 -23
  159. package/dist/src/trails/topo-show.d.ts.map +0 -1
  160. package/dist/src/trails/topo-show.js +0 -53
  161. package/dist/src/trails/topo-show.js.map +0 -1
  162. package/dist/src/trails/topo-store-support.d.ts +0 -13
  163. package/dist/src/trails/topo-store-support.d.ts.map +0 -1
  164. package/dist/src/trails/topo-store-support.js +0 -55
  165. package/dist/src/trails/topo-store-support.js.map +0 -1
  166. package/dist/src/trails/topo-support.d.ts +0 -87
  167. package/dist/src/trails/topo-support.d.ts.map +0 -1
  168. package/dist/src/trails/topo-support.js +0 -165
  169. package/dist/src/trails/topo-support.js.map +0 -1
  170. package/dist/src/trails/topo-unpin.d.ts +0 -15
  171. package/dist/src/trails/topo-unpin.d.ts.map +0 -1
  172. package/dist/src/trails/topo-unpin.js +0 -39
  173. package/dist/src/trails/topo-unpin.js.map +0 -1
  174. package/dist/src/trails/topo-verify.d.ts +0 -5
  175. package/dist/src/trails/topo-verify.d.ts.map +0 -1
  176. package/dist/src/trails/topo-verify.js +0 -28
  177. package/dist/src/trails/topo-verify.js.map +0 -1
  178. package/dist/src/trails/topo.d.ts +0 -5
  179. package/dist/src/trails/topo.d.ts.map +0 -1
  180. package/dist/src/trails/topo.js +0 -67
  181. package/dist/src/trails/topo.js.map +0 -1
  182. package/dist/src/trails/warden.d.ts +0 -19
  183. package/dist/src/trails/warden.d.ts.map +0 -1
  184. package/dist/src/trails/warden.js +0 -89
  185. package/dist/src/trails/warden.js.map +0 -1
  186. package/dist/tsconfig.tsbuildinfo +0 -1
  187. package/src/__tests__/create.test.ts +0 -351
  188. package/src/__tests__/draft-promote.test.ts +0 -144
  189. package/src/__tests__/guide.test.ts +0 -91
  190. package/src/__tests__/load-app.test.ts +0 -58
  191. package/src/__tests__/survey.test.ts +0 -301
  192. package/src/__tests__/topo-dev.test.ts +0 -424
  193. package/src/__tests__/warden.test.ts +0 -74
  194. package/src/trails/add-trailhead.ts +0 -121
  195. package/src/trails/topo-export.ts +0 -39
  196. package/src/trails/topo-show.ts +0 -58
  197. package/tsconfig.json +0 -9
@@ -1,42 +1,51 @@
1
1
  import { Result, trail } from '@ontrails/core';
2
2
  import { z } from 'zod';
3
3
 
4
- import { loadApp } from './load-app.js';
4
+ import { tryLoadFreshAppLease } from './load-app.js';
5
+ import { resolveTrailRootDir } from './root-dir.js';
5
6
  import {
6
- DEFAULT_APP_MODULE,
7
- isolatedExampleInput,
8
- pinCurrentTopo,
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 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 }));
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
- ...isolatedExampleInput('topo-pin'),
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
- pin: topoPinOutput,
40
- save: topoSaveOutput,
49
+ snapshot: topoSnapshotOutput,
41
50
  }),
42
51
  });
@@ -1,212 +1,91 @@
1
1
  /**
2
- * Read-only topo store consumer helpers.
2
+ * Read-only live topo consumer helpers.
3
3
  *
4
- * Extracted from topo-support.ts so this branch (trl-132) owns its own file,
5
- * keeping absorb routing clean across the stack.
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
- createTopoStore,
15
- InternalError,
14
+ deriveTrailsDbPath,
15
+ deriveTrailsDir,
16
16
  NotFoundError,
17
17
  Result,
18
+ SURFACE_LAYER_NAMES_KEY,
19
+ ValidationError,
18
20
  } from '@ontrails/core';
19
- import type { TopoSaveRecord } from '@ontrails/core/internal/topo-saves';
20
- import { listTopoSaves } from '@ontrails/core/internal/topo-saves';
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
- openReadTrailsDb,
23
- resolveTrailsDbPath,
24
- resolveTrailsDir,
25
- } from '@ontrails/core/internal/trails-db';
26
- import { readTrailheadLockData } from '@ontrails/schema';
27
-
28
- import type { BriefReport, SurveyListReport } from './topo-reports.js';
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 { REPORT_CONTRACT_VERSION, REPORT_VERSION } from './topo-constants.js';
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
- interface StoredTrailheadMapEntry {
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 CurrentProvisionDetail {
45
+ export interface CurrentResourceDetail {
59
46
  readonly description: string | null;
60
47
  readonly health: 'available' | 'none';
61
48
  readonly id: string;
62
- readonly kind: 'provision';
49
+ readonly kind: 'resource';
63
50
  readonly lifetime: 'singleton';
64
- readonly usedBy: string[];
51
+ readonly usedBy: readonly string[];
65
52
  }
66
53
 
67
- // ---------------------------------------------------------------------------
68
- // Store helpers
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
- const readTrailheadEntries = (
78
- trailheadMapJson: string
79
- ): readonly StoredTrailheadMapEntry[] =>
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
- return {
102
- contractVersion: REPORT_CONTRACT_VERSION,
103
- features: {
104
- detours: trailEntries.some(
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 buildSurveyListFromStore = (
121
- store: ReturnType<typeof createTopoStore>,
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
- count: trails.length,
129
- entries: trails.map((trail) => ({
130
- examples: trail.exampleCount,
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 buildTrailDetailFromStore = (
148
- detail: ReturnType<ReturnType<typeof createTopoStore>['trails']['get']>
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 = resolveRootDir(options?.rootDir);
220
- const trailsDir = resolveTrailsDir({ rootDir });
221
- return withCurrentTopoStore(app, rootDir, (store, ref, save) => ({
222
- app: buildBriefReportFromStore(app, store, ref, save),
223
- dbPath: resolveTrailsDbPath({ rootDir }),
224
- list: buildSurveyListFromStore(store, ref),
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
- options?: { readonly rootDir?: string }
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
- options?: { readonly rootDir?: string }
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
- options?: { readonly rootDir?: string }
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: string;
258
- }[] => {
259
- const rootDir = resolveRootDir(options?.rootDir);
260
- return withCurrentTopoStore(app, rootDir, (store, ref) =>
261
- store.trails.list({ save: ref }).map((trail) => ({
126
+ readonly kind: 'trail';
127
+ }[] =>
128
+ app
129
+ .list()
130
+ .map((trail) => ({
262
131
  description: trail.description ?? '(no description)',
263
- exampleCount: trail.exampleCount,
132
+ exampleCount: trail.examples?.length ?? 0,
264
133
  id: trail.id,
265
- kind: trail.kind,
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?: { readonly rootDir?: string }
274
- ): CurrentProvisionDetail | CurrentTrailDetail | undefined => {
275
- const rootDir = resolveRootDir(options?.rootDir);
276
- return withCurrentTopoStore(app, rootDir, (store, ref) => {
277
- const trail = store.trails.get(id, { save: ref });
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
- const provision = store.provisions.get(id, { save: ref });
283
- return provision === undefined
284
- ? undefined
285
- : buildProvisionDetailFromStore(provision);
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 = resolveRootDir(options?.rootDir);
294
- const committedLock = await readTrailheadLockData({
295
- dir: resolveTrailsDir({ rootDir }),
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 (committedLock === null) {
234
+ if (lockManifest === null) {
299
235
  return Result.err(
300
236
  new NotFoundError(
301
- 'No committed trails.lock found. Run `trails topo export` first.'
237
+ 'No committed trails.lock found. Run `trails topo compile` first.'
302
238
  )
303
239
  );
304
240
  }
305
241
 
306
- const currentHash = withCurrentTopoStore(
307
- app,
308
- rootDir,
309
- (store, ref) => store.exports.get(ref)?.trailheadHash
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 InternalError('No stored topo export found for the current topo save')
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 (committedLock.hash !== currentHash) {
258
+ if (topoArtifact.sha256 !== currentHash) {
319
259
  return Result.err(
320
260
  new ConflictError(
321
- 'trails.lock is stale. Run `trails topo export` to refresh it.'
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: committedLock.hash,
267
+ committedHash: topoArtifact.sha256,
328
268
  currentHash,
329
269
  lockPath: LOCK_PATH,
330
270
  stale: false,