@ontrails/trails 1.0.0-beta.13 → 1.0.0-beta.14

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 (140) hide show
  1. package/.turbo/turbo-lint.log +1 -1
  2. package/CHANGELOG.md +16 -0
  3. package/__tests__/examples.test.ts +14 -0
  4. package/dist/src/app.d.ts.map +1 -1
  5. package/dist/src/app.js +13 -2
  6. package/dist/src/app.js.map +1 -1
  7. package/dist/src/clack.d.ts +1 -1
  8. package/dist/src/clack.js +1 -1
  9. package/dist/src/cli.js +2 -2
  10. package/dist/src/cli.js.map +1 -1
  11. package/dist/src/trails/add-trail.js +13 -13
  12. package/dist/src/trails/add-trail.js.map +1 -1
  13. package/dist/src/trails/add-trailhead.d.ts +13 -0
  14. package/dist/src/trails/add-trailhead.d.ts.map +1 -0
  15. package/dist/src/trails/add-trailhead.js +88 -0
  16. package/dist/src/trails/add-trailhead.js.map +1 -0
  17. package/dist/src/trails/add-verify.js +10 -10
  18. package/dist/src/trails/add-verify.js.map +1 -1
  19. package/dist/src/trails/create-scaffold.js +26 -26
  20. package/dist/src/trails/create-scaffold.js.map +1 -1
  21. package/dist/src/trails/create.d.ts +6 -6
  22. package/dist/src/trails/create.d.ts.map +1 -1
  23. package/dist/src/trails/create.js +29 -29
  24. package/dist/src/trails/create.js.map +1 -1
  25. package/dist/src/trails/dev-clean.d.ts +9 -0
  26. package/dist/src/trails/dev-clean.d.ts.map +1 -0
  27. package/dist/src/trails/dev-clean.js +65 -0
  28. package/dist/src/trails/dev-clean.js.map +1 -0
  29. package/dist/src/trails/dev-reset.d.ts +6 -0
  30. package/dist/src/trails/dev-reset.d.ts.map +1 -0
  31. package/dist/src/trails/dev-reset.js +38 -0
  32. package/dist/src/trails/dev-reset.js.map +1 -0
  33. package/dist/src/trails/dev-stats.d.ts +7 -0
  34. package/dist/src/trails/dev-stats.d.ts.map +1 -0
  35. package/dist/src/trails/dev-stats.js +61 -0
  36. package/dist/src/trails/dev-stats.js.map +1 -0
  37. package/dist/src/trails/dev-support.d.ts +64 -0
  38. package/dist/src/trails/dev-support.d.ts.map +1 -0
  39. package/dist/src/trails/dev-support.js +178 -0
  40. package/dist/src/trails/dev-support.js.map +1 -0
  41. package/dist/src/trails/draft-promote.d.ts +18 -0
  42. package/dist/src/trails/draft-promote.d.ts.map +1 -0
  43. package/dist/src/trails/draft-promote.js +386 -0
  44. package/dist/src/trails/draft-promote.js.map +1 -0
  45. package/dist/src/trails/guide.d.ts +13 -3
  46. package/dist/src/trails/guide.d.ts.map +1 -1
  47. package/dist/src/trails/guide.js +21 -37
  48. package/dist/src/trails/guide.js.map +1 -1
  49. package/dist/src/trails/load-app.d.ts +3 -1
  50. package/dist/src/trails/load-app.d.ts.map +1 -1
  51. package/dist/src/trails/load-app.js +53 -10
  52. package/dist/src/trails/load-app.js.map +1 -1
  53. package/dist/src/trails/project.d.ts.map +1 -1
  54. package/dist/src/trails/project.js +14 -3
  55. package/dist/src/trails/project.js.map +1 -1
  56. package/dist/src/trails/survey.d.ts +4 -58
  57. package/dist/src/trails/survey.d.ts.map +1 -1
  58. package/dist/src/trails/survey.js +52 -173
  59. package/dist/src/trails/survey.js.map +1 -1
  60. package/dist/src/trails/topo-constants.d.ts +3 -0
  61. package/dist/src/trails/topo-constants.d.ts.map +1 -0
  62. package/dist/src/trails/topo-constants.js +3 -0
  63. package/dist/src/trails/topo-constants.js.map +1 -0
  64. package/dist/src/trails/topo-export.d.ts +18 -0
  65. package/dist/src/trails/topo-export.d.ts.map +1 -0
  66. package/dist/src/trails/topo-export.js +34 -0
  67. package/dist/src/trails/topo-export.js.map +1 -0
  68. package/dist/src/trails/topo-history.d.ts +24 -0
  69. package/dist/src/trails/topo-history.d.ts.map +1 -0
  70. package/dist/src/trails/topo-history.js +33 -0
  71. package/dist/src/trails/topo-history.js.map +1 -0
  72. package/dist/src/trails/topo-pin.d.ts +21 -0
  73. package/dist/src/trails/topo-pin.d.ts.map +1 -0
  74. package/dist/src/trails/topo-pin.js +35 -0
  75. package/dist/src/trails/topo-pin.js.map +1 -0
  76. package/dist/src/trails/topo-read-support.d.ts +54 -0
  77. package/dist/src/trails/topo-read-support.d.ts.map +1 -0
  78. package/dist/src/trails/topo-read-support.js +178 -0
  79. package/dist/src/trails/topo-read-support.js.map +1 -0
  80. package/dist/src/trails/topo-reports.d.ts +50 -0
  81. package/dist/src/trails/topo-reports.d.ts.map +1 -0
  82. package/dist/src/trails/topo-reports.js +122 -0
  83. package/dist/src/trails/topo-reports.js.map +1 -0
  84. package/dist/src/trails/topo-show.d.ts +23 -0
  85. package/dist/src/trails/topo-show.d.ts.map +1 -0
  86. package/dist/src/trails/topo-show.js +53 -0
  87. package/dist/src/trails/topo-show.js.map +1 -0
  88. package/dist/src/trails/topo-store-support.d.ts +13 -0
  89. package/dist/src/trails/topo-store-support.d.ts.map +1 -0
  90. package/dist/src/trails/topo-store-support.js +55 -0
  91. package/dist/src/trails/topo-store-support.js.map +1 -0
  92. package/dist/src/trails/topo-support.d.ts +87 -0
  93. package/dist/src/trails/topo-support.d.ts.map +1 -0
  94. package/dist/src/trails/topo-support.js +165 -0
  95. package/dist/src/trails/topo-support.js.map +1 -0
  96. package/dist/src/trails/topo-unpin.d.ts +15 -0
  97. package/dist/src/trails/topo-unpin.d.ts.map +1 -0
  98. package/dist/src/trails/topo-unpin.js +39 -0
  99. package/dist/src/trails/topo-unpin.js.map +1 -0
  100. package/dist/src/trails/topo-verify.d.ts +5 -0
  101. package/dist/src/trails/topo-verify.d.ts.map +1 -0
  102. package/dist/src/trails/topo-verify.js +28 -0
  103. package/dist/src/trails/topo-verify.js.map +1 -0
  104. package/dist/src/trails/topo.d.ts +5 -0
  105. package/dist/src/trails/topo.d.ts.map +1 -0
  106. package/dist/src/trails/topo.js +67 -0
  107. package/dist/src/trails/topo.js.map +1 -0
  108. package/dist/src/trails/warden.d.ts +1 -1
  109. package/dist/src/trails/warden.d.ts.map +1 -1
  110. package/dist/src/trails/warden.js +28 -27
  111. package/dist/src/trails/warden.js.map +1 -1
  112. package/dist/tsconfig.tsbuildinfo +1 -1
  113. package/package.json +8 -7
  114. package/src/__tests__/draft-promote.test.ts +144 -0
  115. package/src/__tests__/load-app.test.ts +43 -0
  116. package/src/__tests__/survey.test.ts +85 -0
  117. package/src/__tests__/topo-dev.test.ts +424 -0
  118. package/src/app.ts +22 -0
  119. package/src/trails/dev-clean.ts +73 -0
  120. package/src/trails/dev-reset.ts +44 -0
  121. package/src/trails/dev-stats.ts +64 -0
  122. package/src/trails/dev-support.ts +326 -0
  123. package/src/trails/draft-promote.ts +704 -0
  124. package/src/trails/guide.ts +22 -37
  125. package/src/trails/load-app.ts +76 -13
  126. package/src/trails/project.ts +17 -3
  127. package/src/trails/survey.ts +56 -256
  128. package/src/trails/topo-constants.ts +2 -0
  129. package/src/trails/topo-export.ts +39 -0
  130. package/src/trails/topo-history.ts +40 -0
  131. package/src/trails/topo-pin.ts +42 -0
  132. package/src/trails/topo-read-support.ts +332 -0
  133. package/src/trails/topo-reports.ts +221 -0
  134. package/src/trails/topo-show.ts +58 -0
  135. package/src/trails/topo-store-support.ts +96 -0
  136. package/src/trails/topo-support.ts +274 -0
  137. package/src/trails/topo-unpin.ts +51 -0
  138. package/src/trails/topo-verify.ts +29 -0
  139. package/src/trails/topo.ts +73 -0
  140. package/src/trails/warden.ts +1 -0
