@ontrails/trails 1.0.0-beta.14 → 1.0.0-beta.15
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/.turbo/turbo-lint.log +1 -1
- package/CHANGELOG.md +13 -0
- package/__tests__/examples.test.ts +29 -4
- package/dist/src/app.js +2 -2
- package/dist/src/app.js.map +1 -1
- package/dist/src/clack.d.ts +1 -1
- package/dist/src/clack.js +1 -1
- package/dist/src/cli.js +4 -3
- package/dist/src/cli.js.map +1 -1
- package/dist/src/trails/add-surface.d.ts +3 -3
- package/dist/src/trails/add-surface.d.ts.map +1 -1
- package/dist/src/trails/add-surface.js +46 -24
- package/dist/src/trails/add-surface.js.map +1 -1
- package/dist/src/trails/add-trail.d.ts +3 -1
- package/dist/src/trails/add-trail.d.ts.map +1 -1
- package/dist/src/trails/add-trail.js +38 -11
- package/dist/src/trails/add-trail.js.map +1 -1
- package/dist/src/trails/add-trailhead.d.ts +1 -1
- package/dist/src/trails/add-trailhead.d.ts.map +1 -1
- package/dist/src/trails/add-trailhead.js +4 -4
- package/dist/src/trails/add-verify.d.ts +1 -1
- package/dist/src/trails/add-verify.d.ts.map +1 -1
- package/dist/src/trails/add-verify.js +7 -6
- package/dist/src/trails/add-verify.js.map +1 -1
- package/dist/src/trails/create-scaffold.d.ts +1 -1
- package/dist/src/trails/create-scaffold.d.ts.map +1 -1
- package/dist/src/trails/create-scaffold.js +10 -3
- package/dist/src/trails/create-scaffold.js.map +1 -1
- package/dist/src/trails/create.d.ts +10 -14
- package/dist/src/trails/create.d.ts.map +1 -1
- package/dist/src/trails/create.js +42 -37
- package/dist/src/trails/create.js.map +1 -1
- package/dist/src/trails/dev-clean.d.ts +4 -4
- package/dist/src/trails/dev-clean.d.ts.map +1 -1
- package/dist/src/trails/dev-clean.js +22 -21
- package/dist/src/trails/dev-clean.js.map +1 -1
- package/dist/src/trails/dev-reset.d.ts +1 -1
- package/dist/src/trails/dev-reset.d.ts.map +1 -1
- package/dist/src/trails/dev-reset.js +3 -2
- package/dist/src/trails/dev-reset.js.map +1 -1
- package/dist/src/trails/dev-stats.d.ts +4 -4
- package/dist/src/trails/dev-stats.d.ts.map +1 -1
- package/dist/src/trails/dev-stats.js +19 -19
- package/dist/src/trails/dev-stats.js.map +1 -1
- package/dist/src/trails/dev-support.d.ts +17 -17
- package/dist/src/trails/dev-support.d.ts.map +1 -1
- package/dist/src/trails/dev-support.js +60 -57
- package/dist/src/trails/dev-support.js.map +1 -1
- package/dist/src/trails/draft-promote.d.ts +1 -1
- package/dist/src/trails/draft-promote.d.ts.map +1 -1
- package/dist/src/trails/draft-promote.js +30 -16
- package/dist/src/trails/draft-promote.js.map +1 -1
- package/dist/src/trails/guide.d.ts +2 -2
- package/dist/src/trails/guide.d.ts.map +1 -1
- package/dist/src/trails/guide.js +1 -4
- package/dist/src/trails/guide.js.map +1 -1
- package/dist/src/trails/load-app.d.ts +7 -1
- package/dist/src/trails/load-app.d.ts.map +1 -1
- package/dist/src/trails/load-app.js +374 -26
- package/dist/src/trails/load-app.js.map +1 -1
- package/dist/src/trails/survey.d.ts +5 -5
- package/dist/src/trails/survey.d.ts.map +1 -1
- package/dist/src/trails/survey.js +51 -29
- package/dist/src/trails/survey.js.map +1 -1
- package/dist/src/trails/topo-export.d.ts +5 -4
- package/dist/src/trails/topo-export.d.ts.map +1 -1
- package/dist/src/trails/topo-export.js +4 -7
- package/dist/src/trails/topo-export.js.map +1 -1
- package/dist/src/trails/topo-history.d.ts +6 -10
- package/dist/src/trails/topo-history.d.ts.map +1 -1
- package/dist/src/trails/topo-history.js +7 -8
- package/dist/src/trails/topo-history.js.map +1 -1
- package/dist/src/trails/topo-pin.d.ts +5 -9
- package/dist/src/trails/topo-pin.d.ts.map +1 -1
- package/dist/src/trails/topo-pin.js +5 -9
- package/dist/src/trails/topo-pin.js.map +1 -1
- package/dist/src/trails/topo-read-support.d.ts +12 -8
- package/dist/src/trails/topo-read-support.d.ts.map +1 -1
- package/dist/src/trails/topo-read-support.js +71 -82
- package/dist/src/trails/topo-read-support.js.map +1 -1
- package/dist/src/trails/topo-reports.d.ts +15 -11
- package/dist/src/trails/topo-reports.d.ts.map +1 -1
- package/dist/src/trails/topo-reports.js +40 -34
- package/dist/src/trails/topo-reports.js.map +1 -1
- package/dist/src/trails/topo-show.d.ts +4 -4
- package/dist/src/trails/topo-show.d.ts.map +1 -1
- package/dist/src/trails/topo-show.js +8 -12
- package/dist/src/trails/topo-show.js.map +1 -1
- package/dist/src/trails/topo-store-support.d.ts +2 -2
- package/dist/src/trails/topo-store-support.d.ts.map +1 -1
- package/dist/src/trails/topo-store-support.js +22 -22
- package/dist/src/trails/topo-store-support.js.map +1 -1
- package/dist/src/trails/topo-support.d.ts +19 -30
- package/dist/src/trails/topo-support.d.ts.map +1 -1
- package/dist/src/trails/topo-support.js +63 -96
- package/dist/src/trails/topo-support.js.map +1 -1
- package/dist/src/trails/topo-unpin.d.ts +9 -4
- package/dist/src/trails/topo-unpin.d.ts.map +1 -1
- package/dist/src/trails/topo-unpin.js +9 -4
- package/dist/src/trails/topo-unpin.js.map +1 -1
- package/dist/src/trails/topo-verify.d.ts +2 -2
- package/dist/src/trails/topo-verify.d.ts.map +1 -1
- package/dist/src/trails/topo-verify.js +1 -5
- package/dist/src/trails/topo-verify.js.map +1 -1
- package/dist/src/trails/topo.d.ts +2 -2
- package/dist/src/trails/topo.d.ts.map +1 -1
- package/dist/src/trails/topo.js +6 -10
- package/dist/src/trails/topo.js.map +1 -1
- package/dist/src/trails/warden.d.ts +3 -2
- package/dist/src/trails/warden.d.ts.map +1 -1
- package/dist/src/trails/warden.js +11 -2
- package/dist/src/trails/warden.js.map +1 -1
- package/dist/src/versions.d.ts +12 -0
- package/dist/src/versions.d.ts.map +1 -0
- package/dist/src/versions.js +23 -0
- package/dist/src/versions.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -8
- package/src/__tests__/add-trail.test.ts +97 -0
- package/src/__tests__/create.test.ts +91 -27
- package/src/__tests__/guide.test.ts +10 -5
- package/src/__tests__/load-app.test.ts +365 -4
- package/src/__tests__/survey.test.ts +143 -67
- package/src/__tests__/topo-dev.test.ts +48 -46
- package/src/app.ts +2 -2
- package/src/clack.ts +1 -1
- package/src/cli.ts +4 -3
- package/src/trails/add-surface.ts +150 -0
- package/src/trails/add-trail.ts +46 -10
- package/src/trails/add-verify.ts +11 -6
- package/src/trails/create-scaffold.ts +16 -3
- package/src/trails/create.ts +76 -71
- package/src/trails/dev-clean.ts +25 -21
- package/src/trails/dev-reset.ts +3 -2
- package/src/trails/dev-stats.ts +22 -19
- package/src/trails/dev-support.ts +88 -86
- package/src/trails/draft-promote.ts +70 -35
- package/src/trails/guide.ts +1 -4
- package/src/trails/load-app.ts +511 -32
- package/src/trails/survey.ts +60 -35
- package/src/trails/topo-export.ts +5 -9
- package/src/trails/topo-history.ts +8 -10
- package/src/trails/topo-pin.ts +9 -13
- package/src/trails/topo-read-support.ts +103 -106
- package/src/trails/topo-reports.ts +54 -47
- package/src/trails/topo-show.ts +8 -12
- package/src/trails/topo-store-support.ts +36 -28
- package/src/trails/topo-support.ts +90 -134
- package/src/trails/topo-unpin.ts +11 -6
- package/src/trails/topo-verify.ts +1 -5
- package/src/trails/topo.ts +6 -10
- package/src/trails/warden.ts +12 -3
- package/src/versions.ts +43 -0
- package/tsconfig.tests.json +10 -0
- package/src/trails/add-trailhead.ts +0 -121
package/src/trails/dev-reset.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Result, ValidationError, trail } from '@ontrails/core';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
|
|
4
4
|
import { resetDevState } from './dev-support.js';
|
|
5
|
-
import {
|
|
5
|
+
import { createIsolatedExampleInput } from './topo-support.js';
|
|
6
6
|
|
|
7
7
|
export const devResetTrail = trail('dev.reset', {
|
|
8
8
|
blaze: (input, ctx) => {
|
|
@@ -22,7 +22,7 @@ export const devResetTrail = trail('dev.reset', {
|
|
|
22
22
|
{
|
|
23
23
|
input: {
|
|
24
24
|
dryRun: true,
|
|
25
|
-
rootDir:
|
|
25
|
+
rootDir: createIsolatedExampleInput('dev-reset').rootDir,
|
|
26
26
|
},
|
|
27
27
|
name: 'Preview local reset',
|
|
28
28
|
},
|
|
@@ -41,4 +41,5 @@ export const devResetTrail = trail('dev.reset', {
|
|
|
41
41
|
removedCount: z.number(),
|
|
42
42
|
removedFiles: z.array(z.string()),
|
|
43
43
|
}),
|
|
44
|
+
permit: { scopes: ['dev:reset'] },
|
|
44
45
|
});
|
package/src/trails/dev-stats.ts
CHANGED
|
@@ -1,39 +1,42 @@
|
|
|
1
1
|
import { Result, trail } from '@ontrails/core';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
buildDevStats,
|
|
6
|
+
DEFAULT_TOPO_SNAPSHOT_RETENTION,
|
|
7
|
+
} from './dev-support.js';
|
|
8
|
+
import { createIsolatedExampleInput } from './topo-support.js';
|
|
6
9
|
|
|
7
10
|
export const devStatsTrail = trail('dev.stats', {
|
|
8
11
|
blaze: (input, ctx) => {
|
|
9
12
|
const rootDir = input.rootDir ?? ctx.cwd ?? process.cwd();
|
|
10
13
|
return Result.ok(
|
|
11
14
|
buildDevStats({
|
|
12
|
-
maxAge: input.
|
|
13
|
-
maxRecords: input.
|
|
15
|
+
maxAge: input.traceAgeMs,
|
|
16
|
+
maxRecords: input.traces,
|
|
14
17
|
rootDir,
|
|
15
|
-
|
|
18
|
+
snapshotRetention: input.snapshots,
|
|
16
19
|
})
|
|
17
20
|
);
|
|
18
21
|
},
|
|
19
22
|
description: 'Show local Trails workspace state and retention',
|
|
20
23
|
examples: [
|
|
21
24
|
{
|
|
22
|
-
input: { rootDir:
|
|
25
|
+
input: { rootDir: createIsolatedExampleInput('dev-stats').rootDir },
|
|
23
26
|
name: 'Show local dev state',
|
|
24
27
|
},
|
|
25
28
|
],
|
|
26
29
|
input: z.object({
|
|
27
30
|
rootDir: z.string().optional().describe('Workspace root directory'),
|
|
28
|
-
|
|
31
|
+
snapshots: z
|
|
29
32
|
.number()
|
|
30
|
-
.default(
|
|
31
|
-
.describe('Unpinned topo
|
|
32
|
-
|
|
33
|
+
.default(DEFAULT_TOPO_SNAPSHOT_RETENTION)
|
|
34
|
+
.describe('Unpinned topo snapshots to retain'),
|
|
35
|
+
traceAgeMs: z
|
|
33
36
|
.number()
|
|
34
37
|
.default(7 * 24 * 60 * 60 * 1000)
|
|
35
|
-
.describe('Maximum retained
|
|
36
|
-
|
|
38
|
+
.describe('Maximum retained trace age in milliseconds'),
|
|
39
|
+
traces: z.number().default(10_000).describe('Maximum retained trace count'),
|
|
37
40
|
}),
|
|
38
41
|
intent: 'read',
|
|
39
42
|
output: z.object({
|
|
@@ -48,16 +51,16 @@ export const devStatsTrail = trail('dev.stats', {
|
|
|
48
51
|
path: z.string(),
|
|
49
52
|
}),
|
|
50
53
|
retention: z.object({
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
snapshots: z.number(),
|
|
55
|
+
traceAgeMs: z.number(),
|
|
56
|
+
traces: z.number(),
|
|
54
57
|
}),
|
|
55
58
|
topo: z.object({
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
pinnedCount: z.number(),
|
|
60
|
+
prunableSnapshotCount: z.number(),
|
|
61
|
+
snapshotCount: z.number(),
|
|
59
62
|
}),
|
|
60
|
-
|
|
63
|
+
tracing: z.object({
|
|
61
64
|
recordCount: z.number(),
|
|
62
65
|
}),
|
|
63
66
|
}),
|
|
@@ -2,30 +2,28 @@ import { existsSync, rmSync, statSync } from 'node:fs';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from '@ontrails/core/internal/topo-
|
|
5
|
+
countPinnedSnapshots,
|
|
6
|
+
countPrunableSnapshots,
|
|
7
|
+
countTopoSnapshots,
|
|
8
|
+
pruneUnpinnedSnapshots,
|
|
9
|
+
} from '@ontrails/core/internal/topo-snapshots';
|
|
10
10
|
import {
|
|
11
11
|
openReadTrailsDb,
|
|
12
12
|
openWriteTrailsDb,
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
deriveTrailsDbPath,
|
|
14
|
+
deriveTrailsDir,
|
|
15
15
|
} from '@ontrails/core/internal/trails-db';
|
|
16
16
|
import {
|
|
17
17
|
DEFAULT_MAX_AGE,
|
|
18
18
|
DEFAULT_MAX_RECORDS,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
} from '@ontrails/
|
|
19
|
+
applyTraceCleanup,
|
|
20
|
+
countTraceRecords,
|
|
21
|
+
previewTraceCleanup,
|
|
22
|
+
} from '@ontrails/tracing/internal/dev-state';
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
export const DEFAULT_TOPO_SNAPSHOT_RETENTION = 50;
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const resolveRootDir = (cwd?: string): string => cwd ?? process.cwd();
|
|
26
|
+
const deriveRootDir = (cwd?: string): string => cwd ?? process.cwd();
|
|
29
27
|
|
|
30
28
|
const removeIfPresent = (filePath: string): boolean => {
|
|
31
29
|
if (!existsSync(filePath)) {
|
|
@@ -47,16 +45,16 @@ export interface DevStatsReport {
|
|
|
47
45
|
readonly path: string;
|
|
48
46
|
};
|
|
49
47
|
readonly retention: {
|
|
50
|
-
readonly
|
|
51
|
-
readonly
|
|
52
|
-
readonly
|
|
48
|
+
readonly snapshots: number;
|
|
49
|
+
readonly traceAgeMs: number;
|
|
50
|
+
readonly traces: number;
|
|
53
51
|
};
|
|
54
52
|
readonly topo: {
|
|
55
|
-
readonly
|
|
56
|
-
readonly
|
|
57
|
-
readonly
|
|
53
|
+
readonly pinnedCount: number;
|
|
54
|
+
readonly prunableSnapshotCount: number;
|
|
55
|
+
readonly snapshotCount: number;
|
|
58
56
|
};
|
|
59
|
-
readonly
|
|
57
|
+
readonly tracing: {
|
|
60
58
|
readonly recordCount: number;
|
|
61
59
|
};
|
|
62
60
|
}
|
|
@@ -64,18 +62,18 @@ export interface DevStatsReport {
|
|
|
64
62
|
export interface DevCleanReport {
|
|
65
63
|
readonly dryRun: boolean;
|
|
66
64
|
readonly remaining: {
|
|
67
|
-
readonly
|
|
68
|
-
readonly
|
|
69
|
-
readonly
|
|
65
|
+
readonly pinnedCount: number;
|
|
66
|
+
readonly snapshotCount: number;
|
|
67
|
+
readonly traceCount: number;
|
|
70
68
|
};
|
|
71
69
|
readonly removed: {
|
|
72
|
-
readonly
|
|
73
|
-
readonly
|
|
70
|
+
readonly topoSnapshots: number;
|
|
71
|
+
readonly traceRecords: number;
|
|
74
72
|
};
|
|
75
73
|
readonly retention: {
|
|
76
|
-
readonly
|
|
77
|
-
readonly
|
|
78
|
-
readonly
|
|
74
|
+
readonly snapshots: number;
|
|
75
|
+
readonly traceAgeMs: number;
|
|
76
|
+
readonly traces: number;
|
|
79
77
|
};
|
|
80
78
|
}
|
|
81
79
|
|
|
@@ -89,7 +87,7 @@ interface DevRetentionOptions {
|
|
|
89
87
|
readonly maxAge?: number;
|
|
90
88
|
readonly maxRecords?: number;
|
|
91
89
|
readonly rootDir?: string;
|
|
92
|
-
readonly
|
|
90
|
+
readonly snapshotRetention?: number;
|
|
93
91
|
}
|
|
94
92
|
|
|
95
93
|
interface DevCleanupContext {
|
|
@@ -100,9 +98,9 @@ interface DevCleanupContext {
|
|
|
100
98
|
}
|
|
101
99
|
|
|
102
100
|
const buildRetention = (options?: DevRetentionOptions) => ({
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
101
|
+
snapshots: options?.snapshotRetention ?? DEFAULT_TOPO_SNAPSHOT_RETENTION,
|
|
102
|
+
traceAgeMs: options?.maxAge ?? DEFAULT_MAX_AGE,
|
|
103
|
+
traces: options?.maxRecords ?? DEFAULT_MAX_RECORDS,
|
|
106
104
|
});
|
|
107
105
|
|
|
108
106
|
const emptyDevClean = (
|
|
@@ -111,13 +109,13 @@ const emptyDevClean = (
|
|
|
111
109
|
): DevCleanReport => ({
|
|
112
110
|
dryRun,
|
|
113
111
|
remaining: {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
pinnedCount: 0,
|
|
113
|
+
snapshotCount: 0,
|
|
114
|
+
traceCount: 0,
|
|
117
115
|
},
|
|
118
116
|
removed: {
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
topoSnapshots: 0,
|
|
118
|
+
traceRecords: 0,
|
|
121
119
|
},
|
|
122
120
|
retention,
|
|
123
121
|
});
|
|
@@ -146,17 +144,17 @@ const emptyDevStats = (
|
|
|
146
144
|
lock: buildLockStats(lockPath),
|
|
147
145
|
retention,
|
|
148
146
|
topo: {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
147
|
+
pinnedCount: 0,
|
|
148
|
+
prunableSnapshotCount: 0,
|
|
149
|
+
snapshotCount: 0,
|
|
152
150
|
},
|
|
153
|
-
|
|
151
|
+
tracing: {
|
|
154
152
|
recordCount: 0,
|
|
155
153
|
},
|
|
156
154
|
});
|
|
157
155
|
|
|
158
156
|
const liveDevStats = (
|
|
159
|
-
db: Parameters<typeof
|
|
157
|
+
db: Parameters<typeof countPinnedSnapshots>[0],
|
|
160
158
|
dbPath: string,
|
|
161
159
|
lockPath: string,
|
|
162
160
|
retention: DevStatsReport['retention']
|
|
@@ -165,20 +163,22 @@ const liveDevStats = (
|
|
|
165
163
|
lock: buildLockStats(lockPath),
|
|
166
164
|
retention,
|
|
167
165
|
topo: {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
166
|
+
pinnedCount: countPinnedSnapshots(db),
|
|
167
|
+
prunableSnapshotCount: countPrunableSnapshots(db, {
|
|
168
|
+
keep: retention.snapshots,
|
|
169
|
+
}),
|
|
170
|
+
snapshotCount: countTopoSnapshots(db),
|
|
171
171
|
},
|
|
172
|
-
|
|
173
|
-
recordCount:
|
|
172
|
+
tracing: {
|
|
173
|
+
recordCount: countTraceRecords(db),
|
|
174
174
|
},
|
|
175
175
|
});
|
|
176
176
|
|
|
177
|
-
const
|
|
178
|
-
const rootDir =
|
|
179
|
-
const dbPath =
|
|
180
|
-
const trailsDir =
|
|
181
|
-
const lockPath =
|
|
177
|
+
const deriveDevStatsContext = (options?: DevRetentionOptions) => {
|
|
178
|
+
const rootDir = deriveRootDir(options?.rootDir);
|
|
179
|
+
const dbPath = deriveTrailsDbPath({ rootDir });
|
|
180
|
+
const trailsDir = deriveTrailsDir({ rootDir });
|
|
181
|
+
const lockPath = join(trailsDir, 'trails.lock');
|
|
182
182
|
return {
|
|
183
183
|
dbExists: existsSync(dbPath),
|
|
184
184
|
dbPath,
|
|
@@ -188,60 +188,62 @@ const resolveDevStatsContext = (options?: DevRetentionOptions) => {
|
|
|
188
188
|
};
|
|
189
189
|
};
|
|
190
190
|
|
|
191
|
-
const
|
|
191
|
+
const deriveDevCleanupContext = (
|
|
192
192
|
options?: DevRetentionOptions & { readonly dryRun?: boolean }
|
|
193
193
|
): DevCleanupContext => {
|
|
194
|
-
const rootDir =
|
|
194
|
+
const rootDir = deriveRootDir(options?.rootDir);
|
|
195
195
|
return {
|
|
196
|
-
dbPath:
|
|
196
|
+
dbPath: deriveTrailsDbPath({ rootDir }),
|
|
197
197
|
dryRun: options?.dryRun ?? false,
|
|
198
198
|
retention: buildRetention(options),
|
|
199
199
|
rootDir,
|
|
200
200
|
};
|
|
201
201
|
};
|
|
202
202
|
|
|
203
|
-
const
|
|
204
|
-
db: Parameters<typeof
|
|
203
|
+
const cleanupTraces = (
|
|
204
|
+
db: Parameters<typeof countPinnedSnapshots>[0],
|
|
205
205
|
context: DevCleanupContext
|
|
206
206
|
) =>
|
|
207
207
|
context.dryRun
|
|
208
|
-
?
|
|
209
|
-
maxAge: context.retention.
|
|
210
|
-
maxRecords: context.retention.
|
|
208
|
+
? previewTraceCleanup(db, {
|
|
209
|
+
maxAge: context.retention.traceAgeMs,
|
|
210
|
+
maxRecords: context.retention.traces,
|
|
211
211
|
})
|
|
212
|
-
:
|
|
213
|
-
maxAge: context.retention.
|
|
214
|
-
maxRecords: context.retention.
|
|
212
|
+
: applyTraceCleanup(db, {
|
|
213
|
+
maxAge: context.retention.traceAgeMs,
|
|
214
|
+
maxRecords: context.retention.traces,
|
|
215
215
|
});
|
|
216
216
|
|
|
217
|
-
const
|
|
218
|
-
db: Parameters<typeof
|
|
217
|
+
const cleanupTopoSnapshots = (
|
|
218
|
+
db: Parameters<typeof countPinnedSnapshots>[0],
|
|
219
219
|
context: DevCleanupContext
|
|
220
220
|
): number =>
|
|
221
221
|
context.dryRun
|
|
222
|
-
?
|
|
223
|
-
:
|
|
222
|
+
? countPrunableSnapshots(db, { keep: context.retention.snapshots })
|
|
223
|
+
: pruneUnpinnedSnapshots(db, { keep: context.retention.snapshots });
|
|
224
224
|
|
|
225
225
|
const buildCleanReport = (
|
|
226
|
-
db: Parameters<typeof
|
|
226
|
+
db: Parameters<typeof countPinnedSnapshots>[0],
|
|
227
227
|
context: DevCleanupContext
|
|
228
228
|
): DevCleanReport => {
|
|
229
|
-
const
|
|
230
|
-
const topoRemoved =
|
|
231
|
-
const
|
|
229
|
+
const traceReport = cleanupTraces(db, context);
|
|
230
|
+
const topoRemoved = cleanupTopoSnapshots(db, context);
|
|
231
|
+
const snapshotCount = countTopoSnapshots(db);
|
|
232
232
|
|
|
233
233
|
return {
|
|
234
234
|
dryRun: context.dryRun,
|
|
235
235
|
remaining: {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
236
|
+
pinnedCount: countPinnedSnapshots(db),
|
|
237
|
+
snapshotCount: context.dryRun
|
|
238
|
+
? snapshotCount - topoRemoved
|
|
239
|
+
: snapshotCount,
|
|
240
|
+
traceCount: context.dryRun
|
|
241
|
+
? traceReport.remaining - traceReport.removedTotal
|
|
242
|
+
: traceReport.remaining,
|
|
241
243
|
},
|
|
242
244
|
removed: {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
+
topoSnapshots: topoRemoved,
|
|
246
|
+
traceRecords: traceReport.removedTotal,
|
|
245
247
|
},
|
|
246
248
|
retention: context.retention,
|
|
247
249
|
};
|
|
@@ -251,9 +253,9 @@ const RESET_FILES = [
|
|
|
251
253
|
'.trails/trails.db',
|
|
252
254
|
'.trails/trails.db-shm',
|
|
253
255
|
'.trails/trails.db-wal',
|
|
254
|
-
'.trails/dev/
|
|
255
|
-
'.trails/dev/
|
|
256
|
-
'.trails/dev/
|
|
256
|
+
'.trails/dev/tracing.db',
|
|
257
|
+
'.trails/dev/tracing.db-shm',
|
|
258
|
+
'.trails/dev/tracing.db-wal',
|
|
257
259
|
] as const;
|
|
258
260
|
|
|
259
261
|
const presentResetFiles = (
|
|
@@ -265,7 +267,7 @@ export const buildDevStats = (
|
|
|
265
267
|
options?: DevRetentionOptions
|
|
266
268
|
): DevStatsReport => {
|
|
267
269
|
const { dbExists, dbPath, lockPath, retention, rootDir } =
|
|
268
|
-
|
|
270
|
+
deriveDevStatsContext(options);
|
|
269
271
|
|
|
270
272
|
if (!dbExists) {
|
|
271
273
|
return emptyDevStats(dbPath, lockPath, retention);
|
|
@@ -283,7 +285,7 @@ export const buildDevStats = (
|
|
|
283
285
|
export const cleanDevState = (
|
|
284
286
|
options?: DevRetentionOptions & { readonly dryRun?: boolean }
|
|
285
287
|
): DevCleanReport => {
|
|
286
|
-
const context =
|
|
288
|
+
const context = deriveDevCleanupContext(options);
|
|
287
289
|
if (!existsSync(context.dbPath)) {
|
|
288
290
|
return emptyDevClean(context.retention, context.dryRun);
|
|
289
291
|
}
|
|
@@ -303,7 +305,7 @@ export const resetDevState = (options?: {
|
|
|
303
305
|
readonly dryRun?: boolean;
|
|
304
306
|
readonly rootDir?: string;
|
|
305
307
|
}): DevResetReport => {
|
|
306
|
-
const rootDir =
|
|
308
|
+
const rootDir = deriveRootDir(options?.rootDir);
|
|
307
309
|
const files = presentResetFiles(rootDir);
|
|
308
310
|
|
|
309
311
|
if (options?.dryRun === true) {
|
|
@@ -4,10 +4,11 @@ import { basename, dirname, join, relative } from 'node:path';
|
|
|
4
4
|
import {
|
|
5
5
|
Result,
|
|
6
6
|
ValidationError,
|
|
7
|
-
|
|
7
|
+
deriveDraftReport,
|
|
8
8
|
isDraftId,
|
|
9
9
|
trail,
|
|
10
10
|
} from '@ontrails/core';
|
|
11
|
+
import type { Topo } from '@ontrails/core';
|
|
11
12
|
import {
|
|
12
13
|
DRAFT_FILE_PREFIX,
|
|
13
14
|
findStringLiterals,
|
|
@@ -17,7 +18,7 @@ import {
|
|
|
17
18
|
} from '@ontrails/warden';
|
|
18
19
|
import { z } from 'zod';
|
|
19
20
|
|
|
20
|
-
import {
|
|
21
|
+
import { loadFreshAppLease } from './load-app.js';
|
|
21
22
|
import { findTopoPath } from './project.js';
|
|
22
23
|
|
|
23
24
|
interface PromotionEdit {
|
|
@@ -164,10 +165,8 @@ const replaceLiteralValue = (
|
|
|
164
165
|
};
|
|
165
166
|
};
|
|
166
167
|
|
|
167
|
-
const collectOutputId = (
|
|
168
|
-
app
|
|
169
|
-
id: string
|
|
170
|
-
) => app.get(id) ?? app.signals.get(id) ?? app.getProvision(id);
|
|
168
|
+
const collectOutputId = (app: Topo, id: string) =>
|
|
169
|
+
app.get(id) ?? app.signals.get(id) ?? app.getResource(id);
|
|
171
170
|
|
|
172
171
|
const toRelativeOutputPath = (rootDir: string, filePath: string): string =>
|
|
173
172
|
relative(rootDir, filePath).replaceAll('\\', '/');
|
|
@@ -185,7 +184,6 @@ interface PromotionRewriteState {
|
|
|
185
184
|
interface PromotionLoadState {
|
|
186
185
|
readonly appModule: string | null;
|
|
187
186
|
readonly loadError: string | null;
|
|
188
|
-
readonly loadedApp: Awaited<ReturnType<typeof loadApp>> | undefined;
|
|
189
187
|
}
|
|
190
188
|
|
|
191
189
|
const validatePromotionInput = (input: {
|
|
@@ -511,23 +509,56 @@ const resolvePromotionAppModule = async (
|
|
|
511
509
|
);
|
|
512
510
|
};
|
|
513
511
|
|
|
514
|
-
|
|
515
|
-
|
|
512
|
+
/**
|
|
513
|
+
* Run `consume` while holding a fresh-load lease on `appModule`.
|
|
514
|
+
*
|
|
515
|
+
* @remarks
|
|
516
|
+
* The lease deletes its on-disk mirror on release. Any Topo consumption that
|
|
517
|
+
* may trigger deferred filesystem imports (for example inside a trail's
|
|
518
|
+
* `blaze` or a lazy relative `import()`) must run before the lease is
|
|
519
|
+
* released, otherwise those resolutions race the mirror teardown. Collapsing
|
|
520
|
+
* consumption into the leased critical section keeps that contract
|
|
521
|
+
* structural rather than relying on the caller to discover it.
|
|
522
|
+
*/
|
|
523
|
+
type LeaseAttempt =
|
|
524
|
+
| {
|
|
525
|
+
readonly ok: true;
|
|
526
|
+
readonly lease: Awaited<ReturnType<typeof loadFreshAppLease>>;
|
|
527
|
+
}
|
|
528
|
+
| { readonly ok: false; readonly loadError: string };
|
|
529
|
+
|
|
530
|
+
const tryAcquireLease = async (
|
|
531
|
+
appModule: string,
|
|
516
532
|
rootDir: string
|
|
517
|
-
): Promise<
|
|
533
|
+
): Promise<LeaseAttempt> => {
|
|
534
|
+
try {
|
|
535
|
+
return { lease: await loadFreshAppLease(appModule, rootDir), ok: true };
|
|
536
|
+
} catch (error) {
|
|
537
|
+
const loadError = error instanceof Error ? error.message : String(error);
|
|
538
|
+
return { loadError, ok: false };
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
const withVerifiedApp = async <T>(
|
|
543
|
+
appModule: string | null,
|
|
544
|
+
rootDir: string,
|
|
545
|
+
consume: (app: Topo) => T | Promise<T>
|
|
546
|
+
): Promise<{ readonly load: PromotionLoadState; readonly value: T | null }> => {
|
|
518
547
|
if (appModule === null) {
|
|
519
|
-
return { appModule, loadError: null,
|
|
548
|
+
return { load: { appModule, loadError: null }, value: null };
|
|
520
549
|
}
|
|
521
550
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
return undefined;
|
|
527
|
-
}
|
|
528
|
-
);
|
|
551
|
+
const attempt = await tryAcquireLease(appModule, rootDir);
|
|
552
|
+
if (!attempt.ok) {
|
|
553
|
+
return { load: { appModule, loadError: attempt.loadError }, value: null };
|
|
554
|
+
}
|
|
529
555
|
|
|
530
|
-
|
|
556
|
+
try {
|
|
557
|
+
const value = await consume(attempt.lease.app);
|
|
558
|
+
return { load: { appModule, loadError: null }, value };
|
|
559
|
+
} finally {
|
|
560
|
+
attempt.lease.release();
|
|
561
|
+
}
|
|
531
562
|
};
|
|
532
563
|
|
|
533
564
|
const toRenamedFiles = (rootDir: string, renames: readonly FileRename[]) =>
|
|
@@ -562,7 +593,7 @@ const buildUnverifiedPromotionResult = (
|
|
|
562
593
|
|
|
563
594
|
const buildVerifiedPromotionResult = (
|
|
564
595
|
rootDir: string,
|
|
565
|
-
analysis: ReturnType<typeof
|
|
596
|
+
analysis: ReturnType<typeof deriveDraftReport>,
|
|
566
597
|
promotedEstablished: boolean,
|
|
567
598
|
renames: readonly FileRename[],
|
|
568
599
|
updatedSourceFiles: Set<string>,
|
|
@@ -589,13 +620,13 @@ const buildVerifiedPromotionResult = (
|
|
|
589
620
|
|
|
590
621
|
const buildVerifiedPromotionResultFromApp = (
|
|
591
622
|
rootDir: string,
|
|
592
|
-
loadedApp:
|
|
623
|
+
loadedApp: Topo,
|
|
593
624
|
renames: readonly FileRename[],
|
|
594
625
|
updatedSourceFiles: Set<string>,
|
|
595
626
|
appModule: string | null,
|
|
596
627
|
toId: string
|
|
597
628
|
) => {
|
|
598
|
-
const analysis =
|
|
629
|
+
const analysis = deriveDraftReport(loadedApp);
|
|
599
630
|
const promotedNode = collectOutputId(loadedApp, toId);
|
|
600
631
|
const promotedEstablished =
|
|
601
632
|
promotedNode !== undefined && !analysis.contaminatedIds.has(toId);
|
|
@@ -633,13 +664,11 @@ const promoteDraftState = async (
|
|
|
633
664
|
|
|
634
665
|
const { renames, updatedSourceFiles } = rewriteResult.value;
|
|
635
666
|
const appModule = await resolvePromotionAppModule(input, rootDirResult.value);
|
|
636
|
-
const {
|
|
667
|
+
const { load, value } = await withVerifiedApp(
|
|
637
668
|
appModule,
|
|
638
|
-
rootDirResult.value
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
return loadedApp
|
|
642
|
-
? buildVerifiedPromotionResultFromApp(
|
|
669
|
+
rootDirResult.value,
|
|
670
|
+
(loadedApp) =>
|
|
671
|
+
buildVerifiedPromotionResultFromApp(
|
|
643
672
|
rootDirResult.value,
|
|
644
673
|
loadedApp,
|
|
645
674
|
renames,
|
|
@@ -647,13 +676,18 @@ const promoteDraftState = async (
|
|
|
647
676
|
appModule,
|
|
648
677
|
input.toId
|
|
649
678
|
)
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
679
|
+
);
|
|
680
|
+
|
|
681
|
+
return (
|
|
682
|
+
value ??
|
|
683
|
+
buildUnverifiedPromotionResult(
|
|
684
|
+
rootDirResult.value,
|
|
685
|
+
load.loadError,
|
|
686
|
+
renames,
|
|
687
|
+
updatedSourceFiles,
|
|
688
|
+
appModule
|
|
689
|
+
)
|
|
690
|
+
);
|
|
657
691
|
};
|
|
658
692
|
|
|
659
693
|
export const draftPromoteTrail = trail('draft.promote', {
|
|
@@ -664,6 +698,7 @@ export const draftPromoteTrail = trail('draft.promote', {
|
|
|
664
698
|
{
|
|
665
699
|
error: 'ValidationError',
|
|
666
700
|
input: {
|
|
701
|
+
// warden-ignore-next-line
|
|
667
702
|
fromId: '_draft.entity.prepare',
|
|
668
703
|
renameFiles: true,
|
|
669
704
|
rootDir: './__does_not_exist__/draft-promote-example',
|
package/src/trails/guide.ts
CHANGED
|
@@ -62,10 +62,7 @@ export const guideTrail = trail('guide', {
|
|
|
62
62
|
},
|
|
63
63
|
],
|
|
64
64
|
input: z.object({
|
|
65
|
-
module: z
|
|
66
|
-
.string()
|
|
67
|
-
.default('./src/app.ts')
|
|
68
|
-
.describe('Path to the app module'),
|
|
65
|
+
module: z.string().optional().describe('Path to the app module'),
|
|
69
66
|
trailId: z.string().optional().describe('Trail ID for detailed guidance'),
|
|
70
67
|
}),
|
|
71
68
|
intent: 'read',
|