@ontrails/trails 1.0.0-beta.12 → 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 (150) hide show
  1. package/.turbo/turbo-lint.log +1 -1
  2. package/CHANGELOG.md +40 -12
  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__/create.test.ts +35 -33
  115. package/src/__tests__/draft-promote.test.ts +144 -0
  116. package/src/__tests__/guide.test.ts +4 -4
  117. package/src/__tests__/load-app.test.ts +43 -0
  118. package/src/__tests__/survey.test.ts +140 -55
  119. package/src/__tests__/topo-dev.test.ts +424 -0
  120. package/src/__tests__/warden.test.ts +2 -2
  121. package/src/app.ts +24 -2
  122. package/src/clack.ts +1 -1
  123. package/src/cli.ts +2 -2
  124. package/src/trails/add-trail.ts +13 -13
  125. package/src/trails/{add-surface.ts → add-trailhead.ts} +39 -37
  126. package/src/trails/add-verify.ts +10 -10
  127. package/src/trails/create-scaffold.ts +28 -28
  128. package/src/trails/create.ts +42 -42
  129. package/src/trails/dev-clean.ts +73 -0
  130. package/src/trails/dev-reset.ts +44 -0
  131. package/src/trails/dev-stats.ts +64 -0
  132. package/src/trails/dev-support.ts +326 -0
  133. package/src/trails/draft-promote.ts +704 -0
  134. package/src/trails/guide.ts +29 -44
  135. package/src/trails/load-app.ts +76 -13
  136. package/src/trails/project.ts +17 -3
  137. package/src/trails/survey.ts +80 -279
  138. package/src/trails/topo-constants.ts +2 -0
  139. package/src/trails/topo-export.ts +39 -0
  140. package/src/trails/topo-history.ts +40 -0
  141. package/src/trails/topo-pin.ts +42 -0
  142. package/src/trails/topo-read-support.ts +332 -0
  143. package/src/trails/topo-reports.ts +221 -0
  144. package/src/trails/topo-show.ts +58 -0
  145. package/src/trails/topo-store-support.ts +96 -0
  146. package/src/trails/topo-support.ts +274 -0
  147. package/src/trails/topo-unpin.ts +51 -0
  148. package/src/trails/topo-verify.ts +29 -0
  149. package/src/trails/topo.ts +73 -0
  150. package/src/trails/warden.ts +33 -32
@@ -4,11 +4,14 @@
4
4
  * Lists trails with descriptions and examples. Detailed guidance is planned for post-v1.
5
5
  */
6
6
 
7
- import type { Topo, Trail } from '@ontrails/core';
8
- import { Result, trail } from '@ontrails/core';
7
+ import { NotFoundError, Result, trail } from '@ontrails/core';
9
8
  import { z } from 'zod';
10
9
 
11
10
  import { loadApp } from './load-app.js';
11
+ import {
12
+ buildCurrentGuideEntries,
13
+ buildCurrentTopoDetail,
14
+ } from './topo-read-support.js';
12
15
 
13
16
  // ---------------------------------------------------------------------------
14
17
  // Types
