@ontrails/trails 1.0.0-beta.30 → 1.0.0-beta.32

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.
@@ -1,385 +1,14 @@
1
- import type {
2
- ActivationEntry,
3
- ActivationSource,
4
- ActivationSourceProjection,
5
- AnyTrail,
6
- Topo,
7
- } from '@ontrails/core';
8
- import {
9
- activationSourceKey,
10
- projectActivationSourceDeclaration,
11
- } from '@ontrails/core';
12
-
13
- export interface ActivationChainReport {
14
- readonly consumer: string;
15
- readonly producer: string;
16
- readonly signal: string;
17
- }
18
-
19
- type JsonSchemaReport = Readonly<Record<string, unknown>>;
20
-
21
- export interface ActivationSourceReport extends ActivationSourceProjection {
22
- readonly cron?: string | undefined;
23
- readonly hasParse?: true | undefined;
24
- readonly hasPayloadSchema?: true | undefined;
25
- readonly hasVerify?: true | undefined;
26
- readonly id: string;
27
- readonly input?: unknown;
28
- readonly inputSchema?: JsonSchemaReport | undefined;
29
- readonly kind: string;
30
- readonly key: string;
31
- readonly meta?: Readonly<Record<string, unknown>> | undefined;
32
- readonly method?: string | undefined;
33
- readonly parseOutputSchema?: JsonSchemaReport | undefined;
34
- readonly path?: string | undefined;
35
- readonly payloadSchema?: JsonSchemaReport | undefined;
36
- readonly timezone?: string | undefined;
37
- }
38
-
39
- export interface ActivationEdgeReport extends Readonly<
40
- Record<string, unknown>
41
- > {
42
- readonly hasWhere: boolean;
43
- readonly sourceId: string;
44
- readonly sourceKey: string;
45
- readonly sourceKind: string;
46
- readonly trailId: string;
47
- readonly where?: { readonly predicate: true } | undefined;
48
- }
49
-
50
- export interface SignalActivationRelations {
51
- readonly consumers: readonly string[];
52
- readonly producers: readonly string[];
53
- }
54
-
55
- export interface TrailActivationReport {
56
- readonly activatedBy: readonly string[];
57
- readonly activates: readonly string[];
58
- readonly chains: readonly ActivationChainReport[];
59
- readonly edges: readonly ActivationEdgeReport[];
60
- readonly fires: readonly string[];
61
- readonly on: readonly string[];
62
- readonly sources: readonly ActivationSourceReport[];
63
- }
64
-
65
- export interface ActivationOverviewReport {
66
- readonly chainCount: number;
67
- readonly chains: readonly ActivationChainReport[];
68
- readonly edgeCount: number;
69
- readonly edges: readonly ActivationEdgeReport[];
70
- readonly signalIds: readonly string[];
71
- readonly sourceCount: number;
72
- readonly sourceKeys: readonly string[];
73
- readonly trailIds: readonly string[];
74
- }
75
-
76
- export interface ActivationGraphReport {
77
- readonly overview: ActivationOverviewReport;
78
- readonly signals: ReadonlyMap<string, SignalActivationRelations>;
79
- readonly sources: ReadonlyMap<string, ActivationSourceReport>;
80
- readonly trails: ReadonlyMap<string, TrailActivationReport>;
81
- }
82
-
83
- interface MutableSignalRelations {
84
- readonly consumers: Set<string>;
85
- readonly producers: Set<string>;
86
- }
87
-
88
- interface MutableTrailActivation {
89
- readonly activatedBy: Set<string>;
90
- readonly activates: Set<string>;
91
- readonly chains: ActivationChainReport[];
92
- readonly fires: readonly string[];
93
- readonly on: readonly string[];
94
- }
95
-
96
- const canonicalLeaf = (value: unknown): unknown => {
97
- switch (typeof value) {
98
- case 'bigint': {
99
- return value.toString();
100
- }
101
- case 'function': {
102
- return `[Function:${value.name || 'anonymous'}]`;
103
- }
104
- case 'symbol': {
105
- return `[Symbol:${value.description ?? ''}]`;
106
- }
107
- case 'undefined': {
108
- return '[Undefined]';
109
- }
110
- default: {
111
- return value;
112
- }
113
- }
114
- };
115
-
116
- const canonicalize = (value: unknown): unknown => {
117
- if (Array.isArray(value)) {
118
- return value.map(canonicalize);
119
- }
120
- if (value instanceof Date) {
121
- return value.toISOString();
122
- }
123
- if (value instanceof RegExp) {
124
- return value.toString();
125
- }
126
- if (value !== null && typeof value === 'object') {
127
- const sorted: Record<string, unknown> = {};
128
- for (const key of Object.keys(value).toSorted()) {
129
- const next = (value as Record<string, unknown>)[key];
130
- sorted[key] = next === undefined ? '[Undefined]' : canonicalize(next);
131
- }
132
- return sorted;
133
- }
134
- return canonicalLeaf(value);
135
- };
136
-
137
- const sortKeys = <T extends Record<string, unknown>>(value: T): T => {
138
- const sorted: Record<string, unknown> = {};
139
- for (const key of Object.keys(value).toSorted()) {
140
- sorted[key] = value[key];
141
- }
142
- return sorted as T;
143
- };
144
-
145
- const compareChains = (
146
- a: ActivationChainReport,
147
- b: ActivationChainReport
148
- ): number =>
149
- a.producer.localeCompare(b.producer) ||
150
- a.signal.localeCompare(b.signal) ||
151
- a.consumer.localeCompare(b.consumer);
152
-
153
- const sortedUnique = (values: Iterable<string>): readonly string[] =>
154
- [...new Set(values)].toSorted();
155
-
156
- const projectActivationSource = (
157
- source: ActivationSource
158
- ): ActivationSourceReport =>
159
- projectActivationSourceDeclaration(source) as ActivationSourceReport;
160
-
161
- const projectActivationEdge = (
162
- trailId: string,
163
- activation: ActivationEntry
164
- ): ActivationEdgeReport => {
165
- const sourceKey = activationSourceKey(activation.source);
166
- const edge: Record<string, unknown> = {
167
- hasWhere: activation.where !== undefined,
168
- sourceId: activation.source.id,
169
- sourceKey,
170
- sourceKind: activation.source.kind,
171
- trailId,
172
- };
173
-
174
- if (activation.meta !== undefined) {
175
- edge['meta'] = canonicalize(activation.meta);
176
- }
177
- if (activation.where !== undefined) {
178
- edge['where'] = { predicate: true };
179
- }
180
-
181
- return sortKeys(edge) as ActivationEdgeReport;
182
- };
183
-
184
- const collectActivationSourceCatalog = (
185
- trails: readonly AnyTrail[]
186
- ): readonly ActivationSourceReport[] => {
187
- const sources = new Map<string, ActivationSourceReport>();
188
- for (const trail of trails) {
189
- for (const activation of trail.activationSources) {
190
- const projected = projectActivationSource(activation.source);
191
- sources.set(projected.key, projected);
192
- }
193
- }
194
- return [...sources.values()].toSorted((a, b) => a.key.localeCompare(b.key));
195
- };
196
-
197
- const collectActivationEdges = (
198
- trails: readonly AnyTrail[]
199
- ): readonly ActivationEdgeReport[] => {
200
- const edges = new Map<string, ActivationEdgeReport>();
201
- for (const trail of trails) {
202
- for (const activation of trail.activationSources) {
203
- const edge = projectActivationEdge(trail.id, activation);
204
- const key = `${edge.sourceKey}\0${edge.trailId}`;
205
- const previous = edges.get(key);
206
- edges.set(
207
- key,
208
- previous === undefined || (!previous.hasWhere && edge.hasWhere)
209
- ? edge
210
- : previous
211
- );
212
- }
213
- }
214
-
215
- return [...edges.values()].toSorted(
216
- (a, b) =>
217
- a.sourceKey.localeCompare(b.sourceKey) ||
218
- a.trailId.localeCompare(b.trailId)
219
- );
220
- };
221
-
222
- const getSignalRelations = (
223
- relations: Map<string, MutableSignalRelations>,
224
- signalId: string
225
- ): MutableSignalRelations => {
226
- const existing = relations.get(signalId);
227
- if (existing !== undefined) {
228
- return existing;
229
- }
230
-
231
- const created = {
232
- consumers: new Set<string>(),
233
- producers: new Set<string>(),
234
- };
235
- relations.set(signalId, created);
236
- return created;
237
- };
238
-
239
- const getTrailActivation = (
240
- trails: Map<string, MutableTrailActivation>,
241
- trail: AnyTrail
242
- ): MutableTrailActivation => {
243
- const existing = trails.get(trail.id);
244
- if (existing !== undefined) {
245
- return existing;
246
- }
247
-
248
- const created = {
249
- activatedBy: new Set<string>(),
250
- activates: new Set<string>(),
251
- chains: [],
252
- fires: sortedUnique(trail.fires),
253
- on: sortedUnique(trail.on),
254
- };
255
- trails.set(trail.id, created);
256
- return created;
257
- };
258
-
259
- export const deriveDeclaredTrailActivation = (
260
- trail: AnyTrail
261
- ): TrailActivationReport => {
262
- const sources = collectActivationSourceCatalog([trail]);
263
- return {
264
- activatedBy: [],
265
- activates: [],
266
- chains: [],
267
- edges: collectActivationEdges([trail]),
268
- fires: sortedUnique(trail.fires),
269
- on: sortedUnique(trail.on),
270
- sources,
271
- };
272
- };
273
-
274
- export const deriveSignalActivationRelations = (
275
- app: Topo,
276
- signalId: string
277
- ): SignalActivationRelations => {
278
- const consumers: string[] = [];
279
- const producers: string[] = [];
280
-
281
- for (const trail of app.list()) {
282
- if (trail.fires.includes(signalId)) {
283
- producers.push(trail.id);
284
- }
285
- if (trail.on.includes(signalId)) {
286
- consumers.push(trail.id);
287
- }
288
- }
289
-
290
- return {
291
- consumers: sortedUnique(consumers),
292
- producers: sortedUnique(producers),
293
- };
294
- };
295
-
296
- export const deriveActivationGraph = (app: Topo): ActivationGraphReport => {
297
- const signalRelations = new Map<string, MutableSignalRelations>();
298
- const trailActivations = new Map<string, MutableTrailActivation>();
299
- const trails = app.list();
300
- const sources = collectActivationSourceCatalog(trails);
301
- const edges = collectActivationEdges(trails);
302
-
303
- for (const signal of app.listSignals()) {
304
- getSignalRelations(signalRelations, signal.id);
305
- }
306
-
307
- for (const trail of trails) {
308
- const trailActivation = getTrailActivation(trailActivations, trail);
309
- for (const signalId of trailActivation.fires) {
310
- getSignalRelations(signalRelations, signalId).producers.add(trail.id);
311
- }
312
- for (const signalId of trailActivation.on) {
313
- getSignalRelations(signalRelations, signalId).consumers.add(trail.id);
314
- }
315
- }
316
-
317
- const chains: ActivationChainReport[] = [];
318
- for (const [signal, related] of signalRelations) {
319
- for (const producer of related.producers) {
320
- const producerActivation = trailActivations.get(producer);
321
- for (const consumer of related.consumers) {
322
- const chain = { consumer, producer, signal };
323
- chains.push(chain);
324
- producerActivation?.activates.add(consumer);
325
- producerActivation?.chains.push(chain);
326
- const consumerActivation = trailActivations.get(consumer);
327
- consumerActivation?.activatedBy.add(producer);
328
- if (consumerActivation !== producerActivation) {
329
- consumerActivation?.chains.push(chain);
330
- }
331
- }
332
- }
333
- }
334
-
335
- chains.sort(compareChains);
336
- const activeTrailIds = sortedUnique([
337
- ...[...trailActivations.entries()].flatMap(([trailId, trail]) =>
338
- trail.fires.length > 0 || trail.on.length > 0
339
- ? [trailId, ...trail.activatedBy, ...trail.activates]
340
- : []
341
- ),
342
- ...edges.map((edge) => edge.trailId),
343
- ]);
344
-
345
- return {
346
- overview: {
347
- chainCount: chains.length,
348
- chains,
349
- edgeCount: edges.length,
350
- edges,
351
- signalIds: [...signalRelations.keys()].toSorted(),
352
- sourceCount: sources.length,
353
- sourceKeys: sources.map((source) => source.key),
354
- trailIds: activeTrailIds,
355
- },
356
- signals: new Map(
357
- [...signalRelations.entries()].map(([id, relations]) => [
358
- id,
359
- {
360
- consumers: sortedUnique(relations.consumers),
361
- producers: sortedUnique(relations.producers),
362
- },
363
- ])
364
- ),
365
- sources: new Map(sources.map((source) => [source.key, source])),
366
- trails: new Map(
367
- [...trailActivations.entries()].map(([id, activation]) => [
368
- id,
369
- {
370
- activatedBy: sortedUnique(activation.activatedBy),
371
- activates: sortedUnique(activation.activates),
372
- chains: activation.chains.toSorted(compareChains),
373
- edges: edges.filter((edge) => edge.trailId === id),
374
- fires: activation.fires,
375
- on: activation.on,
376
- sources: sources.filter((source) =>
377
- edges.some(
378
- (edge) => edge.trailId === id && edge.sourceKey === source.key
379
- )
380
- ),
381
- },
382
- ])
383
- ),
384
- };
385
- };
1
+ export {
2
+ deriveActivationGraph,
3
+ deriveDeclaredTrailActivation,
4
+ deriveSignalActivationRelations,
5
+ } from '@ontrails/topographer';
6
+ export type {
7
+ ActivationChainReport,
8
+ ActivationEdgeReport,
9
+ ActivationGraphReport,
10
+ ActivationOverviewReport,
11
+ ActivationSourceReport,
12
+ SignalActivationRelations,
13
+ TrailActivationReport,
14
+ } from '@ontrails/topographer';
@@ -144,7 +144,7 @@ const readCommittedLock = async (
144
144
  // Public read-only consumers
145
145
  // ---------------------------------------------------------------------------
146
146
 
147
- export const buildTopoSummary = (
147
+ export const deriveTopoSummary = (
148
148
  app: Topo,
149
149
  options?: { readonly rootDir?: string }
150
150
  ): TopoSummaryReport => {
@@ -160,17 +160,17 @@ export const buildTopoSummary = (
160
160
  };
161
161
  };
162
162
 
163
- export const buildCurrentTopoBrief = (
163
+ export const deriveCurrentTopoBrief = (
164
164
  app: Topo,
165
165
  _options?: { readonly rootDir?: string }
166
166
  ): BriefReport => deriveBriefReport(app);
167
167
 
168
- export const buildCurrentTopoList = (
168
+ export const deriveCurrentTopoList = (
169
169
  app: Topo,
170
170
  _options?: { readonly rootDir?: string }
171
171
  ): SurveyListReport => deriveSurveyList(app);
172
172
 
173
- export const buildCurrentGuideEntries = (
173
+ export const deriveCurrentGuideEntries = (
174
174
  app: Topo,
175
175
  _options?: { readonly rootDir?: string }
176
176
  ): readonly {
@@ -189,7 +189,7 @@ export const buildCurrentGuideEntries = (
189
189
  }))
190
190
  .toSorted((a, b) => a.id.localeCompare(b.id));
191
191
 
192
- export const buildCurrentTrailDetail = (
192
+ export const deriveCurrentTrailDetail = (
193
193
  app: Topo,
194
194
  id: string,
195
195
  options?: CurrentTopoReadOptions
@@ -203,7 +203,7 @@ export const buildCurrentTrailDetail = (
203
203
  });
204
204
  };
205
205
 
206
- export const buildCurrentResourceDetail = (
206
+ export const deriveCurrentResourceDetail = (
207
207
  app: Topo,
208
208
  id: string,
209
209
  _options?: { readonly rootDir?: string }
@@ -212,22 +212,22 @@ export const buildCurrentResourceDetail = (
212
212
  ? undefined
213
213
  : (deriveResourceDetail(app, id) as CurrentResourceDetail);
214
214
 
215
- export const buildCurrentSignalDetail = (
215
+ export const deriveCurrentSignalDetail = (
216
216
  app: Topo,
217
217
  id: string,
218
218
  _options?: { readonly rootDir?: string }
219
219
  ): SignalDetailReport | undefined => deriveSignalDetail(app, id);
220
220
 
221
- export const buildCurrentTopoDetail = (
221
+ export const deriveCurrentTopoDetail = (
222
222
  app: Topo,
223
223
  id: string,
224
224
  options?: CurrentTopoReadOptions
225
225
  ): CurrentTopoDetail | undefined =>
226
- buildCurrentTrailDetail(app, id, options) ??
227
- buildCurrentResourceDetail(app, id) ??
228
- buildCurrentSignalDetail(app, id);
226
+ deriveCurrentTrailDetail(app, id, options) ??
227
+ deriveCurrentResourceDetail(app, id) ??
228
+ deriveCurrentSignalDetail(app, id);
229
229
 
230
- export const buildCurrentTopoMatches = (
230
+ export const deriveCurrentTopoMatches = (
231
231
  app: Topo,
232
232
  id: string,
233
233
  options?: CurrentTopoReadOptions
@@ -251,7 +251,7 @@ export const buildCurrentTopoMatches = (
251
251
  });
252
252
  }
253
253
 
254
- const resource = buildCurrentResourceDetail(app, id);
254
+ const resource = deriveCurrentResourceDetail(app, id);
255
255
  if (resource !== undefined) {
256
256
  matches.push({ detail: resource, kind: 'resource' });
257
257
  }
@@ -3,7 +3,7 @@ import { z } from 'zod';
3
3
 
4
4
  import { withFreshOperatorApp } from './operator-context.js';
5
5
  import { activationOverviewOutput } from './topo-output-schemas.js';
6
- import { buildTopoSummary } from './topo-read-support.js';
6
+ import { deriveTopoSummary } from './topo-read-support.js';
7
7
  import { createIsolatedExampleInput } from './topo-support.js';
8
8
 
9
9
  const summaryOutput = z.object({
@@ -74,7 +74,7 @@ const summaryOutput = z.object({
74
74
  export const topoTrail = trail('topo', {
75
75
  blaze: async (input, ctx) =>
76
76
  withFreshOperatorApp(input, ctx, ({ lease, rootDir }) =>
77
- Result.ok(buildTopoSummary(lease.app, { rootDir }))
77
+ Result.ok(deriveTopoSummary(lease.app, { rootDir }))
78
78
  ),
79
79
  description: 'Show the current topo summary and entry list',
80
80
  examples: [
@@ -23,7 +23,7 @@ export interface LifecycleCommandInput {
23
23
  }
24
24
 
25
25
  export interface LifecycleWriteResult {
26
- readonly file: string;
26
+ readonly filePath: string;
27
27
  readonly trailId: string;
28
28
  readonly updated: boolean;
29
29
  readonly warnings?: readonly string[] | undefined;
@@ -32,8 +32,8 @@ export interface LifecycleWriteResult {
32
32
  interface TrailSourceMatch {
33
33
  readonly configEnd: number;
34
34
  readonly configStart: number;
35
- readonly filePath: string;
36
35
  readonly source: string;
36
+ readonly sourcePath: string;
37
37
  }
38
38
 
39
39
  interface PropertyMatch {
@@ -108,8 +108,8 @@ const findTrailSource = (
108
108
  return Result.ok({
109
109
  configEnd: match.config.end,
110
110
  configStart: match.config.start,
111
- filePath,
112
111
  source: source.value,
112
+ sourcePath: filePath,
113
113
  });
114
114
  }
115
115
  }
@@ -566,13 +566,13 @@ export const reviseTrailSource = (
566
566
  blaze?.value
567
567
  )
568
568
  );
569
- const written = writeLifecycleSourceFile(match.value.filePath, nextSource);
569
+ const written = writeLifecycleSourceFile(match.value.sourcePath, nextSource);
570
570
  if (written.isErr()) {
571
571
  return written;
572
572
  }
573
573
 
574
574
  return Result.ok({
575
- file: match.value.filePath,
575
+ filePath: match.value.sourcePath,
576
576
  trailId: trail.id,
577
577
  updated: true,
578
578
  ...(usesForkPlaceholder
@@ -724,18 +724,18 @@ export const setVersionStatusSource = (
724
724
  );
725
725
  if (nextSource === match.value.source) {
726
726
  return Result.ok({
727
- file: match.value.filePath,
727
+ filePath: match.value.sourcePath,
728
728
  trailId: target.trailId,
729
729
  updated: false,
730
730
  });
731
731
  }
732
- const written = writeLifecycleSourceFile(match.value.filePath, nextSource);
732
+ const written = writeLifecycleSourceFile(match.value.sourcePath, nextSource);
733
733
  if (written.isErr()) {
734
734
  return written;
735
735
  }
736
736
 
737
737
  return Result.ok({
738
- file: match.value.filePath,
738
+ filePath: match.value.sourcePath,
739
739
  trailId: target.trailId,
740
740
  updated: true,
741
741
  });
@@ -768,18 +768,18 @@ export const forkVersionEntrySource = (
768
768
  const nextSource = rewrite.source;
769
769
  if (nextSource === match.value.source) {
770
770
  return Result.ok({
771
- file: match.value.filePath,
771
+ filePath: match.value.sourcePath,
772
772
  trailId: target.trailId,
773
773
  updated: false,
774
774
  });
775
775
  }
776
- const written = writeLifecycleSourceFile(match.value.filePath, nextSource);
776
+ const written = writeLifecycleSourceFile(match.value.sourcePath, nextSource);
777
777
  if (written.isErr()) {
778
778
  return written;
779
779
  }
780
780
 
781
781
  return Result.ok({
782
- file: match.value.filePath,
782
+ filePath: match.value.sourcePath,
783
783
  trailId: target.trailId,
784
784
  updated: true,
785
785
  ...(rewrite.usedPlaceholder
@@ -37,6 +37,12 @@ const wardenRuleGuideEntrySchema = z.object({
37
37
  .object({
38
38
  class: z.enum(wardenFixClasses),
39
39
  safety: z.enum(wardenFixSafeties),
40
+ scanTargets: z
41
+ .object({
42
+ exclude: z.array(z.string()).readonly().optional(),
43
+ extensions: z.array(z.string()).readonly().optional(),
44
+ })
45
+ .optional(),
40
46
  })
41
47
  .optional(),
42
48
  guidance: wardenGuidanceSchema.optional(),
@@ -54,10 +60,9 @@ const wardenRuleGuideEntrySchema = z.object({
54
60
  const wardenGuideManifestSchema = z.object({
55
61
  generatedFrom: z.object({
56
62
  package: z.literal('@ontrails/warden'),
57
- registries: z.tuple([
58
- z.literal('wardenRules'),
59
- z.literal('wardenTopoRules'),
60
- ]),
63
+ registries: z
64
+ .tuple([z.literal('wardenRules'), z.literal('wardenTopoRules')])
65
+ .readonly(),
61
66
  source: z.literal('builtin-rule-metadata'),
62
67
  }),
63
68
  kind: z.literal('trails-warden-guide-manifest'),
@@ -66,6 +66,10 @@ const wardenInputSchema = z.object({
66
66
  prePush: z.boolean().default(false).describe('Use the pre-push preset'),
67
67
  refresh: z.boolean().default(false).describe('Alias for --lock refresh'),
68
68
  rootDir: z.string().optional().describe('Root directory to scan'),
69
+ scopeExclude: z
70
+ .array(z.string())
71
+ .optional()
72
+ .describe('Root-relative path globs that Warden should not govern'),
69
73
  skipLock: z.boolean().default(false).describe('Alias for --lock skip'),
70
74
  strict: z.boolean().default(false).describe('Alias for --fail-on warning'),
71
75
  summary: z.boolean().default(false).describe('Alias for --format summary'),
@@ -106,6 +110,16 @@ const pushApps = (
106
110
  }
107
111
  };
108
112
 
113
+ const pushRepeatedValues = (
114
+ args: string[],
115
+ flag: string,
116
+ values: readonly string[] | undefined
117
+ ): void => {
118
+ for (const value of values ?? []) {
119
+ args.push(flag, value);
120
+ }
121
+ };
122
+
109
123
  export const buildWardenCommandArgs = (
110
124
  input: WardenTrailInput
111
125
  ): readonly string[] => {
@@ -150,6 +164,7 @@ export const buildWardenCommandArgs = (
150
164
  pushFlag(args, input.fix, '--fix');
151
165
  pushFlag(args, input.adapterCheck, '--adapter-check');
152
166
  pushValue(args, '--config-path', input.configPath);
167
+ pushRepeatedValues(args, '--scope-exclude', input.scopeExclude);
153
168
  pushApps(args, input.apps);
154
169
 
155
170
  return args;