@ontrails/testing 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.
- package/.turbo/turbo-lint.log +1 -1
- package/CHANGELOG.md +14 -0
- package/dist/harness-cli.d.ts.map +1 -1
- package/dist/harness-cli.js +9 -22
- package/dist/harness-cli.js.map +1 -1
- package/package.json +8 -5
- package/src/__tests__/all.test.ts +85 -16
- package/src/__tests__/harness-cli.test.ts +66 -0
- package/src/harness-cli.ts +15 -39
package/.turbo/turbo-lint.log
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @ontrails/testing
|
|
2
2
|
|
|
3
|
+
## 1.0.0-beta.14
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 69057e9: Add hierarchical CLI command trees and structured input, enforce established-only topo exports across trailheads, move developer topo and tracker state onto shared `trails.db` with pins and maintenance flows, and ship schema-derived stores through `@ontrails/store` and its Drizzle runtime.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [69057e9]
|
|
12
|
+
- @ontrails/cli@1.0.0-beta.14
|
|
13
|
+
- @ontrails/core@1.0.0-beta.14
|
|
14
|
+
- @ontrails/logging@1.0.0-beta.14
|
|
15
|
+
- @ontrails/mcp@1.0.0-beta.14
|
|
16
|
+
|
|
3
17
|
## 1.0.0-beta.13
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harness-cli.d.ts","sourceRoot":"","sources":["../src/harness-cli.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EACV,UAAU,EACV,iBAAiB,EAElB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"harness-cli.d.ts","sourceRoot":"","sources":["../src/harness-cli.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EACV,UAAU,EACV,iBAAiB,EAElB,MAAM,YAAY,CAAC;AA4PpB;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,GAAI,SAAS,iBAAiB,KAAG,UAM7D,CAAC"}
|
package/dist/harness-cli.js
CHANGED
|
@@ -17,33 +17,20 @@ const parseCommandString = (input) => input
|
|
|
17
17
|
// ---------------------------------------------------------------------------
|
|
18
18
|
// Command resolution
|
|
19
19
|
// ---------------------------------------------------------------------------
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const match = commands.find((c) => c.group === firstToken && c.name === secondToken);
|
|
26
|
-
if (match === undefined) {
|
|
20
|
+
const matchesPath = (path, tokens) => path.length <= tokens.length &&
|
|
21
|
+
path.every((segment, index) => tokens[index] === segment);
|
|
22
|
+
/** Resolve a command from tokens using the longest matching command path. */
|
|
23
|
+
const resolveCommand = (commands, tokens) => {
|
|
24
|
+
if (tokens.length === 0) {
|
|
27
25
|
return undefined;
|
|
28
26
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const tryDirectMatch = (commands, firstToken, tokens) => {
|
|
33
|
-
const match = commands.find((c) => c.name === firstToken && c.group === undefined);
|
|
27
|
+
const [match] = commands
|
|
28
|
+
.filter((command) => matchesPath(command.path, tokens))
|
|
29
|
+
.toSorted((a, b) => b.path.length - a.path.length);
|
|
34
30
|
if (match === undefined) {
|
|
35
31
|
return undefined;
|
|
36
32
|
}
|
|
37
|
-
return { command: match, flagTokens: tokens.slice(
|
|
38
|
-
};
|
|
39
|
-
/** Resolve a command from tokens, handling group.name patterns. */
|
|
40
|
-
const resolveCommand = (commands, tokens) => {
|
|
41
|
-
const [firstToken, secondToken] = tokens;
|
|
42
|
-
if (firstToken === undefined) {
|
|
43
|
-
return undefined;
|
|
44
|
-
}
|
|
45
|
-
return (tryGroupMatch(commands, firstToken, secondToken, tokens) ??
|
|
46
|
-
tryDirectMatch(commands, firstToken, tokens));
|
|
33
|
+
return { command: match, flagTokens: tokens.slice(match.path.length) };
|
|
47
34
|
};
|
|
48
35
|
// ---------------------------------------------------------------------------
|
|
49
36
|
// Flag parsing
|
package/dist/harness-cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harness-cli.js","sourceRoot":"","sources":["../src/harness-cli.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAOjD,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E,6EAA6E;AAC7E,MAAM,kBAAkB,GAAG,CAAC,KAAa,EAAY,EAAE,CACrD,KAAK;KACF,IAAI,EAAE;KACN,KAAK,CAAC,KAAK,CAAC;KACZ,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAEjC,8EAA8E;AAC9E,qBAAqB;AACrB,8EAA8E;AAE9E,
|
|
1
|
+
{"version":3,"file":"harness-cli.js","sourceRoot":"","sources":["../src/harness-cli.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ontrails/testing",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./src/index.ts",
|
|
@@ -13,11 +13,14 @@
|
|
|
13
13
|
"lint": "oxlint ./src",
|
|
14
14
|
"clean": "rm -rf dist *.tsbuildinfo"
|
|
15
15
|
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@ontrails/store": "^1.0.0-beta.13"
|
|
18
|
+
},
|
|
16
19
|
"peerDependencies": {
|
|
17
|
-
"@ontrails/cli": "^1.0.0-beta.
|
|
18
|
-
"@ontrails/core": "^1.0.0-beta.
|
|
19
|
-
"@ontrails/logging": "^1.0.0-beta.
|
|
20
|
-
"@ontrails/mcp": "^1.0.0-beta.
|
|
20
|
+
"@ontrails/cli": "^1.0.0-beta.13",
|
|
21
|
+
"@ontrails/core": "^1.0.0-beta.13",
|
|
22
|
+
"@ontrails/logging": "^1.0.0-beta.13",
|
|
23
|
+
"@ontrails/mcp": "^1.0.0-beta.13",
|
|
21
24
|
"zod": "^4.3.5"
|
|
22
25
|
}
|
|
23
26
|
}
|
|
@@ -1,44 +1,111 @@
|
|
|
1
1
|
import { describe } from 'bun:test';
|
|
2
2
|
|
|
3
|
-
import { Result,
|
|
3
|
+
import { Result, trail, topo } from '@ontrails/core';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
|
|
6
6
|
import { testAll } from '../all.js';
|
|
7
|
+
import { store as defineStore } from '@ontrails/store';
|
|
8
|
+
import { connectDrizzle } from '@ontrails/store/drizzle';
|
|
7
9
|
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
const dbDefinition = defineStore({
|
|
11
|
+
entities: {
|
|
12
|
+
fixtures: [
|
|
13
|
+
{
|
|
14
|
+
id: 'seed-1',
|
|
15
|
+
name: 'Alpha',
|
|
16
|
+
source: 'mock',
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
generated: ['id'],
|
|
20
|
+
primaryKey: 'id',
|
|
21
|
+
schema: z.object({
|
|
22
|
+
id: z.string(),
|
|
23
|
+
name: z.string(),
|
|
24
|
+
source: z.string(),
|
|
25
|
+
}),
|
|
26
|
+
},
|
|
11
27
|
});
|
|
12
28
|
|
|
29
|
+
const createDbProvision = (
|
|
30
|
+
seed?: readonly {
|
|
31
|
+
readonly id: string;
|
|
32
|
+
readonly name: string;
|
|
33
|
+
readonly source: string;
|
|
34
|
+
}[]
|
|
35
|
+
) =>
|
|
36
|
+
connectDrizzle(dbDefinition, {
|
|
37
|
+
id: 'db.mock.all',
|
|
38
|
+
...(seed === undefined ? {} : { mockSeed: { entities: seed } }),
|
|
39
|
+
url: ':memory:',
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const createOverrideStore = () => {
|
|
43
|
+
const { mock } = createDbProvision([
|
|
44
|
+
{
|
|
45
|
+
id: 'seed-1',
|
|
46
|
+
name: 'Override',
|
|
47
|
+
source: 'override',
|
|
48
|
+
},
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
if (mock === undefined) {
|
|
52
|
+
throw new Error('Expected drizzle test store to expose a mock factory');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const created = mock();
|
|
56
|
+
if (created instanceof Promise) {
|
|
57
|
+
throw new TypeError(
|
|
58
|
+
'Expected drizzle test store mock to resolve synchronously'
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return created;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const mockDbProvision = createDbProvision();
|
|
66
|
+
|
|
13
67
|
const mockedTrail = trail('provision.mocked.all', {
|
|
14
|
-
blaze: (_input, ctx) =>
|
|
15
|
-
|
|
16
|
-
|
|
68
|
+
blaze: async (_input, ctx) => {
|
|
69
|
+
const entity = await mockDbProvision.from(ctx).entities.get('seed-1');
|
|
70
|
+
if (entity === null) {
|
|
71
|
+
return Result.err(new Error('expected seeded entity to exist'));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return Result.ok({ name: entity.name, source: entity.source });
|
|
75
|
+
},
|
|
76
|
+
description:
|
|
77
|
+
'Trail that uses a mocked connector-bound provision through testAll',
|
|
17
78
|
examples: [
|
|
18
79
|
{
|
|
19
|
-
expected: { source: 'mock' },
|
|
80
|
+
expected: { name: 'Alpha', source: 'mock' },
|
|
20
81
|
input: {},
|
|
21
82
|
name: 'Uses auto-resolved provision mock',
|
|
22
83
|
},
|
|
23
84
|
],
|
|
24
85
|
input: z.object({}),
|
|
25
|
-
output: z.object({ source: z.string() }),
|
|
86
|
+
output: z.object({ name: z.string(), source: z.string() }),
|
|
26
87
|
provisions: [mockDbProvision],
|
|
27
88
|
});
|
|
28
89
|
|
|
29
90
|
const overrideTrail = trail('provision.override.all', {
|
|
30
|
-
blaze: (_input, ctx) =>
|
|
31
|
-
|
|
91
|
+
blaze: async (_input, ctx) => {
|
|
92
|
+
const entity = await mockDbProvision.from(ctx).entities.get('seed-1');
|
|
93
|
+
if (entity === null) {
|
|
94
|
+
return Result.err(new Error('expected overridden entity to exist'));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return Result.ok({ name: entity.name, source: entity.source });
|
|
98
|
+
},
|
|
32
99
|
description: 'Trail that prefers explicit overrides over mock factories',
|
|
33
100
|
examples: [
|
|
34
101
|
{
|
|
35
|
-
expected: { source: 'override' },
|
|
102
|
+
expected: { name: 'Override', source: 'override' },
|
|
36
103
|
input: {},
|
|
37
104
|
name: 'Explicit provision override wins',
|
|
38
105
|
},
|
|
39
106
|
],
|
|
40
107
|
input: z.object({}),
|
|
41
|
-
output: z.object({ source: z.string() }),
|
|
108
|
+
output: z.object({ name: z.string(), source: z.string() }),
|
|
42
109
|
provisions: [mockDbProvision],
|
|
43
110
|
});
|
|
44
111
|
|
|
@@ -59,8 +126,10 @@ describe('testAll explicit provision overrides', () => {
|
|
|
59
126
|
mockDbProvision,
|
|
60
127
|
overrideTrail,
|
|
61
128
|
} as Record<string, unknown>),
|
|
62
|
-
{
|
|
63
|
-
provisions: {
|
|
64
|
-
|
|
129
|
+
() => ({
|
|
130
|
+
provisions: {
|
|
131
|
+
'db.mock.all': createOverrideStore(),
|
|
132
|
+
},
|
|
133
|
+
})
|
|
65
134
|
);
|
|
66
135
|
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import { Result, topo, trail } from '@ontrails/core';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
|
|
6
|
+
import { createCliHarness } from '../harness-cli.js';
|
|
7
|
+
|
|
8
|
+
describe('createCliHarness', () => {
|
|
9
|
+
test('runs top-level commands', async () => {
|
|
10
|
+
const greet = trail('greet', {
|
|
11
|
+
blaze: (input: { name: string }) => Result.ok(`Hello, ${input.name}!`),
|
|
12
|
+
input: z.object({ name: z.string() }),
|
|
13
|
+
});
|
|
14
|
+
const harness = createCliHarness({
|
|
15
|
+
app: topo('test-app', { greet }),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const result = await harness.run('greet --name Trails');
|
|
19
|
+
|
|
20
|
+
expect(result.exitCode).toBe(0);
|
|
21
|
+
expect(result.stdout).toContain('Hello, Trails!');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('runs nested commands using the full ordered path', async () => {
|
|
25
|
+
const pin = trail('topo.pin', {
|
|
26
|
+
blaze: (input: { name: string }) => Result.ok(`Pinned ${input.name}`),
|
|
27
|
+
input: z.object({ name: z.string() }),
|
|
28
|
+
});
|
|
29
|
+
const harness = createCliHarness({
|
|
30
|
+
app: topo('test-app', { pin }),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const result = await harness.run('topo pin --name before-auth');
|
|
34
|
+
|
|
35
|
+
expect(result.exitCode).toBe(0);
|
|
36
|
+
expect(result.stdout).toContain('Pinned before-auth');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('prefers the deepest matching executable path', async () => {
|
|
40
|
+
const calls: string[] = [];
|
|
41
|
+
const topoShow = trail('topo', {
|
|
42
|
+
blaze: () => {
|
|
43
|
+
calls.push('topo');
|
|
44
|
+
return Result.ok('topo');
|
|
45
|
+
},
|
|
46
|
+
input: z.object({}),
|
|
47
|
+
});
|
|
48
|
+
const topoPin = trail('topo.pin', {
|
|
49
|
+
blaze: () => {
|
|
50
|
+
calls.push('topo.pin');
|
|
51
|
+
return Result.ok('topo.pin');
|
|
52
|
+
},
|
|
53
|
+
input: z.object({}),
|
|
54
|
+
});
|
|
55
|
+
const harness = createCliHarness({
|
|
56
|
+
app: topo('test-app', { topoPin, topoShow }),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const child = await harness.run('topo pin');
|
|
60
|
+
const parent = await harness.run('topo');
|
|
61
|
+
|
|
62
|
+
expect(child.exitCode).toBe(0);
|
|
63
|
+
expect(parent.exitCode).toBe(0);
|
|
64
|
+
expect(calls).toEqual(['topo.pin', 'topo']);
|
|
65
|
+
});
|
|
66
|
+
});
|
package/src/harness-cli.ts
CHANGED
|
@@ -30,55 +30,31 @@ const parseCommandString = (input: string): string[] =>
|
|
|
30
30
|
// Command resolution
|
|
31
31
|
// ---------------------------------------------------------------------------
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
tokens
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
const match = commands.find(
|
|
44
|
-
(c) => c.group === firstToken && c.name === secondToken
|
|
45
|
-
);
|
|
46
|
-
if (match === undefined) {
|
|
47
|
-
return undefined;
|
|
48
|
-
}
|
|
49
|
-
return { command: match, flagTokens: tokens.slice(2) };
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
/** Try to match a direct name from the first token. */
|
|
53
|
-
const tryDirectMatch = (
|
|
33
|
+
const matchesPath = (
|
|
34
|
+
path: readonly string[],
|
|
35
|
+
tokens: readonly string[]
|
|
36
|
+
): boolean =>
|
|
37
|
+
path.length <= tokens.length &&
|
|
38
|
+
path.every((segment, index) => tokens[index] === segment);
|
|
39
|
+
|
|
40
|
+
/** Resolve a command from tokens using the longest matching command path. */
|
|
41
|
+
const resolveCommand = (
|
|
54
42
|
commands: CliCommand[],
|
|
55
|
-
firstToken: string,
|
|
56
43
|
tokens: string[]
|
|
57
44
|
): { command: CliCommand; flagTokens: string[] } | undefined => {
|
|
58
|
-
|
|
59
|
-
(c) => c.name === firstToken && c.group === undefined
|
|
60
|
-
);
|
|
61
|
-
if (match === undefined) {
|
|
45
|
+
if (tokens.length === 0) {
|
|
62
46
|
return undefined;
|
|
63
47
|
}
|
|
64
|
-
return { command: match, flagTokens: tokens.slice(1) };
|
|
65
|
-
};
|
|
66
48
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
tokens: string[]
|
|
71
|
-
): { command: CliCommand; flagTokens: string[] } | undefined => {
|
|
72
|
-
const [firstToken, secondToken] = tokens;
|
|
49
|
+
const [match] = commands
|
|
50
|
+
.filter((command) => matchesPath(command.path, tokens))
|
|
51
|
+
.toSorted((a, b) => b.path.length - a.path.length);
|
|
73
52
|
|
|
74
|
-
if (
|
|
53
|
+
if (match === undefined) {
|
|
75
54
|
return undefined;
|
|
76
55
|
}
|
|
77
56
|
|
|
78
|
-
return (
|
|
79
|
-
tryGroupMatch(commands, firstToken, secondToken, tokens) ??
|
|
80
|
-
tryDirectMatch(commands, firstToken, tokens)
|
|
81
|
-
);
|
|
57
|
+
return { command: match, flagTokens: tokens.slice(match.path.length) };
|
|
82
58
|
};
|
|
83
59
|
|
|
84
60
|
// ---------------------------------------------------------------------------
|