@@ -25,41 +28,36 @@ interface GuideEntry {
25
28
  // Helpers
26
29
  // ---------------------------------------------------------------------------
27
30
 
28
- const toGuideEntries = (app: Topo): GuideEntry[] => {
29
- const entries: GuideEntry[] = [];
30
-
31
- for (const item of app.list()) {
32
- const raw = item as unknown as Record<string, unknown>;
33
- entries.push({
34
- description:
35
- typeof raw['description'] === 'string'
36
- ? raw['description']
37
- : '(no description)',
38
- exampleCount: Array.isArray(raw['examples'])
39
- ? (raw['examples'] as unknown[]).length
40
- : 0,
41
- id: item.id,
42
- kind: item.kind,
43
- });
44
- }
45
-
46
- return entries;
47
- };
31
+ export const guideTrail = trail('guide', {
32
+ blaze: async (input, ctx) => {
33
+ const rootDir = ctx.cwd ?? '.';
34
+ const app = await loadApp(input.module, rootDir);
48
35
 
49
- const toGuideDetail = (item: Trail<unknown, unknown>): object => ({
50
- description: item.description ?? null,
51
- detours: item.detours ?? null,
52
- examples: item.examples ?? [],
53
- id: item.id,
54
- kind: item.kind,
55
- });
36
+ if (input.trailId) {
37
+ const detail = buildCurrentTopoDetail(app, input.trailId, { rootDir });
38
+ if (detail === undefined || detail.kind !== 'trail') {
39
+ return Result.err(
40
+ new NotFoundError(`Trail not found: ${input.trailId}`)
41
+ );
42
+ }
43
+ return Result.ok({
44
+ description: detail.description,
45
+ detours: detail.detours,
46
+ examples: detail.examples,
47
+ id: detail.id,
48
+ kind: detail.kind,
49
+ });
50
+ }
56
51
 
57
- export const guideTrail = trail('guide', {
52
+ return Result.ok(
53
+ buildCurrentGuideEntries(app, { rootDir }) as GuideEntry[]
54
+ );
55
+ },
58
56
  description: 'Runtime guidance for trails',
59
57
  examples: [
60
58
  {
61
59
  description: 'Lists all trails with descriptions and example counts',
62
- input: {},
60
+ input: { module: './src/app.ts' },
63
61
  name: 'List trail guidance',
64
62
  },
65
63
  ],
@@ -88,17 +86,4 @@ export const guideTrail = trail('guide', {
88
86
  kind: z.string(),
89
87
  }),
90
88
  ]),
91
- run: async (input, ctx) => {
92
- const app = await loadApp(input.module, ctx.cwd ?? '.');
93
-
94
- if (input.trailId) {
95
- const item = app.get(input.trailId);
96
- if (!item) {
97
- return Result.err(new Error(`Trail not found: ${input.trailId}`));
98
- }
99
- return Result.ok(toGuideDetail(item as Trail<unknown, unknown>));
100
- }
101
-
102
- return Result.ok(toGuideEntries(app));
103
- },
104
89
  });
@@ -1,31 +1,94 @@
1
- import { isAbsolute, resolve } from 'node:path';
2
- import { pathToFileURL } from 'node:url';
1
+ import { existsSync, rmSync } from 'node:fs';
2
+ import { basename, dirname, isAbsolute, join, resolve } from 'node:path';
3
+ import { fileURLToPath, pathToFileURL } from 'node:url';
3
4
 
4
5
  import type { Topo } from '@ontrails/core';
5
6
 
6
7
  const URL_SCHEME = /^[a-zA-Z][a-zA-Z\d+.-]*:/;
7
8
 
8
- /** Resolve a module path from cwd so CLI defaults behave like shell paths. */
9
- const resolveModuleSpecifier = (modulePath: string, cwd: string): string => {
10
- if (URL_SCHEME.test(modulePath)) {
11
- return modulePath;
12
- }
9
+ const resolveUrlModulePath = (modulePath: string): string => {
10
+ const url = new URL(modulePath);
11
+ return url.protocol === 'file:' ? fileURLToPath(url) : modulePath;
12
+ };
13
13
 
14
+ const resolveFilesystemModulePath = (
15
+ modulePath: string,
16
+ cwd: string
17
+ ): string => {
14
18
  const absolutePath = isAbsolute(modulePath)
15
19
  ? modulePath
16
20
  : resolve(cwd, modulePath);
17
- return pathToFileURL(absolutePath).href;
21
+ if (!absolutePath.endsWith('.js') || existsSync(absolutePath)) {
22
+ return absolutePath;
23
+ }
24
+
25
+ const tsPath = absolutePath.replace(/\.js$/, '.ts');
26
+ return existsSync(tsPath) ? tsPath : absolutePath;
27
+ };
28
+
29
+ /** Resolve a module path from cwd so CLI defaults behave like shell paths. */
30
+ const resolveAbsoluteModulePath = (modulePath: string, cwd: string): string =>
31
+ URL_SCHEME.test(modulePath)
32
+ ? resolveUrlModulePath(modulePath)
33
+ : resolveFilesystemModulePath(modulePath, cwd);
34
+
35
+ const freshModuleCopyPath = (absolutePath: string): string =>
36
+ join(
37
+ dirname(absolutePath),
38
+ `.__fresh-${Date.now()}-${Math.random().toString(36).slice(2)}-${basename(absolutePath)}`
39
+ );
40
+
41
+ /**
42
+ * Import a module bypassing the ESM cache for the entry file.
43
+ *
44
+ * @remarks
45
+ * Cache-busting applies to the entry module only. Transitive imports resolved
46
+ * by the entry file are still served from Bun's module cache. This is
47
+ * acceptable for the draft promotion workflow (the only caller) because
48
+ * promotion changes which modules the entry file imports, not the modules
49
+ * themselves. If a deeper cache-bust is needed in the future, consider
50
+ * Bun's `Loader.registry` or a full process restart.
51
+ */
52
+ const importFreshModule = async (
53
+ modulePath: string,
54
+ cwd: string
55
+ ): Promise<Record<string, unknown>> => {
56
+ const absolutePath = resolveAbsoluteModulePath(modulePath, cwd);
57
+ if (URL_SCHEME.test(absolutePath) && !absolutePath.startsWith('/')) {
58
+ const url = new URL(absolutePath);
59
+ url.searchParams.set('t', Date.now().toString());
60
+ return (await import(url.href)) as Record<string, unknown>;
61
+ }
62
+
63
+ const freshPath = freshModuleCopyPath(absolutePath);
64
+ await Bun.write(freshPath, await Bun.file(absolutePath).text());
65
+
66
+ try {
67
+ return (await import(pathToFileURL(freshPath).href)) as Record<
68
+ string,
69
+ unknown
70
+ >;
71
+ } finally {
72
+ rmSync(freshPath, { force: true });
73
+ }
18
74
  };
19
75
 
20
76
  /** Load a Topo export from a module path relative to cwd. */
21
77
  export const loadApp = async (
22
78
  modulePath: string,
23
- cwd: string
79
+ cwd: string,
80
+ options: { fresh?: boolean | undefined } = {}
24
81
  ): Promise<Topo> => {
25
- const mod = (await import(resolveModuleSpecifier(modulePath, cwd))) as Record<
26
- string,
27
- unknown
28
- >;
82
+ const resolvedModulePath = resolveAbsoluteModulePath(modulePath, cwd);
83
+ const mod =
84
+ options.fresh === true
85
+ ? await importFreshModule(modulePath, cwd)
86
+ : ((await import(
87
+ URL_SCHEME.test(resolvedModulePath) &&
88
+ !resolvedModulePath.startsWith('/')
89
+ ? new URL(resolvedModulePath).href
90
+ : pathToFileURL(resolvedModulePath).href
91
+ )) as Record<string, unknown>);
29
92
  const app = (mod['default'] ?? mod['app']) as Topo | undefined;
30
93
  if (!app?.trails) {
31
94
  throw new Error(
@@ -5,10 +5,24 @@
5
5
  import { existsSync } from 'node:fs';
6
6
  import { join } from 'node:path';
7
7
 
8
+ import { isDraftMarkedFile } from '@ontrails/warden';
9
+
8
10
  /** Return all TypeScript entries in a project's src directory. */
9
- const scanSourceEntries = (srcDir: string): string[] => [
10
- ...new Bun.Glob('*.ts').scanSync({ cwd: srcDir }),
11
- ];
11
+ const sourceEntryPriority = (entry: string): number => {
12
+ if (entry === 'app.ts') {
13
+ return 0;
14
+ }
15
+ return isDraftMarkedFile(entry) ? 2 : 1;
16
+ };
17
+
18
+ const scanSourceEntries = (srcDir: string): string[] =>
19
+ [...new Bun.Glob('*.ts').scanSync({ cwd: srcDir })].toSorted((a, b) => {
20
+ const priority = sourceEntryPriority(a) - sourceEntryPriority(b);
21
+ if (priority === 0) {
22
+ return a.localeCompare(b);
23
+ }
24
+ return priority;
25
+ });
12
26
 
13
27
  /** Resolve an entry to an app import if it contains topo(). */
14
28
  const toTopoImport = async (