@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
@@ -0,0 +1,273 @@
1
+ /**
2
+ * CLI-surface bridge for the `--trace` flag.
3
+ *
4
+ * `--trace` installs a per-invocation in-memory {@link TraceSink} so the
5
+ * intrinsic tracing pipeline in `@ontrails/core` records every trail-,
6
+ * span-, signal-, and activation-level event during the invocation. After
7
+ * the trail completes (success or failure), the records are rendered as a
8
+ * tree to stderr via `renderTraceTree` from `@ontrails/observe`. Under
9
+ * `--json`, the structured `TraceRecord[]` is also emitted on stdout as
10
+ * the `tracing` field of a Result envelope.
11
+ *
12
+ * Design notes:
13
+ *
14
+ * - The sink is **per-invocation**, not module-global. Each call to
15
+ * {@link installTraceSink} replaces the registry entry with a fresh
16
+ * `MemoryTraceSink` and returns a handle whose {@link TraceSession.finalize}
17
+ * restores the previous sink and returns the captured records.
18
+ * - Tracing output is split across streams: the tree always goes to stderr,
19
+ * structured records only enter stdout when both `--trace` and `--json`
20
+ * are set. Under `--quiet`, the inner trail value remains the only stdout
21
+ * payload (no envelope) per ADR-0044's pipe-friendly contract.
22
+ * - `--trace` on `run.examples` is a metadata read; the run trail short-circuits
23
+ * before any execution, so no trace tree is rendered.
24
+ */
25
+
26
+ import { getTraceSink, registerTraceSink } from '@ontrails/core';
27
+ import type { TraceRecord, TraceSink } from '@ontrails/core';
28
+ import { createMemorySink, renderTraceTree } from '@ontrails/observe';
29
+ import type { MemoryTraceSink } from '@ontrails/observe';
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Argv detection
33
+ // ---------------------------------------------------------------------------
34
+
35
+ /**
36
+ * Detect whether `--trace` appears in argv.
37
+ *
38
+ * Pre-parsed argv detection lets the CLI install the sink before
39
+ * `surface()` parses argv. The flag is also wired through the build
40
+ * pipeline as a meta flag, so trail input is unaffected.
41
+ */
42
+ export const argvHasTraceFlag = (argv: readonly string[]): boolean =>
43
+ argv.includes('--trace');
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // Sink session
47
+ // ---------------------------------------------------------------------------
48
+
49
+ /** Handle returned by {@link installTraceSink}. */
50
+ export interface TraceSession {
51
+ /** The fresh in-memory sink that received records during this invocation. */
52
+ readonly sink: MemoryTraceSink;
53
+ /**
54
+ * Restore the previous trace sink and return a stable snapshot of the
55
+ * records collected during this session. Safe to call once; subsequent
56
+ * calls return an empty array.
57
+ */
58
+ readonly finalize: () => readonly TraceRecord[];
59
+ }
60
+
61
+ const restoreSink = (previous: TraceSink): void => {
62
+ // `registerTraceSink(undefined)` collapses back to `NOOP_SINK`, which is
63
+ // what we want when the prior sink was the default no-op singleton. For
64
+ // any other prior sink (e.g. an OTel adapter wired by the host), restore
65
+ // it directly so we do not accidentally drop the host's configured sink.
66
+ registerTraceSink(previous);
67
+ };
68
+
69
+ /**
70
+ * Register a fresh {@link MemoryTraceSink} as the active trace sink and
71
+ * return a handle that can later restore the previous sink.
72
+ */
73
+ export const installTraceSink = (): TraceSession => {
74
+ const previous = getTraceSink();
75
+ const sink = createMemorySink();
76
+ registerTraceSink(sink);
77
+
78
+ let finalized = false;
79
+ return {
80
+ finalize: () => {
81
+ if (finalized) {
82
+ return [];
83
+ }
84
+ finalized = true;
85
+ const records = sink.records();
86
+ restoreSink(previous);
87
+ return records;
88
+ },
89
+ sink,
90
+ };
91
+ };
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // Stderr rendering
95
+ // ---------------------------------------------------------------------------
96
+
97
+ /**
98
+ * Render the captured records as a tree to stderr, followed by a newline.
99
+ *
100
+ * No-ops on an empty record list so that quiet trails (e.g. metadata reads
101
+ * that never invoke `executeTrail`) do not produce a stray blank line.
102
+ */
103
+ export const writeTraceTreeToStderr = (
104
+ records: readonly TraceRecord[]
105
+ ): void => {
106
+ if (records.length === 0) {
107
+ return;
108
+ }
109
+ const tree = renderTraceTree(records);
110
+ if (tree.length === 0) {
111
+ return;
112
+ }
113
+ process.stderr.write(`${tree}\n`);
114
+ };
115
+
116
+ // ---------------------------------------------------------------------------
117
+ // JSON envelope shaping
118
+ // ---------------------------------------------------------------------------
119
+
120
+ /**
121
+ * Result-style envelope emitted on stdout under `--trace --json`.
122
+ *
123
+ * Mirrors the shape of `Result<T, E>` but adds a `tracing` field so a
124
+ * downstream consumer can deserialize the run outcome and the structured
125
+ * trace from a single document.
126
+ */
127
+ export type TraceJsonEnvelope =
128
+ | {
129
+ readonly ok: true;
130
+ readonly value: unknown;
131
+ readonly tracing: readonly TraceRecord[];
132
+ }
133
+ | {
134
+ readonly ok: false;
135
+ readonly error: { readonly message: string; readonly name: string };
136
+ readonly tracing: readonly TraceRecord[];
137
+ };
138
+
139
+ /**
140
+ * Minimal Result-shape contract used by the JSON envelope builder.
141
+ *
142
+ * The `value` and `error` properties may be present at the same time on a
143
+ * Result instance (the discriminated union narrows by `isOk` / `isErr`),
144
+ * so this interface keeps both optional and lets the builder branch without
145
+ * asserting either side.
146
+ */
147
+ interface ResultLike {
148
+ readonly isOk: () => boolean;
149
+ readonly isErr: () => boolean;
150
+ readonly value?: unknown;
151
+ readonly error?: unknown;
152
+ }
153
+
154
+ const errorFromUnknown = (
155
+ value: unknown
156
+ ): { readonly message: string; readonly name: string } => {
157
+ if (value instanceof Error) {
158
+ return { message: value.message, name: value.name };
159
+ }
160
+ return { message: String(value), name: 'Error' };
161
+ };
162
+
163
+ /**
164
+ * Build the stdout envelope for `--trace --json`.
165
+ *
166
+ * On success, the inner value is taken straight from the trail's
167
+ * `Result.ok(...)` payload. On failure the envelope captures the error's
168
+ * `name` and `message` -- both safe to serialize and consistent with how
169
+ * other Trails surfaces project errors.
170
+ */
171
+ export const buildTraceJsonEnvelope = (
172
+ result: ResultLike,
173
+ records: readonly TraceRecord[]
174
+ ): TraceJsonEnvelope => {
175
+ if (result.isOk()) {
176
+ return {
177
+ ok: true,
178
+ tracing: records,
179
+ value: result.value,
180
+ };
181
+ }
182
+ return {
183
+ error: errorFromUnknown(result.error),
184
+ ok: false,
185
+ tracing: records,
186
+ };
187
+ };
188
+
189
+ /**
190
+ * Serialize a {@link TraceJsonEnvelope} as a single JSON document for stdout.
191
+ *
192
+ * Indented with two spaces to match the rest of the CLI's `--json`
193
+ * formatting (see `output()` in `@ontrails/cli`).
194
+ */
195
+ export const formatTraceJsonEnvelope = (envelope: TraceJsonEnvelope): string =>
196
+ `${JSON.stringify(envelope, null, 2)}\n`;
197
+
198
+ // ---------------------------------------------------------------------------
199
+ // onResult bridge
200
+ // ---------------------------------------------------------------------------
201
+
202
+ interface TryTraceCtx {
203
+ readonly flags: Record<string, unknown>;
204
+ readonly result: ResultLike;
205
+ readonly trail?: { readonly id: string } | undefined;
206
+ }
207
+
208
+ const isJsonMode = (flags: Record<string, unknown>): boolean => {
209
+ if (flags['json'] === true) {
210
+ return true;
211
+ }
212
+ if (typeof flags['output'] === 'string' && flags['output'] === 'json') {
213
+ return true;
214
+ }
215
+ return false;
216
+ };
217
+
218
+ const shouldEmitTraceEnvelope = (flags: Record<string, unknown>): boolean => {
219
+ if (flags['trace'] !== true) {
220
+ return false;
221
+ }
222
+ if (flags['quiet'] === true) {
223
+ // `--quiet` is the explicit pipe-friendly mode. Adding the tracing
224
+ // envelope to stdout would defeat the contract -- defer to the
225
+ // existing quiet handler for stdout and only render the tree on
226
+ // stderr (handled outside this helper).
227
+ return false;
228
+ }
229
+ if (flags['jsonl'] === true) {
230
+ // `--jsonl` streams items per line; the structured envelope cannot be
231
+ // expressed without breaking the line-delimited contract.
232
+ return false;
233
+ }
234
+ return isJsonMode(flags);
235
+ };
236
+
237
+ const traceOutputIsOwnedByRunFamily = (ctx: TryTraceCtx): boolean => {
238
+ if (ctx.trail?.id === 'run.example') {
239
+ // `run.example` owns stdout via its comparison envelope. Still render the
240
+ // trace tree to stderr, but do not override the example helper output.
241
+ return false;
242
+ }
243
+ if (ctx.trail?.id === 'run.examples') {
244
+ // Pure metadata read; no execution to trace.
245
+ return false;
246
+ }
247
+ return true;
248
+ };
249
+
250
+ /**
251
+ * If the invocation requested both `--trace` and `--json`, serialize the
252
+ * trace envelope to stdout and return `true`. Otherwise return `false` so
253
+ * the caller falls through to the regular on-result chain.
254
+ *
255
+ * The stderr tree is **not** rendered here -- it is rendered uniformly
256
+ * for every `--trace` invocation in the CLI entry-point's `finally`
257
+ * block, even when this helper short-circuits.
258
+ */
259
+ export const tryTraceJsonOutput = (
260
+ ctx: TryTraceCtx,
261
+ session: TraceSession
262
+ ): boolean => {
263
+ if (
264
+ !shouldEmitTraceEnvelope(ctx.flags) ||
265
+ !traceOutputIsOwnedByRunFamily(ctx)
266
+ ) {
267
+ return false;
268
+ }
269
+ const records = session.sink.records();
270
+ const envelope = buildTraceJsonEnvelope(ctx.result, records);
271
+ process.stdout.write(formatTraceJsonEnvelope(envelope));
272
+ return true;
273
+ };
@@ -0,0 +1,39 @@
1
+ import type { ActionResultContext } from '@ontrails/cli';
2
+
3
+ interface WardenResultValue {
4
+ readonly formatted: string;
5
+ readonly passed?: boolean | undefined;
6
+ }
7
+
8
+ const isWardenResultValue = (value: unknown): value is WardenResultValue => {
9
+ if (typeof value !== 'object' || value === null) {
10
+ return false;
11
+ }
12
+ const candidate = value as Record<string, unknown>;
13
+ return (
14
+ typeof candidate['formatted'] === 'string' &&
15
+ (candidate['passed'] === undefined ||
16
+ typeof candidate['passed'] === 'boolean')
17
+ );
18
+ };
19
+
20
+ export const tryWardenOutput = (ctx: ActionResultContext): boolean => {
21
+ if (
22
+ (ctx.trail.id !== 'warden' && ctx.trail.id !== 'warden.guide') ||
23
+ ctx.result.isErr()
24
+ ) {
25
+ return false;
26
+ }
27
+ const { value } = ctx.result;
28
+ if (!isWardenResultValue(value)) {
29
+ return false;
30
+ }
31
+
32
+ if (value.formatted.length > 0) {
33
+ process.stdout.write(`${value.formatted}\n`);
34
+ }
35
+ if (typeof value.passed === 'boolean') {
36
+ process.exitCode = value.passed ? 0 : 1;
37
+ }
38
+ return true;
39
+ };