@ontrails/trails 1.0.0-beta.13 → 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 +29 -0
- package/__tests__/examples.test.ts +39 -0
- package/dist/src/app.d.ts.map +1 -1
- package/dist/src/app.js +12 -1
- package/dist/src/app.js.map +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 +49 -22
- package/dist/src/trails/add-trail.js.map +1 -1
- package/dist/src/trails/add-trailhead.d.ts +13 -0
- package/dist/src/trails/add-trailhead.d.ts.map +1 -0
- package/dist/src/trails/add-trailhead.js +88 -0
- package/dist/src/trails/add-trailhead.js.map +1 -0
- 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 +17 -16
- 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 +34 -27
- package/dist/src/trails/create-scaffold.js.map +1 -1
- package/dist/src/trails/create.d.ts +9 -13
- package/dist/src/trails/create.d.ts.map +1 -1
- package/dist/src/trails/create.js +40 -35
- package/dist/src/trails/create.js.map +1 -1
- package/dist/src/trails/dev-clean.d.ts +9 -0
- package/dist/src/trails/dev-clean.d.ts.map +1 -0
- package/dist/src/trails/dev-clean.js +66 -0
- package/dist/src/trails/dev-clean.js.map +1 -0
- package/dist/src/trails/dev-reset.d.ts +6 -0
- package/dist/src/trails/dev-reset.d.ts.map +1 -0
- package/dist/src/trails/dev-reset.js +39 -0
- package/dist/src/trails/dev-reset.js.map +1 -0
- package/dist/src/trails/dev-stats.d.ts +7 -0
- package/dist/src/trails/dev-stats.d.ts.map +1 -0
- package/dist/src/trails/dev-stats.js +61 -0
- package/dist/src/trails/dev-stats.js.map +1 -0
- package/dist/src/trails/dev-support.d.ts +64 -0
- package/dist/src/trails/dev-support.d.ts.map +1 -0
- package/dist/src/trails/dev-support.js +181 -0
- package/dist/src/trails/dev-support.js.map +1 -0
- package/dist/src/trails/draft-promote.d.ts +18 -0
- package/dist/src/trails/draft-promote.d.ts.map +1 -0
- package/dist/src/trails/draft-promote.js +400 -0
- package/dist/src/trails/draft-promote.js.map +1 -0
- package/dist/src/trails/guide.d.ts +14 -4
- package/dist/src/trails/guide.d.ts.map +1 -1
- package/dist/src/trails/guide.js +22 -41
- package/dist/src/trails/guide.js.map +1 -1
- package/dist/src/trails/load-app.d.ts +9 -1
- package/dist/src/trails/load-app.d.ts.map +1 -1
- package/dist/src/trails/load-app.js +404 -13
- package/dist/src/trails/load-app.js.map +1 -1
- package/dist/src/trails/project.d.ts.map +1 -1
- package/dist/src/trails/project.js +14 -3
- package/dist/src/trails/project.js.map +1 -1
- package/dist/src/trails/survey.d.ts +6 -60
- package/dist/src/trails/survey.d.ts.map +1 -1
- package/dist/src/trails/survey.js +83 -182
- package/dist/src/trails/survey.js.map +1 -1
- package/dist/src/trails/topo-constants.d.ts +3 -0
- package/dist/src/trails/topo-constants.d.ts.map +1 -0
- package/dist/src/trails/topo-constants.js +3 -0
- package/dist/src/trails/topo-constants.js.map +1 -0
- package/dist/src/trails/topo-export.d.ts +19 -0
- package/dist/src/trails/topo-export.d.ts.map +1 -0
- package/dist/src/trails/topo-export.js +31 -0
- package/dist/src/trails/topo-export.js.map +1 -0
- package/dist/src/trails/topo-history.d.ts +20 -0
- package/dist/src/trails/topo-history.d.ts.map +1 -0
- package/dist/src/trails/topo-history.js +32 -0
- package/dist/src/trails/topo-history.js.map +1 -0
- package/dist/src/trails/topo-pin.d.ts +17 -0
- package/dist/src/trails/topo-pin.d.ts.map +1 -0
- package/dist/src/trails/topo-pin.js +31 -0
- package/dist/src/trails/topo-pin.js.map +1 -0
- package/dist/src/trails/topo-read-support.d.ts +58 -0
- package/dist/src/trails/topo-read-support.d.ts.map +1 -0
- package/dist/src/trails/topo-read-support.js +167 -0
- package/dist/src/trails/topo-read-support.js.map +1 -0
- package/dist/src/trails/topo-reports.d.ts +54 -0
- package/dist/src/trails/topo-reports.d.ts.map +1 -0
- package/dist/src/trails/topo-reports.js +128 -0
- package/dist/src/trails/topo-reports.js.map +1 -0
- package/dist/src/trails/topo-show.d.ts +23 -0
- package/dist/src/trails/topo-show.d.ts.map +1 -0
- package/dist/src/trails/topo-show.js +49 -0
- package/dist/src/trails/topo-show.js.map +1 -0
- package/dist/src/trails/topo-store-support.d.ts +13 -0
- package/dist/src/trails/topo-store-support.d.ts.map +1 -0
- package/dist/src/trails/topo-store-support.js +55 -0
- package/dist/src/trails/topo-store-support.js.map +1 -0
- package/dist/src/trails/topo-support.d.ts +76 -0
- package/dist/src/trails/topo-support.d.ts.map +1 -0
- package/dist/src/trails/topo-support.js +132 -0
- package/dist/src/trails/topo-support.js.map +1 -0
- package/dist/src/trails/topo-unpin.d.ts +20 -0
- package/dist/src/trails/topo-unpin.d.ts.map +1 -0
- package/dist/src/trails/topo-unpin.js +44 -0
- package/dist/src/trails/topo-unpin.js.map +1 -0
- package/dist/src/trails/topo-verify.d.ts +5 -0
- package/dist/src/trails/topo-verify.d.ts.map +1 -0
- package/dist/src/trails/topo-verify.js +24 -0
- package/dist/src/trails/topo-verify.js.map +1 -0
- package/dist/src/trails/topo.d.ts +5 -0
- package/dist/src/trails/topo.d.ts.map +1 -0
- package/dist/src/trails/topo.js +63 -0
- package/dist/src/trails/topo.js.map +1 -0
- 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 +37 -27
- 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 -7
- package/src/__tests__/add-trail.test.ts +97 -0
- package/src/__tests__/create.test.ts +91 -27
- package/src/__tests__/draft-promote.test.ts +144 -0
- package/src/__tests__/guide.test.ts +10 -5
- package/src/__tests__/load-app.test.ts +406 -2
- package/src/__tests__/survey.test.ts +221 -60
- package/src/__tests__/topo-dev.test.ts +426 -0
- package/src/app.ts +24 -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 +77 -0
- package/src/trails/dev-reset.ts +45 -0
- package/src/trails/dev-stats.ts +67 -0
- package/src/trails/dev-support.ts +328 -0
- package/src/trails/draft-promote.ts +739 -0
- package/src/trails/guide.ts +23 -41
- package/src/trails/load-app.ts +556 -14
- package/src/trails/project.ts +17 -3
- package/src/trails/survey.ts +110 -285
- package/src/trails/topo-constants.ts +2 -0
- package/src/trails/topo-export.ts +35 -0
- package/src/trails/topo-history.ts +38 -0
- package/src/trails/topo-pin.ts +38 -0
- package/src/trails/topo-read-support.ts +329 -0
- package/src/trails/topo-reports.ts +228 -0
- package/src/trails/topo-show.ts +54 -0
- package/src/trails/topo-store-support.ts +104 -0
- package/src/trails/topo-support.ts +230 -0
- package/src/trails/topo-unpin.ts +56 -0
- package/src/trails/topo-verify.ts +25 -0
- package/src/trails/topo.ts +69 -0
- package/src/trails/warden.ts +13 -3
- package/src/versions.ts +43 -0
- package/tsconfig.tests.json +10 -0
- package/src/trails/add-trailhead.ts +0 -121
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ontrails/trails",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.15",
|
|
4
4
|
"bin": {
|
|
5
5
|
"trails": "./bin/trails.ts"
|
|
6
6
|
},
|
|
@@ -14,15 +14,16 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@clack/prompts": "^1.1.0",
|
|
17
|
-
"@ontrails/cli": "^1.0.0-beta.
|
|
18
|
-
"@ontrails/core": "^1.0.0-beta.
|
|
19
|
-
"@ontrails/logging": "^1.0.0-beta.
|
|
20
|
-
"@ontrails/schema": "^1.0.0-beta.
|
|
21
|
-
"@ontrails/
|
|
17
|
+
"@ontrails/cli": "^1.0.0-beta.14",
|
|
18
|
+
"@ontrails/core": "^1.0.0-beta.14",
|
|
19
|
+
"@ontrails/logging": "^1.0.0-beta.14",
|
|
20
|
+
"@ontrails/schema": "^1.0.0-beta.14",
|
|
21
|
+
"@ontrails/tracing": "^1.0.0-beta.14",
|
|
22
|
+
"@ontrails/warden": "^1.0.0-beta.14",
|
|
22
23
|
"commander": "^14.0.3",
|
|
23
24
|
"zod": "^4.3.5"
|
|
24
25
|
},
|
|
25
26
|
"devDependencies": {
|
|
26
|
-
"@ontrails/testing": "^1.0.0-beta.
|
|
27
|
+
"@ontrails/testing": "^1.0.0-beta.14"
|
|
27
28
|
}
|
|
28
29
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
|
|
5
|
+
import type { Result } from '@ontrails/core';
|
|
6
|
+
import { ValidationError, validateInput } from '@ontrails/core';
|
|
7
|
+
|
|
8
|
+
import { addTrail } from '../trails/add-trail.js';
|
|
9
|
+
|
|
10
|
+
const repoTempDir = (): string =>
|
|
11
|
+
join(
|
|
12
|
+
resolve(import.meta.dir, '../..'),
|
|
13
|
+
'.tmp-tests',
|
|
14
|
+
`add-trail-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
const expectOk = <T>(result: Result<T, Error>): T => {
|
|
18
|
+
if (result.isErr()) {
|
|
19
|
+
throw result.error;
|
|
20
|
+
}
|
|
21
|
+
return result.value;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const expectValidationError = (
|
|
25
|
+
result: Result<unknown, Error>
|
|
26
|
+
): ValidationError => {
|
|
27
|
+
if (result.isOk()) {
|
|
28
|
+
throw new Error('Expected validation error');
|
|
29
|
+
}
|
|
30
|
+
expect(result.error).toBeInstanceOf(ValidationError);
|
|
31
|
+
return result.error as ValidationError;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const readGeneratedFile = (dir: string, relativePath: string): string => {
|
|
35
|
+
const filePath = join(dir, relativePath);
|
|
36
|
+
expect(existsSync(filePath)).toBe(true);
|
|
37
|
+
return readFileSync(filePath, 'utf8');
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const assertGeneratedScaffold = (dir: string): void => {
|
|
41
|
+
const trailSource = readGeneratedFile(dir, 'src/trails/entity-prepare.ts');
|
|
42
|
+
const testSource = readGeneratedFile(dir, '__tests__/entity-prepare.test.ts');
|
|
43
|
+
|
|
44
|
+
expect(trailSource).toContain('description: "Prepare an entity for export"');
|
|
45
|
+
expect(trailSource).toContain('name: "Prepare a draft entity"');
|
|
46
|
+
expect(trailSource).toContain(
|
|
47
|
+
'expected: { message: "entity.prepare completed" }'
|
|
48
|
+
);
|
|
49
|
+
expect(testSource).toContain('description: "Prepare a draft entity"');
|
|
50
|
+
expect(testSource).toContain(
|
|
51
|
+
'expectValue: { message: "entity.prepare completed" }'
|
|
52
|
+
);
|
|
53
|
+
expect(trailSource).not.toContain('TODO');
|
|
54
|
+
expect(testSource).not.toContain('TODO');
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
describe('add.trail', () => {
|
|
58
|
+
test('requires authored description and example metadata', () => {
|
|
59
|
+
const error = expectValidationError(
|
|
60
|
+
validateInput(addTrail.input, {
|
|
61
|
+
id: 'entity.prepare',
|
|
62
|
+
intent: 'write',
|
|
63
|
+
})
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
expect(error.message).toContain('description');
|
|
67
|
+
expect(error.message).toContain('exampleName');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('writes starter files without TODO placeholders', async () => {
|
|
71
|
+
const dir = repoTempDir();
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
mkdirSync(dir, { recursive: true });
|
|
75
|
+
|
|
76
|
+
const result = expectOk(
|
|
77
|
+
await addTrail.blaze(
|
|
78
|
+
{
|
|
79
|
+
description: 'Prepare an entity for export',
|
|
80
|
+
exampleName: 'Prepare a draft entity',
|
|
81
|
+
id: 'entity.prepare',
|
|
82
|
+
intent: 'write',
|
|
83
|
+
},
|
|
84
|
+
{ cwd: dir } as never
|
|
85
|
+
)
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
expect(result.created).toEqual([
|
|
89
|
+
'src/trails/entity-prepare.ts',
|
|
90
|
+
'__tests__/entity-prepare.test.ts',
|
|
91
|
+
]);
|
|
92
|
+
assertGeneratedScaffold(dir);
|
|
93
|
+
} finally {
|
|
94
|
+
rmSync(dir, { force: true, recursive: true });
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -11,14 +11,18 @@ import { basename, dirname, join } from 'node:path';
|
|
|
11
11
|
|
|
12
12
|
import { Result } from '@ontrails/core';
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import { addSurface } from '../trails/add-surface.js';
|
|
15
15
|
import { addVerify } from '../trails/add-verify.js';
|
|
16
16
|
import { createRoute } from '../trails/create.js';
|
|
17
17
|
import { createScaffold } from '../trails/create-scaffold.js';
|
|
18
18
|
import { isInsideProject } from '../trails/project.js';
|
|
19
|
+
import {
|
|
20
|
+
ontrailsPackageRange,
|
|
21
|
+
scaffoldDependencyVersions,
|
|
22
|
+
} from '../versions.js';
|
|
19
23
|
|
|
20
24
|
type Starter = 'empty' | 'entity' | 'hello';
|
|
21
|
-
type
|
|
25
|
+
type Surface = 'cli' | 'http' | 'mcp';
|
|
22
26
|
|
|
23
27
|
const makeTempProject = (): string =>
|
|
24
28
|
join(
|
|
@@ -76,8 +80,8 @@ const runCross = async (
|
|
|
76
80
|
case 'create.scaffold': {
|
|
77
81
|
return await createScaffold.blaze(input as never, {} as never);
|
|
78
82
|
}
|
|
79
|
-
case 'add.
|
|
80
|
-
return await
|
|
83
|
+
case 'add.surface': {
|
|
84
|
+
return await addSurface.blaze(input as never, {} as never);
|
|
81
85
|
}
|
|
82
86
|
case 'add.verify': {
|
|
83
87
|
return await addVerify.blaze(input as never, {} as never);
|
|
@@ -92,7 +96,7 @@ const runCreate = (
|
|
|
92
96
|
projectDir: string,
|
|
93
97
|
overrides?: Partial<{
|
|
94
98
|
starter: Starter;
|
|
95
|
-
|
|
99
|
+
surfaces: readonly Surface[];
|
|
96
100
|
verify: boolean;
|
|
97
101
|
}>
|
|
98
102
|
) =>
|
|
@@ -101,7 +105,7 @@ const runCreate = (
|
|
|
101
105
|
dir: dirname(projectDir),
|
|
102
106
|
name: basename(projectDir),
|
|
103
107
|
starter: overrides?.starter ?? 'hello',
|
|
104
|
-
|
|
108
|
+
surfaces: [...(overrides?.surfaces ?? ['cli'])],
|
|
105
109
|
verify: overrides?.verify ?? true,
|
|
106
110
|
},
|
|
107
111
|
{ cross: runCross } as never
|
|
@@ -117,7 +121,10 @@ const setupMinimalProject = (dir: string): void => {
|
|
|
117
121
|
writeFileSync(
|
|
118
122
|
join(dir, 'package.json'),
|
|
119
123
|
JSON.stringify(
|
|
120
|
-
{
|
|
124
|
+
{
|
|
125
|
+
dependencies: { '@ontrails/core': ontrailsPackageRange },
|
|
126
|
+
name: 'test',
|
|
127
|
+
},
|
|
121
128
|
null,
|
|
122
129
|
2
|
|
123
130
|
)
|
|
@@ -149,13 +156,28 @@ const assertCliPackage = (dir: string): void => {
|
|
|
149
156
|
expect(pkg['name']).toBe(basename(dir));
|
|
150
157
|
|
|
151
158
|
const deps = pkg['dependencies'] as Record<string, string>;
|
|
152
|
-
expect(deps['@ontrails/core']).toBe(
|
|
153
|
-
expect(deps['@ontrails/cli']).toBe(
|
|
154
|
-
expect(deps['commander']).
|
|
159
|
+
expect(deps['@ontrails/core']).toBe(ontrailsPackageRange);
|
|
160
|
+
expect(deps['@ontrails/cli']).toBe(ontrailsPackageRange);
|
|
161
|
+
expect(deps['commander']).toBe(scaffoldDependencyVersions.commander);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const assertVerifyPackage = (dir: string): void => {
|
|
165
|
+
const pkg = readJson(dir, 'package.json');
|
|
166
|
+
const devDeps = pkg['devDependencies'] as Record<string, string>;
|
|
167
|
+
expect(devDeps['@ontrails/testing']).toBe(ontrailsPackageRange);
|
|
168
|
+
expect(devDeps['@ontrails/warden']).toBe(ontrailsPackageRange);
|
|
169
|
+
expect(devDeps['lefthook']).toBe(scaffoldDependencyVersions.lefthook);
|
|
170
|
+
expect(readText(dir, 'lefthook.yml')).toContain('bunx trails warden');
|
|
171
|
+
expect(readText(dir, 'lefthook.yml')).not.toContain('--exit-code');
|
|
172
|
+
};
|
|
155
173
|
|
|
174
|
+
const assertGeneratedToolingDeps = (dir: string): void => {
|
|
175
|
+
const pkg = readJson(dir, 'package.json');
|
|
156
176
|
const devDeps = pkg['devDependencies'] as Record<string, string>;
|
|
157
|
-
expect(devDeps['@
|
|
158
|
-
expect(devDeps['
|
|
177
|
+
expect(devDeps['@types/bun']).toBe(scaffoldDependencyVersions.bunTypes);
|
|
178
|
+
expect(devDeps['oxlint']).toBe(scaffoldDependencyVersions.oxlint);
|
|
179
|
+
expect(devDeps['typescript']).toBe(scaffoldDependencyVersions.typescript);
|
|
180
|
+
expect(devDeps['ultracite']).toBe(scaffoldDependencyVersions.ultracite);
|
|
159
181
|
};
|
|
160
182
|
|
|
161
183
|
const assertHelloApp = (dir: string): void => {
|
|
@@ -204,25 +226,45 @@ const assertEntityStarter = (dir: string): void => {
|
|
|
204
226
|
]);
|
|
205
227
|
};
|
|
206
228
|
|
|
207
|
-
const
|
|
229
|
+
const assertMcpSurface = (dir: string): void => {
|
|
208
230
|
expectPaths(dir, ['src/mcp.ts'], true);
|
|
209
231
|
expectPaths(dir, ['src/cli.ts'], false);
|
|
210
232
|
expectContainsAll(readText(dir, 'src/mcp.ts'), [
|
|
211
|
-
"import {
|
|
212
|
-
'await
|
|
233
|
+
"import { surface } from '@ontrails/mcp'",
|
|
234
|
+
'await surface(app)',
|
|
213
235
|
]);
|
|
214
236
|
|
|
215
237
|
const deps = readJson(dir, 'package.json')['dependencies'] as Record<
|
|
216
238
|
string,
|
|
217
239
|
string
|
|
218
240
|
>;
|
|
219
|
-
expect(deps['@ontrails/mcp']).toBe(
|
|
241
|
+
expect(deps['@ontrails/mcp']).toBe(ontrailsPackageRange);
|
|
220
242
|
expect(deps['@ontrails/cli']).toBeUndefined();
|
|
221
243
|
};
|
|
222
244
|
|
|
245
|
+
const assertHttpSurface = (dir: string): void => {
|
|
246
|
+
expectPaths(dir, ['src/http.ts'], true);
|
|
247
|
+
expectContainsAll(readText(dir, 'src/http.ts'), [
|
|
248
|
+
"import { surface } from '@ontrails/hono'",
|
|
249
|
+
'await surface(app, { port: 3000 })',
|
|
250
|
+
]);
|
|
251
|
+
|
|
252
|
+
const deps = readJson(dir, 'package.json')['dependencies'] as Record<
|
|
253
|
+
string,
|
|
254
|
+
string
|
|
255
|
+
>;
|
|
256
|
+
expect(deps['@ontrails/hono']).toBe(ontrailsPackageRange);
|
|
257
|
+
expect(deps['@ontrails/http']).toBe(ontrailsPackageRange);
|
|
258
|
+
};
|
|
259
|
+
|
|
223
260
|
const assertVerifySkipped = (dir: string): void => {
|
|
224
261
|
expectPaths(dir, ['__tests__/examples.test.ts', 'lefthook.yml'], false);
|
|
225
|
-
|
|
262
|
+
const devDeps = readJson(dir, 'package.json')['devDependencies'] as Record<
|
|
263
|
+
string,
|
|
264
|
+
string
|
|
265
|
+
>;
|
|
266
|
+
expect(devDeps['@ontrails/testing']).toBeUndefined();
|
|
267
|
+
expect(devDeps['@ontrails/warden']).toBeUndefined();
|
|
226
268
|
};
|
|
227
269
|
|
|
228
270
|
const assertEmptyStarter = (dir: string): void => {
|
|
@@ -251,6 +293,8 @@ describe('trails create', () => {
|
|
|
251
293
|
expectOk(await runCreate(dir));
|
|
252
294
|
assertDefaultProjectFiles(dir);
|
|
253
295
|
assertCliPackage(dir);
|
|
296
|
+
assertVerifyPackage(dir);
|
|
297
|
+
assertGeneratedToolingDeps(dir);
|
|
254
298
|
assertHelloApp(dir);
|
|
255
299
|
});
|
|
256
300
|
});
|
|
@@ -262,10 +306,17 @@ describe('trails create', () => {
|
|
|
262
306
|
});
|
|
263
307
|
});
|
|
264
308
|
|
|
265
|
-
test('generates with MCP
|
|
309
|
+
test('generates with MCP surface', async () => {
|
|
310
|
+
await withTempProject(async (dir) => {
|
|
311
|
+
expectOk(await runCreate(dir, { surfaces: ['mcp'] }));
|
|
312
|
+
assertMcpSurface(dir);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test('generates with HTTP surface', async () => {
|
|
266
317
|
await withTempProject(async (dir) => {
|
|
267
|
-
expectOk(await runCreate(dir, {
|
|
268
|
-
|
|
318
|
+
expectOk(await runCreate(dir, { surfaces: ['http'] }));
|
|
319
|
+
assertHttpSurface(dir);
|
|
269
320
|
});
|
|
270
321
|
});
|
|
271
322
|
|
|
@@ -284,39 +335,52 @@ describe('trails create', () => {
|
|
|
284
335
|
});
|
|
285
336
|
});
|
|
286
337
|
|
|
287
|
-
describe('add-
|
|
338
|
+
describe('add-surface mode', () => {
|
|
288
339
|
test('adds MCP to existing project', async () => {
|
|
289
340
|
await withTempProject(async (dir) => {
|
|
290
341
|
setupMinimalProject(dir);
|
|
291
342
|
const result = expectOk(
|
|
292
|
-
await
|
|
343
|
+
await addSurface.blaze({ dir, surface: 'mcp' }, {} as never)
|
|
293
344
|
);
|
|
294
345
|
|
|
295
346
|
expect(result.created).toBe('src/mcp.ts');
|
|
296
347
|
expect(result.dependency).toBe('@ontrails/mcp');
|
|
297
348
|
expectPaths(dir, ['src/mcp.ts'], true);
|
|
298
349
|
expectContainsAll(readText(dir, 'src/mcp.ts'), [
|
|
299
|
-
"import {
|
|
350
|
+
"import { surface } from '@ontrails/mcp'",
|
|
300
351
|
]);
|
|
301
352
|
const deps = readJson(dir, 'package.json')['dependencies'] as Record<
|
|
302
353
|
string,
|
|
303
354
|
string
|
|
304
355
|
>;
|
|
305
|
-
expect(deps['@ontrails/mcp']).toBe(
|
|
356
|
+
expect(deps['@ontrails/mcp']).toBe(ontrailsPackageRange);
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test('adds HTTP to existing project', async () => {
|
|
361
|
+
await withTempProject(async (dir) => {
|
|
362
|
+
setupMinimalProject(dir);
|
|
363
|
+
const result = expectOk(
|
|
364
|
+
await addSurface.blaze({ dir, surface: 'http' }, {} as never)
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
expect(result.created).toBe('src/http.ts');
|
|
368
|
+
expect(result.dependency).toBe('@ontrails/hono');
|
|
369
|
+
assertHttpSurface(dir);
|
|
306
370
|
});
|
|
307
371
|
});
|
|
308
372
|
|
|
309
|
-
test('detects existing
|
|
373
|
+
test('detects existing surface entrypoint', async () => {
|
|
310
374
|
await withTempProject(async (dir) => {
|
|
311
375
|
mkdirSync(join(dir, 'src'), { recursive: true });
|
|
312
376
|
mkdirSync(join(dir, '.trails'), { recursive: true });
|
|
313
377
|
writeFileSync(join(dir, 'src', 'mcp.ts'), 'existing content');
|
|
314
378
|
|
|
315
379
|
const error = expectErr(
|
|
316
|
-
await
|
|
380
|
+
await addSurface.blaze({ dir, surface: 'mcp' }, {} as never)
|
|
317
381
|
);
|
|
318
382
|
expect(error.message).toBe(
|
|
319
|
-
'MCP
|
|
383
|
+
'MCP surface already exists. Nothing to do.'
|
|
320
384
|
);
|
|
321
385
|
});
|
|
322
386
|
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import {
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
rmSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
} from 'node:fs';
|
|
9
|
+
import { join, resolve } from 'node:path';
|
|
10
|
+
|
|
11
|
+
import type { Result } from '@ontrails/core';
|
|
12
|
+
import { ValidationError } from '@ontrails/core';
|
|
13
|
+
|
|
14
|
+
import { draftPromoteTrail } from '../trails/draft-promote.js';
|
|
15
|
+
|
|
16
|
+
const repoTempDir = (): string =>
|
|
17
|
+
join(
|
|
18
|
+
resolve(import.meta.dir, '../..'),
|
|
19
|
+
'.tmp-tests',
|
|
20
|
+
`draft-promote-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const expectOk = <T>(result: Result<T, Error>): T => {
|
|
24
|
+
if (result.isErr()) {
|
|
25
|
+
throw result.error;
|
|
26
|
+
}
|
|
27
|
+
return result.value;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const expectErr = <E extends Error>(result: Result<unknown, E>): E => {
|
|
31
|
+
if (result.isOk()) {
|
|
32
|
+
throw new Error('expected result to be an error');
|
|
33
|
+
}
|
|
34
|
+
return result.error;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const writeDraftPromoteFixture = (dir: string): void => {
|
|
38
|
+
mkdirSync(join(dir, 'src'), { recursive: true });
|
|
39
|
+
|
|
40
|
+
writeFileSync(
|
|
41
|
+
join(dir, 'src', 'app.ts'),
|
|
42
|
+
`import { topo } from '@ontrails/core';
|
|
43
|
+
import { draftPrepare } from './_draft.prepare.js';
|
|
44
|
+
import { exportTrail } from './export.js';
|
|
45
|
+
|
|
46
|
+
export const app = topo('draft-test', { draftPrepare, exportTrail });
|
|
47
|
+
`
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
writeFileSync(
|
|
51
|
+
join(dir, 'src', '_draft.prepare.ts'),
|
|
52
|
+
`import { Result, trail } from '@ontrails/core';
|
|
53
|
+
import { z } from 'zod';
|
|
54
|
+
|
|
55
|
+
export const draftPrepare = trail('_draft.entity.prepare', {
|
|
56
|
+
blaze: async () => Result.ok({ ready: true }),
|
|
57
|
+
input: z.object({}),
|
|
58
|
+
output: z.object({ ready: z.boolean() }),
|
|
59
|
+
});
|
|
60
|
+
`
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
writeFileSync(
|
|
64
|
+
join(dir, 'src', 'export.ts'),
|
|
65
|
+
`import { Result, trail } from '@ontrails/core';
|
|
66
|
+
import { z } from 'zod';
|
|
67
|
+
|
|
68
|
+
export const exportTrail = trail('entity.export', {
|
|
69
|
+
blaze: async () => Result.ok({ exported: true }),
|
|
70
|
+
crosses: ['_draft.entity.prepare'],
|
|
71
|
+
input: z.object({}),
|
|
72
|
+
output: z.object({ exported: z.boolean() }),
|
|
73
|
+
});
|
|
74
|
+
`
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const expectDraftPromoteResults = (dir: string): void => {
|
|
79
|
+
expect(existsSync(join(dir, 'src', '_draft.prepare.ts'))).toBe(false);
|
|
80
|
+
expect(existsSync(join(dir, 'src', 'prepare.ts'))).toBe(true);
|
|
81
|
+
expect(readFileSync(join(dir, 'src', 'prepare.ts'), 'utf8')).toContain(
|
|
82
|
+
"trail('entity.prepare'"
|
|
83
|
+
);
|
|
84
|
+
expect(readFileSync(join(dir, 'src', 'export.ts'), 'utf8')).toContain(
|
|
85
|
+
"crosses: ['entity.prepare']"
|
|
86
|
+
);
|
|
87
|
+
expect(readFileSync(join(dir, 'src', 'app.ts'), 'utf8')).toContain(
|
|
88
|
+
"from './prepare.js'"
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
describe('draft.promote', () => {
|
|
93
|
+
test('promotes draft ids, renames files, and updates imports', async () => {
|
|
94
|
+
const dir = repoTempDir();
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
writeDraftPromoteFixture(dir);
|
|
98
|
+
|
|
99
|
+
const result = expectOk(
|
|
100
|
+
await draftPromoteTrail.blaze(
|
|
101
|
+
{
|
|
102
|
+
fromId: '_draft.entity.prepare',
|
|
103
|
+
renameFiles: true,
|
|
104
|
+
rootDir: dir,
|
|
105
|
+
toId: 'entity.prepare',
|
|
106
|
+
},
|
|
107
|
+
{ cwd: dir } as never
|
|
108
|
+
)
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
expect(result.promotedEstablished).toBe(true);
|
|
112
|
+
expect(result.remainingDraftIds).toEqual([]);
|
|
113
|
+
expect(result.updatedFiles).toEqual(
|
|
114
|
+
expect.arrayContaining(['src/app.ts', 'src/export.ts'])
|
|
115
|
+
);
|
|
116
|
+
expect(result.renamedFiles).toEqual([
|
|
117
|
+
{
|
|
118
|
+
from: 'src/_draft.prepare.ts',
|
|
119
|
+
to: 'src/prepare.ts',
|
|
120
|
+
},
|
|
121
|
+
]);
|
|
122
|
+
expectDraftPromoteResults(dir);
|
|
123
|
+
} finally {
|
|
124
|
+
rmSync(dir, { force: true, recursive: true });
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('returns ValidationError when rootDir does not exist', async () => {
|
|
129
|
+
const error = expectErr(
|
|
130
|
+
await draftPromoteTrail.blaze(
|
|
131
|
+
{
|
|
132
|
+
fromId: '_draft.entity.prepare',
|
|
133
|
+
renameFiles: true,
|
|
134
|
+
rootDir: join(repoTempDir(), 'missing'),
|
|
135
|
+
toId: 'entity.prepare',
|
|
136
|
+
},
|
|
137
|
+
{ cwd: process.cwd() } as never
|
|
138
|
+
)
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
expect(error).toBeInstanceOf(ValidationError);
|
|
142
|
+
expect(error.message).toContain('rootDir does not exist');
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test';
|
|
2
2
|
|
|
3
3
|
import type { AnyTrail } from '@ontrails/core';
|
|
4
|
-
import { trail, topo, Result } from '@ontrails/core';
|
|
4
|
+
import { ConflictError, trail, topo, Result } from '@ontrails/core';
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
|
|
7
7
|
// ---------------------------------------------------------------------------
|
|
@@ -14,9 +14,13 @@ const helloTrail = trail('hello', {
|
|
|
14
14
|
return Result.ok({ message: `Hello, ${name}!` });
|
|
15
15
|
},
|
|
16
16
|
description: 'Say hello',
|
|
17
|
-
detours:
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
detours: [
|
|
18
|
+
{
|
|
19
|
+
on: ConflictError,
|
|
20
|
+
/* oxlint-disable-next-line require-await -- test stub */
|
|
21
|
+
recover: async () => Result.ok({ message: 'recovered' }),
|
|
22
|
+
},
|
|
23
|
+
],
|
|
20
24
|
examples: [
|
|
21
25
|
{
|
|
22
26
|
expected: { message: 'Hello, world!' },
|
|
@@ -86,6 +90,7 @@ describe('trails guide', () => {
|
|
|
86
90
|
test('detours are accessible on trail', () => {
|
|
87
91
|
const t = app.get('hello') as AnyTrail;
|
|
88
92
|
expect(t).toBeDefined();
|
|
89
|
-
expect(t.detours
|
|
93
|
+
expect(t.detours).toHaveLength(1);
|
|
94
|
+
expect(t.detours[0]?.on).toBe(ConflictError);
|
|
90
95
|
});
|
|
91
96
|
});
|