@@ -0,0 +1,326 @@
1
+ import { existsSync, rmSync, statSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+
4
+ import {
5
+ countPrunableTopoSaves,
6
+ countTopoPins,
7
+ countTopoSaves,
8
+ pruneUnpinnedTopoSaves,
9
+ } from '@ontrails/core/internal/topo-saves';
10
+ import {
11
+ openReadTrailsDb,
12
+ openWriteTrailsDb,
13
+ resolveTrailsDbPath,
14
+ resolveTrailsDir,
15
+ } from '@ontrails/core/internal/trails-db';
16
+ import {
17
+ DEFAULT_MAX_AGE,
18
+ DEFAULT_MAX_RECORDS,
19
+ applyTrackCleanup,
20
+ countTrackRecords,
21
+ previewTrackCleanup,
22
+ } from '@ontrails/tracker/internal/dev-state';
23
+
24
+ import { resolveLockPath } from './topo-support.js';
25
+
26
+ export const DEFAULT_TOPO_SAVE_RETENTION = 50;
27
+
28
+ const resolveRootDir = (cwd?: string): string => cwd ?? process.cwd();
29
+
30
+ const removeIfPresent = (filePath: string): boolean => {
31
+ if (!existsSync(filePath)) {
32
+ return false;
33
+ }
34
+ rmSync(filePath, { force: true });
35
+ return true;
36
+ };
37
+
38
+ export interface DevStatsReport {
39
+ readonly db: {
40
+ readonly exists: boolean;
41
+ readonly fileSizeBytes: number;
42
+ readonly path: string;
43
+ };
44
+ readonly lock: {
45
+ readonly exists: boolean;
46
+ readonly fileSizeBytes: number;
47
+ readonly path: string;
48
+ };
49
+ readonly retention: {
50
+ readonly saves: number;
51
+ readonly trackAgeMs: number;
52
+ readonly tracks: number;
53
+ };
54
+ readonly topo: {
55
+ readonly pinCount: number;
56
+ readonly prunableSaveCount: number;
57
+ readonly saveCount: number;
58
+ };
59
+ readonly tracker: {
60
+ readonly recordCount: number;
61
+ };
62
+ }
63
+
64
+ export interface DevCleanReport {
65
+ readonly dryRun: boolean;
66
+ readonly remaining: {
67
+ readonly pinCount: number;
68
+ readonly saveCount: number;
69
+ readonly trackCount: number;
70
+ };
71
+ readonly removed: {
72
+ readonly topoSaves: number;
73
+ readonly trackRecords: number;
74
+ };
75
+ readonly retention: {
76
+ readonly saves: number;
77
+ readonly trackAgeMs: number;
78
+ readonly tracks: number;
79
+ };
80
+ }
81
+
82
+ export interface DevResetReport {
83
+ readonly dryRun: boolean;
84
+ readonly removedCount: number;
85
+ readonly removedFiles: readonly string[];
86
+ }
87
+
88
+ interface DevRetentionOptions {
89
+ readonly maxAge?: number;
90
+ readonly maxRecords?: number;
91
+ readonly rootDir?: string;
92
+ readonly saveRetention?: number;
93
+ }
94
+
95
+ interface DevCleanupContext {
96
+ readonly dbPath: string;
97
+ readonly dryRun: boolean;
98
+ readonly retention: DevCleanReport['retention'];
99
+ readonly rootDir: string;
100
+ }
101
+
102
+ const buildRetention = (options?: DevRetentionOptions) => ({
103
+ saves: options?.saveRetention ?? DEFAULT_TOPO_SAVE_RETENTION,
104
+ trackAgeMs: options?.maxAge ?? DEFAULT_MAX_AGE,
105
+ tracks: options?.maxRecords ?? DEFAULT_MAX_RECORDS,
106
+ });
107
+
108
+ const emptyDevClean = (
109
+ retention: DevCleanReport['retention'],
110
+ dryRun: boolean
111
+ ): DevCleanReport => ({
112
+ dryRun,
113
+ remaining: {
114
+ pinCount: 0,
115
+ saveCount: 0,
116
+ trackCount: 0,
117
+ },
118
+ removed: {
119
+ topoSaves: 0,
120
+ trackRecords: 0,
121
+ },
122
+ retention,
123
+ });
124
+
125
+ const buildLockStats = (lockPath: string): DevStatsReport['lock'] => ({
126
+ exists: existsSync(lockPath),
127
+ fileSizeBytes: existsSync(lockPath) ? statSync(lockPath).size : 0,
128
+ path: lockPath,
129
+ });
130
+
131
+ const buildDbStats = (
132
+ dbPath: string,
133
+ exists: boolean
134
+ ): DevStatsReport['db'] => ({
135
+ exists,
136
+ fileSizeBytes: exists ? statSync(dbPath).size : 0,
137
+ path: '.trails/trails.db',
138
+ });
139
+
140
+ const emptyDevStats = (
141
+ dbPath: string,
142
+ lockPath: string,
143
+ retention: DevStatsReport['retention']
144
+ ): DevStatsReport => ({
145
+ db: buildDbStats(dbPath, false),
146
+ lock: buildLockStats(lockPath),
147
+ retention,
148
+ topo: {
149
+ pinCount: 0,
150
+ prunableSaveCount: 0,
151
+ saveCount: 0,
152
+ },
153
+ tracker: {
154
+ recordCount: 0,
155
+ },
156
+ });
157
+
158
+ const liveDevStats = (
159
+ db: Parameters<typeof countTopoPins>[0],
160
+ dbPath: string,
161
+ lockPath: string,
162
+ retention: DevStatsReport['retention']
163
+ ): DevStatsReport => ({
164
+ db: buildDbStats(dbPath, true),
165
+ lock: buildLockStats(lockPath),
166
+ retention,
167
+ topo: {
168
+ pinCount: countTopoPins(db),
169
+ prunableSaveCount: countPrunableTopoSaves(db, { keep: retention.saves }),
170
+ saveCount: countTopoSaves(db),
171
+ },
172
+ tracker: {
173
+ recordCount: countTrackRecords(db),
174
+ },
175
+ });
176
+
177
+ const resolveDevStatsContext = (options?: DevRetentionOptions) => {
178
+ const rootDir = resolveRootDir(options?.rootDir);
179
+ const dbPath = resolveTrailsDbPath({ rootDir });
180
+ const trailsDir = resolveTrailsDir({ rootDir });
181
+ const lockPath = resolveLockPath(trailsDir);
182
+ return {
183
+ dbExists: existsSync(dbPath),
184
+ dbPath,
185
+ lockPath,
186
+ retention: buildRetention(options),
187
+ rootDir,
188
+ };
189
+ };
190
+
191
+ const resolveDevCleanupContext = (
192
+ options?: DevRetentionOptions & { readonly dryRun?: boolean }
193
+ ): DevCleanupContext => {
194
+ const rootDir = resolveRootDir(options?.rootDir);
195
+ return {
196
+ dbPath: resolveTrailsDbPath({ rootDir }),
197
+ dryRun: options?.dryRun ?? false,
198
+ retention: buildRetention(options),
199
+ rootDir,
200
+ };
201
+ };
202
+
203
+ const cleanupTracks = (
204
+ db: Parameters<typeof countTopoPins>[0],
205
+ context: DevCleanupContext
206
+ ) =>
207
+ context.dryRun
208
+ ? previewTrackCleanup(db, {
209
+ maxAge: context.retention.trackAgeMs,
210
+ maxRecords: context.retention.tracks,
211
+ })
212
+ : applyTrackCleanup(db, {
213
+ maxAge: context.retention.trackAgeMs,
214
+ maxRecords: context.retention.tracks,
215
+ });
216
+
217
+ const cleanupTopoSaves = (
218
+ db: Parameters<typeof countTopoPins>[0],
219
+ context: DevCleanupContext
220
+ ): number =>
221
+ context.dryRun
222
+ ? countPrunableTopoSaves(db, { keep: context.retention.saves })
223
+ : pruneUnpinnedTopoSaves(db, { keep: context.retention.saves });
224
+
225
+ const buildCleanReport = (
226
+ db: Parameters<typeof countTopoPins>[0],
227
+ context: DevCleanupContext
228
+ ): DevCleanReport => {
229
+ const trackReport = cleanupTracks(db, context);
230
+ const topoRemoved = cleanupTopoSaves(db, context);
231
+ const saveCount = countTopoSaves(db);
232
+
233
+ return {
234
+ dryRun: context.dryRun,
235
+ remaining: {
236
+ pinCount: countTopoPins(db),
237
+ saveCount: context.dryRun ? saveCount - topoRemoved : saveCount,
238
+ trackCount: context.dryRun
239
+ ? trackReport.remaining - trackReport.removedTotal
240
+ : trackReport.remaining,
241
+ },
242
+ removed: {
243
+ topoSaves: topoRemoved,
244
+ trackRecords: trackReport.removedTotal,
245
+ },
246
+ retention: context.retention,
247
+ };
248
+ };
249
+
250
+ const RESET_FILES = [
251
+ '.trails/trails.db',
252
+ '.trails/trails.db-shm',
253
+ '.trails/trails.db-wal',
254
+ '.trails/dev/tracker.db',
255
+ '.trails/dev/tracker.db-shm',
256
+ '.trails/dev/tracker.db-wal',
257
+ ] as const;
258
+
259
+ const presentResetFiles = (
260
+ rootDir: string
261
+ ): readonly (typeof RESET_FILES)[number][] =>
262
+ RESET_FILES.filter((relativePath) => existsSync(join(rootDir, relativePath)));
263
+
264
+ export const buildDevStats = (
265
+ options?: DevRetentionOptions
266
+ ): DevStatsReport => {
267
+ const { dbExists, dbPath, lockPath, retention, rootDir } =
268
+ resolveDevStatsContext(options);
269
+
270
+ if (!dbExists) {
271
+ return emptyDevStats(dbPath, lockPath, retention);
272
+ }
273
+
274
+ const db = openReadTrailsDb({ rootDir });
275
+
276
+ try {
277
+ return liveDevStats(db, dbPath, lockPath, retention);
278
+ } finally {
279
+ db.close();
280
+ }
281
+ };
282
+
283
+ export const cleanDevState = (
284
+ options?: DevRetentionOptions & { readonly dryRun?: boolean }
285
+ ): DevCleanReport => {
286
+ const context = resolveDevCleanupContext(options);
287
+ if (!existsSync(context.dbPath)) {
288
+ return emptyDevClean(context.retention, context.dryRun);
289
+ }
290
+
291
+ const db = context.dryRun
292
+ ? openReadTrailsDb({ rootDir: context.rootDir })
293
+ : openWriteTrailsDb({ rootDir: context.rootDir });
294
+
295
+ try {
296
+ return buildCleanReport(db, context);
297
+ } finally {
298
+ db.close();
299
+ }
300
+ };
301
+
302
+ export const resetDevState = (options?: {
303
+ readonly dryRun?: boolean;
304
+ readonly rootDir?: string;
305
+ }): DevResetReport => {
306
+ const rootDir = resolveRootDir(options?.rootDir);
307
+ const files = presentResetFiles(rootDir);
308
+
309
+ if (options?.dryRun === true) {
310
+ return {
311
+ dryRun: true,
312
+ removedCount: files.length,
313
+ removedFiles: files,
314
+ };
315
+ }
316
+
317
+ const removedFiles = files.filter((relativePath) =>
318
+ removeIfPresent(join(rootDir, relativePath))
319
+ );
320
+
321
+ return {
322
+ dryRun: false,
323
+ removedCount: removedFiles.length,
324
+ removedFiles,
325
+ };
326
+ };